@openobserve/browser-core 0.2.12-beta.26 → 0.2.12-beta.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/boot/init.js +1 -1
- package/cjs/domain/configuration/endpointBuilder.js +1 -9
- package/cjs/domain/configuration/endpointBuilder.js.map +1 -1
- package/cjs/domain/tags.js +1 -1
- package/cjs/domain/telemetry/telemetry.js +1 -1
- package/cjs/transport/httpRequest.js +0 -4
- package/cjs/transport/httpRequest.js.map +1 -1
- package/esm/boot/init.js +1 -1
- package/esm/domain/configuration/endpointBuilder.js +1 -9
- package/esm/domain/configuration/endpointBuilder.js.map +1 -1
- package/esm/domain/tags.js +1 -1
- package/esm/domain/telemetry/telemetry.js +1 -1
- package/esm/transport/httpRequest.js +0 -4
- package/esm/transport/httpRequest.js.map +1 -1
- package/package.json +2 -2
- package/src/boot/displayAlreadyInitializedError.spec.ts +18 -0
- package/src/boot/init.spec.ts +50 -0
- package/src/browser/addEventListener.spec.ts +141 -0
- package/src/browser/cookie.spec.ts +53 -0
- package/src/browser/fetchObservable.spec.ts +334 -0
- package/src/browser/pageMayExitObservable.spec.ts +56 -0
- package/src/browser/xhrObservable.spec.ts +405 -0
- package/src/domain/allowedTrackingOrigins.spec.ts +210 -0
- package/src/domain/bufferedData.spec.ts +34 -0
- package/src/domain/configuration/configuration.spec.ts +236 -0
- package/src/domain/configuration/endpointBuilder.spec.ts +173 -0
- package/src/domain/configuration/endpointBuilder.ts +1 -9
- package/src/domain/configuration/transportConfiguration.spec.ts +130 -0
- package/src/domain/connectivity/connectivity.spec.ts +50 -0
- package/src/domain/console/consoleObservable.spec.ts +151 -0
- package/src/domain/context/contextManager.spec.ts +152 -0
- package/src/domain/context/contextUtils.spec.ts +26 -0
- package/src/domain/context/storeContextManager.spec.ts +103 -0
- package/src/domain/contexts/accountContext.spec.ts +99 -0
- package/src/domain/contexts/globalContext.spec.ts +88 -0
- package/src/domain/contexts/userContext.spec.ts +156 -0
- package/src/domain/error/error.spec.ts +294 -0
- package/src/domain/error/trackRuntimeError.spec.ts +343 -0
- package/src/domain/eventRateLimiter/createEventRateLimiter.spec.ts +119 -0
- package/src/domain/extension/extensionUtils.spec.ts +47 -0
- package/src/domain/report/reportObservable.spec.ts +71 -0
- package/src/domain/session/oldCookiesMigration.spec.ts +76 -0
- package/src/domain/session/sessionManager.spec.ts +684 -0
- package/src/domain/session/sessionState.spec.ts +88 -0
- package/src/domain/session/sessionStore.spec.ts +647 -0
- package/src/domain/session/sessionStoreOperations.spec.ts +234 -0
- package/src/domain/session/storeStrategies/sessionInCookie.spec.ts +228 -0
- package/src/domain/session/storeStrategies/sessionInLocalStorage.spec.ts +75 -0
- package/src/domain/synthetics/syntheticsWorkerValues.spec.ts +82 -0
- package/src/domain/tags.spec.ts +74 -0
- package/src/domain/telemetry/telemetry.spec.ts +494 -0
- package/src/domain/trackingConsent.spec.ts +44 -0
- package/src/tools/abstractHooks.spec.ts +76 -0
- package/src/tools/abstractLifeCycle.spec.ts +46 -0
- package/src/tools/boundedBuffer.spec.ts +40 -0
- package/src/tools/catchUserErrors.spec.ts +20 -0
- package/src/tools/encoder.spec.ts +112 -0
- package/src/tools/experimentalFeatures.spec.ts +60 -0
- package/src/tools/getZoneJsOriginalValue.spec.ts +36 -0
- package/src/tools/instrumentMethod.spec.ts +381 -0
- package/src/tools/matchOption.spec.ts +41 -0
- package/src/tools/mergeInto.spec.ts +198 -0
- package/src/tools/monitor.spec.ts +172 -0
- package/src/tools/observable.spec.ts +259 -0
- package/src/tools/queueMicrotask.spec.ts +24 -0
- package/src/tools/readBytesFromStream.spec.ts +62 -0
- package/src/tools/requestIdleCallback.spec.ts +65 -0
- package/src/tools/serialisation/jsonStringify.spec.ts +76 -0
- package/src/tools/serialisation/sanitize.spec.ts +284 -0
- package/src/tools/stackTrace/capturedExceptions.specHelper.ts +316 -0
- package/src/tools/stackTrace/computeStackTrace.spec.ts +1007 -0
- package/src/tools/stackTrace/handlingStack.spec.ts +20 -0
- package/src/tools/taskQueue.spec.ts +60 -0
- package/src/tools/timer.spec.ts +76 -0
- package/src/tools/utils/browserDetection.spec.ts +120 -0
- package/src/tools/utils/byteUtils.spec.ts +29 -0
- package/src/tools/utils/functionUtils.spec.ts +229 -0
- package/src/tools/utils/numberUtils.spec.ts +27 -0
- package/src/tools/utils/stringUtils.spec.ts +74 -0
- package/src/tools/utils/typeUtils.spec.ts +25 -0
- package/src/tools/utils/urlPolyfill.spec.ts +55 -0
- package/src/tools/valueHistory.spec.ts +180 -0
- package/src/transport/batch.spec.ts +261 -0
- package/src/transport/eventBridge.spec.ts +90 -0
- package/src/transport/flushController.spec.ts +267 -0
- package/src/transport/httpRequest.spec.ts +400 -0
- package/src/transport/httpRequest.ts +3 -7
- package/src/transport/sendWithRetryStrategy.spec.ts +393 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { AbstractLifeCycle } from './abstractLifeCycle'
|
|
2
|
+
|
|
3
|
+
describe('AbstractLifeCycle', () => {
|
|
4
|
+
const LifeCycle = AbstractLifeCycle<{
|
|
5
|
+
foo: 'bar'
|
|
6
|
+
no_data: void
|
|
7
|
+
}>
|
|
8
|
+
|
|
9
|
+
it('does nothing when notifying without subscribers', () => {
|
|
10
|
+
const lifeCycle = new LifeCycle()
|
|
11
|
+
expect(() => lifeCycle.notify('foo', 'bar')).not.toThrow()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('notifies subscribers', () => {
|
|
15
|
+
const lifeCycle = new LifeCycle()
|
|
16
|
+
const subscriber1Spy = jasmine.createSpy()
|
|
17
|
+
const subscriber2Spy = jasmine.createSpy()
|
|
18
|
+
lifeCycle.subscribe('foo', subscriber1Spy)
|
|
19
|
+
lifeCycle.subscribe('foo', subscriber2Spy)
|
|
20
|
+
|
|
21
|
+
lifeCycle.notify('foo', 'bar')
|
|
22
|
+
|
|
23
|
+
expect(subscriber1Spy).toHaveBeenCalledOnceWith('bar')
|
|
24
|
+
expect(subscriber2Spy).toHaveBeenCalledOnceWith('bar')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('notifies subscribers for events without data', () => {
|
|
28
|
+
const lifeCycle = new LifeCycle()
|
|
29
|
+
const subscriberSpy = jasmine.createSpy()
|
|
30
|
+
lifeCycle.subscribe('no_data', subscriberSpy)
|
|
31
|
+
|
|
32
|
+
lifeCycle.notify('no_data')
|
|
33
|
+
|
|
34
|
+
expect(subscriberSpy).toHaveBeenCalledOnceWith(undefined)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('does not notify unsubscribed subscribers', () => {
|
|
38
|
+
const lifeCycle = new LifeCycle()
|
|
39
|
+
const subscriberSpy = jasmine.createSpy()
|
|
40
|
+
lifeCycle.subscribe('foo', subscriberSpy).unsubscribe()
|
|
41
|
+
|
|
42
|
+
lifeCycle.notify('foo', 'bar')
|
|
43
|
+
|
|
44
|
+
expect(subscriberSpy).not.toHaveBeenCalled()
|
|
45
|
+
})
|
|
46
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createBoundedBuffer } from './boundedBuffer'
|
|
2
|
+
|
|
3
|
+
describe('BoundedBuffer', () => {
|
|
4
|
+
it('collect and drain the callbacks', () => {
|
|
5
|
+
const spy = jasmine.createSpy<() => void>()
|
|
6
|
+
const buffered = createBoundedBuffer()
|
|
7
|
+
|
|
8
|
+
buffered.add(spy)
|
|
9
|
+
expect(spy.calls.count()).toBe(0)
|
|
10
|
+
|
|
11
|
+
buffered.drain()
|
|
12
|
+
expect(spy.calls.count()).toBe(1)
|
|
13
|
+
|
|
14
|
+
buffered.drain()
|
|
15
|
+
expect(spy.calls.count()).toBe(1)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('store at most 500 callbacks', () => {
|
|
19
|
+
const spy = jasmine.createSpy<() => void>()
|
|
20
|
+
const buffered = createBoundedBuffer()
|
|
21
|
+
const limit = 500
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < limit + 1; i += 1) {
|
|
24
|
+
buffered.add(spy)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
buffered.drain()
|
|
28
|
+
expect(spy.calls.count()).toEqual(limit)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('removes a callback', () => {
|
|
32
|
+
const spy = jasmine.createSpy<() => void>()
|
|
33
|
+
const buffered = createBoundedBuffer()
|
|
34
|
+
|
|
35
|
+
buffered.add(spy)
|
|
36
|
+
buffered.remove(spy)
|
|
37
|
+
buffered.drain()
|
|
38
|
+
expect(spy).not.toHaveBeenCalled()
|
|
39
|
+
})
|
|
40
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { catchUserErrors } from './catchUserErrors'
|
|
2
|
+
import { display } from './display'
|
|
3
|
+
|
|
4
|
+
describe('catchUserErrors', () => {
|
|
5
|
+
it('returns the same result as the original function', () => {
|
|
6
|
+
const wrappedFn = catchUserErrors((a: number, b: number) => a + b, 'Error during callback')
|
|
7
|
+
expect(wrappedFn(10, 2)).toBe(12)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('logs errors using console.error and returns undefined', () => {
|
|
11
|
+
const displaySpy = spyOn(display, 'error')
|
|
12
|
+
const myError = 'Ooops!'
|
|
13
|
+
const wrappedFn = catchUserErrors(() => {
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
15
|
+
throw myError
|
|
16
|
+
}, 'Error during callback')
|
|
17
|
+
expect(wrappedFn()).toBe(undefined)
|
|
18
|
+
expect(displaySpy).toHaveBeenCalledWith('Error during callback', myError)
|
|
19
|
+
})
|
|
20
|
+
})
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createIdentityEncoder } from './encoder'
|
|
2
|
+
import { noop } from './utils/functionUtils'
|
|
3
|
+
|
|
4
|
+
describe('createIdentityEncoder', () => {
|
|
5
|
+
it('creates an encoder with initial values', () => {
|
|
6
|
+
const encoder = createIdentityEncoder()
|
|
7
|
+
|
|
8
|
+
expect(encoder.isEmpty).toBe(true)
|
|
9
|
+
expect(encoder.finishSync()).toEqual({
|
|
10
|
+
output: '',
|
|
11
|
+
outputBytesCount: 0,
|
|
12
|
+
rawBytesCount: 0,
|
|
13
|
+
pendingData: '',
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe('write()', () => {
|
|
18
|
+
it('writes data to the encoder', () => {
|
|
19
|
+
const encoder = createIdentityEncoder()
|
|
20
|
+
const data = 'Hello, world!'
|
|
21
|
+
|
|
22
|
+
encoder.write(data)
|
|
23
|
+
|
|
24
|
+
expect(encoder.isEmpty).toBe(false)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('calls the callback when writing data with a callback', () => {
|
|
28
|
+
const encoder = createIdentityEncoder()
|
|
29
|
+
const data = 'Callback test'
|
|
30
|
+
const callbackSpy = jasmine.createSpy()
|
|
31
|
+
|
|
32
|
+
encoder.write(data, callbackSpy)
|
|
33
|
+
|
|
34
|
+
expect(callbackSpy).toHaveBeenCalledOnceWith(data.length)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('finish()', () => {
|
|
39
|
+
it('calls the callback with the result', () => {
|
|
40
|
+
const encoder = createIdentityEncoder()
|
|
41
|
+
const data = 'Final data'
|
|
42
|
+
encoder.write(data)
|
|
43
|
+
const callbackSpy = jasmine.createSpy()
|
|
44
|
+
|
|
45
|
+
encoder.finish(callbackSpy)
|
|
46
|
+
|
|
47
|
+
expect(callbackSpy).toHaveBeenCalledWith({
|
|
48
|
+
output: data,
|
|
49
|
+
outputBytesCount: data.length,
|
|
50
|
+
rawBytesCount: data.length,
|
|
51
|
+
pendingData: '',
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('after calling finish(), the encoder should be considered empty', () => {
|
|
56
|
+
const encoder = createIdentityEncoder()
|
|
57
|
+
encoder.write('Some data')
|
|
58
|
+
|
|
59
|
+
encoder.finish(noop)
|
|
60
|
+
|
|
61
|
+
expect(encoder.isEmpty).toBe(true)
|
|
62
|
+
expect(encoder.finishSync()).toEqual({
|
|
63
|
+
output: '',
|
|
64
|
+
outputBytesCount: 0,
|
|
65
|
+
rawBytesCount: 0,
|
|
66
|
+
pendingData: '',
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('finishSync()', () => {
|
|
72
|
+
it('returns the encoder result', () => {
|
|
73
|
+
const encoder = createIdentityEncoder()
|
|
74
|
+
const data = 'Hello, world!'
|
|
75
|
+
|
|
76
|
+
encoder.write(data)
|
|
77
|
+
|
|
78
|
+
expect(encoder.finishSync()).toEqual({
|
|
79
|
+
output: data,
|
|
80
|
+
outputBytesCount: data.length,
|
|
81
|
+
rawBytesCount: data.length,
|
|
82
|
+
pendingData: '',
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('after calling finish(), the encoder should be considered empty', () => {
|
|
87
|
+
const encoder = createIdentityEncoder()
|
|
88
|
+
encoder.write('Some data')
|
|
89
|
+
|
|
90
|
+
encoder.finishSync()
|
|
91
|
+
|
|
92
|
+
expect(encoder.isEmpty).toBe(true)
|
|
93
|
+
expect(encoder.finishSync()).toEqual({
|
|
94
|
+
output: '',
|
|
95
|
+
outputBytesCount: 0,
|
|
96
|
+
rawBytesCount: 0,
|
|
97
|
+
pendingData: '',
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('estimateEncodedBytesCount()', () => {
|
|
103
|
+
it('estimates encoded bytes count accurately', () => {
|
|
104
|
+
const encoder = createIdentityEncoder()
|
|
105
|
+
const data = 'Estimation test'
|
|
106
|
+
|
|
107
|
+
const estimatedBytes = encoder.estimateEncodedBytesCount(data)
|
|
108
|
+
|
|
109
|
+
expect(estimatedBytes).toBe(data.length)
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ExperimentalFeature,
|
|
3
|
+
addExperimentalFeatures,
|
|
4
|
+
initFeatureFlags,
|
|
5
|
+
isExperimentalFeatureEnabled,
|
|
6
|
+
resetExperimentalFeatures,
|
|
7
|
+
} from './experimentalFeatures'
|
|
8
|
+
|
|
9
|
+
const TEST_FEATURE_FLAG_ONE = 'foo' as ExperimentalFeature
|
|
10
|
+
const TEST_FEATURE_FLAG_TWO = 'bar' as ExperimentalFeature
|
|
11
|
+
|
|
12
|
+
describe('experimentalFeatures', () => {
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
resetExperimentalFeatures()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('initial state is empty', () => {
|
|
18
|
+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_ONE)).toBeFalse()
|
|
19
|
+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_TWO)).toBeFalse()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should define enabled experimental features', () => {
|
|
23
|
+
addExperimentalFeatures([TEST_FEATURE_FLAG_ONE])
|
|
24
|
+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_ONE)).toBeTrue()
|
|
25
|
+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_TWO)).toBeFalse()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('should allow to be shared between products', () => {
|
|
29
|
+
addExperimentalFeatures([TEST_FEATURE_FLAG_ONE])
|
|
30
|
+
addExperimentalFeatures([TEST_FEATURE_FLAG_TWO])
|
|
31
|
+
|
|
32
|
+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_ONE)).toBeTrue()
|
|
33
|
+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_TWO)).toBeTrue()
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('initFeatureFlags', () => {
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
;(ExperimentalFeature as any).FOO = TEST_FEATURE_FLAG_ONE
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
delete (ExperimentalFeature as any).FOO
|
|
44
|
+
resetExperimentalFeatures()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('ignores unknown experimental features', () => {
|
|
48
|
+
initFeatureFlags(['bar', undefined as any, null as any, 11 as any])
|
|
49
|
+
|
|
50
|
+
expect(isExperimentalFeatureEnabled('bar' as any)).toBeFalse()
|
|
51
|
+
expect(isExperimentalFeatureEnabled(undefined as any)).toBeFalse()
|
|
52
|
+
expect(isExperimentalFeatureEnabled(null as any)).toBeFalse()
|
|
53
|
+
expect(isExperimentalFeatureEnabled(11 as any)).toBeFalse()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('updates experimental feature flags', () => {
|
|
57
|
+
initFeatureFlags(['foo'])
|
|
58
|
+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_ONE)).toBeTrue()
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { mockZoneJs } from '../../test'
|
|
2
|
+
|
|
3
|
+
import type { BrowserWindowWithZoneJs } from './getZoneJsOriginalValue'
|
|
4
|
+
import { getZoneJsOriginalValue } from './getZoneJsOriginalValue'
|
|
5
|
+
import { noop } from './utils/functionUtils'
|
|
6
|
+
|
|
7
|
+
describe('getZoneJsOriginalValue', () => {
|
|
8
|
+
function originalValue() {
|
|
9
|
+
// just a function that does nothing but different than 'noop' as we'll want to differentiate
|
|
10
|
+
// them.
|
|
11
|
+
}
|
|
12
|
+
const object = {
|
|
13
|
+
name: originalValue,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
it('returns the original value directly if Zone is not not defined', () => {
|
|
17
|
+
expect(getZoneJsOriginalValue(object, 'name')).toBe(originalValue)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it("returns the original value if Zone is defined but didn't patch that method", () => {
|
|
21
|
+
mockZoneJs()
|
|
22
|
+
expect(getZoneJsOriginalValue(object, 'name')).toBe(originalValue)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('returns the original value if Zone is defined but does not define the __symbol__ function', () => {
|
|
26
|
+
mockZoneJs()
|
|
27
|
+
delete (window as BrowserWindowWithZoneJs).Zone!.__symbol__
|
|
28
|
+
expect(getZoneJsOriginalValue(object, 'name')).toBe(originalValue)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('returns the original value if Zone did patch the method', () => {
|
|
32
|
+
const zoneJs = mockZoneJs()
|
|
33
|
+
zoneJs.replaceProperty(object, 'name', noop)
|
|
34
|
+
expect(getZoneJsOriginalValue(object, 'name')).toBe(originalValue)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { mockClock, mockZoneJs } from '../../test'
|
|
2
|
+
import type { Clock, MockZoneJs } from '../../test'
|
|
3
|
+
import type { InstrumentedMethodCall } from './instrumentMethod'
|
|
4
|
+
import { instrumentMethod, instrumentSetter } from './instrumentMethod'
|
|
5
|
+
import { noop } from './utils/functionUtils'
|
|
6
|
+
|
|
7
|
+
describe('instrumentMethod', () => {
|
|
8
|
+
const THIRD_PARTY_RESULT = 42
|
|
9
|
+
|
|
10
|
+
it('replaces the original method', () => {
|
|
11
|
+
const original = () => 1
|
|
12
|
+
const object = { method: original }
|
|
13
|
+
|
|
14
|
+
instrumentMethod(object, 'method', noop)
|
|
15
|
+
|
|
16
|
+
expect(object.method).not.toBe(original)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('calls the instrumentation before the original method', () => {
|
|
20
|
+
const originalSpy = jasmine.createSpy()
|
|
21
|
+
const instrumentationSpy = jasmine.createSpy()
|
|
22
|
+
const object = { method: originalSpy }
|
|
23
|
+
|
|
24
|
+
instrumentMethod(object, 'method', instrumentationSpy)
|
|
25
|
+
|
|
26
|
+
object.method()
|
|
27
|
+
|
|
28
|
+
expect(instrumentationSpy).toHaveBeenCalledBefore(originalSpy)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('does not set a method originally undefined', () => {
|
|
32
|
+
const object: { method?: () => number } = {}
|
|
33
|
+
|
|
34
|
+
instrumentMethod(object, 'method', noop)
|
|
35
|
+
|
|
36
|
+
expect(object.method).toBeUndefined()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('sets an event handler even if it was originally undefined', () => {
|
|
40
|
+
const object: { onevent?: () => void } = { onevent: undefined }
|
|
41
|
+
|
|
42
|
+
const instrumentationSpy = jasmine.createSpy()
|
|
43
|
+
instrumentMethod(object, 'onevent', instrumentationSpy)
|
|
44
|
+
|
|
45
|
+
expect(object.onevent).toBeDefined()
|
|
46
|
+
|
|
47
|
+
object.onevent!()
|
|
48
|
+
expect(instrumentationSpy).toHaveBeenCalled()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('do not set an event handler even if the event is not supported (i.e. property does not exist on object)', () => {
|
|
52
|
+
const object: { onevent?: () => void } = {}
|
|
53
|
+
|
|
54
|
+
const instrumentationSpy = jasmine.createSpy()
|
|
55
|
+
instrumentMethod(object, 'onevent', instrumentationSpy)
|
|
56
|
+
|
|
57
|
+
expect('onevent' in object).toBeFalse()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('calls the instrumentation with method target and parameters', () => {
|
|
61
|
+
const object = { method: (a: number, b: number) => a + b }
|
|
62
|
+
const instrumentationSpy = jasmine.createSpy<(call: InstrumentedMethodCall<typeof object, 'method'>) => void>()
|
|
63
|
+
instrumentMethod(object, 'method', instrumentationSpy)
|
|
64
|
+
|
|
65
|
+
object.method(2, 3)
|
|
66
|
+
|
|
67
|
+
expect(instrumentationSpy).toHaveBeenCalledOnceWith({
|
|
68
|
+
target: object,
|
|
69
|
+
parameters: jasmine.any(Object),
|
|
70
|
+
onPostCall: jasmine.any(Function),
|
|
71
|
+
handlingStack: undefined,
|
|
72
|
+
})
|
|
73
|
+
expect(instrumentationSpy.calls.mostRecent().args[0].parameters[0]).toBe(2)
|
|
74
|
+
expect(instrumentationSpy.calls.mostRecent().args[0].parameters[1]).toBe(3)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('allows replacing a parameter', () => {
|
|
78
|
+
const object = { method: (a: number) => a }
|
|
79
|
+
instrumentMethod(object, 'method', ({ parameters }) => {
|
|
80
|
+
parameters[0] = 2
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
expect(object.method(1)).toBe(2)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('allows adding a parameter', () => {
|
|
87
|
+
const object = { method: (a?: number) => a }
|
|
88
|
+
instrumentMethod(object, 'method', ({ parameters }) => {
|
|
89
|
+
parameters[0] = 2
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
expect(object.method()).toBe(2)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('calls the "onPostCall" callback with the original method result', () => {
|
|
96
|
+
const object = { method: () => 1 }
|
|
97
|
+
const onPostCallSpy = jasmine.createSpy()
|
|
98
|
+
instrumentMethod(object, 'method', ({ onPostCall }) => onPostCall(onPostCallSpy))
|
|
99
|
+
|
|
100
|
+
object.method()
|
|
101
|
+
|
|
102
|
+
expect(onPostCallSpy).toHaveBeenCalledOnceWith(1)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('allows other instrumentations from third parties', () => {
|
|
106
|
+
const object = { method: () => 1 }
|
|
107
|
+
const instrumentationSpy = jasmine.createSpy()
|
|
108
|
+
instrumentMethod(object, 'method', instrumentationSpy)
|
|
109
|
+
|
|
110
|
+
thirdPartyInstrumentation(object)
|
|
111
|
+
|
|
112
|
+
expect(object.method()).toBe(THIRD_PARTY_RESULT)
|
|
113
|
+
expect(instrumentationSpy).toHaveBeenCalled()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('computes the handling stack', () => {
|
|
117
|
+
const object = { method: () => 1 }
|
|
118
|
+
const instrumentationSpy = jasmine.createSpy()
|
|
119
|
+
instrumentMethod(object, 'method', instrumentationSpy, { computeHandlingStack: true })
|
|
120
|
+
|
|
121
|
+
function foo() {
|
|
122
|
+
object.method()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
foo()
|
|
126
|
+
|
|
127
|
+
expect(instrumentationSpy.calls.mostRecent().args[0].handlingStack).toEqual(
|
|
128
|
+
jasmine.stringMatching(/^HandlingStack: instrumented method\n {2}at foo @/)
|
|
129
|
+
)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
describe('stop()', () => {
|
|
133
|
+
it('does not call the instrumentation anymore', () => {
|
|
134
|
+
const object = { method: () => 1 }
|
|
135
|
+
const instrumentationSpy = jasmine.createSpy()
|
|
136
|
+
const { stop } = instrumentMethod(object, 'method', () => instrumentationSpy)
|
|
137
|
+
|
|
138
|
+
stop()
|
|
139
|
+
|
|
140
|
+
object.method()
|
|
141
|
+
expect(instrumentationSpy).not.toHaveBeenCalled()
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
describe('when the method has been instrumented by a third party', () => {
|
|
145
|
+
it('should not break the third party instrumentation', () => {
|
|
146
|
+
const object = { method: () => 1 }
|
|
147
|
+
const { stop } = instrumentMethod(object, 'method', noop)
|
|
148
|
+
|
|
149
|
+
thirdPartyInstrumentation(object)
|
|
150
|
+
const instrumentedMethod = object.method
|
|
151
|
+
|
|
152
|
+
stop()
|
|
153
|
+
|
|
154
|
+
expect(object.method).toBe(instrumentedMethod)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('does not call the instrumentation', () => {
|
|
158
|
+
const object = { method: () => 1 }
|
|
159
|
+
const instrumentationSpy = jasmine.createSpy()
|
|
160
|
+
const { stop } = instrumentMethod(object, 'method', instrumentationSpy)
|
|
161
|
+
|
|
162
|
+
thirdPartyInstrumentation(object)
|
|
163
|
+
|
|
164
|
+
stop()
|
|
165
|
+
|
|
166
|
+
expect(instrumentationSpy).not.toHaveBeenCalled()
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('should not throw errors if original method was undefined', () => {
|
|
170
|
+
const object: { onevent?: () => number } = {}
|
|
171
|
+
const instrumentationStub = () => 2
|
|
172
|
+
const { stop } = instrumentMethod(object, 'onevent', instrumentationStub)
|
|
173
|
+
|
|
174
|
+
thirdPartyInstrumentation(object)
|
|
175
|
+
|
|
176
|
+
stop()
|
|
177
|
+
|
|
178
|
+
expect(object.onevent).not.toThrow()
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
function thirdPartyInstrumentation(object: { method?: () => number; onevent?: () => void }) {
|
|
184
|
+
const originalMethod = object.method
|
|
185
|
+
if (typeof originalMethod === 'function') {
|
|
186
|
+
object.method = () => {
|
|
187
|
+
originalMethod()
|
|
188
|
+
return THIRD_PARTY_RESULT
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const originalOnEvent = object.onevent
|
|
193
|
+
object.onevent = () => {
|
|
194
|
+
if (originalOnEvent) {
|
|
195
|
+
originalOnEvent()
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
describe('instrumentSetter', () => {
|
|
202
|
+
let clock: Clock
|
|
203
|
+
let zoneJs: MockZoneJs
|
|
204
|
+
|
|
205
|
+
beforeEach(() => {
|
|
206
|
+
clock = mockClock()
|
|
207
|
+
zoneJs = mockZoneJs()
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('replaces the original setter', () => {
|
|
211
|
+
const originalSetter = () => {
|
|
212
|
+
// do nothing particular, only used to test if this setter gets replaced
|
|
213
|
+
}
|
|
214
|
+
const object = {} as { foo: number }
|
|
215
|
+
Object.defineProperty(object, 'foo', { set: originalSetter, configurable: true })
|
|
216
|
+
|
|
217
|
+
instrumentSetter(object, 'foo', noop)
|
|
218
|
+
|
|
219
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
220
|
+
expect(Object.getOwnPropertyDescriptor(object, 'foo')!.set).not.toBe(originalSetter)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('skips instrumentation if there is no original setter', () => {
|
|
224
|
+
const object = { foo: 1 }
|
|
225
|
+
|
|
226
|
+
instrumentSetter(object, 'foo', noop)
|
|
227
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
228
|
+
expect(Object.getOwnPropertyDescriptor(object, 'foo')!.set).toBeUndefined()
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('skips instrumentation if the descriptor is not configurable', () => {
|
|
232
|
+
const originalSetter = () => {
|
|
233
|
+
// do nothing particular, only used to test if this setter gets replaced
|
|
234
|
+
}
|
|
235
|
+
const object = {} as { foo: number }
|
|
236
|
+
Object.defineProperty(object, 'foo', { set: originalSetter, configurable: false })
|
|
237
|
+
|
|
238
|
+
instrumentSetter(object, 'foo', noop)
|
|
239
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
240
|
+
expect(Object.getOwnPropertyDescriptor(object, 'foo')!.set).toBe(originalSetter)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('calls the original setter', () => {
|
|
244
|
+
const originalSetterSpy = jasmine.createSpy()
|
|
245
|
+
const object = {} as { foo: number }
|
|
246
|
+
Object.defineProperty(object, 'foo', { set: originalSetterSpy, configurable: true })
|
|
247
|
+
|
|
248
|
+
instrumentSetter(object, 'foo', noop)
|
|
249
|
+
|
|
250
|
+
object.foo = 1
|
|
251
|
+
expect(originalSetterSpy).toHaveBeenCalledOnceWith(1)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('calls the instrumentation asynchronously', () => {
|
|
255
|
+
const instrumentationSetterSpy = jasmine.createSpy()
|
|
256
|
+
const object = {} as { foo: number }
|
|
257
|
+
Object.defineProperty(object, 'foo', { set: noop, configurable: true })
|
|
258
|
+
|
|
259
|
+
instrumentSetter(object, 'foo', instrumentationSetterSpy)
|
|
260
|
+
|
|
261
|
+
object.foo = 1
|
|
262
|
+
expect(instrumentationSetterSpy).not.toHaveBeenCalled()
|
|
263
|
+
clock.tick(0)
|
|
264
|
+
expect(instrumentationSetterSpy).toHaveBeenCalledOnceWith(object, 1)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('does not use the Zone.js setTimeout function', () => {
|
|
268
|
+
const zoneJsSetTimeoutSpy = jasmine.createSpy()
|
|
269
|
+
zoneJs.replaceProperty(window, 'setTimeout', zoneJsSetTimeoutSpy)
|
|
270
|
+
|
|
271
|
+
const object = {} as { foo: number }
|
|
272
|
+
Object.defineProperty(object, 'foo', { set: noop, configurable: true })
|
|
273
|
+
|
|
274
|
+
instrumentSetter(object, 'foo', noop)
|
|
275
|
+
object.foo = 2
|
|
276
|
+
|
|
277
|
+
clock.tick(0)
|
|
278
|
+
|
|
279
|
+
expect(zoneJsSetTimeoutSpy).not.toHaveBeenCalled()
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('allows other instrumentations from third parties', () => {
|
|
283
|
+
const object = {} as { foo: number }
|
|
284
|
+
Object.defineProperty(object, 'foo', { set: noop, configurable: true })
|
|
285
|
+
const instrumentationSetterSpy = jasmine.createSpy()
|
|
286
|
+
instrumentSetter(object, 'foo', instrumentationSetterSpy)
|
|
287
|
+
|
|
288
|
+
const thirdPartyInstrumentationSpy = thirdPartyInstrumentation(object)
|
|
289
|
+
|
|
290
|
+
object.foo = 2
|
|
291
|
+
expect(thirdPartyInstrumentationSpy).toHaveBeenCalledOnceWith(2)
|
|
292
|
+
clock.tick(0)
|
|
293
|
+
expect(instrumentationSetterSpy).toHaveBeenCalledOnceWith(object, 2)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
describe('stop()', () => {
|
|
297
|
+
it('restores the original behavior', () => {
|
|
298
|
+
const object = {} as { foo: number }
|
|
299
|
+
const originalSetter = () => {
|
|
300
|
+
// do nothing particular, only used to test if this setter gets replaced
|
|
301
|
+
}
|
|
302
|
+
Object.defineProperty(object, 'foo', { set: originalSetter, configurable: true })
|
|
303
|
+
const { stop } = instrumentSetter(object, 'foo', noop)
|
|
304
|
+
|
|
305
|
+
stop()
|
|
306
|
+
|
|
307
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
308
|
+
expect(Object.getOwnPropertyDescriptor(object, 'foo')!.set).toBe(originalSetter)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it('does not call the instrumentation anymore', () => {
|
|
312
|
+
const object = {} as { foo: number }
|
|
313
|
+
Object.defineProperty(object, 'foo', { set: noop, configurable: true })
|
|
314
|
+
const instrumentationSetterSpy = jasmine.createSpy()
|
|
315
|
+
const { stop } = instrumentSetter(object, 'foo', instrumentationSetterSpy)
|
|
316
|
+
|
|
317
|
+
stop()
|
|
318
|
+
|
|
319
|
+
object.foo = 2
|
|
320
|
+
clock.tick(0)
|
|
321
|
+
|
|
322
|
+
expect(instrumentationSetterSpy).not.toHaveBeenCalled()
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('does not call instrumentation pending in the event loop via setTimeout', () => {
|
|
326
|
+
const object = {} as { foo: number }
|
|
327
|
+
Object.defineProperty(object, 'foo', { set: noop, configurable: true })
|
|
328
|
+
const instrumentationSetterSpy = jasmine.createSpy()
|
|
329
|
+
const { stop } = instrumentSetter(object, 'foo', instrumentationSetterSpy)
|
|
330
|
+
|
|
331
|
+
object.foo = 2
|
|
332
|
+
stop()
|
|
333
|
+
clock.tick(0)
|
|
334
|
+
|
|
335
|
+
expect(instrumentationSetterSpy).not.toHaveBeenCalled()
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
describe('when the method has been instrumented by a third party', () => {
|
|
339
|
+
it('should not break the third party instrumentation', () => {
|
|
340
|
+
const object = {} as { foo: number }
|
|
341
|
+
Object.defineProperty(object, 'foo', { set: noop, configurable: true })
|
|
342
|
+
const { stop } = instrumentSetter(object, 'foo', noop)
|
|
343
|
+
|
|
344
|
+
const thirdPartyInstrumentationSpy = thirdPartyInstrumentation(object)
|
|
345
|
+
|
|
346
|
+
stop()
|
|
347
|
+
|
|
348
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
349
|
+
expect(Object.getOwnPropertyDescriptor(object, 'foo')!.set).toBe(thirdPartyInstrumentationSpy)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
it('does not call the instrumentation', () => {
|
|
353
|
+
const object = {} as { foo: number }
|
|
354
|
+
Object.defineProperty(object, 'foo', { set: noop, configurable: true })
|
|
355
|
+
const instrumentationSetterSpy = jasmine.createSpy()
|
|
356
|
+
const { stop } = instrumentSetter(object, 'foo', instrumentationSetterSpy)
|
|
357
|
+
|
|
358
|
+
thirdPartyInstrumentation(object)
|
|
359
|
+
|
|
360
|
+
stop()
|
|
361
|
+
|
|
362
|
+
object.foo = 2
|
|
363
|
+
clock.tick(0)
|
|
364
|
+
|
|
365
|
+
expect(instrumentationSetterSpy).not.toHaveBeenCalled()
|
|
366
|
+
})
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
function thirdPartyInstrumentation(object: { foo: number }) {
|
|
371
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
372
|
+
const originalSetter = Object.getOwnPropertyDescriptor(object, 'foo')!.set
|
|
373
|
+
const thirdPartyInstrumentationSpy = jasmine.createSpy().and.callFake(function (this: any, value) {
|
|
374
|
+
if (originalSetter) {
|
|
375
|
+
originalSetter.call(this, value)
|
|
376
|
+
}
|
|
377
|
+
})
|
|
378
|
+
Object.defineProperty(object, 'foo', { set: thirdPartyInstrumentationSpy })
|
|
379
|
+
return thirdPartyInstrumentationSpy
|
|
380
|
+
}
|
|
381
|
+
})
|