@dotcms/experiments 0.0.1-alpha.39 → 0.0.1-alpha.41

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 (48) hide show
  1. package/index.esm.d.ts +1 -0
  2. package/index.esm.js +7174 -0
  3. package/package.json +10 -6
  4. package/src/lib/components/{DotExperimentHandlingComponent.tsx → DotExperimentHandlingComponent.d.ts} +3 -20
  5. package/src/lib/components/{DotExperimentsProvider.tsx → DotExperimentsProvider.d.ts} +3 -41
  6. package/src/lib/components/withExperiments.d.ts +20 -0
  7. package/src/lib/contexts/{DotExperimentsContext.tsx → DotExperimentsContext.d.ts} +2 -5
  8. package/src/lib/dot-experiments.d.ts +289 -0
  9. package/src/lib/hooks/useExperimentVariant.d.ts +21 -0
  10. package/src/lib/hooks/useExperiments.d.ts +14 -0
  11. package/src/lib/shared/{constants.ts → constants.d.ts} +18 -35
  12. package/src/lib/shared/mocks/mock.d.ts +43 -0
  13. package/src/lib/shared/{models.ts → models.d.ts} +2 -35
  14. package/src/lib/shared/parser/parser.d.ts +54 -0
  15. package/src/lib/shared/persistence/index-db-database-handler.d.ts +87 -0
  16. package/src/lib/shared/utils/DotLogger.d.ts +15 -0
  17. package/src/lib/shared/utils/memoize.d.ts +7 -0
  18. package/src/lib/shared/utils/utils.d.ts +73 -0
  19. package/src/lib/standalone.d.ts +7 -0
  20. package/.babelrc +0 -12
  21. package/.eslintrc.json +0 -26
  22. package/jest.config.ts +0 -11
  23. package/project.json +0 -55
  24. package/src/lib/components/DotExperimentsProvider.spec.tsx +0 -62
  25. package/src/lib/components/withExperiments.tsx +0 -52
  26. package/src/lib/contexts/DotExperimentsContext.spec.tsx +0 -42
  27. package/src/lib/dot-experiments.spec.ts +0 -285
  28. package/src/lib/dot-experiments.ts +0 -716
  29. package/src/lib/hooks/useExperimentVariant.spec.tsx +0 -111
  30. package/src/lib/hooks/useExperimentVariant.ts +0 -55
  31. package/src/lib/hooks/useExperiments.ts +0 -90
  32. package/src/lib/shared/mocks/mock.ts +0 -209
  33. package/src/lib/shared/parser/parse.spec.ts +0 -187
  34. package/src/lib/shared/parser/parser.ts +0 -171
  35. package/src/lib/shared/persistence/index-db-database-handler.spec.ts +0 -100
  36. package/src/lib/shared/persistence/index-db-database-handler.ts +0 -218
  37. package/src/lib/shared/utils/DotLogger.ts +0 -57
  38. package/src/lib/shared/utils/memoize.spec.ts +0 -49
  39. package/src/lib/shared/utils/memoize.ts +0 -49
  40. package/src/lib/shared/utils/utils.spec.ts +0 -142
  41. package/src/lib/shared/utils/utils.ts +0 -203
  42. package/src/lib/standalone.spec.ts +0 -36
  43. package/src/lib/standalone.ts +0 -28
  44. package/tsconfig.json +0 -20
  45. package/tsconfig.lib.json +0 -20
  46. package/tsconfig.spec.json +0 -9
  47. package/vite.config.ts +0 -41
  48. /package/src/{index.ts → index.d.ts} +0 -0
@@ -1,111 +0,0 @@
1
- import { renderHook } from '@testing-library/react-hooks';
2
- import React from 'react';
3
-
4
- import * as dotcmsClient from '@dotcms/client';
5
-
6
- import { useExperimentVariant } from './useExperimentVariant';
7
-
8
- import MockDotExperimentsContext from '../contexts/DotExperimentsContext';
9
- import { EXPERIMENT_DEFAULT_VARIANT_NAME } from '../shared/constants';
10
- import { LocationMock } from '../shared/mocks/mock';
11
-
12
- interface WrapperProps {
13
- children: React.ReactNode;
14
- }
15
-
16
- const createMockDotExperimentsContext = (variantResponse: unknown) => {
17
- const mockGetVariantFromHref = jest.fn().mockImplementation(() => variantResponse);
18
-
19
- return React.createContext({
20
- getVariantFromHref: mockGetVariantFromHref
21
- });
22
- };
23
-
24
- jest.mock('../contexts/DotExperimentsContext', () => ({
25
- __esModule: true,
26
- default: createMockDotExperimentsContext({ name: 'variant-1' })
27
- }));
28
-
29
- const wrapper = ({ children }: WrapperProps) => (
30
- // eslint-disable-next-line react-hooks/rules-of-hooks
31
- <MockDotExperimentsContext.Provider value={React.useContext(MockDotExperimentsContext)}>
32
- {children}
33
- </MockDotExperimentsContext.Provider>
34
- );
35
-
36
- describe('useExperimentVariant', () => {
37
- describe('shouldWaitForVariant `false`', () => {
38
- it('if is insideEditor is `false`', () => {
39
- const mockData = {
40
- runningExperimentId: '1',
41
- viewAs: { variantId: '1' }
42
- };
43
-
44
- jest.spyOn(dotcmsClient, 'isInsideEditor').mockReturnValue(true);
45
-
46
- const { result } = renderHook(() => useExperimentVariant(mockData));
47
-
48
- const { shouldWaitForVariant } = result.current;
49
-
50
- expect(shouldWaitForVariant).toBe(false);
51
- });
52
-
53
- it(' if `runningExperimentId` is undefined', () => {
54
- const mockData = {
55
- viewAs: { variantId: EXPERIMENT_DEFAULT_VARIANT_NAME }
56
- };
57
-
58
- jest.spyOn(dotcmsClient, 'isInsideEditor').mockReturnValue(false);
59
-
60
- const { result } = renderHook(() => useExperimentVariant(mockData));
61
-
62
- const { shouldWaitForVariant } = result.current;
63
-
64
- expect(shouldWaitForVariant).toBe(false);
65
- });
66
-
67
- it(' if VariantId get from `PageApi` is same of VariantAssigned', () => {
68
- const locationSpy = jest.spyOn(window, 'location', 'get');
69
-
70
- const locationMock = { ...LocationMock, pathname: '/blog' };
71
-
72
- locationSpy.mockReturnValue(locationMock);
73
-
74
- jest.spyOn(dotcmsClient, 'isInsideEditor').mockReturnValue(false);
75
-
76
- const { result } = renderHook(
77
- () =>
78
- useExperimentVariant({
79
- runningExperimentId: 'exp-id',
80
- viewAs: { variantId: 'variant-1' }
81
- }),
82
- { wrapper }
83
- );
84
-
85
- expect(result.current.shouldWaitForVariant).toBe(false);
86
- });
87
-
88
- describe('shouldWaitForVariant `true`', () => {
89
- it(' if VariantId get from `PageApi` is different of VariantAssigned', () => {
90
- const locationSpy = jest.spyOn(window, 'location', 'get');
91
-
92
- const locationMock = { ...LocationMock, pathname: '/blog' };
93
-
94
- locationSpy.mockReturnValue(locationMock);
95
-
96
- jest.spyOn(dotcmsClient, 'isInsideEditor').mockReturnValue(false);
97
-
98
- const { result } = renderHook(
99
- () =>
100
- useExperimentVariant({
101
- runningExperimentId: 'exp-id',
102
- viewAs: { variantId: EXPERIMENT_DEFAULT_VARIANT_NAME }
103
- }),
104
- { wrapper }
105
- );
106
-
107
- expect(result.current.shouldWaitForVariant).toBe(true);
108
- });
109
- });
110
- });
111
- });
@@ -1,55 +0,0 @@
1
- import { useContext, useEffect, useState } from 'react';
2
-
3
- import { isInsideEditor } from '@dotcms/client';
4
-
5
- import DotExperimentsContext from '../contexts/DotExperimentsContext';
6
-
7
- /**
8
- * A React Hook that determines whether to wait for the correct variant in an A/B testing scenario.
9
- * This is used to avoid flickering - showing the original content before redirecting to the assigned variant.
10
- *
11
- * The hook uses the running experiment id and viewAs (containing variantId) from the provided data.
12
- * It then works with the DotExperimentsContext to synchronize between the assigned variant and the one requested.
13
- * If the hook is executed inside an editor or if no running experiment id is provided, it immediately signals not to wait for the variant.
14
- * Similarly, if the assigned variant matches the requested one, it signals not to wait for the variant.
15
- * By default, the hook signals to wait for the variant.
16
- *
17
- * @param {Object} data - An object containing the runningExperimentId and viewAs (containing variantId).
18
- * @returns {Object} An object with a function `shouldWaitForVariant` that, when called, returns `true` if it should wait for the correct variant, `false` otherwise.
19
- */
20
- export const useExperimentVariant = (data: {
21
- runningExperimentId?: string;
22
- viewAs: { variantId: string };
23
- }): { shouldWaitForVariant: boolean } => {
24
- const dotExperimentInstance = useContext(DotExperimentsContext);
25
-
26
- const { runningExperimentId, viewAs } = data;
27
-
28
- const { variantId } = viewAs;
29
-
30
- // By default, wait for the variant
31
- const [shouldWaitForVariant, setShouldWaitForVariant] = useState<boolean>(true);
32
-
33
- useEffect(() => {
34
- if (isInsideEditor() || !runningExperimentId) {
35
- setShouldWaitForVariant(false);
36
-
37
- return;
38
- }
39
-
40
- const location = typeof window !== 'undefined' ? window.location : undefined;
41
-
42
- if (location && dotExperimentInstance) {
43
- const variantAssigned = dotExperimentInstance.getVariantFromHref(location.pathname);
44
-
45
- if (variantAssigned && variantId === variantAssigned.name) {
46
- // the data requested and the variant assigned is the correct no need to wait
47
- setShouldWaitForVariant(false);
48
-
49
- return;
50
- }
51
- }
52
- }, [dotExperimentInstance, data]);
53
-
54
- return { shouldWaitForVariant };
55
- };
@@ -1,90 +0,0 @@
1
- import { useEffect } from 'react';
2
-
3
- import { isInsideEditor } from '@dotcms/client';
4
-
5
- import { DotExperiments } from '../dot-experiments';
6
- import { EXPERIMENT_DEFAULT_VARIANT_NAME, EXPERIMENT_QUERY_PARAM_KEY } from '../shared/constants';
7
-
8
- /**
9
- * Custom hook `useExperiments`.
10
- *
11
- * This hook is designed to handle changes in the location of the current DotExperiments
12
- * instance and set a global click handler that redirects the application when an element with an
13
- * assigned variant is clicked.
14
- *
15
- * It also manages adding or removing the experimentation query parameter from the URL as
16
- * appropriate when a click occurs.
17
- *
18
- * @returns {void}
19
- */
20
- export const useExperiments = (instance: DotExperiments | null): void => {
21
- /**
22
- * This `useEffect` hook is responsible for tracking location changes when not inside an editor environment, and invoking the
23
- * `locationChanged` method from `experimentContext` with current location and custom redirection function.
24
- *
25
- */
26
- useEffect(() => {
27
- if (!instance || typeof document === 'undefined') {
28
- return;
29
- }
30
-
31
- const insideEditor = isInsideEditor();
32
-
33
- if (!insideEditor) {
34
- const location = typeof window !== 'undefined' ? window.location : undefined;
35
-
36
- if (instance && location) {
37
- instance.locationChanged(location, instance.customRedirectFn);
38
- }
39
- }
40
- }, [instance]);
41
-
42
- /**
43
- * This effect sets a click handler on the document.
44
- * It captures click events and redirects to a new URL if the clicked anchor has a variant assigned,
45
- * additional removing the experiment query param from the URL.
46
- */
47
- useEffect(() => {
48
- if (!instance) {
49
- return;
50
- }
51
-
52
- const customClickHandler = (event: MouseEvent) => {
53
- const target = (event.target as HTMLElement).closest('a');
54
-
55
- if (!target) {
56
- return;
57
- }
58
-
59
- const clickedHref = target.getAttribute('href');
60
-
61
- if (clickedHref) {
62
- const modifiedUrl = new URL(clickedHref, instance.location.href);
63
-
64
- // Remove the experiment query param from the URL
65
- modifiedUrl.searchParams.delete(EXPERIMENT_QUERY_PARAM_KEY);
66
-
67
- event.preventDefault();
68
-
69
- // Get the variant from the href of the clicked anchor
70
- const variant = instance.getVariantFromHref(clickedHref);
71
-
72
- if (variant && variant.name !== EXPERIMENT_DEFAULT_VARIANT_NAME) {
73
- // Set the experiment query param in the URL if the variant is not the default one
74
- modifiedUrl.searchParams.set(EXPERIMENT_QUERY_PARAM_KEY, variant.name);
75
- }
76
-
77
- // Redirect to the new URL using the custom redirect function
78
- instance.customRedirectFn(modifiedUrl.toString());
79
- }
80
- };
81
-
82
- // Register the click handler to all elements in the document
83
- document.addEventListener('click', customClickHandler);
84
-
85
- return () => {
86
- // Remove the click handler when the component is unmounted
87
- document.removeEventListener('click', customClickHandler);
88
- };
89
- }, [instance]);
90
- };
@@ -1,209 +0,0 @@
1
- import { Experiment, ExperimentEvent, IsUserIncludedApiResponse } from '../models';
2
-
3
- /**
4
- * Represents the response object for the IsUserIncluded API.
5
- * @typedef {Object} IsUserIncludedResponse
6
- * @property {IsUserIncludedApiResponse} entity - The response entity.
7
- * @property {string[]} errors - Array of error messages, if any.
8
- * @property {Object} i18nMessagesMap - Map of internationalization messages.
9
- * @property {string[]} messages - Array of additional messages, if any.
10
- */
11
- export const IsUserIncludedResponse: IsUserIncludedApiResponse = {
12
- entity: {
13
- excludedExperimentIds: [],
14
- excludedExperimentIdsEnded: [],
15
- experiments: [
16
- {
17
- id: '11111-11111-11111-11111-11111',
18
- lookBackWindow: {
19
- expireMillis: 1209600000,
20
- value: 'AAAAAAAAAA'
21
- },
22
- name: 'Exp1',
23
- pageUrl: '/blog/index',
24
- regexs: {
25
- isExperimentPage:
26
- '^(http|https):\\/\\/(localhost|127.0.0.1|\\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,})(:\\d{1,5})?\\/blog(\\/index|\\/)?(\\/?\\?.*)?$',
27
- isTargetPage: '.*destinations.*'
28
- },
29
- runningId: '1111111-22222222',
30
- variant: {
31
- name: 'variant-1',
32
- url: '/blog/index?variantName=variant-1'
33
- }
34
- }
35
- ],
36
- includedExperimentIds: ['11111-11111-11111-11111-11111']
37
- },
38
- errors: [],
39
- i18nMessagesMap: {},
40
- messages: []
41
- };
42
-
43
- export const NewIsUserIncludedResponse: IsUserIncludedApiResponse = {
44
- entity: {
45
- excludedExperimentIds: ['11111-11111-11111-11111-11111'],
46
- excludedExperimentIdsEnded: ['11111-11111-11111-11111-11111'],
47
- experiments: [
48
- {
49
- id: '222222-222222-222222-222222-222222',
50
- lookBackWindow: {
51
- expireMillis: 1209600000,
52
- value: 'BBBBBBBBBBBBBB'
53
- },
54
- name: 'Exp2',
55
- pageUrl: '/destinations/index',
56
- regexs: {
57
- isExperimentPage:
58
- '^(http|https):\\/\\/(localhost|127.0.0.1|\\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,})(:\\d{1,5})?\\/destinations(\\/index|\\/)?(\\/?\\?.*)?$',
59
- isTargetPage: null
60
- },
61
- runningId: '33333333-3333333333',
62
- variant: {
63
- name: 'variant-1',
64
- url: '/blog/index?variantName=variant-1'
65
- }
66
- }
67
- ],
68
- includedExperimentIds: ['222222-222222-222222-222222-222222']
69
- },
70
- errors: [],
71
- i18nMessagesMap: {},
72
- messages: []
73
- };
74
-
75
- export const After15DaysIsUserIncludedResponse: IsUserIncludedApiResponse = {
76
- entity: {
77
- excludedExperimentIds: ['222222-222222-222222-222222-222222'],
78
- excludedExperimentIdsEnded: [],
79
- experiments: [],
80
- includedExperimentIds: []
81
- },
82
- errors: [],
83
- i18nMessagesMap: {},
84
- messages: []
85
- };
86
-
87
- export const NoExperimentsIsUserIncludedResponse: IsUserIncludedApiResponse = {
88
- entity: {
89
- excludedExperimentIds: [],
90
- excludedExperimentIdsEnded: [],
91
- experiments: [],
92
- includedExperimentIds: []
93
- },
94
- errors: [],
95
- i18nMessagesMap: {},
96
- messages: []
97
- };
98
-
99
- export const MOCK_CURRENT_TIMESTAMP = 1704096000000;
100
-
101
- export const TIME_15_DAYS_MILLISECONDS = 1296000 * 1000;
102
-
103
- export const TIME_5_DAYS_MILLISECONDS = 432000 * 1000;
104
-
105
- export const MockDataStoredIndexDB: Experiment[] = [
106
- {
107
- ...IsUserIncludedResponse.entity.experiments[0],
108
- lookBackWindow: {
109
- ...IsUserIncludedResponse.entity.experiments[0].lookBackWindow,
110
- // Added expireTime to the lookBackWindow
111
- expireTime:
112
- MOCK_CURRENT_TIMESTAMP +
113
- IsUserIncludedResponse.entity.experiments[0].lookBackWindow.expireMillis
114
- }
115
- }
116
- ];
117
-
118
- export const MockDataStoredIndexDBNew: Experiment[] = [
119
- {
120
- ...NewIsUserIncludedResponse.entity.experiments[0],
121
- lookBackWindow: {
122
- ...NewIsUserIncludedResponse.entity.experiments[0].lookBackWindow,
123
- // Added expireTime to the lookBackWindow
124
- expireTime:
125
- // 2nd request, 5 days later, so expireTime is 5 days from MOCK_CURRENT_TIMESTAMP
126
- MOCK_CURRENT_TIMESTAMP +
127
- TIME_5_DAYS_MILLISECONDS +
128
- NewIsUserIncludedResponse.entity.experiments[0].lookBackWindow.expireMillis
129
- }
130
- }
131
- ];
132
-
133
- // Final data to be stored in IndexedDB only the last, the first will be removed by was ended from `excludedExperimentIdsEnded`
134
- export const MockDataStoredIndexDBWithNew: Experiment[] = [...MockDataStoredIndexDBNew];
135
-
136
- // Mock Store after 15 days
137
- export const MockDataStoredIndexDBWithNew15DaysLater: Experiment[] = [...MockDataStoredIndexDBNew];
138
-
139
- /**
140
- * Represents an event that indicates the expected experiments parsed from a response to send to Analytics.
141
- *
142
- * @typedef {Object} ExpectedExperimentsParsedEvent
143
- * @property {ExperimentEvent[]} experiments - An array of experiment events.
144
- */
145
- export const ExpectedExperimentsParsedEvent: ExperimentEvent[] = [
146
- {
147
- experiment: IsUserIncludedResponse.entity.experiments[0].id,
148
- runningId: IsUserIncludedResponse.entity.experiments[0].runningId,
149
- variant: IsUserIncludedResponse.entity.experiments[0].variant.name,
150
- lookBackWindow: IsUserIncludedResponse.entity.experiments[0].lookBackWindow.value,
151
- isExperimentPage: false,
152
- isTargetPage: false
153
- }
154
- ];
155
-
156
- /**
157
- * Represents a mock location object.
158
- *
159
- * @typedef {Object} LocationMock
160
- * @property {string} href - The complete URL.
161
- *
162
- */
163
- export const LocationMock: Location = {
164
- hash: '',
165
- host: '',
166
- hostname: '',
167
- href: 'http://localhost/blog',
168
- origin: '',
169
- pathname: '',
170
- port: '',
171
- protocol: '',
172
- search: '',
173
- assign: () => {
174
- //
175
- },
176
- replace: () => {
177
- //
178
- },
179
- reload: () => {
180
- //
181
- },
182
- toString: () => {
183
- return '';
184
- },
185
- ancestorOrigins: {} as DOMStringList
186
- };
187
-
188
- const store: { [key: string]: string } = {};
189
-
190
- export const sessionStorageMock = {
191
- getItem: function (key: string): string | null {
192
- return store[key] || null;
193
- },
194
- setItem: function (key: string, value: string) {
195
- store[key] = value;
196
- },
197
- removeItem: function (key: string) {
198
- delete store[key];
199
- },
200
- clear: function () {
201
- Object.keys(store).forEach((key) => delete store[key]);
202
- },
203
- key: function (index: number) {
204
- return Object.keys(store)[index] || null;
205
- },
206
- get length() {
207
- return Object.keys(store).length;
208
- }
209
- };
@@ -1,187 +0,0 @@
1
- import { parseData, parseDataForAnalytics } from './parser';
2
-
3
- import {
4
- IsUserIncludedResponse,
5
- LocationMock,
6
- MOCK_CURRENT_TIMESTAMP,
7
- MockDataStoredIndexDB,
8
- MockDataStoredIndexDBWithNew,
9
- MockDataStoredIndexDBWithNew15DaysLater,
10
- NewIsUserIncludedResponse,
11
- TIME_15_DAYS_MILLISECONDS,
12
- TIME_5_DAYS_MILLISECONDS
13
- } from '../mocks/mock';
14
- import { Experiment, ExperimentParsed, FetchExperiments } from '../models';
15
-
16
- const assignedExperiments: Experiment[] = IsUserIncludedResponse.entity.experiments;
17
-
18
- const experimentMock = IsUserIncludedResponse.entity.experiments[0];
19
-
20
- describe('Parsers', () => {
21
- describe('parseData For Analytics', () => {
22
- it('returns `isExperimentPage` true and `isTargetPage` false when location is /blog', () => {
23
- const expectedURL = 'http://localhost/blog';
24
-
25
- const expectedExperimentsParsed: ExperimentParsed = {
26
- href: expectedURL,
27
- experiments: [
28
- {
29
- experiment: experimentMock.id,
30
- runningId: experimentMock.runningId,
31
- variant: experimentMock.variant.name,
32
- lookBackWindow: experimentMock.lookBackWindow.value,
33
- isExperimentPage: true,
34
- isTargetPage: false
35
- }
36
- ]
37
- };
38
-
39
- const location: Location = { ...LocationMock, href: expectedURL };
40
-
41
- const parsedData: ExperimentParsed = parseDataForAnalytics(
42
- assignedExperiments,
43
- location
44
- );
45
-
46
- expect(parsedData).toStrictEqual(expectedExperimentsParsed);
47
- });
48
-
49
- it('returns `isExperimentPage` false and `isTargetPage` true when location is /destinations', () => {
50
- const expectedURL = 'http://localhost/destinations';
51
-
52
- const expectedExperimentsParsed: ExperimentParsed = {
53
- href: expectedURL,
54
- experiments: [
55
- {
56
- experiment: experimentMock.id,
57
- runningId: experimentMock.runningId,
58
- variant: experimentMock.variant.name,
59
- lookBackWindow: experimentMock.lookBackWindow.value,
60
- isExperimentPage: false,
61
- isTargetPage: true
62
- }
63
- ]
64
- };
65
-
66
- const location: Location = { ...LocationMock, href: expectedURL };
67
-
68
- const parsedData: ExperimentParsed = parseDataForAnalytics(
69
- assignedExperiments,
70
- location
71
- );
72
-
73
- expect(parsedData).toStrictEqual(expectedExperimentsParsed);
74
- });
75
-
76
- it('returns `isExperimentPage` false and `isTargetPage` false when location is /other-url', () => {
77
- const expectedURL = 'http://localhost/other-url';
78
-
79
- const expectedExperimentsParsed: ExperimentParsed = {
80
- href: expectedURL,
81
- experiments: [
82
- {
83
- experiment: experimentMock.id,
84
- runningId: experimentMock.runningId,
85
- variant: experimentMock.variant.name,
86
- lookBackWindow: experimentMock.lookBackWindow.value,
87
- isExperimentPage: false,
88
- isTargetPage: false
89
- }
90
- ]
91
- };
92
-
93
- const location: Location = { ...LocationMock, href: expectedURL };
94
-
95
- const parsedData: ExperimentParsed = parseDataForAnalytics(
96
- assignedExperiments,
97
- location
98
- );
99
-
100
- expect(parsedData).toStrictEqual(expectedExperimentsParsed);
101
- });
102
- });
103
-
104
- describe('parseData For Store', () => {
105
- const mockNow = jest.spyOn(Date, 'now');
106
-
107
- mockNow.mockImplementation(() => MOCK_CURRENT_TIMESTAMP);
108
-
109
- beforeEach(() => {
110
- jest.clearAllMocks();
111
- });
112
-
113
- it('should handle case where only NEW data is available', () => {
114
- // First request, expire in now + experiment.lookBackWindow.expireMillis
115
- const newData: FetchExperiments = {
116
- experiments: IsUserIncludedResponse.entity.experiments,
117
- excludedExperimentIdsEnded: []
118
- };
119
-
120
- const dataFromIndexDB: Experiment[] | undefined = undefined;
121
-
122
- const parsedData = parseData(newData, dataFromIndexDB);
123
-
124
- expect(parsedData).toStrictEqual(MockDataStoredIndexDB);
125
- expect(newData.experiments.length).toBe(parsedData.length);
126
- });
127
-
128
- it('should handle case where only OLD data is available', () => {
129
- //No new request, not touch anything if not expired
130
-
131
- const newData: FetchExperiments = {
132
- experiments: [],
133
- excludedExperimentIdsEnded: []
134
- };
135
-
136
- const dataFromIndexDB: Experiment[] | undefined = MockDataStoredIndexDB;
137
-
138
- const parsedData = parseData(newData, dataFromIndexDB);
139
-
140
- expect(parsedData).toStrictEqual(MockDataStoredIndexDB);
141
- expect(MockDataStoredIndexDB.length).toBe(parsedData.length);
142
- });
143
-
144
- it('should handle case where both OLD and NEW data are available', () => {
145
- //new request, stored data + new data. No delete anything only 5 days passed, 2 to store
146
-
147
- const nowPlus5Days = MOCK_CURRENT_TIMESTAMP + TIME_5_DAYS_MILLISECONDS;
148
-
149
- mockNow.mockImplementation(() => nowPlus5Days);
150
-
151
- const newData: FetchExperiments = {
152
- experiments: NewIsUserIncludedResponse.entity.experiments,
153
- excludedExperimentIdsEnded: [
154
- ...NewIsUserIncludedResponse.entity.excludedExperimentIdsEnded
155
- ]
156
- };
157
-
158
- const dataFromIndexDB: Experiment[] | undefined = MockDataStoredIndexDB;
159
-
160
- const parsedData = parseData(newData, dataFromIndexDB);
161
-
162
- expect(parsedData).toStrictEqual(MockDataStoredIndexDBWithNew);
163
-
164
- expect(parsedData.length).toBe(MockDataStoredIndexDBWithNew.length);
165
- });
166
-
167
- it('should remove from stored experiment the experiments expired', () => {
168
- // no new request, 15 days later, so expireTime is 15 days from MOCK_CURRENT_TIMESTAMP
169
- // 1st experiment expired, so only 1 to store
170
- const now15Days = MOCK_CURRENT_TIMESTAMP + TIME_15_DAYS_MILLISECONDS;
171
-
172
- mockNow.mockImplementation(() => now15Days);
173
-
174
- const newData: FetchExperiments = {
175
- experiments: [],
176
- excludedExperimentIdsEnded: []
177
- };
178
-
179
- const dataFromIndexDB: Experiment[] | undefined = MockDataStoredIndexDBWithNew;
180
-
181
- const parsedData = parseData(newData, dataFromIndexDB);
182
-
183
- expect(parsedData.length).toBe(MockDataStoredIndexDBWithNew15DaysLater.length);
184
- expect(parsedData).toStrictEqual(MockDataStoredIndexDBWithNew15DaysLater);
185
- });
186
- });
187
- });