@thumbmarkjs/thumbmarkjs 1.9.0 → 1.10.0

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.
Files changed (68) hide show
  1. package/README.md +96 -102
  2. package/dist/thumbmark.cjs.js +1 -1
  3. package/dist/thumbmark.cjs.js.map +1 -1
  4. package/dist/thumbmark.esm.d.ts +6 -0
  5. package/dist/thumbmark.esm.js +1 -1
  6. package/dist/thumbmark.esm.js.map +1 -1
  7. package/dist/thumbmark.umd.js +1 -1
  8. package/dist/thumbmark.umd.js.map +1 -1
  9. package/dist/types/components/audio/index.d.ts +2 -0
  10. package/dist/types/components/canvas/index.d.ts +3 -0
  11. package/dist/types/components/fonts/index.d.ts +4 -0
  12. package/dist/types/components/hardware/index.d.ts +2 -0
  13. package/dist/types/components/locales/index.d.ts +2 -0
  14. package/dist/types/components/math/index.d.ts +2 -0
  15. package/dist/types/components/mathml/index.d.ts +2 -0
  16. package/dist/types/components/permissions/index.d.ts +3 -0
  17. package/dist/types/components/plugins/index.d.ts +2 -0
  18. package/dist/types/components/screen/index.d.ts +2 -0
  19. package/dist/types/components/speech/index.d.ts +2 -0
  20. package/dist/types/components/system/browser.d.ts +9 -0
  21. package/dist/types/components/system/index.d.ts +2 -0
  22. package/dist/types/components/webgl/index.d.ts +13 -0
  23. package/dist/types/components/webrtc/index.d.ts +3 -0
  24. package/dist/types/factory.d.ts +62 -0
  25. package/dist/types/functions/api.d.ts +73 -0
  26. package/dist/types/functions/filterComponents.d.ts +15 -0
  27. package/dist/types/functions/index.d.ts +63 -0
  28. package/dist/types/functions/legacy_functions.d.ts +27 -0
  29. package/dist/types/index.d.ts +9 -0
  30. package/dist/types/options.d.ts +82 -0
  31. package/dist/types/thumbmark.d.ts +28 -0
  32. package/dist/types/utils/cache.d.ts +23 -0
  33. package/dist/types/utils/commonPixels.d.ts +1 -0
  34. package/dist/types/utils/ephemeralIFrame.d.ts +4 -0
  35. package/dist/types/utils/getMostFrequent.d.ts +5 -0
  36. package/dist/types/utils/hash.d.ts +5 -0
  37. package/dist/types/utils/imageDataToDataURL.d.ts +1 -0
  38. package/dist/types/utils/log.d.ts +9 -0
  39. package/dist/types/utils/raceAll.d.ts +10 -0
  40. package/dist/types/utils/sort.d.ts +8 -0
  41. package/dist/types/utils/stableStringify.d.ts +22 -0
  42. package/dist/types/utils/version.d.ts +4 -0
  43. package/dist/types/utils/visitorId.d.ts +14 -0
  44. package/package.json +102 -70
  45. package/src/factory.ts +0 -4
  46. package/src/functions/api.ts +19 -6
  47. package/src/functions/filterComponents.ts +2 -3
  48. package/src/functions/index.ts +1 -1
  49. package/src/options.ts +7 -0
  50. package/src/components/canvas/index.test.ts +0 -38
  51. package/src/components/hardware/index.test.ts +0 -80
  52. package/src/components/intl/index.test.ts +0 -124
  53. package/src/components/intl/index.ts +0 -41
  54. package/src/components/mediaDevices/index.test.ts +0 -120
  55. package/src/components/mediaDevices/index.ts +0 -26
  56. package/src/components/system/browser.test.ts +0 -108
  57. package/src/components/webgl/index.test.ts +0 -223
  58. package/src/functions/api.test.ts +0 -366
  59. package/src/functions/filterComponents.test.ts +0 -238
  60. package/src/functions/functions.test.ts +0 -142
  61. package/src/functions/metadata.test.ts +0 -211
  62. package/src/options.test.ts +0 -10
  63. package/src/thumbmark.custom-components.test.ts +0 -87
  64. package/src/thumbmark.test.ts +0 -59
  65. package/src/utils/cache.test.ts +0 -95
  66. package/src/utils/raceAll.test.ts +0 -86
  67. package/src/utils/stableStringify.test.ts +0 -335
  68. package/src/utils/visitorId.test.ts +0 -102
@@ -1,142 +0,0 @@
1
- import { componentInterface } from '../factory'
2
- import { filterThumbmarkData } from './filterComponents'
3
- import { resolveClientComponents, getThumbmark } from '.';
4
- import { defaultOptions } from '../options';
5
-
6
- const test_components: componentInterface = {
7
- 'one': '1',
8
- 'two': 2,
9
- 'three': {'a': true, 'b': false}
10
- }
11
-
12
- describe('component filtering tests', () => {
13
- test("excluding top level works", () => {
14
- expect(filterThumbmarkData(test_components,
15
- { ...defaultOptions, ...{ exclude: ['one']} }
16
- )).toMatchObject({
17
- 'two': 2, 'three': {'a': true, 'b': false}
18
- })
19
- });
20
- test("including top level works", () => {
21
- expect(filterThumbmarkData(test_components, { ...defaultOptions, ...{ include: ['one', 'two']} })).toMatchObject({
22
- 'one': '1', 'two': 2
23
- })
24
- });
25
- test("excluding low-level works", () => {
26
- expect(filterThumbmarkData(test_components,
27
- { ...defaultOptions, ...{ exclude: ['two', 'three.a']} }
28
- )).toMatchObject({
29
- 'one': '1',
30
- 'three': {'b': false}
31
- })
32
- });
33
- test("including low-level works", () => {
34
- expect(filterThumbmarkData(test_components,
35
- { ...defaultOptions, ...{ include: ['one', 'three.b']} })).toMatchObject({
36
- 'one': '1',
37
- 'three': {'b': false}
38
- })
39
- });
40
- });
41
-
42
- describe('resolveClientComponents runtime stability', () => {
43
- test('continues resolving when one component promise rejects', async () => {
44
- const components = {
45
- stable: async () => ({ value: 'ok' } as componentInterface),
46
- unstable: async () => Promise.reject(new Error('component failed')),
47
- };
48
-
49
- const { resolvedComponents, elapsed, errors } = await resolveClientComponents(components, {
50
- ...defaultOptions,
51
- stabilize: [],
52
- });
53
-
54
- expect(resolvedComponents.stable).toEqual({ value: 'ok' });
55
- expect(resolvedComponents.unstable).toEqual({ timeout: 'true' });
56
- expect(elapsed.stable).toBeGreaterThanOrEqual(0);
57
- expect(elapsed.unstable).toBeGreaterThanOrEqual(0);
58
- expect(errors).toEqual([
59
- { type: 'component_error', message: 'component failed', component: 'unstable' }
60
- ]);
61
- });
62
-
63
- test('reports component timeout in errors', async () => {
64
- const components = {
65
- fast: async () => ({ value: 'ok' } as componentInterface),
66
- slow: () => new Promise<componentInterface>(() => {}),
67
- };
68
-
69
- const { resolvedComponents, errors } = await resolveClientComponents(components, {
70
- ...defaultOptions,
71
- timeout: 10,
72
- stabilize: [],
73
- });
74
-
75
- expect(resolvedComponents.fast).toEqual({ value: 'ok' });
76
- expect(errors).toEqual([
77
- { type: 'component_timeout', message: "Component 'slow' timed out", component: 'slow' }
78
- ]);
79
- });
80
-
81
- test('returns empty errors when all components succeed', async () => {
82
- const components = {
83
- a: async () => ({ value: '1' } as componentInterface),
84
- b: async () => ({ value: '2' } as componentInterface),
85
- };
86
-
87
- const { errors } = await resolveClientComponents(components, {
88
- ...defaultOptions,
89
- stabilize: [],
90
- });
91
-
92
- expect(errors).toEqual([]);
93
- });
94
- });
95
-
96
- describe('getThumbmark error reporting', () => {
97
- test('returns api_unauthorized error for 403 response', async () => {
98
- const originalFetch = global.fetch;
99
- global.fetch = jest.fn().mockResolvedValue({
100
- ok: false,
101
- status: 403,
102
- });
103
-
104
- const result = await getThumbmark({
105
- ...defaultOptions,
106
- api_key: 'fake-invalid-key',
107
- cache_api_call: false,
108
- });
109
-
110
- expect(result.error).toEqual([
111
- { type: 'api_unauthorized', message: 'Invalid API key or quota exceeded' }
112
- ]);
113
- expect(result.thumbmark).toBe('');
114
-
115
- global.fetch = originalFetch;
116
- });
117
-
118
- test('returns api_error for 5xx response', async () => {
119
- const originalFetch = global.fetch;
120
- global.fetch = jest.fn().mockResolvedValue({
121
- ok: false,
122
- status: 500,
123
- });
124
-
125
- const result = await getThumbmark({
126
- ...defaultOptions,
127
- api_key: 'some-key',
128
- cache_api_call: false,
129
- });
130
-
131
- expect(result.error).toEqual(
132
- expect.arrayContaining([
133
- expect.objectContaining({ type: 'api_error', message: 'HTTP error! status: 500' })
134
- ])
135
- );
136
- // Should still return a fingerprint (graceful degradation)
137
- expect(result.thumbmark).toBeDefined();
138
- expect(result.thumbmark.length).toBeGreaterThan(0);
139
-
140
- global.fetch = originalFetch;
141
- });
142
- });
@@ -1,211 +0,0 @@
1
- import { getApiPromise, apiResponse } from './api';
2
- import { OptionsAfterDefaults } from '../options';
3
- import { componentInterface } from '../factory';
4
-
5
- // Mock fetch globally
6
- global.fetch = jest.fn();
7
-
8
- describe('Metadata Support', () => {
9
- beforeEach(() => {
10
- jest.clearAllMocks();
11
- (global.fetch as jest.Mock).mockResolvedValue({
12
- ok: true,
13
- json: async () => ({
14
- thumbmark: 'test-thumbmark',
15
- visitorId: 'test-visitor',
16
- requestId: 'test-request',
17
- metadata: 'test-metadata',
18
- }),
19
- });
20
- // Spy on console.error
21
- jest.spyOn(console, 'error').mockImplementation(() => { });
22
- });
23
-
24
- afterEach(() => {
25
- jest.restoreAllMocks();
26
- });
27
-
28
- it('should include static metadata string in request body', async () => {
29
- const options: Partial<OptionsAfterDefaults> = {
30
- api_key: 'test-key',
31
- metadata: 'event-123',
32
- cache_api_call: false,
33
- };
34
- const components: componentInterface = { system: { browser: 'chrome' } };
35
-
36
- await getApiPromise(options as OptionsAfterDefaults, components);
37
-
38
- expect(global.fetch).toHaveBeenCalledTimes(1);
39
- const callArgs = (global.fetch as jest.Mock).mock.calls[0];
40
- const requestBody = JSON.parse(callArgs[1].body);
41
- expect(requestBody.metadata).toBe('event-123');
42
- });
43
-
44
- it('should evaluate metadata function and include result in request body', async () => {
45
- let counter = 0;
46
- const options: Partial<OptionsAfterDefaults> = {
47
- api_key: 'test-key',
48
- metadata: () => `call-${++counter}`,
49
- cache_api_call: false,
50
- };
51
- const components: componentInterface = { system: { browser: 'chrome' } };
52
-
53
- await getApiPromise(options as OptionsAfterDefaults, components);
54
-
55
- expect(global.fetch).toHaveBeenCalledTimes(1);
56
- const callArgs = (global.fetch as jest.Mock).mock.calls[0];
57
- const requestBody = JSON.parse(callArgs[1].body);
58
- expect(requestBody.metadata).toBe('call-1');
59
- });
60
-
61
- it('should call metadata function each time get is invoked', async () => {
62
- let counter = 0;
63
- const options: Partial<OptionsAfterDefaults> = {
64
- api_key: 'test-key',
65
- metadata: () => `call-${++counter}`,
66
- cache_api_call: false,
67
- };
68
- const components: componentInterface = { system: { browser: 'chrome' } };
69
-
70
- // First call
71
- await getApiPromise(options as OptionsAfterDefaults, components);
72
- const firstCallArgs = (global.fetch as jest.Mock).mock.calls[0];
73
- const firstRequestBody = JSON.parse(firstCallArgs[1].body);
74
- expect(firstRequestBody.metadata).toBe('call-1');
75
-
76
- // Second call
77
- await getApiPromise(options as OptionsAfterDefaults, components);
78
- const secondCallArgs = (global.fetch as jest.Mock).mock.calls[1];
79
- const secondRequestBody = JSON.parse(secondCallArgs[1].body);
80
- expect(secondRequestBody.metadata).toBe('call-2');
81
- });
82
-
83
- it('should omit metadata from request body when not provided', async () => {
84
- const options: Partial<OptionsAfterDefaults> = {
85
- api_key: 'test-key',
86
- cache_api_call: false,
87
- };
88
- const components: componentInterface = { system: { browser: 'chrome' } };
89
-
90
- await getApiPromise(options as OptionsAfterDefaults, components);
91
-
92
- expect(global.fetch).toHaveBeenCalledTimes(1);
93
- const callArgs = (global.fetch as jest.Mock).mock.calls[0];
94
- const requestBody = JSON.parse(callArgs[1].body);
95
- expect(requestBody.metadata).toBeUndefined();
96
- });
97
-
98
- it('should echo metadata back in API response', async () => {
99
- const options: Partial<OptionsAfterDefaults> = {
100
- api_key: 'test-key',
101
- metadata: '{"eventId": "12345"}',
102
- cache_api_call: false,
103
- };
104
- const components: componentInterface = { system: { browser: 'chrome' } };
105
-
106
- const response = await getApiPromise(options as OptionsAfterDefaults, components);
107
-
108
- expect(response).toBeDefined();
109
- expect(response?.metadata).toBe('test-metadata');
110
- });
111
-
112
- it('should handle various string metadata types', async () => {
113
- const testCases = [
114
- 'simple-string',
115
- '{"json": "object"}',
116
- 'encrypted:abc123def456',
117
- 'multi\nline\nstring',
118
- '🎉 unicode 中文',
119
- ];
120
-
121
- for (const metadata of testCases) {
122
- const options: Partial<OptionsAfterDefaults> = {
123
- api_key: 'test-key',
124
- metadata,
125
- cache_api_call: false,
126
- };
127
- const components: componentInterface = { system: { browser: 'chrome' } };
128
-
129
- await getApiPromise(options as OptionsAfterDefaults, components);
130
-
131
- const callArgs = (global.fetch as jest.Mock).mock.calls[
132
- (global.fetch as jest.Mock).mock.calls.length - 1
133
- ];
134
- const requestBody = JSON.parse(callArgs[1].body);
135
- expect(requestBody.metadata).toBe(metadata);
136
- }
137
- });
138
-
139
- it('should handle empty string metadata', async () => {
140
- const options: Partial<OptionsAfterDefaults> = {
141
- api_key: 'test-key',
142
- metadata: '',
143
- cache_api_call: false,
144
- };
145
- const components: componentInterface = { system: { browser: 'chrome' } };
146
-
147
- await getApiPromise(options as OptionsAfterDefaults, components);
148
-
149
- const callArgs = (global.fetch as jest.Mock).mock.calls[0];
150
- const requestBody = JSON.parse(callArgs[1].body);
151
- // Empty string is falsy, so it won't be included
152
- expect(requestBody.metadata).toBeUndefined();
153
- });
154
-
155
- it('should handle metadata function returning empty string', async () => {
156
- const options: Partial<OptionsAfterDefaults> = {
157
- api_key: 'test-key',
158
- metadata: () => '',
159
- cache_api_call: false,
160
- };
161
- const components: componentInterface = { system: { browser: 'chrome' } };
162
-
163
- await getApiPromise(options as OptionsAfterDefaults, components);
164
-
165
- const callArgs = (global.fetch as jest.Mock).mock.calls[0];
166
- const requestBody = JSON.parse(callArgs[1].body);
167
- // Empty string is falsy, so it won't be included
168
- expect(requestBody.metadata).toBeUndefined();
169
- });
170
-
171
- it('should log error and omit metadata if length exceeds 1000 characters', async () => {
172
- const longMetadata = 'a'.repeat(1001);
173
- const options: Partial<OptionsAfterDefaults> = {
174
- api_key: 'test-key',
175
- metadata: longMetadata,
176
- cache_api_call: false,
177
- };
178
- const components: componentInterface = { system: { browser: 'chrome' } };
179
-
180
- await getApiPromise(options as OptionsAfterDefaults, components);
181
-
182
- // Check that fetch was called
183
- expect(global.fetch).toHaveBeenCalledTimes(1);
184
-
185
- // precise console error check
186
- expect(console.error).toHaveBeenCalledWith('ThumbmarkJS: Metadata exceeds 1000 characters. Skipping metadata.');
187
-
188
- // Check that metadata was omitted
189
- const callArgs = (global.fetch as jest.Mock).mock.calls[0];
190
- const requestBody = JSON.parse(callArgs[1].body);
191
- expect(requestBody.metadata).toBeUndefined();
192
- });
193
-
194
- it('should accept metadata exactly 1000 characters check', async () => {
195
- const validMetadata = 'a'.repeat(1000);
196
- const options: Partial<OptionsAfterDefaults> = {
197
- api_key: 'test-key',
198
- metadata: validMetadata,
199
- cache_api_call: false,
200
- };
201
- const components: componentInterface = { system: { browser: 'chrome' } };
202
-
203
- await getApiPromise(options as OptionsAfterDefaults, components);
204
-
205
- expect(console.error).not.toHaveBeenCalledWith('ThumbmarkJS: Metadata exceeds 1000 characters. Skipping metadata.');
206
-
207
- const callArgs = (global.fetch as jest.Mock).mock.calls[0];
208
- const requestBody = JSON.parse(callArgs[1].body);
209
- expect(requestBody.metadata).toBe(validMetadata);
210
- });
211
- });
@@ -1,10 +0,0 @@
1
- import { DEFAULT_STORAGE_PREFIX, defaultOptions } from "./options";
2
-
3
- describe('property_name_factory', () => {
4
- test('it should default to the default value', () => {
5
- const name = 'mykey';
6
- expect(
7
- defaultOptions.property_name_factory(name)
8
- ).toEqual(`${DEFAULT_STORAGE_PREFIX}_${name}`);
9
- })
10
- })
@@ -1,87 +0,0 @@
1
- import { Thumbmark } from './thumbmark';
2
- import { customComponents } from './factory';
3
- import { getThumbmark, includeComponent as includeGlobalComponent } from './index';
4
-
5
- describe('Thumbmark custom components', () => {
6
- beforeEach(() => {
7
- for (const key of Object.keys(customComponents)) {
8
- delete customComponents[key];
9
- }
10
- });
11
-
12
- test('custom components stay scoped to the instance that registered them', async () => {
13
- const tm1 = new Thumbmark({ logging: false });
14
- const tm2 = new Thumbmark({ logging: false });
15
-
16
- tm1.includeComponent('instanceOnly', async () => ({
17
- value: 'tm1'
18
- }));
19
-
20
- const tm1Result = await tm1.get({ include: ['instanceOnly'] });
21
- const tm2Result = await tm2.get({ include: ['instanceOnly'] });
22
-
23
- expect(tm1Result.components.instanceOnly).toEqual({ value: 'tm1' });
24
- expect(tm2Result.components.instanceOnly).toBeUndefined();
25
- });
26
-
27
- test('deprecated global includeComponent still registers components for getThumbmark', async () => {
28
- includeGlobalComponent('legacyGlobal', async () => ({
29
- value: 'global'
30
- }));
31
-
32
- const result = await getThumbmark({
33
- logging: false,
34
- include: ['legacyGlobal']
35
- });
36
-
37
- expect(result.components.legacyGlobal).toEqual({ value: 'global' });
38
- });
39
-
40
- test('getThumbmark accepts an optional custom component registry', async () => {
41
- const result = await getThumbmark({
42
- logging: false,
43
- include: ['directCustom']
44
- }, {
45
- directCustom: async () => ({
46
- value: 'direct'
47
- })
48
- });
49
-
50
- expect(result.components.directCustom).toEqual({ value: 'direct' });
51
- });
52
-
53
- test('deprecated global includeComponent still registers components for Thumbmark.get()', async () => {
54
- includeGlobalComponent('legacyGlobal', async () => ({
55
- value: 'global'
56
- }));
57
-
58
- const tm = new Thumbmark({ logging: false });
59
- const result = await tm.get({
60
- include: ['legacyGlobal']
61
- });
62
-
63
- expect(result.components.legacyGlobal).toEqual({ value: 'global' });
64
- });
65
-
66
- test('instance custom components override deprecated global ones for the same key', async () => {
67
- includeGlobalComponent('sharedKey', async () => ({
68
- value: 'global'
69
- }));
70
-
71
- const tm = new Thumbmark({ logging: false });
72
- tm.includeComponent('sharedKey', async () => ({
73
- value: 'instance'
74
- }));
75
-
76
- const globalResult = await getThumbmark({
77
- logging: false,
78
- include: ['sharedKey']
79
- });
80
- const instanceResult = await tm.get({
81
- include: ['sharedKey']
82
- });
83
-
84
- expect(globalResult.components.sharedKey).toEqual({ value: 'global' });
85
- expect(instanceResult.components.sharedKey).toEqual({ value: 'instance' });
86
- });
87
- });
@@ -1,59 +0,0 @@
1
- /**
2
- * Tests for Thumbmark class behavior in Node/Jest environment
3
- *
4
- * These tests verify that Thumbmark works gracefully when browser APIs
5
- * are not available (e.g., in Jest/jsdom or server-side environments).
6
- */
7
-
8
- import { Thumbmark } from './thumbmark';
9
- import { jest } from '@jest/globals';
10
-
11
- // Polyfill for sessionStorage in Node environment
12
- if (typeof sessionStorage === 'undefined') {
13
- (global as any).sessionStorage = {
14
- getItem: jest.fn(),
15
- setItem: jest.fn(),
16
- removeItem: jest.fn(),
17
- clear: jest.fn(),
18
- length: 0,
19
- key: jest.fn(),
20
- };
21
- }
22
-
23
-
24
- describe('Thumbmark in Node/Jest environment', () => {
25
- test('Thumbmark.get() should not throw in non-browser environment', async () => {
26
- const tm = new Thumbmark();
27
-
28
- // This should not throw, even if browser APIs are unavailable
29
- const result = await tm.get();
30
-
31
- // Should return a valid result with structure
32
- expect(result).toBeDefined();
33
- expect(result.thumbmark).toBeDefined();
34
- expect(typeof result.thumbmark).toBe('string');
35
- });
36
-
37
- test('Thumbmark.get() returns valid structure even with missing browser APIs', async () => {
38
- const tm = new Thumbmark();
39
-
40
- const result = await tm.get();
41
-
42
- // Verify the response structure
43
- expect(result).toHaveProperty('thumbmark');
44
- expect(result).toHaveProperty('components');
45
- expect(result).toHaveProperty('version');
46
- expect(result).toHaveProperty('info');
47
-
48
- // Components should be an object (possibly with fewer values than in real browser)
49
- expect(typeof result.components).toBe('object');
50
- });
51
-
52
- test('Thumbmark.getVersion() works in any environment', () => {
53
- const tm = new Thumbmark();
54
- const version = tm.getVersion();
55
-
56
- expect(version).toBeDefined();
57
- expect(typeof version).toBe('string');
58
- });
59
- });
@@ -1,95 +0,0 @@
1
- import { Cache, getCache, setCache, getApiResponseExpiry, CACHE_KEY } from "./cache";
2
- import { DEFAULT_STORAGE_PREFIX, defaultOptions, MAXIMUM_CACHE_LIFETIME } from "../options";
3
-
4
- const _options = {
5
- property_name_factory: (name: string) => {
6
- return `${name}-mypostfix`;
7
- },
8
- cache_lifetime_in_ms: 0,
9
- };
10
-
11
- const values: Cache = {
12
- apiResponseExpiry: 100,
13
- apiResponse: {
14
- version: '1.2.3',
15
- },
16
- };
17
-
18
- const cacheProperty = defaultOptions.property_name_factory(CACHE_KEY);
19
- const customCacheProperty = _options.property_name_factory(CACHE_KEY);
20
-
21
- describe('getCache', () => {
22
- beforeEach(() => {
23
- localStorage.clear();
24
- });
25
-
26
- test('it should return all values from cache', () => {
27
- localStorage.setItem(cacheProperty, JSON.stringify(values));
28
- const fromStorage = getCache(defaultOptions);
29
-
30
- expect(fromStorage).toEqual(values);
31
- });
32
-
33
- test('it should return an empty object in case of nothing cached', () => {
34
- expect(getCache(defaultOptions)).toEqual({});
35
- });
36
-
37
- test('it should return an empty object in case non-json content', () => {
38
- localStorage.setItem(cacheProperty, 'abc123');
39
- expect(getCache(defaultOptions)).toEqual({});
40
- });
41
-
42
- test('it should return from the correct property', () => {
43
- localStorage.setItem(cacheProperty, JSON.stringify(values));
44
-
45
- expect(getCache(_options)).toEqual({});
46
- expect(getCache(defaultOptions)).toEqual(values);
47
- });
48
- })
49
-
50
- describe('setCache', () => {
51
- beforeEach(() => {
52
- localStorage.clear();
53
- });
54
-
55
- test('it should write given values', () => {
56
- setCache(defaultOptions, values);
57
- expect(JSON.parse(localStorage.getItem(cacheProperty)!))
58
- .toEqual(values);
59
- });
60
-
61
- test('it should not touch values that are not provided', () => {
62
- setCache(_options, values);
63
- setCache(_options, {
64
- apiResponseExpiry: 200
65
- });
66
- const fromStorage = JSON.parse(localStorage.getItem(customCacheProperty)!) as Cache;
67
- expect(fromStorage.apiResponseExpiry).toEqual(200);
68
- expect(fromStorage.apiResponse!.version).toEqual(values.apiResponse!.version);
69
- })
70
- })
71
-
72
- describe('getApiResponseExpiry', () => {
73
- const systemTime = 1000;
74
- beforeAll(() => {
75
- jest.useFakeTimers().setSystemTime(systemTime);
76
- localStorage.clear();
77
- });
78
-
79
- test('it should return a value based on options', () => {
80
- expect(getApiResponseExpiry({
81
- cache_lifetime_in_ms: 100,
82
- })).toBe(systemTime + 100);
83
- });
84
-
85
- test('it should not go over maximum cache lifetime', () => {
86
- expect(getApiResponseExpiry({
87
- cache_lifetime_in_ms: MAXIMUM_CACHE_LIFETIME + 200,
88
- })).toBe(systemTime + MAXIMUM_CACHE_LIFETIME);
89
- })
90
-
91
- afterAll(() => {
92
- jest.useRealTimers()
93
- })
94
- })
95
-
@@ -1,86 +0,0 @@
1
- import { raceAllPerformance, raceAll } from './raceAll';
2
-
3
- describe('raceAll', () => {
4
- test('returns resolved values when promises fulfill', async () => {
5
- const results = await raceAll(
6
- [Promise.resolve('ok')],
7
- 50,
8
- 'timeout'
9
- );
10
-
11
- expect(results).toHaveLength(1);
12
- expect(results[0]).toBe('ok');
13
- });
14
-
15
- test('returns timeout fallback when promise does not settle in time', async () => {
16
- const neverResolves = new Promise<string>(() => { });
17
- const results = await raceAll(
18
- [neverResolves],
19
- 10,
20
- 'timeout'
21
- );
22
-
23
- expect(results).toHaveLength(1);
24
- expect(results[0]).toBe('timeout');
25
- });
26
-
27
- test('does not reject the whole batch when one promise rejects', async () => {
28
- const rejects = Promise.reject(new Error('component failed'));
29
- const resolves = Promise.resolve('ok');
30
-
31
- const results = await raceAll(
32
- [rejects, resolves],
33
- 50,
34
- 'timeout'
35
- );
36
-
37
- expect(results).toHaveLength(2);
38
- expect(results[0]).toBe('timeout');
39
- expect(results[1]).toBe('ok');
40
- });
41
- });
42
-
43
- describe('raceAllPerformance', () => {
44
- test('returns resolved values when promises fulfill', async () => {
45
- const results = await raceAllPerformance(
46
- [Promise.resolve('ok')],
47
- 50,
48
- 'timeout'
49
- );
50
-
51
- expect(results).toHaveLength(1);
52
- expect(results[0].value).toBe('ok');
53
- expect(typeof results[0].elapsed).toBe('number');
54
- expect(results[0].error).toBeUndefined();
55
- });
56
-
57
- test('returns timeout fallback when promise does not settle in time', async () => {
58
- const neverResolves = new Promise<string>(() => { });
59
- const results = await raceAllPerformance(
60
- [neverResolves],
61
- 10,
62
- 'timeout'
63
- );
64
-
65
- expect(results).toHaveLength(1);
66
- expect(results[0].value).toBe('timeout');
67
- expect(results[0].error).toBe('timeout');
68
- });
69
-
70
- test('does not reject the whole batch when one promise rejects', async () => {
71
- const rejects = Promise.reject(new Error('component failed'));
72
- const resolves = Promise.resolve('ok');
73
-
74
- const results = await raceAllPerformance(
75
- [rejects, resolves],
76
- 50,
77
- 'timeout'
78
- );
79
-
80
- expect(results).toHaveLength(2);
81
- expect(results[0].value).toBe('timeout');
82
- expect(results[0].error).toBe('component failed');
83
- expect(results[1].value).toBe('ok');
84
- expect(results[1].error).toBeUndefined();
85
- });
86
- });