@openobserve/browser-core 0.3.3 → 0.3.4
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 -1
- package/cjs/domain/tags.js +1 -1
- package/cjs/domain/telemetry/telemetry.js +1 -1
- package/cjs/domain/telemetry/telemetryEvent.types.d.ts +1 -1
- package/esm/boot/init.js +1 -1
- package/esm/domain/configuration/endpointBuilder.js +1 -1
- package/esm/domain/tags.js +1 -1
- package/esm/domain/telemetry/telemetry.js +1 -1
- package/esm/domain/telemetry/telemetryEvent.types.d.ts +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/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/telemetry/telemetryEvent.types.ts +1 -1
- 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/sendWithRetryStrategy.spec.ts +393 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readBytesFromStream } from './readBytesFromStream'
|
|
2
|
+
|
|
3
|
+
describe('readBytesFromStream', () => {
|
|
4
|
+
const str = 'Lorem ipsum dolor sit amet.'
|
|
5
|
+
let stream: ReadableStream
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
stream = new ReadableStream({
|
|
9
|
+
start: (controller) => {
|
|
10
|
+
controller.enqueue(new TextEncoder().encode(str))
|
|
11
|
+
controller.close()
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should read full stream', async () => {
|
|
17
|
+
const bytes = await readBytesFromStream(stream, {
|
|
18
|
+
collectStreamBody: true,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
expect(bytes?.length).toBe(27)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should read full stream without body', async () => {
|
|
25
|
+
const bytes = await readBytesFromStream(stream, {
|
|
26
|
+
collectStreamBody: false,
|
|
27
|
+
})
|
|
28
|
+
expect(bytes).toBeUndefined()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should handle rejection error on read', async () => {
|
|
32
|
+
const stream = new ReadableStream({
|
|
33
|
+
start: (controller) => {
|
|
34
|
+
controller.error(new Error('foo'))
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await readBytesFromStream(stream, {
|
|
40
|
+
collectStreamBody: true,
|
|
41
|
+
})
|
|
42
|
+
fail('Should have thrown an error')
|
|
43
|
+
} catch (error) {
|
|
44
|
+
expect(error).toEqual(jasmine.any(Error))
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should handle rejection error on cancel', async () => {
|
|
49
|
+
const stream = new ReadableStream({
|
|
50
|
+
start: (controller) => {
|
|
51
|
+
controller.enqueue(new TextEncoder().encode('f'))
|
|
52
|
+
controller.close()
|
|
53
|
+
},
|
|
54
|
+
cancel: () => Promise.reject(new Error('foo')),
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const bytes = await readBytesFromStream(stream, {
|
|
58
|
+
collectStreamBody: true,
|
|
59
|
+
})
|
|
60
|
+
expect(bytes).toBeDefined()
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { mockClock, registerCleanupTask, type Clock } from '../../test'
|
|
2
|
+
import { MAX_TASK_TIME, requestIdleCallbackShim, requestIdleCallback } from './requestIdleCallback'
|
|
3
|
+
|
|
4
|
+
describe('requestIdleCallback', () => {
|
|
5
|
+
it('fallbacks to the shim when requestIdleCallback is not available', () => {
|
|
6
|
+
const clock = mockClock()
|
|
7
|
+
removeGlobalRequestIdleCallback()
|
|
8
|
+
|
|
9
|
+
const spy = jasmine.createSpy<(deadline: IdleDeadline) => void>()
|
|
10
|
+
|
|
11
|
+
requestIdleCallback(spy)
|
|
12
|
+
expect(spy).not.toHaveBeenCalled()
|
|
13
|
+
|
|
14
|
+
clock.tick(0)
|
|
15
|
+
expect(spy).toHaveBeenCalledTimes(1)
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('requestIdleCallbackShim', () => {
|
|
20
|
+
let clock: Clock
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
clock = mockClock()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('calls the callback asynchronously', () => {
|
|
27
|
+
const spy = jasmine.createSpy<(deadline: IdleDeadline) => void>()
|
|
28
|
+
requestIdleCallbackShim(spy)
|
|
29
|
+
expect(spy).not.toHaveBeenCalled()
|
|
30
|
+
clock.tick(0)
|
|
31
|
+
expect(spy).toHaveBeenCalledOnceWith({ didTimeout: false, timeRemaining: jasmine.any(Function) })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('notifies the remaining time', () => {
|
|
35
|
+
const spy = jasmine.createSpy<(deadline: IdleDeadline) => void>()
|
|
36
|
+
requestIdleCallbackShim(spy)
|
|
37
|
+
|
|
38
|
+
clock.tick(10)
|
|
39
|
+
const deadline = spy.calls.mostRecent().args[0]
|
|
40
|
+
|
|
41
|
+
expect(deadline.timeRemaining()).toBe(MAX_TASK_TIME - 10)
|
|
42
|
+
|
|
43
|
+
clock.tick(10)
|
|
44
|
+
expect(deadline.timeRemaining()).toBe(MAX_TASK_TIME - 20)
|
|
45
|
+
|
|
46
|
+
clock.tick(MAX_TASK_TIME + 100)
|
|
47
|
+
expect(deadline.timeRemaining()).toBe(0)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('cancels the callback when calling the stop function', () => {
|
|
51
|
+
const spy = jasmine.createSpy<(deadline: IdleDeadline) => void>()
|
|
52
|
+
const stop = requestIdleCallbackShim(spy)
|
|
53
|
+
stop()
|
|
54
|
+
clock.tick(0)
|
|
55
|
+
expect(spy).not.toHaveBeenCalled()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
function removeGlobalRequestIdleCallback() {
|
|
60
|
+
const original = window.requestIdleCallback
|
|
61
|
+
;(window as any).requestIdleCallback = undefined
|
|
62
|
+
registerCleanupTask(() => {
|
|
63
|
+
window.requestIdleCallback = original
|
|
64
|
+
})
|
|
65
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsonStringify } from './jsonStringify'
|
|
2
|
+
|
|
3
|
+
describe('jsonStringify', () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
delete (Array.prototype as any).toJSON
|
|
6
|
+
delete (Object.prototype as any).toJSON
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('should jsonStringify a value with toJSON directly defined', () => {
|
|
10
|
+
const value = { 1: 'a' }
|
|
11
|
+
const expectedJson = JSON.stringify(value)
|
|
12
|
+
|
|
13
|
+
expect(jsonStringify(value)).toEqual(expectedJson)
|
|
14
|
+
;(value as any).toJSON = () => '42'
|
|
15
|
+
expect(jsonStringify(value)).toEqual(expectedJson)
|
|
16
|
+
expect(JSON.stringify(value)).toEqual('"42"')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should jsonStringify a value with toJSON defined on its prototype', () => {
|
|
20
|
+
const value = createSampleClassInstance()
|
|
21
|
+
const expectedJson = JSON.stringify(value)
|
|
22
|
+
|
|
23
|
+
expect(jsonStringify(value)).toEqual(expectedJson)
|
|
24
|
+
Object.getPrototypeOf(value).toJSON = () => '42'
|
|
25
|
+
expect(jsonStringify(value)).toEqual(expectedJson)
|
|
26
|
+
expect(JSON.stringify(value)).toEqual('"42"')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should jsonStringify a value when toJSON is defined on Object prototype', () => {
|
|
30
|
+
const value = createSampleClassInstance()
|
|
31
|
+
const expectedJson = JSON.stringify(value)
|
|
32
|
+
|
|
33
|
+
expect(jsonStringify(value)).toEqual(expectedJson)
|
|
34
|
+
;(Object.prototype as any).toJSON = () => '42'
|
|
35
|
+
expect(jsonStringify(value)).toEqual(expectedJson)
|
|
36
|
+
expect(JSON.stringify(value)).toEqual('"42"')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('should jsonStringify a value when toJSON is defined on Array prototype', () => {
|
|
40
|
+
const value = createSampleClassInstance([1])
|
|
41
|
+
const expectedJson = JSON.stringify(value)
|
|
42
|
+
|
|
43
|
+
expect(jsonStringify(value)).toEqual(expectedJson)
|
|
44
|
+
;(Array.prototype as any).toJSON = () => '42'
|
|
45
|
+
expect(jsonStringify(value)).toEqual(expectedJson)
|
|
46
|
+
expect(JSON.stringify(value)).toEqual('{"value":"42"}')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should not restore the toJSON method on the wrong prototype', () => {
|
|
50
|
+
const value = [{ 1: 'a' }]
|
|
51
|
+
;(Object.prototype as any).toJSON = () => '42'
|
|
52
|
+
jsonStringify(value)
|
|
53
|
+
expect(Object.prototype.hasOwnProperty.call(Array.prototype, 'toJSON')).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should jsonStringify edge cases', () => {
|
|
57
|
+
expect(jsonStringify(undefined)).toEqual(undefined)
|
|
58
|
+
expect(jsonStringify(null)).toEqual('null')
|
|
59
|
+
expect(jsonStringify(1)).toEqual('1')
|
|
60
|
+
expect(jsonStringify(true)).toEqual('true')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should not crash on serialization error', () => {
|
|
64
|
+
const circularReference: any = { otherData: 123 }
|
|
65
|
+
circularReference.myself = circularReference
|
|
66
|
+
|
|
67
|
+
expect(jsonStringify(circularReference)).toEqual('<error: unable to serialize object>')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
function createSampleClassInstance(value: any = 'value') {
|
|
71
|
+
class Foo {
|
|
72
|
+
value = value
|
|
73
|
+
}
|
|
74
|
+
return new Foo()
|
|
75
|
+
}
|
|
76
|
+
})
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { display } from '../display'
|
|
2
|
+
import { registerCleanupTask } from '../../../test'
|
|
3
|
+
import { sanitize } from './sanitize'
|
|
4
|
+
|
|
5
|
+
describe('sanitize', () => {
|
|
6
|
+
it('should deep clone an object', () => {
|
|
7
|
+
const obj = { a: 1, b: { c: 42 } }
|
|
8
|
+
const clone = sanitize(obj)
|
|
9
|
+
|
|
10
|
+
expect(clone).toEqual(obj)
|
|
11
|
+
expect(clone).not.toBe(obj)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('should survive an undefined input', () => {
|
|
15
|
+
const obj = undefined
|
|
16
|
+
expect(sanitize(obj)).toBe(undefined)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('simple types handling', () => {
|
|
20
|
+
it('should handle numbers', () => {
|
|
21
|
+
expect(sanitize(42)).toBe(42)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should handle strings', () => {
|
|
25
|
+
expect(sanitize('test')).toBe('test')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('should handle functions', () => {
|
|
29
|
+
function testFunction() {
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
expect(sanitize(testFunction)).toBe('[Function] testFunction')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should handle bigint', () => {
|
|
36
|
+
const bigIntFunction: (val: number) => any = (window as any).BigInt
|
|
37
|
+
if (typeof bigIntFunction === 'function') {
|
|
38
|
+
const bigint = bigIntFunction(2)
|
|
39
|
+
expect(sanitize(bigint)).toEqual('[BigInt] 2')
|
|
40
|
+
} else {
|
|
41
|
+
pending('BigInt is not supported on this browser')
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('shoud handle symbols', () => {
|
|
46
|
+
const symbolFunction: (description: string) => any = (window as any).Symbol
|
|
47
|
+
if (typeof symbolFunction === 'function') {
|
|
48
|
+
const symbol = symbolFunction('description')
|
|
49
|
+
expect(sanitize(symbol)).toMatch(/\[Symbol\] (?:Symbol\()?description\)?/)
|
|
50
|
+
} else {
|
|
51
|
+
pending('Symbol is not supported on this browser')
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('objects handling', () => {
|
|
57
|
+
it('should serialize a Date as a string', () => {
|
|
58
|
+
const date = new Date('2022-12-12')
|
|
59
|
+
expect(sanitize(date)).toBe('2022-12-12T00:00:00.000Z')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should not traverse instrumented DOM nodes', () => {
|
|
63
|
+
const node = document.createElement('div')
|
|
64
|
+
;(node as any).__hiddenProp = { value: 42 }
|
|
65
|
+
|
|
66
|
+
expect(sanitize(node)).toBe('[HTMLDivElement]')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should serialize events', (done) => {
|
|
70
|
+
const button = document.createElement('button')
|
|
71
|
+
document.body.appendChild(button)
|
|
72
|
+
|
|
73
|
+
registerCleanupTask(() => {
|
|
74
|
+
document.body.removeChild(button)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
document.addEventListener(
|
|
78
|
+
'click',
|
|
79
|
+
(event) => {
|
|
80
|
+
expect(sanitize(event)).toEqual({
|
|
81
|
+
type: 'click',
|
|
82
|
+
isTrusted: false,
|
|
83
|
+
target: '[HTMLButtonElement]',
|
|
84
|
+
currentTarget: '[HTMLDocument]',
|
|
85
|
+
})
|
|
86
|
+
done()
|
|
87
|
+
},
|
|
88
|
+
{ once: true }
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
button.click()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should serialize errors as JSON.stringify does', () => {
|
|
95
|
+
// Explicitely keep the previous behavior to avoid breaking changes in 4.x
|
|
96
|
+
// Browsers have different behaviors:
|
|
97
|
+
// Safari IOS12 adds parts of the stack
|
|
98
|
+
const error = new Error('My Error')
|
|
99
|
+
expect(sanitize(error)).toEqual({ ...error })
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('should keep additional properties from errors', () => {
|
|
103
|
+
// JSON.stringify does not serialize message/name/stack from an Error, but
|
|
104
|
+
// will serialize all other additional properties
|
|
105
|
+
const error = new Error('My Error')
|
|
106
|
+
;(error as any).additionalProperty = { inner: 'test' }
|
|
107
|
+
expect(sanitize(error)).toEqual({ ...error })
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should serialize objects like maps as a string', () => {
|
|
111
|
+
const map = new Map([
|
|
112
|
+
['a', 13],
|
|
113
|
+
['b', 37],
|
|
114
|
+
])
|
|
115
|
+
expect(sanitize(map)).toBe('[Map]')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('should survive when toStringTag throws', () => {
|
|
119
|
+
class CannotSerialize {
|
|
120
|
+
get [Symbol.toStringTag]() {
|
|
121
|
+
throw Error('Cannot serialize')
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const cannotSerialize = new CannotSerialize()
|
|
125
|
+
|
|
126
|
+
expect(sanitize(cannotSerialize)).toEqual('[Unserializable]')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('should handle objects with properties including null or undefined', () => {
|
|
130
|
+
const obj = { a: null, b: undefined }
|
|
131
|
+
expect(sanitize(obj)).toEqual({ a: null, b: undefined })
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should handle regular expression', () => {
|
|
135
|
+
expect(sanitize(/[a-zA-Z0-9]+/g)).toEqual('[RegExp] /[a-zA-Z0-9]+/g')
|
|
136
|
+
expect(sanitize(new RegExp('[a-zA-Z0-9]+', 'g'))).toEqual('[RegExp] /[a-zA-Z0-9]+/g')
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('arrays handling', () => {
|
|
141
|
+
// JSON.stringify ignores properties on arrays - We replicate the behavior
|
|
142
|
+
it('should ignore non-numerical properties on arrays', () => {
|
|
143
|
+
const arr = [1, 2, 3, 4]
|
|
144
|
+
;(arr as any)['test'] = 'test'
|
|
145
|
+
|
|
146
|
+
expect(sanitize(arr)).toEqual([1, 2, 3, 4])
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should handle arrays containing null or undefined', () => {
|
|
150
|
+
const arr = [null, undefined]
|
|
151
|
+
expect(sanitize(arr)).toEqual([null, undefined])
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
describe('circular references handling', () => {
|
|
156
|
+
it('should remove circular references', () => {
|
|
157
|
+
const obj: any = { a: 42 }
|
|
158
|
+
obj.self = obj
|
|
159
|
+
|
|
160
|
+
expect(sanitize(obj)).toEqual({ a: 42, self: '[Reference seen at $]' })
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('should remove deep circular references', () => {
|
|
164
|
+
const obj: any = {}
|
|
165
|
+
obj.toto = { inner: obj }
|
|
166
|
+
|
|
167
|
+
expect(sanitize(obj)).toEqual({ toto: { inner: '[Reference seen at $]' } })
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('should remove circular references between two branches in a tree', () => {
|
|
171
|
+
const a: any = {}
|
|
172
|
+
const b: any = {}
|
|
173
|
+
a.link = b
|
|
174
|
+
b.link = a
|
|
175
|
+
const obj = { a, b }
|
|
176
|
+
|
|
177
|
+
expect(sanitize(obj)).toEqual({ a: { link: '[Reference seen at $.b]' }, b: { link: '[Reference seen at $.a]' } })
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('should replace already visited objects with a json path', () => {
|
|
181
|
+
const inner = [1]
|
|
182
|
+
const obj = { a: inner, b: inner }
|
|
183
|
+
|
|
184
|
+
expect(sanitize(obj)).toEqual({ a: [1], b: '[Reference seen at $.a]' })
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('should create an understandable path for visited objects in arrays', () => {
|
|
188
|
+
const inner = { a: 42 }
|
|
189
|
+
const arr = [inner, inner]
|
|
190
|
+
|
|
191
|
+
expect(sanitize(arr)).toEqual([{ a: 42 }, '[Reference seen at $.0]'])
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
describe('toJson functions handling', () => {
|
|
196
|
+
it('should use toJSON functions if available on root object', () => {
|
|
197
|
+
const toJSON = jasmine.createSpy('toJSON', () => 'Specific').and.callThrough()
|
|
198
|
+
const obj = { a: 1, b: 2, toJSON }
|
|
199
|
+
|
|
200
|
+
expect(sanitize(obj)).toEqual('Specific')
|
|
201
|
+
expect(toJSON).toHaveBeenCalledTimes(1)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('should use toJSON functions if available on nested objects', () => {
|
|
205
|
+
const toJSON = jasmine.createSpy('toJSON', () => ({ d: 4 })).and.callThrough()
|
|
206
|
+
const obj = { a: 1, b: 2, c: { a: 3, toJSON } }
|
|
207
|
+
|
|
208
|
+
expect(sanitize(obj)).toEqual({ a: 1, b: 2, c: { d: 4 } })
|
|
209
|
+
expect(toJSON).toHaveBeenCalledTimes(1)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('should switch to the proper container type after applying toJSON', () => {
|
|
213
|
+
const obj = { a: 42, toJSON: () => [42] }
|
|
214
|
+
expect(sanitize(obj)).toEqual([42])
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should not use toJSON methods added to arrays and objects prototypes', () => {
|
|
218
|
+
const toJSONArray = jasmine.createSpy('toJSONArray', () => 'Array').and.callThrough()
|
|
219
|
+
const toJSONObject = jasmine.createSpy('toJSONObject', () => 'Object').and.callThrough()
|
|
220
|
+
;(Array.prototype as any).toJSON = toJSONArray
|
|
221
|
+
;(Object.prototype as any).toJSON = toJSONObject
|
|
222
|
+
|
|
223
|
+
const arr = [{ a: 1, b: 2 }]
|
|
224
|
+
expect(sanitize(arr)).toEqual([{ a: 1, b: 2 }])
|
|
225
|
+
expect(toJSONArray).toHaveBeenCalledTimes(0)
|
|
226
|
+
expect(toJSONObject).toHaveBeenCalledTimes(0)
|
|
227
|
+
delete (Array.prototype as any).toJSON
|
|
228
|
+
delete (Object.prototype as any).toJSON
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('should survive a faulty toJSON', () => {
|
|
232
|
+
const faulty = () => {
|
|
233
|
+
throw new Error('')
|
|
234
|
+
}
|
|
235
|
+
const obj = { b: 42, toJSON: faulty }
|
|
236
|
+
|
|
237
|
+
// Since toJSON throws, sanitize falls back to serialize property by property
|
|
238
|
+
expect(sanitize(obj)).toEqual({ b: 42, toJSON: '[Function] faulty' })
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
describe('maxSize verification', () => {
|
|
243
|
+
it('should return nothing if a simple type is over max size ', () => {
|
|
244
|
+
const displaySpy = spyOn(display, 'warn')
|
|
245
|
+
const str = 'A not so long string...'
|
|
246
|
+
|
|
247
|
+
expect(sanitize(str, 5)).toBe(undefined)
|
|
248
|
+
expect(displaySpy).toHaveBeenCalled()
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('should stop cloning if an object container type reaches max size', () => {
|
|
252
|
+
const displaySpy = spyOn(display, 'warn')
|
|
253
|
+
const obj = { a: 'abc', b: 'def', c: 'ghi' } // Length of 31 after JSON.stringify
|
|
254
|
+
const sanitized = sanitize(obj, 21)
|
|
255
|
+
expect(sanitized).toEqual({ a: 'abc', b: 'def' }) // Length of 21 after JSON.stringify
|
|
256
|
+
expect(displaySpy).toHaveBeenCalled()
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('should stop cloning if an array container type reaches max size', () => {
|
|
260
|
+
const displaySpy = spyOn(display, 'warn')
|
|
261
|
+
const obj = [1, 2, 3, 4] // Length of 9 after JSON.stringify
|
|
262
|
+
const sanitized = sanitize(obj, 5)
|
|
263
|
+
expect(sanitized).toEqual([1, 2]) // Length of 5 after JSON.stringify
|
|
264
|
+
expect(displaySpy).toHaveBeenCalled()
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('should count size properly when array contains undefined values', () => {
|
|
268
|
+
// This is a special case: JSON.stringify([undefined]) => '[null]'
|
|
269
|
+
const displaySpy = spyOn(display, 'warn')
|
|
270
|
+
const arr = [undefined, undefined] // Length of 11 after JSON.stringify
|
|
271
|
+
const sanitized = sanitize(arr, 10)
|
|
272
|
+
expect(sanitized).toEqual([undefined])
|
|
273
|
+
expect(displaySpy).toHaveBeenCalled()
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it('should count size properly when an object contains properties with undefined values', () => {
|
|
277
|
+
const displaySpy = spyOn(display, 'warn')
|
|
278
|
+
const obj = { a: undefined, b: 42 } // Length of 8 after JSON.stringify
|
|
279
|
+
const sanitized = sanitize(obj, 8)
|
|
280
|
+
expect(sanitized).toEqual({ a: undefined, b: 42 })
|
|
281
|
+
expect(displaySpy).not.toHaveBeenCalled()
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
|
+
})
|