From 5578fe2a32f8c11c32b564a6233be245aefe29fc Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Wed, 29 Mar 2023 14:16:14 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F[RUMF-1517]=20split=20core=20?= =?UTF-8?q?specHelper=20(#2111)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ♻️ move clearAllCookies to its usage * ♻️ extract browserChecks * ♻️ extract buildEnv * ♻️ extract requests.specHelper * ♻️ extract createNewEvent * ♻️ extract location.specHelper * ♻️ extract mockClock * ♻️ rename specHelper to simpleTestDoubles * ♻️ move capturedExceptions to tracekit * 👌 remove specHelper prefix for files under test directory * 👌 extract test/emulate directory * 👌 split simpleTestDoubles --- .../capturedExceptions.specHelper.ts} | 0 .../domain/tracekit/computeStackTrace.spec.ts | 2 +- packages/core/test/browserChecks.ts | 11 + packages/core/test/buildEnv.ts | 4 + packages/core/test/emulate/cookie.ts | 13 + packages/core/test/emulate/createNewEvent.ts | 26 ++ packages/core/test/emulate/eventBridge.ts | 14 + packages/core/test/emulate/location.ts | 32 ++ packages/core/test/emulate/mockClock.ts | 20 ++ packages/core/test/emulate/navigatorOnLine.ts | 12 + .../core/test/{ => emulate}/stubReportApis.ts | 4 +- .../core/test/{ => emulate}/stubZoneJs.ts | 2 +- .../{ => emulate}/syntheticsWorkerValues.ts | 8 +- packages/core/test/emulate/visibilityState.ts | 12 + packages/core/test/emulate/windowOnError.ts | 13 + packages/core/test/forEach.spec.ts | 9 +- packages/core/test/index.ts | 21 +- .../core/test/{specHelper.ts => requests.ts} | 296 ++++-------------- 18 files changed, 256 insertions(+), 243 deletions(-) rename packages/core/{test/capturedExceptions.ts => src/domain/tracekit/capturedExceptions.specHelper.ts} (100%) create mode 100644 packages/core/test/browserChecks.ts create mode 100644 packages/core/test/buildEnv.ts create mode 100644 packages/core/test/emulate/cookie.ts create mode 100644 packages/core/test/emulate/createNewEvent.ts create mode 100644 packages/core/test/emulate/eventBridge.ts create mode 100644 packages/core/test/emulate/location.ts create mode 100644 packages/core/test/emulate/mockClock.ts create mode 100644 packages/core/test/emulate/navigatorOnLine.ts rename packages/core/test/{ => emulate}/stubReportApis.ts (95%) rename packages/core/test/{ => emulate}/stubZoneJs.ts (89%) rename packages/core/test/{ => emulate}/syntheticsWorkerValues.ts (85%) create mode 100644 packages/core/test/emulate/visibilityState.ts create mode 100644 packages/core/test/emulate/windowOnError.ts rename packages/core/test/{specHelper.ts => requests.ts} (64%) diff --git a/packages/core/test/capturedExceptions.ts b/packages/core/src/domain/tracekit/capturedExceptions.specHelper.ts similarity index 100% rename from packages/core/test/capturedExceptions.ts rename to packages/core/src/domain/tracekit/capturedExceptions.specHelper.ts diff --git a/packages/core/src/domain/tracekit/computeStackTrace.spec.ts b/packages/core/src/domain/tracekit/computeStackTrace.spec.ts index 2426ddeb2e..1ccc14980c 100644 --- a/packages/core/src/domain/tracekit/computeStackTrace.spec.ts +++ b/packages/core/src/domain/tracekit/computeStackTrace.spec.ts @@ -1,5 +1,5 @@ -import * as CapturedExceptions from '../../../test' import { isSafari } from '../../../test' +import * as CapturedExceptions from './capturedExceptions.specHelper' import { computeStackTrace } from './computeStackTrace' describe('computeStackTrace', () => { diff --git a/packages/core/test/browserChecks.ts b/packages/core/test/browserChecks.ts new file mode 100644 index 0000000000..fbf38bba55 --- /dev/null +++ b/packages/core/test/browserChecks.ts @@ -0,0 +1,11 @@ +export function isSafari() { + return /^((?!chrome|android).)*safari/i.test(navigator.userAgent) +} + +export function isFirefox() { + return navigator.userAgent.toLowerCase().indexOf('firefox') > -1 +} + +export function isAdoptedStyleSheetsSupported() { + return Boolean((document as any).adoptedStyleSheets) +} diff --git a/packages/core/test/buildEnv.ts b/packages/core/test/buildEnv.ts new file mode 100644 index 0000000000..b258cecc60 --- /dev/null +++ b/packages/core/test/buildEnv.ts @@ -0,0 +1,4 @@ +// to simulate different build env behavior +export interface BuildEnvWindow { + __BUILD_ENV__SDK_VERSION__: string +} diff --git a/packages/core/test/emulate/cookie.ts b/packages/core/test/emulate/cookie.ts new file mode 100644 index 0000000000..a053a560c8 --- /dev/null +++ b/packages/core/test/emulate/cookie.ts @@ -0,0 +1,13 @@ +export function stubCookie() { + let cookie = '' + return { + getSpy: spyOnProperty(Document.prototype, 'cookie', 'get').and.callFake(() => cookie), + setSpy: spyOnProperty(Document.prototype, 'cookie', 'set').and.callFake((newCookie) => { + cookie = newCookie + }), + currentValue: () => cookie, + setCurrentValue: (newCookie: string) => { + cookie = newCookie + }, + } +} diff --git a/packages/core/test/emulate/createNewEvent.ts b/packages/core/test/emulate/createNewEvent.ts new file mode 100644 index 0000000000..e5a9089000 --- /dev/null +++ b/packages/core/test/emulate/createNewEvent.ts @@ -0,0 +1,26 @@ +import { objectEntries } from '../../src' + +export function createNewEvent

>(eventName: 'click', properties?: P): MouseEvent & P +export function createNewEvent

>( + eventName: 'pointerup', + properties?: P +): PointerEvent & P +export function createNewEvent(eventName: string, properties?: { [name: string]: unknown }): Event +export function createNewEvent(eventName: string, properties: { [name: string]: unknown } = {}) { + let event: Event + if (typeof Event === 'function') { + event = new Event(eventName) + } else { + event = document.createEvent('Event') + event.initEvent(eventName, true, true) + } + objectEntries(properties).forEach(([name, value]) => { + // Setting values directly or with a `value` descriptor seems unsupported in IE11 + Object.defineProperty(event, name, { + get() { + return value + }, + }) + }) + return event +} diff --git a/packages/core/test/emulate/eventBridge.ts b/packages/core/test/emulate/eventBridge.ts new file mode 100644 index 0000000000..178d84cc2c --- /dev/null +++ b/packages/core/test/emulate/eventBridge.ts @@ -0,0 +1,14 @@ +import type { BrowserWindowWithEventBridge } from '../../src/transport' + +export function initEventBridgeStub(allowedWebViewHosts: string[] = [window.location.hostname]) { + const eventBridgeStub = { + send: (_msg: string) => undefined, + getAllowedWebViewHosts: () => JSON.stringify(allowedWebViewHosts), + } + ;(window as BrowserWindowWithEventBridge).DatadogEventBridge = eventBridgeStub + return eventBridgeStub +} + +export function deleteEventBridgeStub() { + delete (window as BrowserWindowWithEventBridge).DatadogEventBridge +} diff --git a/packages/core/test/emulate/location.ts b/packages/core/test/emulate/location.ts new file mode 100644 index 0000000000..866a2ecc21 --- /dev/null +++ b/packages/core/test/emulate/location.ts @@ -0,0 +1,32 @@ +import { assign, buildUrl } from '../../src' + +export function mockLocation(initialUrl: string) { + const fakeLocation = buildLocation(initialUrl) + spyOn(history, 'pushState').and.callFake((_: any, __: string, pathname: string) => { + assign(fakeLocation, buildLocation(pathname, fakeLocation.href)) + }) + + function hashchangeCallBack() { + fakeLocation.hash = window.location.hash + fakeLocation.href = fakeLocation.href.replace(/#.*/, '') + window.location.hash + } + + window.addEventListener('hashchange', hashchangeCallBack) + return { + location: fakeLocation, + cleanup: () => { + window.removeEventListener('hashchange', hashchangeCallBack) + window.location.hash = '' + }, + } +} + +export function buildLocation(url: string, base = location.href) { + const urlObject = buildUrl(url, base) + return { + hash: urlObject.hash, + href: urlObject.href, + pathname: urlObject.pathname, + search: urlObject.search, + } as Location +} diff --git a/packages/core/test/emulate/mockClock.ts b/packages/core/test/emulate/mockClock.ts new file mode 100644 index 0000000000..a0ce1ba31c --- /dev/null +++ b/packages/core/test/emulate/mockClock.ts @@ -0,0 +1,20 @@ +import { resetNavigationStart } from '../../src/tools/timeUtils' + +export type Clock = ReturnType + +export function mockClock(date?: Date) { + jasmine.clock().install() + jasmine.clock().mockDate(date) + const start = Date.now() + spyOn(performance, 'now').and.callFake(() => Date.now() - start) + spyOnProperty(performance.timing, 'navigationStart', 'get').and.callFake(() => start) + resetNavigationStart() + return { + tick: (ms: number) => jasmine.clock().tick(ms), + setDate: (date: Date) => jasmine.clock().mockDate(date), + cleanup: () => { + jasmine.clock().uninstall() + resetNavigationStart() + }, + } +} diff --git a/packages/core/test/emulate/navigatorOnLine.ts b/packages/core/test/emulate/navigatorOnLine.ts new file mode 100644 index 0000000000..2e915cc568 --- /dev/null +++ b/packages/core/test/emulate/navigatorOnLine.ts @@ -0,0 +1,12 @@ +export function setNavigatorOnLine(onLine: boolean) { + Object.defineProperty(navigator, 'onLine', { + get() { + return onLine + }, + configurable: true, + }) +} + +export function restoreNavigatorOnLine() { + delete (navigator as any).onLine +} diff --git a/packages/core/test/stubReportApis.ts b/packages/core/test/emulate/stubReportApis.ts similarity index 95% rename from packages/core/test/stubReportApis.ts rename to packages/core/test/emulate/stubReportApis.ts index 4db46b59a0..dd9d940961 100644 --- a/packages/core/test/stubReportApis.ts +++ b/packages/core/test/emulate/stubReportApis.ts @@ -4,8 +4,8 @@ import type { Report, ReportingObserverConstructor, ReportingObserverOption, -} from '../src/domain/report/browser.types' -import { noop } from '../src/tools/utils' +} from '../../src/domain/report/browser.types' +import { noop } from '../../src/tools/utils' export function stubReportingObserver() { const originalReportingObserver = (window as BrowserWindow).ReportingObserver diff --git a/packages/core/test/stubZoneJs.ts b/packages/core/test/emulate/stubZoneJs.ts similarity index 89% rename from packages/core/test/stubZoneJs.ts rename to packages/core/test/emulate/stubZoneJs.ts index 53242c2d23..dccd7070c1 100644 --- a/packages/core/test/stubZoneJs.ts +++ b/packages/core/test/emulate/stubZoneJs.ts @@ -1,4 +1,4 @@ -import type { BrowserWindowWithZoneJs } from '../src/tools/getZoneJsOriginalValue' +import type { BrowserWindowWithZoneJs } from '../../src/tools/getZoneJsOriginalValue' export function stubZoneJs() { const browserWindow = window as BrowserWindowWithZoneJs diff --git a/packages/core/test/syntheticsWorkerValues.ts b/packages/core/test/emulate/syntheticsWorkerValues.ts similarity index 85% rename from packages/core/test/syntheticsWorkerValues.ts rename to packages/core/test/emulate/syntheticsWorkerValues.ts index 48443ed70e..47fb7c564a 100644 --- a/packages/core/test/syntheticsWorkerValues.ts +++ b/packages/core/test/emulate/syntheticsWorkerValues.ts @@ -1,11 +1,11 @@ -import { deleteCookie, setCookie } from '../src/browser/cookie' -import type { BrowserWindow } from '../src/domain/synthetics/syntheticsWorkerValues' +import { deleteCookie, setCookie } from '../../src/browser/cookie' +import type { BrowserWindow } from '../../src/domain/synthetics/syntheticsWorkerValues' import { SYNTHETICS_INJECTS_RUM_COOKIE_NAME, SYNTHETICS_RESULT_ID_COOKIE_NAME, SYNTHETICS_TEST_ID_COOKIE_NAME, -} from '../src/domain/synthetics/syntheticsWorkerValues' -import { ONE_MINUTE } from '../src/tools/utils' +} from '../../src/domain/synthetics/syntheticsWorkerValues' +import { ONE_MINUTE } from '../../src/tools/utils' // Duration to create a cookie lasting at least until the end of the test const COOKIE_DURATION = ONE_MINUTE diff --git a/packages/core/test/emulate/visibilityState.ts b/packages/core/test/emulate/visibilityState.ts new file mode 100644 index 0000000000..4586a97bca --- /dev/null +++ b/packages/core/test/emulate/visibilityState.ts @@ -0,0 +1,12 @@ +export function setPageVisibility(visibility: 'visible' | 'hidden') { + Object.defineProperty(document, 'visibilityState', { + get() { + return visibility + }, + configurable: true, + }) +} + +export function restorePageVisibility() { + delete (document as any).visibilityState +} diff --git a/packages/core/test/emulate/windowOnError.ts b/packages/core/test/emulate/windowOnError.ts new file mode 100644 index 0000000000..e2d237aacc --- /dev/null +++ b/packages/core/test/emulate/windowOnError.ts @@ -0,0 +1,13 @@ +import { instrumentMethod, noop } from '../../src' + +/** + * Opt out of jasmine uncaught error interception during test. This is useful for tests that are + * instrumenting `window.onerror`. See https://github.com/jasmine/jasmine/pull/1860 for more + * information. + */ +export function disableJasmineUncaughtErrorHandler() { + const { stop } = instrumentMethod(window, 'onerror', () => noop) + return { + reset: stop, + } +} diff --git a/packages/core/test/forEach.spec.ts b/packages/core/test/forEach.spec.ts index 1b3b535384..2c9eeb3acc 100644 --- a/packages/core/test/forEach.spec.ts +++ b/packages/core/test/forEach.spec.ts @@ -1,5 +1,4 @@ -import type { BuildEnvWindow } from './specHelper' -import { clearAllCookies } from './specHelper' +import type { BuildEnvWindow } from './buildEnv' beforeEach(() => { ;(window as unknown as BuildEnvWindow).__BUILD_ENV__SDK_VERSION__ = 'test' @@ -13,3 +12,9 @@ beforeEach(() => { afterEach(() => { clearAllCookies() }) + +function clearAllCookies() { + document.cookie.split(';').forEach((c) => { + document.cookie = c.replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/;samesite=strict`) + }) +} diff --git a/packages/core/test/index.ts b/packages/core/test/index.ts index 38a780b548..cdffa284cc 100644 --- a/packages/core/test/index.ts +++ b/packages/core/test/index.ts @@ -1,6 +1,17 @@ -export * from './capturedExceptions' +export * from './browserChecks' +export * from './buildEnv' export * from './collectAsyncCalls' -export * from './stubReportApis' -export * from './stubZoneJs' -export * from './syntheticsWorkerValues' -export * from './specHelper' +export * from './requests' +export * from './emulate/createNewEvent' +export * from './emulate/location' +export * from './emulate/mockClock' +export * from './emulate/stubReportApis' +export * from './emulate/stubZoneJs' +export * from './emulate/syntheticsWorkerValues' +export * from './emulate/visibilityState' +export * from './emulate/navigatorOnLine' +export * from './emulate/navigatorOnLine' +export * from './emulate/eventBridge' +export * from './emulate/eventBridge' +export * from './emulate/windowOnError' +export * from './emulate/cookie' diff --git a/packages/core/test/specHelper.ts b/packages/core/test/requests.ts similarity index 64% rename from packages/core/test/specHelper.ts rename to packages/core/test/requests.ts index a5ecc57553..35b1c22874 100644 --- a/packages/core/test/specHelper.ts +++ b/packages/core/test/requests.ts @@ -1,18 +1,5 @@ -import type { EndpointBuilder } from '../src/domain/configuration' -import { instrumentMethod } from '../src/tools/instrumentMethod' -import { resetNavigationStart } from '../src/tools/timeUtils' -import { buildUrl } from '../src/tools/urlPolyfill' -import { noop, objectEntries, assign } from '../src/tools/utils' -import type { BrowserWindowWithEventBridge } from '../src/transport' - -// to simulate different build env behavior -export interface BuildEnvWindow { - __BUILD_ENV__SDK_VERSION__: string -} - -export function stubEndpointBuilder(url: string) { - return { build: (_: any) => url } as EndpointBuilder -} +import type { EndpointBuilder } from '../src' +import { noop } from '../src' export const SPEC_ENDPOINTS = { logsEndpointBuilder: stubEndpointBuilder('https://logs-intake.com/v1/input/abcde?foo=bar'), @@ -24,70 +11,79 @@ export const SPEC_ENDPOINTS = { }, } -export function isSafari() { - return /^((?!chrome|android).)*safari/i.test(navigator.userAgent) -} - -export function isFirefox() { - return navigator.userAgent.toLowerCase().indexOf('firefox') > -1 +export function stubEndpointBuilder(url: string) { + return { build: (_: any) => url } as EndpointBuilder } -export function clearAllCookies() { - document.cookie.split(';').forEach((c) => { - document.cookie = c.replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/;samesite=strict`) - }) +export interface Request { + type: 'xhr' | 'sendBeacon' | 'fetch' + url: string + body: string } -export type Clock = ReturnType +export function interceptRequests() { + const requests: Request[] = [] + const originalSendBeacon = isSendBeaconSupported() && navigator.sendBeacon.bind(navigator) + const originalRequest = window.Request + const originalFetch = window.fetch + let stubXhrManager: { reset(): void } | undefined -export function mockClock(date?: Date) { - jasmine.clock().install() - jasmine.clock().mockDate(date) - const start = Date.now() - spyOn(performance, 'now').and.callFake(() => Date.now() - start) - spyOnProperty(performance.timing, 'navigationStart', 'get').and.callFake(() => start) - resetNavigationStart() - return { - tick: (ms: number) => jasmine.clock().tick(ms), - setDate: (date: Date) => jasmine.clock().mockDate(date), - cleanup: () => { - jasmine.clock().uninstall() - resetNavigationStart() - }, + spyOn(XMLHttpRequest.prototype, 'open').and.callFake((_, url) => requests.push({ type: 'xhr', url } as Request)) + spyOn(XMLHttpRequest.prototype, 'send').and.callFake((body) => (requests[requests.length - 1].body = body as string)) + if (isSendBeaconSupported()) { + spyOn(navigator, 'sendBeacon').and.callFake((url, body) => { + requests.push({ type: 'sendBeacon', url: url as string, body: body as string }) + return true + }) + } + if (isFetchKeepAliveSupported()) { + spyOn(window, 'fetch').and.callFake((url, config) => { + requests.push({ type: 'fetch', url: url as string, body: config!.body as string }) + return new Promise(() => undefined) + }) } -} -export function mockLocation(initialUrl: string) { - const fakeLocation = buildLocation(initialUrl) - spyOn(history, 'pushState').and.callFake((_: any, __: string, pathname: string) => { - assign(fakeLocation, buildLocation(pathname, fakeLocation.href)) - }) + function isSendBeaconSupported() { + return !!navigator.sendBeacon + } - function hashchangeCallBack() { - fakeLocation.hash = window.location.hash - fakeLocation.href = fakeLocation.href.replace(/#.*/, '') + window.location.hash + function isFetchKeepAliveSupported() { + return 'fetch' in window && 'keepalive' in new window.Request('') } - window.addEventListener('hashchange', hashchangeCallBack) return { - location: fakeLocation, - cleanup: () => { - window.removeEventListener('hashchange', hashchangeCallBack) - window.location.hash = '' + requests, + isSendBeaconSupported, + isFetchKeepAliveSupported, + withSendBeacon(newSendBeacon: any) { + navigator.sendBeacon = newSendBeacon + }, + withRequest(newRequest: any) { + window.Request = newRequest + }, + withFetch(newFetch: any) { + window.fetch = newFetch + }, + withStubXhr(onSend: (xhr: StubXhr) => void) { + stubXhrManager = stubXhr() + StubXhr.onSend = onSend + }, + restore() { + if (originalSendBeacon) { + navigator.sendBeacon = originalSendBeacon + } + if (originalRequest) { + window.Request = originalRequest + } + if (originalFetch) { + window.fetch = originalFetch + } + stubXhrManager?.reset() + StubXhr.onSend = noop }, } } -export function buildLocation(url: string, base = location.href) { - const urlObject = buildUrl(url, base) - return { - hash: urlObject.hash, - href: urlObject.href, - pathname: urlObject.pathname, - search: urlObject.search, - } as Location -} - export interface FetchStubManager { reset: () => void whenAllComplete: (callback: () => void) => void @@ -149,9 +145,6 @@ export interface ResponseStubOptions { bodyUsed?: boolean bodyDisturbed?: boolean } -function notYetImplemented(): never { - throw new Error('not yet implemented') -} export class ResponseStub implements Response { private _body: ReadableStream | undefined @@ -220,22 +213,28 @@ export class ResponseStub implements Response { blob = notYetImplemented formData = notYetImplemented json = notYetImplemented + /* eslint-enable @typescript-eslint/member-ordering */ get ok() { return notYetImplemented() } + get headers() { return notYetImplemented() } + get redirected() { return notYetImplemented() } + get statusText() { return notYetImplemented() } + get trailer() { return notYetImplemented() } + get url() { return notYetImplemented() } @@ -328,31 +327,6 @@ class StubXhr extends StubEventEmitter { } } -export function createNewEvent

>(eventName: 'click', properties?: P): MouseEvent & P -export function createNewEvent

>( - eventName: 'pointerup', - properties?: P -): PointerEvent & P -export function createNewEvent(eventName: string, properties?: { [name: string]: unknown }): Event -export function createNewEvent(eventName: string, properties: { [name: string]: unknown } = {}) { - let event: Event - if (typeof Event === 'function') { - event = new Event(eventName) - } else { - event = document.createEvent('Event') - event.initEvent(eventName, true, true) - } - objectEntries(properties).forEach(([name, value]) => { - // Setting values directly or with a `value` descriptor seems unsupported in IE11 - Object.defineProperty(event, name, { - get() { - return value - }, - }) - }) - return event -} - export function stubXhr() { const originalXhr = XMLHttpRequest @@ -383,140 +357,6 @@ export function withXhr({ setup(xhr as unknown as StubXhr) } -export function setPageVisibility(visibility: 'visible' | 'hidden') { - Object.defineProperty(document, 'visibilityState', { - get() { - return visibility - }, - configurable: true, - }) -} - -export function restorePageVisibility() { - delete (document as any).visibilityState -} - -export function setNavigatorOnLine(onLine: boolean) { - Object.defineProperty(navigator, 'onLine', { - get() { - return onLine - }, - configurable: true, - }) -} - -export function restoreNavigatorOnLine() { - delete (navigator as any).onLine -} - -export function initEventBridgeStub(allowedWebViewHosts: string[] = [window.location.hostname]) { - const eventBridgeStub = { - send: (msg: string) => undefined, - getAllowedWebViewHosts: () => JSON.stringify(allowedWebViewHosts), - } - ;(window as BrowserWindowWithEventBridge).DatadogEventBridge = eventBridgeStub - return eventBridgeStub -} - -export function deleteEventBridgeStub() { - delete (window as BrowserWindowWithEventBridge).DatadogEventBridge -} - -/** - * Opt out of jasmine uncaught error interception during test. This is useful for tests that are - * instrumenting `window.onerror`. See https://github.com/jasmine/jasmine/pull/1860 for more - * information. - */ -export function disableJasmineUncaughtErrorHandler() { - const { stop } = instrumentMethod(window, 'onerror', () => noop) - return { - reset: stop, - } -} - -export function stubCookie() { - let cookie = '' - return { - getSpy: spyOnProperty(Document.prototype, 'cookie', 'get').and.callFake(() => cookie), - setSpy: spyOnProperty(Document.prototype, 'cookie', 'set').and.callFake((newCookie) => { - cookie = newCookie - }), - currentValue: () => cookie, - setCurrentValue: (newCookie: string) => { - cookie = newCookie - }, - } -} - -export interface Request { - type: 'xhr' | 'sendBeacon' | 'fetch' - url: string - body: string -} - -export function interceptRequests() { - const requests: Request[] = [] - const originalSendBeacon = isSendBeaconSupported() && navigator.sendBeacon.bind(navigator) - const originalRequest = window.Request - const originalFetch = window.fetch - let stubXhrManager: { reset(): void } | undefined - - spyOn(XMLHttpRequest.prototype, 'open').and.callFake((_, url) => requests.push({ type: 'xhr', url } as Request)) - spyOn(XMLHttpRequest.prototype, 'send').and.callFake((body) => (requests[requests.length - 1].body = body as string)) - if (isSendBeaconSupported()) { - spyOn(navigator, 'sendBeacon').and.callFake((url, body) => { - requests.push({ type: 'sendBeacon', url: url as string, body: body as string }) - return true - }) - } - if (isFetchKeepAliveSupported()) { - spyOn(window, 'fetch').and.callFake((url, config) => { - requests.push({ type: 'fetch', url: url as string, body: config!.body as string }) - return new Promise(() => undefined) - }) - } - - function isSendBeaconSupported() { - return !!navigator.sendBeacon - } - - function isFetchKeepAliveSupported() { - return 'fetch' in window && 'keepalive' in new window.Request('') - } - - return { - requests, - isSendBeaconSupported, - isFetchKeepAliveSupported, - withSendBeacon(newSendBeacon: any) { - navigator.sendBeacon = newSendBeacon - }, - withRequest(newRequest: any) { - window.Request = newRequest - }, - withFetch(newFetch: any) { - window.fetch = newFetch - }, - withStubXhr(onSend: (xhr: StubXhr) => void) { - stubXhrManager = stubXhr() - StubXhr.onSend = onSend - }, - restore() { - if (originalSendBeacon) { - navigator.sendBeacon = originalSendBeacon - } - if (originalRequest) { - window.Request = originalRequest - } - if (originalFetch) { - window.fetch = originalFetch - } - stubXhrManager?.reset() - StubXhr.onSend = noop - }, - } -} - -export function isAdoptedStyleSheetsSupported() { - return Boolean((document as any).adoptedStyleSheets) +function notYetImplemented(): never { + throw new Error('not yet implemented') }