@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,142 +0,0 @@
1
- import {
2
- checkFlagExperimentAlreadyChecked,
3
- getDataExperimentAttributes,
4
- getExperimentScriptTag,
5
- getFullUrl,
6
- getScriptDataAttributes
7
- } from './utils';
8
-
9
- import { EXPERIMENT_ALREADY_CHECKED_KEY, EXPERIMENT_SCRIPT_FILE_NAME } from '../constants';
10
- import { LocationMock } from '../mocks/mock';
11
-
12
- describe('Utility ', () => {
13
- describe('getExperimentScriptTag', () => {
14
- it('should throw an error if the experiment script is not found', () => {
15
- document.body.innerHTML = `<script src="other-script.js"></script>`;
16
- expect(() => getExperimentScriptTag()).toThrow('Experiment script not found');
17
- });
18
-
19
- it('should return the script element when the experiment script is found', () => {
20
- const experimentScriptUrl = 'http://example.com/' + EXPERIMENT_SCRIPT_FILE_NAME;
21
-
22
- document.body.innerHTML = `<script src="${experimentScriptUrl}"></script>`;
23
-
24
- const scriptTag = getExperimentScriptTag();
25
-
26
- expect(scriptTag).toBeDefined();
27
- expect(scriptTag.src).toBe(experimentScriptUrl);
28
- });
29
- });
30
-
31
- describe('getDataExperimentAttributes', () => {
32
- const location: Location = { ...LocationMock, href: 'http:/localhost/' };
33
-
34
- it('should return null and warn if data-experiment-api-key is not specified but script is present', () => {
35
- const experimentScriptUrl = 'http://example.com/' + EXPERIMENT_SCRIPT_FILE_NAME;
36
-
37
- document.body.innerHTML = `<script src="${experimentScriptUrl}"></script>`;
38
-
39
- try {
40
- getDataExperimentAttributes(location);
41
- expect('This should not be reached if an error is thrown').toBeNull();
42
- } catch (error) {
43
- expect(() => getDataExperimentAttributes(location)).toThrow(
44
- 'You need specify the `data-experiment-api-key`'
45
- );
46
- }
47
- });
48
-
49
- it('should return the experiment attributes if they are present', () => {
50
- const experimentScriptUrl = 'http://example.com/' + EXPERIMENT_SCRIPT_FILE_NAME;
51
-
52
- document.body.innerHTML = `<script src="${experimentScriptUrl}" data-experiment-api-key="testKey" data-experiment-server="http://localhost"></script>`;
53
-
54
- const attributes = getDataExperimentAttributes(location);
55
-
56
- expect(attributes).toEqual({
57
- apiKey: 'testKey',
58
- server: 'http://localhost',
59
- debug: false
60
- });
61
- });
62
- });
63
-
64
- describe('getScriptDataAttributes', () => {
65
- it('should return the experiment attributes if they are present', () => {
66
- const experimentScriptUrl = 'http://example.com/' + EXPERIMENT_SCRIPT_FILE_NAME;
67
-
68
- document.body.innerHTML = `<script src="${experimentScriptUrl}" data-experiment-api-key="testKey" data-experiment-server="http://localhost"></script>`;
69
-
70
- // eslint-disable-next-line no-restricted-globals
71
- const attributes = getScriptDataAttributes(location);
72
-
73
- expect(attributes).toEqual({
74
- apiKey: 'testKey',
75
- server: 'http://localhost',
76
- debug: false
77
- });
78
- });
79
- });
80
-
81
- describe('SessionStorage EXPERIMENT_ALREADY_CHECKED_KEY handle', () => {
82
- Object.defineProperty(window, 'sessionStorage', {
83
- value: {
84
- setItem: jest.fn(),
85
- getItem: jest.fn()
86
- },
87
- writable: true
88
- });
89
-
90
- describe('checkFlagExperimentAlreadyChecked', () => {
91
- const getItemMock = window.sessionStorage.getItem as jest.MockedFunction<
92
- typeof window.sessionStorage.getItem
93
- >;
94
-
95
- const testCases = [
96
- { value: '', expected: false, description: 'sessionStorage value is ""' },
97
- { value: 'true', expected: true, description: 'sessionStorage value is "true"' },
98
- { value: null, expected: false, description: 'sessionStorage value is null' }
99
- ];
100
-
101
- testCases.forEach(({ description, value, expected }) => {
102
- it(`returns ${expected} when ${description}`, () => {
103
- getItemMock.mockReturnValue(value);
104
-
105
- expect(checkFlagExperimentAlreadyChecked()).toBe(expected);
106
- expect(getItemMock).toHaveBeenCalledWith(EXPERIMENT_ALREADY_CHECKED_KEY);
107
- });
108
- });
109
- });
110
- });
111
-
112
- describe('getFullUrl', () => {
113
- const href = 'http://localhost/';
114
-
115
- const location: Location = { ...LocationMock, href };
116
-
117
- it('should return null if absolutePath is null', () => {
118
- const result = getFullUrl(location, null);
119
-
120
- expect(result).toBeNull();
121
- });
122
-
123
- it('should return the same absolutePath if it is a full URL', () => {
124
- const absolutePath = href + '/test';
125
-
126
- const expectedUrl = absolutePath;
127
-
128
- const result = getFullUrl(location, absolutePath);
129
-
130
- expect(result).toBe(absolutePath);
131
- expect(result).toBe(expectedUrl);
132
- });
133
-
134
- it('should return a full URL if absolutePath is a relative path', () => {
135
- const absolutePath = '/test';
136
-
137
- const result = getFullUrl(location, absolutePath);
138
-
139
- expect(result).toBe(`${location.origin}${absolutePath}`);
140
- });
141
- });
142
- });
@@ -1,203 +0,0 @@
1
- import {
2
- EXPERIMENT_ALLOWED_DATA_ATTRIBUTES,
3
- EXPERIMENT_ALREADY_CHECKED_KEY,
4
- EXPERIMENT_FETCH_EXPIRE_TIME_KEY,
5
- EXPERIMENT_QUERY_PARAM_KEY,
6
- EXPERIMENT_SCRIPT_FILE_NAME
7
- } from '../constants';
8
- import { DotExperimentConfig, Experiment, Variant } from '../models';
9
-
10
- /**
11
- * Returns the first script element that includes the experiment script identifier.
12
- *
13
- * @return {HTMLScriptElement|undefined} - The found script element or undefined if none is found.
14
- */
15
- export const getExperimentScriptTag = (): HTMLScriptElement => {
16
- const experimentScript = Array.from(document.getElementsByTagName('script')).find((script) =>
17
- script.src.includes(EXPERIMENT_SCRIPT_FILE_NAME)
18
- );
19
-
20
- if (!experimentScript) {
21
- throw new Error('Experiment script not found');
22
- }
23
-
24
- return experimentScript;
25
- };
26
-
27
- /**
28
- * Retrieves experiment attributes from a given script element.
29
- *
30
- *
31
- * @return {DotExperimentConfig | null} - The experiment attributes or null if there are no valid attributes present.
32
- */
33
- export const getDataExperimentAttributes = (location: Location): DotExperimentConfig | null => {
34
- const script = getExperimentScriptTag();
35
-
36
- const defaultExperimentAttributes: DotExperimentConfig = {
37
- apiKey: '',
38
- server: location.href,
39
- debug: false
40
- };
41
-
42
- let experimentAttribute: Partial<DotExperimentConfig> = {};
43
-
44
- if (!script.hasAttribute('data-experiment-api-key')) {
45
- throw new Error('You need specify the `data-experiment-api-key`');
46
- }
47
-
48
- Array.from(script.attributes).forEach((attr) => {
49
- if (EXPERIMENT_ALLOWED_DATA_ATTRIBUTES.includes(attr.name)) {
50
- // Server of dotCMS
51
- if (attr.name === 'data-experiment-server') {
52
- experimentAttribute = { ...experimentAttribute, server: attr.value };
53
- }
54
-
55
- // Api Key for Analytics App
56
- if (attr.name === 'data-experiment-api-key') {
57
- experimentAttribute = { ...experimentAttribute, apiKey: attr.value };
58
- }
59
-
60
- // Show debug
61
- if (attr.name === 'data-experiment-debug') {
62
- experimentAttribute = {
63
- ...experimentAttribute,
64
- debug: true
65
- };
66
- }
67
- }
68
- });
69
-
70
- return { ...defaultExperimentAttributes, ...experimentAttribute };
71
- };
72
-
73
- /**
74
- * Retrieves the data attributes from the experiment script tag.
75
- *
76
- * @example
77
- * Given the custom script tag in your HTML:
78
- * <script src="/dot-experiments.iife.js"
79
- * defer=""
80
- * data-experiment-api-key="api-token"
81
- * data-experiment-server="http://localhost:8080/"
82
- * data-experiment-debug>
83
- * </script>
84
- *
85
- * @returns {DotExperimentConfig | null} The data attributes of the experiment script tag, or null if no experiment script is found.
86
- */
87
- export const getScriptDataAttributes = (location: Location): DotExperimentConfig | null => {
88
- const dataExperimentAttributes = getDataExperimentAttributes(location);
89
-
90
- if (dataExperimentAttributes) {
91
- return dataExperimentAttributes;
92
- }
93
-
94
- return null;
95
- };
96
-
97
- /**
98
- * Checks the flag indicating whether the experiment has already been checked.
99
- *
100
- * @function checkFlagExperimentAlreadyChecked
101
- * @returns {boolean} - returns true if experiment has already been checked, otherwise false.
102
- */
103
- export const checkFlagExperimentAlreadyChecked = (): boolean => {
104
- const flag = sessionStorage.getItem(EXPERIMENT_ALREADY_CHECKED_KEY);
105
-
106
- return flag === 'true';
107
- };
108
-
109
- /**
110
- * Checks if the data needs to be invalidated based on the creation date.
111
- *
112
- * @returns {boolean} - True if the data needs to be invalidated, false otherwise.
113
- */
114
- export const isDataCreateValid = (): boolean => {
115
- try {
116
- const timeValidUntil = Number(localStorage.getItem(EXPERIMENT_FETCH_EXPIRE_TIME_KEY));
117
-
118
- if (isNaN(timeValidUntil)) {
119
- return false;
120
- }
121
-
122
- const now = Date.now();
123
-
124
- return timeValidUntil > now;
125
- } catch (error) {
126
- return false;
127
- }
128
- };
129
-
130
- /**
131
- * Ad to an absolute path the baseUrl depending on the location.
132
- *
133
- * @param {string | null} absolutePath - The absolute path of the URL.
134
- * @param {Location} location - The location object representing the current URL.
135
- * @returns {string | null} - The full URL or null if absolutePath is null.
136
- */
137
- export const getFullUrl = (location: Location, absolutePath: string | null): string | null => {
138
- if (absolutePath === null) {
139
- return null;
140
- }
141
-
142
- if (!isFullUrl(absolutePath)) {
143
- const baseUrl = location.origin;
144
-
145
- return `${baseUrl}${absolutePath}`;
146
- }
147
-
148
- return absolutePath;
149
- };
150
-
151
- const isFullUrl = (url: string): boolean => {
152
- const pattern = /^https?:\/\//i;
153
-
154
- return pattern.test(url);
155
- };
156
-
157
- /**
158
- * Updates the URL with the queryParam with the experiment variant name.
159
- *
160
- * @param {Location|string} location - The current location object or the URL string.
161
- * @param {Variant} variant - The experiment variant to update the URL with.
162
- * @returns {string} The updated URL string.
163
- */
164
- export const updateUrlWithExperimentVariant = (
165
- location: Location | string,
166
- variant: Variant | null
167
- ): string => {
168
- const href = typeof location === 'string' ? location : location.href;
169
-
170
- const url = new URL(href);
171
-
172
- if (variant !== null) {
173
- const params = url.searchParams;
174
-
175
- params.set(EXPERIMENT_QUERY_PARAM_KEY, variant.name);
176
- url.search = params.toString();
177
- }
178
-
179
- return url.toString();
180
- };
181
-
182
- /**
183
- * Check if two arrays of Experiment objects are equal.
184
- *
185
- * @param {Experiment[]} obj1 - The first array of Experiment objects.
186
- * @param {Experiment[]} obj2 - The second array of Experiment objects.
187
- * @return {boolean} - True if the arrays are equal, false otherwise.
188
- */
189
- export const objectsAreEqual = (obj1: Experiment[], obj2: Experiment[]): boolean => {
190
- if (obj1.length === 0 && obj2.length === 0) {
191
- return false;
192
- }
193
-
194
- return JSON.stringify(obj1) === JSON.stringify(obj2);
195
- };
196
-
197
- /**
198
- * A function to redirect the user to a new URL.
199
- *
200
- * @param {string} href - The URL to redirect to.
201
- * @returns {void}
202
- */
203
- export const defaultRedirectFn = (href: string) => (window.location.href = href);
@@ -1,36 +0,0 @@
1
- import { DotExperiments } from './dot-experiments';
2
- import { EXPERIMENT_WINDOWS_KEY } from './shared/constants';
3
- import { getScriptDataAttributes } from './shared/utils/utils';
4
-
5
- declare global {
6
- interface Window {
7
- [EXPERIMENT_WINDOWS_KEY]: DotExperiments;
8
- }
9
- }
10
-
11
- jest.mock('./shared/utils/utils', () => ({
12
- getScriptDataAttributes: jest.fn().mockReturnValue({ server: 'http://localhost' }),
13
- Logger: jest.fn()
14
- }));
15
-
16
- describe('IIFE Execution', () => {
17
- it('should call getScriptDataAttributes and set window[EXPERIMENT_WINDOWS_KEY]', () => {
18
- const fakeInstance = {
19
- initialize: jest.fn()
20
- } as unknown as DotExperiments;
21
-
22
- const getInstanceMock = jest
23
- .spyOn(DotExperiments, 'getInstance')
24
- .mockReturnValue(fakeInstance);
25
-
26
- require('./standalone');
27
-
28
- expect(getScriptDataAttributes).toHaveBeenCalled();
29
-
30
- expect(getInstanceMock).toHaveBeenCalledWith({ server: 'http://localhost' });
31
- expect(getInstanceMock).toHaveBeenCalled();
32
-
33
- expect(window[EXPERIMENT_WINDOWS_KEY]).toBeDefined();
34
- expect(window[EXPERIMENT_WINDOWS_KEY]).toEqual(fakeInstance);
35
- });
36
- });
@@ -1,28 +0,0 @@
1
- import { DotExperiments } from './dot-experiments';
2
- import { EXPERIMENT_WINDOWS_KEY } from './shared/constants';
3
- import { getScriptDataAttributes } from './shared/utils/utils';
4
-
5
- declare global {
6
- interface Window {
7
- [EXPERIMENT_WINDOWS_KEY]: DotExperiments;
8
- }
9
- }
10
-
11
- /**
12
- * This file sets up everything necessary to run an Experiment in Standalone Mode(Immediately Invoked Function Expressions).
13
- * It checks which experiments are currently running, and generates the essential data
14
- * needed for storing in DotCMS for subsequent A/B Testing validation.
15
- *
16
- */
17
- if (window) {
18
- // TODO: make this file buildable by task and publish to dotCMS/src/main/webapp/html
19
- try {
20
- const dataAttributes = getScriptDataAttributes(window.location);
21
-
22
- if (dataAttributes) {
23
- window[EXPERIMENT_WINDOWS_KEY] = DotExperiments.getInstance({ ...dataAttributes });
24
- }
25
- } catch (error) {
26
- throw new Error(`Error instancing DotExperiments: ${error}`);
27
- }
28
- }
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "jsx": "react-jsx",
5
- "allowJs": false,
6
- "esModuleInterop": false,
7
- "allowSyntheticDefaultImports": true,
8
- "strict": true
9
- },
10
- "files": [],
11
- "include": ["src"],
12
- "references": [
13
- {
14
- "path": "./tsconfig.lib.json"
15
- },
16
- {
17
- "path": "./tsconfig.spec.json"
18
- }
19
- ]
20
- }
package/tsconfig.lib.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../../dist/out-tsc",
5
- "declaration": true,
6
- "types": ["node", "vite/client"]
7
- },
8
- "exclude": [
9
- "jest.config.ts",
10
- "src/**/*.spec.ts",
11
- "src/**/*.test.ts",
12
- "src/**/*.spec.tsx",
13
- "src/**/*.test.tsx",
14
- "src/**/*.spec.js",
15
- "src/**/*.test.js",
16
- "src/**/*.spec.jsx",
17
- "src/**/*.test.jsx"
18
- ],
19
- "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
20
- }
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../../dist/out-tsc",
5
- "module": "commonjs",
6
- "types": ["jest", "node"]
7
- },
8
- "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"]
9
- }
package/vite.config.ts DELETED
@@ -1,41 +0,0 @@
1
- import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
2
- import { defineConfig } from 'vite';
3
- import dts from 'vite-plugin-dts';
4
-
5
- import * as path from 'path';
6
-
7
- export default defineConfig({
8
- root: __dirname,
9
- cacheDir: '../../../node_modules/.vite/libs/sdk/experiments',
10
-
11
- plugins: [
12
- nxViteTsPaths(),
13
- dts({
14
- entryRoot: 'src',
15
- tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
16
- skipDiagnostics: true
17
- })
18
- ],
19
-
20
- // Configuration for building the DotExperiment as IIFE file to use in
21
- // plain HTML or other projects.
22
- build: {
23
- outDir: '../../../dist/libs/sdk/experiments',
24
- reportCompressedSize: true,
25
- emptyOutDir: true,
26
- commonjsOptions: {
27
- transformMixedEsModules: true
28
- },
29
- minify: 'terser',
30
- lib: {
31
- entry: 'src/lib/standalone.ts',
32
- name: 'DotExperiment',
33
- fileName: 'dot-experiments.min',
34
- formats: ['iife']
35
- },
36
- rollupOptions: {
37
- // External packages that should not be bundled into your library.
38
- external: []
39
- }
40
- }
41
- });
File without changes