@envive-ai/react-hooks 0.3.63 → 0.3.65

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 (112) hide show
  1. package/dist/application/models/featureGates.cjs +2 -2
  2. package/dist/application/models/featureGates.d.cts +2 -2
  3. package/dist/application/models/featureGates.d.ts +2 -2
  4. package/dist/application/models/featureGates.js +2 -2
  5. package/dist/application/utils/elementObserver.d.cts +2 -2
  6. package/dist/atoms/app/index.d.cts +7 -7
  7. package/dist/atoms/app/index.d.ts +1 -1
  8. package/dist/atoms/app/variant.d.ts +6 -6
  9. package/dist/atoms/chat/chatState.d.cts +19 -19
  10. package/dist/atoms/chat/chatState.d.ts +19 -19
  11. package/dist/atoms/chat/form.d.cts +2 -2
  12. package/dist/atoms/chat/form.d.ts +3 -3
  13. package/dist/atoms/chat/index.d.ts +3 -3
  14. package/dist/atoms/chat/lastMessage.d.cts +2 -2
  15. package/dist/atoms/chat/lastMessage.d.ts +2 -2
  16. package/dist/atoms/chat/messageQueue.d.ts +7 -7
  17. package/dist/atoms/chat/performanceMetrics.d.cts +6 -6
  18. package/dist/atoms/chat/performanceMetrics.d.ts +6 -6
  19. package/dist/atoms/chat/renderedWidgetRefs.d.cts +2 -2
  20. package/dist/atoms/chat/renderedWidgetRefs.d.ts +3 -3
  21. package/dist/atoms/chat/replies.d.cts +3 -3
  22. package/dist/atoms/chat/suggestions.d.ts +3 -3
  23. package/dist/atoms/envive/enviveConfig.d.cts +14 -14
  24. package/dist/atoms/globalSearch/globalSearch.d.cts +5 -5
  25. package/dist/atoms/org/customerService.d.cts +6 -6
  26. package/dist/atoms/org/customerService.d.ts +6 -6
  27. package/dist/atoms/org/graphqlConfig.d.cts +4 -4
  28. package/dist/atoms/org/graphqlConfig.d.ts +4 -4
  29. package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
  30. package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
  31. package/dist/atoms/org/orgAnalyticsConfig.d.cts +4 -4
  32. package/dist/atoms/org/orgAnalyticsConfig.d.ts +4 -4
  33. package/dist/atoms/search/types.d.cts +1 -1
  34. package/dist/atoms/search/utils.d.cts +1 -1
  35. package/dist/atoms/widget/chatPreviewLoading.d.cts +2 -2
  36. package/dist/atoms/widget/chatPreviewLoading.d.ts +2 -2
  37. package/dist/contexts/pageContext/pageContext.cjs +12 -24
  38. package/dist/contexts/pageContext/pageContext.js +12 -24
  39. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.cts +2 -2
  40. package/dist/contexts/types.d.cts +1 -1
  41. package/dist/contexts/types.d.ts +1 -1
  42. package/dist/contexts/typesV3.cjs +8 -1
  43. package/dist/contexts/typesV3.d.cts +23 -6
  44. package/dist/contexts/typesV3.d.ts +23 -6
  45. package/dist/contexts/typesV3.js +8 -2
  46. package/dist/hooks/Intersection/useIntersection.cjs +9 -2
  47. package/dist/hooks/Intersection/useIntersection.d.cts +2 -2
  48. package/dist/hooks/Intersection/useIntersection.d.ts +2 -2
  49. package/dist/hooks/Intersection/useIntersection.js +9 -2
  50. package/dist/hooks/SystemSettingsContext/useSystemSettingsContext.d.cts +2 -2
  51. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.cjs +12 -3
  52. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.cts +2 -1
  53. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.ts +2 -1
  54. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.js +13 -4
  55. package/dist/hooks/WidgetLoadDiagnostics/index.cjs +4 -0
  56. package/dist/hooks/WidgetLoadDiagnostics/index.d.cts +2 -0
  57. package/dist/hooks/WidgetLoadDiagnostics/index.d.ts +2 -0
  58. package/dist/hooks/WidgetLoadDiagnostics/index.js +3 -0
  59. package/dist/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.cjs +122 -0
  60. package/dist/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.d.cts +48 -0
  61. package/dist/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.d.ts +48 -0
  62. package/dist/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.js +120 -0
  63. package/dist/hooks/utils.d.cts +1 -1
  64. package/dist/hooks/utils.d.ts +1 -1
  65. package/dist/services/amplitudeService/eventNames.cjs +2 -1
  66. package/dist/services/amplitudeService/eventNames.d.cts +2 -1
  67. package/dist/services/amplitudeService/eventNames.d.ts +2 -1
  68. package/dist/services/amplitudeService/eventNames.js +2 -1
  69. package/dist/services/enviveConfigService/enviveConfigService.cjs +21 -35
  70. package/dist/services/enviveConfigService/enviveConfigService.d.cts +1 -2
  71. package/dist/services/enviveConfigService/enviveConfigService.d.ts +1 -2
  72. package/dist/services/enviveConfigService/enviveConfigService.js +21 -35
  73. package/dist/services/enviveConfigService/fetchBootstrapConfig.cjs +15 -16
  74. package/dist/services/enviveConfigService/fetchBootstrapConfig.js +15 -16
  75. package/dist/services/enviveConfigService/fetchGraphQLConfig.cjs +1 -69
  76. package/dist/services/enviveConfigService/fetchGraphQLConfig.js +2 -69
  77. package/dist/services/featureFlagService/index.cjs +7 -5
  78. package/dist/services/featureFlagService/index.d.cts +2 -1
  79. package/dist/services/featureFlagService/index.d.ts +2 -1
  80. package/dist/services/featureFlagService/index.js +7 -5
  81. package/dist/services/ga4ProjectionService/ga4EventSchema.cjs +3 -2
  82. package/dist/services/ga4ProjectionService/ga4EventSchema.js +3 -2
  83. package/dist/services/hardcopyService/hardcopyService.cjs +12 -3
  84. package/dist/services/hardcopyService/hardcopyService.d.cts +3 -1
  85. package/dist/services/hardcopyService/hardcopyService.d.ts +3 -1
  86. package/dist/services/hardcopyService/hardcopyService.js +12 -3
  87. package/package.json +5 -1
  88. package/src/application/models/featureGates.ts +11 -8
  89. package/src/contexts/pageContext/__tests__/pageContext.test.tsx +4 -59
  90. package/src/contexts/pageContext/pageContext.tsx +20 -41
  91. package/src/contexts/typesV3.ts +22 -4
  92. package/src/hooks/Intersection/useIntersection.ts +11 -0
  93. package/src/hooks/TrackComponentVisibleEvent/__tests__/useTrackComponentVisibleEvent.test.tsx +64 -0
  94. package/src/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.ts +23 -4
  95. package/src/hooks/WidgetLoadDiagnostics/__tests__/useWidgetLoadDiagnostics.test.ts +161 -0
  96. package/src/hooks/WidgetLoadDiagnostics/index.ts +5 -0
  97. package/src/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.ts +178 -0
  98. package/src/services/amplitudeService/eventNames.ts +4 -0
  99. package/src/services/enviveConfigService/__tests__/enviveConfigService.test.ts +72 -158
  100. package/src/services/enviveConfigService/__tests__/fetchBootstrapConfig.test.ts +111 -13
  101. package/src/services/enviveConfigService/__tests__/fetchGraphQLConfig.test.ts +161 -490
  102. package/src/services/enviveConfigService/enviveConfigService.ts +51 -87
  103. package/src/services/enviveConfigService/fetchBootstrapConfig.ts +74 -66
  104. package/src/services/enviveConfigService/fetchGraphQLConfig.ts +0 -120
  105. package/src/services/featureFlagService/__tests__/getFeatureFlagOverrides.test.ts +54 -0
  106. package/src/services/featureFlagService/index.ts +19 -6
  107. package/src/services/ga4ProjectionService/ga4EventSchema.ts +5 -0
  108. package/src/services/hardcopyService/__tests__/hardcopyService.test.ts +16 -5
  109. package/src/services/hardcopyService/hardcopyService.ts +10 -2
  110. package/dist/application/models/graphql/queries/getWidgetConfigQuery.cjs +0 -42
  111. package/dist/application/models/graphql/queries/getWidgetConfigQuery.js +0 -41
  112. package/src/application/models/graphql/queries/getWidgetConfigQuery.ts +0 -55
@@ -1,36 +1,25 @@
1
1
  import { mockV3ColorsConfig, mockV3FrontendConfig } from 'src/contexts/graphqlContext/mockV3Config';
2
2
  import { FloatingButtonLocation } from '@envive-ai/react-toolkit-v3/FloatingButton';
3
- import { DEFAULT_PAGE_VARIANTS, fetchUnifiedGraphQLConfig } from '../fetchGraphQLConfig';
3
+ import { DEFAULT_PAGE_VARIANTS, transformV3ProductsConfig } from '../fetchGraphQLConfig';
4
4
 
5
5
  const { MockLogger } = vi.hoisted(() => {
6
6
  const MockLogger = vi.fn(function () {
7
- return { logDebug: vi.fn(), logError: vi.fn() };
7
+ return { logDebug: vi.fn(), logError: vi.fn(), logWarn: vi.fn() };
8
8
  });
9
9
  return { MockLogger };
10
10
  });
11
11
 
12
12
  vi.mock('src/application/logging/logger', () => ({ default: MockLogger }));
13
13
 
14
- const mockFetch = vi.fn();
15
- globalThis.fetch = mockFetch;
16
-
17
- function makeOkFetch(v3RootConfigValues: unknown, frontendValues?: unknown) {
18
- mockFetch.mockResolvedValueOnce({
19
- ok: true,
20
- json: () =>
21
- Promise.resolve({
22
- data: {
23
- me: {
24
- getWidgetConfig: {
25
- productsConfig: {
26
- v_three_config: { values: v3RootConfigValues },
27
- frontend: frontendValues == null ? undefined : { values: frontendValues },
28
- },
29
- },
30
- },
31
- },
32
- }),
33
- });
14
+ // transformV3ProductsConfig takes the `productsConfig` object (the shape both
15
+ // getWidgetConfig and /v1/session/bootstrap's widget_config carry). It is the
16
+ // single products-config transform on both config paths; bootstrap reuses it, so
17
+ // its behaviour is covered here directly rather than through a fetch wrapper.
18
+ function makeProductsConfig(v3RootConfigValues: any, frontendValues?: any) {
19
+ return {
20
+ v_three_config: { values: v3RootConfigValues },
21
+ frontend: frontendValues == null ? undefined : { values: frontendValues },
22
+ };
34
23
  }
35
24
 
36
25
  // Minimal valid raw v3 config with all fields present
@@ -57,49 +46,12 @@ beforeEach(() => {
57
46
  vi.clearAllMocks();
58
47
  });
59
48
 
60
- describe('productsConfig transform (via getWidgetConfig)', () => {
61
- describe('error handling', () => {
62
- it('throws when the HTTP response is not ok', async () => {
63
- mockFetch.mockResolvedValueOnce({ ok: false, statusText: 'Unauthorized' });
64
-
65
- await expect(
66
- fetchUnifiedGraphQLConfig('https://api.example.com', 'key', 'user_1', [], 'app'),
67
- ).rejects.toThrow('Unified config request failed');
68
- });
69
-
70
- it('throws when the response contains GraphQL errors', async () => {
71
- mockFetch.mockResolvedValueOnce({
72
- ok: true,
73
- json: () => Promise.resolve({ errors: [{ message: 'Not authorized' }], data: null }),
74
- });
75
-
76
- await expect(
77
- fetchUnifiedGraphQLConfig('https://api.example.com', 'key', 'user_1', [], 'app'),
78
- ).rejects.toThrow('Unified config GraphQL errors');
79
- });
80
-
81
- it('propagates the error when fetch throws', async () => {
82
- mockFetch.mockRejectedValueOnce(new Error('Network failure'));
83
-
84
- await expect(
85
- fetchUnifiedGraphQLConfig('https://api.example.com', 'key', 'user_1', [], 'app'),
86
- ).rejects.toThrow('Network failure');
87
- });
88
- });
89
-
49
+ describe('transformV3ProductsConfig', () => {
90
50
  describe('no colors fallback', () => {
91
- it('returns mock config when v3RootConfig has no colors field', async () => {
92
- makeOkFetch({ ui_configs: {} });
93
-
94
- const result = await fetchUnifiedGraphQLConfig(
95
- 'https://api.example.com',
96
- 'key',
97
- 'user_1',
98
- [],
99
- 'app',
100
- );
51
+ it('returns mock config when v3RootConfig has no colors field', () => {
52
+ const result = transformV3ProductsConfig(makeProductsConfig({ ui_configs: {} }));
101
53
 
102
- expect(result.orgConfig).toEqual({
54
+ expect(result).toEqual({
103
55
  colorsConfig: mockV3ColorsConfig,
104
56
  frontendConfig: mockV3FrontendConfig,
105
57
  orgPageConfig: {
@@ -110,24 +62,10 @@ describe('productsConfig transform (via getWidgetConfig)', () => {
110
62
  });
111
63
  });
112
64
 
113
- it('returns mock config when v3RootConfig is absent entirely', async () => {
114
- mockFetch.mockResolvedValueOnce({
115
- ok: true,
116
- json: () =>
117
- Promise.resolve({
118
- data: { me: { getWidgetConfig: { productsConfig: {} } } },
119
- }),
120
- });
121
-
122
- const result = await fetchUnifiedGraphQLConfig(
123
- 'https://api.example.com',
124
- 'key',
125
- 'user_1',
126
- [],
127
- 'app',
128
- );
65
+ it('returns mock config when productsConfig is empty', () => {
66
+ const result = transformV3ProductsConfig({});
129
67
 
130
- expect(result.orgConfig).toEqual({
68
+ expect(result).toEqual({
131
69
  colorsConfig: mockV3ColorsConfig,
132
70
  frontendConfig: mockV3FrontendConfig,
133
71
  orgPageConfig: {
@@ -145,97 +83,70 @@ describe('productsConfig transform (via getWidgetConfig)', () => {
145
83
  ['middle-left', FloatingButtonLocation.MIDDLE_LEFT],
146
84
  ['middle-right', FloatingButtonLocation.MIDDLE_RIGHT],
147
85
  ['bottom-right', FloatingButtonLocation.BOTTOM_RIGHT],
148
- ])('maps position "%s" to the correct enum value', async (position, expected) => {
149
- makeOkFetch({
150
- ...baseV3Config,
151
- ui_configs: { ...baseV3Config.ui_configs, floating_button: { position } },
152
- });
153
-
154
- const result = await fetchUnifiedGraphQLConfig(
155
- 'https://api.example.com',
156
- 'key',
157
- 'user_1',
158
- [],
159
- 'app',
86
+ ])('maps position "%s" to the correct enum value', (position, expected) => {
87
+ const result = transformV3ProductsConfig(
88
+ makeProductsConfig({
89
+ ...baseV3Config,
90
+ ui_configs: { ...baseV3Config.ui_configs, floating_button: { position } },
91
+ }),
160
92
  );
161
93
 
162
- expect((result.orgConfig?.frontendConfig as any)?.uiConfigs?.floatingButton?.position).toBe(
163
- expected,
164
- );
94
+ expect((result.frontendConfig as any)?.uiConfigs?.floatingButton?.position).toBe(expected);
165
95
  });
166
96
 
167
- it('returns empty config for an unrecognised position string', async () => {
168
- makeOkFetch({
169
- ...baseV3Config,
170
- ui_configs: { ...baseV3Config.ui_configs, floating_button: { position: 'top-center' } },
171
- });
172
-
173
- const result = await fetchUnifiedGraphQLConfig(
174
- 'https://api.example.com',
175
- 'key',
176
- 'user_1',
177
- [],
178
- 'app',
179
- );
180
-
181
- expect(result.orgConfig).toEqual({ colorsConfig: undefined, frontendConfig: undefined });
97
+ it('throws on an unrecognised position string (caller degrades to mock)', () => {
98
+ expect(() =>
99
+ transformV3ProductsConfig(
100
+ makeProductsConfig({
101
+ ...baseV3Config,
102
+ ui_configs: { ...baseV3Config.ui_configs, floating_button: { position: 'top-center' } },
103
+ }),
104
+ ),
105
+ ).toThrow('Invalid position');
182
106
  });
183
107
  });
184
108
 
185
109
  describe('typography normalization', () => {
186
- it('uses underscore-separated font size keys (b_1) when camelCase keys are absent', async () => {
187
- makeOkFetch({
188
- ...baseV3Config,
189
- ui_configs: {
190
- ...baseV3Config.ui_configs,
191
- look_and_feel: {
192
- ...baseV3Config.ui_configs.look_and_feel,
193
- typography: {
194
- ...baseV3Config.ui_configs.look_and_feel.typography,
195
- font_size: { b_1: '14px', h_1: '20px' },
110
+ it('uses underscore-separated font size keys (b_1) when camelCase keys are absent', () => {
111
+ const result = transformV3ProductsConfig(
112
+ makeProductsConfig({
113
+ ...baseV3Config,
114
+ ui_configs: {
115
+ ...baseV3Config.ui_configs,
116
+ look_and_feel: {
117
+ ...baseV3Config.ui_configs.look_and_feel,
118
+ typography: {
119
+ ...baseV3Config.ui_configs.look_and_feel.typography,
120
+ font_size: { b_1: '14px', h_1: '20px' },
121
+ },
196
122
  },
197
123
  },
198
- },
199
- });
200
-
201
- const result = await fetchUnifiedGraphQLConfig(
202
- 'https://api.example.com',
203
- 'key',
204
- 'user_1',
205
- [],
206
- 'app',
124
+ }),
207
125
  );
208
- const fontSize = (result.orgConfig?.frontendConfig as any)?.uiConfigs?.lookAndFeel?.typography
209
- ?.fontSize;
126
+ const fontSize = (result.frontendConfig as any)?.uiConfigs?.lookAndFeel?.typography?.fontSize;
210
127
 
211
128
  expect(fontSize?.b1).toBe('14px');
212
129
  expect(fontSize?.h1).toBe('20px');
213
130
  });
214
131
 
215
- it('normalises lh_N line height keys to numeric string keys', async () => {
216
- makeOkFetch({
217
- ...baseV3Config,
218
- ui_configs: {
219
- ...baseV3Config.ui_configs,
220
- look_and_feel: {
221
- ...baseV3Config.ui_configs.look_and_feel,
222
- typography: {
223
- ...baseV3Config.ui_configs.look_and_feel.typography,
224
- line_height: { lh_114: '1.14', lh_128: '1.28' },
132
+ it('normalises lh_N line height keys to numeric string keys', () => {
133
+ const result = transformV3ProductsConfig(
134
+ makeProductsConfig({
135
+ ...baseV3Config,
136
+ ui_configs: {
137
+ ...baseV3Config.ui_configs,
138
+ look_and_feel: {
139
+ ...baseV3Config.ui_configs.look_and_feel,
140
+ typography: {
141
+ ...baseV3Config.ui_configs.look_and_feel.typography,
142
+ line_height: { lh_114: '1.14', lh_128: '1.28' },
143
+ },
225
144
  },
226
145
  },
227
- },
228
- });
229
-
230
- const result = await fetchUnifiedGraphQLConfig(
231
- 'https://api.example.com',
232
- 'key',
233
- 'user_1',
234
- [],
235
- 'app',
146
+ }),
236
147
  );
237
- const lineHeight = (result.orgConfig?.frontendConfig as any)?.uiConfigs?.lookAndFeel
238
- ?.typography?.lineHeight;
148
+ const lineHeight = (result.frontendConfig as any)?.uiConfigs?.lookAndFeel?.typography
149
+ ?.lineHeight;
239
150
 
240
151
  expect(lineHeight?.['114']).toBe('1.14');
241
152
  expect(lineHeight?.['128']).toBe('1.28');
@@ -243,69 +154,49 @@ describe('productsConfig transform (via getWidgetConfig)', () => {
243
154
  });
244
155
 
245
156
  describe('chat header logo fallback', () => {
246
- it('falls back chatHeaderLogoDarkSrc to chatHeaderLogoSrc when not set', async () => {
247
- makeOkFetch({
248
- ...baseV3Config,
249
- ui_configs: {
250
- ...baseV3Config.ui_configs,
251
- look_and_feel: {
252
- ...baseV3Config.ui_configs.look_and_feel,
253
- chat_header_logo_src: 'logo.png',
254
- chat_header_logo_dark_src: null,
255
- chat_header_logo_light_src: 'logo-light.png',
157
+ it('falls back chatHeaderLogoDarkSrc to chatHeaderLogoSrc when not set', () => {
158
+ const result = transformV3ProductsConfig(
159
+ makeProductsConfig({
160
+ ...baseV3Config,
161
+ ui_configs: {
162
+ ...baseV3Config.ui_configs,
163
+ look_and_feel: {
164
+ ...baseV3Config.ui_configs.look_and_feel,
165
+ chat_header_logo_src: 'logo.png',
166
+ chat_header_logo_dark_src: null,
167
+ chat_header_logo_light_src: 'logo-light.png',
168
+ },
256
169
  },
257
- },
258
- });
259
-
260
- const result = await fetchUnifiedGraphQLConfig(
261
- 'https://api.example.com',
262
- 'key',
263
- 'user_1',
264
- [],
265
- 'app',
170
+ }),
266
171
  );
267
- const lookAndFeel = (result.orgConfig?.frontendConfig as any)?.uiConfigs?.lookAndFeel;
172
+ const lookAndFeel = (result.frontendConfig as any)?.uiConfigs?.lookAndFeel;
268
173
 
269
174
  expect(lookAndFeel?.chatHeaderLogoDarkSrc).toBe('logo.png');
270
175
  });
271
176
 
272
- it('falls back chatHeaderLogoLightSrc to chatHeaderLogoSrc when not set', async () => {
273
- makeOkFetch({
274
- ...baseV3Config,
275
- ui_configs: {
276
- ...baseV3Config.ui_configs,
277
- look_and_feel: {
278
- ...baseV3Config.ui_configs.look_and_feel,
279
- chat_header_logo_src: 'logo.png',
280
- chat_header_logo_dark_src: 'logo-dark.png',
281
- chat_header_logo_light_src: null,
177
+ it('falls back chatHeaderLogoLightSrc to chatHeaderLogoSrc when not set', () => {
178
+ const result = transformV3ProductsConfig(
179
+ makeProductsConfig({
180
+ ...baseV3Config,
181
+ ui_configs: {
182
+ ...baseV3Config.ui_configs,
183
+ look_and_feel: {
184
+ ...baseV3Config.ui_configs.look_and_feel,
185
+ chat_header_logo_src: 'logo.png',
186
+ chat_header_logo_dark_src: 'logo-dark.png',
187
+ chat_header_logo_light_src: null,
188
+ },
282
189
  },
283
- },
284
- });
285
-
286
- const result = await fetchUnifiedGraphQLConfig(
287
- 'https://api.example.com',
288
- 'key',
289
- 'user_1',
290
- [],
291
- 'app',
190
+ }),
292
191
  );
293
- const lookAndFeel = (result.orgConfig?.frontendConfig as any)?.uiConfigs?.lookAndFeel;
192
+ const lookAndFeel = (result.frontendConfig as any)?.uiConfigs?.lookAndFeel;
294
193
 
295
194
  expect(lookAndFeel?.chatHeaderLogoLightSrc).toBe('logo.png');
296
195
  });
297
196
 
298
- it('uses explicit dark/light src when both are set', async () => {
299
- makeOkFetch(baseV3Config);
300
-
301
- const result = await fetchUnifiedGraphQLConfig(
302
- 'https://api.example.com',
303
- 'key',
304
- 'user_1',
305
- [],
306
- 'app',
307
- );
308
- const lookAndFeel = (result.orgConfig?.frontendConfig as any)?.uiConfigs?.lookAndFeel;
197
+ it('uses explicit dark/light src when both are set', () => {
198
+ const result = transformV3ProductsConfig(makeProductsConfig(baseV3Config));
199
+ const lookAndFeel = (result.frontendConfig as any)?.uiConfigs?.lookAndFeel;
309
200
 
310
201
  expect(lookAndFeel?.chatHeaderLogoDarkSrc).toBe('logo-dark.png');
311
202
  expect(lookAndFeel?.chatHeaderLogoLightSrc).toBe('logo-light.png');
@@ -313,61 +204,39 @@ describe('productsConfig transform (via getWidgetConfig)', () => {
313
204
  });
314
205
 
315
206
  describe('merchantOverrideCss', () => {
316
- it('uses merchantOverrideCss from the v3 frontend config when present', async () => {
317
- makeOkFetch({
318
- ...baseV3Config,
319
- ui_configs: {
320
- ...baseV3Config.ui_configs,
321
- merchant_override_css: 'body { color: red; }',
322
- },
323
- });
324
-
325
- const result = await fetchUnifiedGraphQLConfig(
326
- 'https://api.example.com',
327
- 'key',
328
- 'user_1',
329
- [],
330
- 'app',
207
+ it('uses merchantOverrideCss from the v3 frontend config when present', () => {
208
+ const result = transformV3ProductsConfig(
209
+ makeProductsConfig({
210
+ ...baseV3Config,
211
+ ui_configs: {
212
+ ...baseV3Config.ui_configs,
213
+ merchant_override_css: 'body { color: red; }',
214
+ },
215
+ }),
331
216
  );
332
217
 
333
- expect((result.orgConfig?.frontendConfig as any)?.merchantOverrideCss).toBe(
334
- 'body { color: red; }',
335
- );
218
+ expect((result.frontendConfig as any)?.merchantOverrideCss).toBe('body { color: red; }');
336
219
  });
337
220
 
338
- it('falls back to frontendValues.merchant_override_css when absent from v3 config', async () => {
339
- makeOkFetch(baseV3Config, { merchant_override_css: 'body { color: blue; }' });
340
-
341
- const result = await fetchUnifiedGraphQLConfig(
342
- 'https://api.example.com',
343
- 'key',
344
- 'user_1',
345
- [],
346
- 'app',
221
+ it('falls back to frontendValues.merchant_override_css when absent from v3 config', () => {
222
+ const result = transformV3ProductsConfig(
223
+ makeProductsConfig(baseV3Config, { merchant_override_css: 'body { color: blue; }' }),
347
224
  );
348
225
 
349
- expect((result.orgConfig?.frontendConfig as any)?.merchantOverrideCss).toBe(
350
- 'body { color: blue; }',
351
- );
226
+ expect((result.frontendConfig as any)?.merchantOverrideCss).toBe('body { color: blue; }');
352
227
  });
353
228
  });
354
229
 
355
230
  describe('page variants', () => {
356
- it('uses DEFAULT_PAGE_VARIANTS when page_variants is not an array', async () => {
357
- makeOkFetch({ ...baseV3Config, page_variants: null });
358
-
359
- const result = await fetchUnifiedGraphQLConfig(
360
- 'https://api.example.com',
361
- 'key',
362
- 'user_1',
363
- [],
364
- 'app',
231
+ it('uses DEFAULT_PAGE_VARIANTS when page_variants is not an array', () => {
232
+ const result = transformV3ProductsConfig(
233
+ makeProductsConfig({ ...baseV3Config, page_variants: null }),
365
234
  );
366
235
 
367
- expect(result.orgConfig?.orgPageConfig?.pageVariants).toEqual(DEFAULT_PAGE_VARIANTS);
236
+ expect(result.orgPageConfig?.pageVariants).toEqual(DEFAULT_PAGE_VARIANTS);
368
237
  });
369
238
 
370
- it('transforms each page variant entry from the API response', async () => {
239
+ it('transforms each page variant entry from the API response', () => {
371
240
  const rawVariant = {
372
241
  variant_id: 'plp',
373
242
  variant_type: 'plp',
@@ -375,67 +244,47 @@ describe('productsConfig transform (via getWidgetConfig)', () => {
375
244
  variant_checks: [{ check_type: 'UrlResolver' }],
376
245
  widget_mounting: [],
377
246
  };
378
- makeOkFetch({ ...baseV3Config, page_variants: [rawVariant] });
379
-
380
- const result = await fetchUnifiedGraphQLConfig(
381
- 'https://api.example.com',
382
- 'key',
383
- 'user_1',
384
- [],
385
- 'app',
247
+ const result = transformV3ProductsConfig(
248
+ makeProductsConfig({ ...baseV3Config, page_variants: [rawVariant] }),
386
249
  );
387
250
 
388
- expect(result.orgConfig?.orgPageConfig?.pageVariants).toHaveLength(1);
389
- expect(result.orgConfig?.orgPageConfig?.pageVariants[0].variantId).toBe('plp');
390
- expect(result.orgConfig?.orgPageConfig?.pageVariants[0].variantTests?.[0].testType).toBe(
391
- 'UrlResolver',
392
- );
251
+ expect(result.orgPageConfig?.pageVariants).toHaveLength(1);
252
+ expect(result.orgPageConfig?.pageVariants[0].variantId).toBe('plp');
253
+ expect(result.orgPageConfig?.pageVariants[0].variantTests?.[0].testType).toBe('UrlResolver');
393
254
  });
394
255
  });
395
256
 
396
257
  describe('widget configs', () => {
397
- it('keys widget configs by widgetConfigId', async () => {
258
+ it('keys widget configs by widgetConfigId', () => {
398
259
  const rawWidgets = [
399
260
  { widget_config_id: 'widget-1', widget_type: 'floating_button' },
400
261
  { widget_config_id: 'widget-2', widget_type: 'search_bar' },
401
262
  ];
402
- makeOkFetch({ ...baseV3Config, widget_configs: rawWidgets });
403
-
404
- const result = await fetchUnifiedGraphQLConfig(
405
- 'https://api.example.com',
406
- 'key',
407
- 'user_1',
408
- [],
409
- 'app',
263
+ const result = transformV3ProductsConfig(
264
+ makeProductsConfig({ ...baseV3Config, widget_configs: rawWidgets }),
410
265
  );
411
266
 
412
- expect(Object.keys(result.orgConfig?.orgPageConfig?.widgetConfigs ?? {})).toEqual([
267
+ expect(Object.keys(result.orgPageConfig?.widgetConfigs ?? {})).toEqual([
413
268
  'widget-1',
414
269
  'widget-2',
415
270
  ]);
416
- expect(
417
- (result.orgConfig?.orgPageConfig?.widgetConfigs as any)?.['widget-1']?.widgetConfigId,
418
- ).toBe('widget-1');
271
+ expect((result.orgPageConfig?.widgetConfigs as any)?.['widget-1']?.widgetConfigId).toBe(
272
+ 'widget-1',
273
+ );
419
274
  });
420
275
 
421
- it('also exposes widget configs on frontendConfig', async () => {
276
+ it('also exposes widget configs on frontendConfig', () => {
422
277
  const rawWidgets = [{ widget_config_id: 'widget-1', widget_type: 'floating_button' }];
423
- makeOkFetch({ ...baseV3Config, widget_configs: rawWidgets });
424
-
425
- const result = await fetchUnifiedGraphQLConfig(
426
- 'https://api.example.com',
427
- 'key',
428
- 'user_1',
429
- [],
430
- 'app',
278
+ const result = transformV3ProductsConfig(
279
+ makeProductsConfig({ ...baseV3Config, widget_configs: rawWidgets }),
431
280
  );
432
281
 
433
- expect((result.orgConfig?.frontendConfig as any)?.widgetConfigs?.['widget-1']).toBeDefined();
282
+ expect((result.frontendConfig as any)?.widgetConfigs?.['widget-1']).toBeDefined();
434
283
  });
435
284
  });
436
285
 
437
286
  describe('mounting configs', () => {
438
- it('keys mounting configs by their key field', async () => {
287
+ it('keys mounting configs by their key field', () => {
439
288
  const rawMountingConfigs = [
440
289
  {
441
290
  key: 'plp-mount',
@@ -447,237 +296,59 @@ describe('productsConfig transform (via getWidgetConfig)', () => {
447
296
  },
448
297
  },
449
298
  ];
450
- makeOkFetch({ ...baseV3Config, mounting_configs: rawMountingConfigs });
451
-
452
- const result = await fetchUnifiedGraphQLConfig(
453
- 'https://api.example.com',
454
- 'key',
455
- 'user_1',
456
- [],
457
- 'app',
299
+ const result = transformV3ProductsConfig(
300
+ makeProductsConfig({ ...baseV3Config, mounting_configs: rawMountingConfigs }),
458
301
  );
459
302
 
460
- expect(result.orgConfig?.orgPageConfig?.mountingConfigs?.['plp-mount']).toBeDefined();
461
- expect(
462
- result.orgConfig?.orgPageConfig?.mountingConfigs?.['plp-mount']?.mountingConfigId,
463
- ).toBe('mc-1');
303
+ expect(result.orgPageConfig?.mountingConfigs?.['plp-mount']).toBeDefined();
304
+ expect(result.orgPageConfig?.mountingConfigs?.['plp-mount']?.mountingConfigId).toBe('mc-1');
464
305
  });
465
306
  });
466
307
 
467
308
  describe('colors config', () => {
468
- it('camelCases the colors values from the API response', async () => {
469
- makeOkFetch({
470
- ...baseV3Config,
471
- colors: { values: { text_primary: '#111', background_primary: '#fff' } },
472
- });
473
-
474
- const result = await fetchUnifiedGraphQLConfig(
475
- 'https://api.example.com',
476
- 'key',
477
- 'user_1',
478
- [],
479
- 'app',
309
+ it('camelCases the colors values from the API response', () => {
310
+ const result = transformV3ProductsConfig(
311
+ makeProductsConfig({
312
+ ...baseV3Config,
313
+ colors: { values: { text_primary: '#111', background_primary: '#fff' } },
314
+ }),
480
315
  );
481
316
 
482
- expect((result.orgConfig?.colorsConfig as any)?.textPrimary).toBe('#111');
483
- expect((result.orgConfig?.colorsConfig as any)?.backgroundPrimary).toBe('#fff');
317
+ expect((result.colorsConfig as any)?.textPrimary).toBe('#111');
318
+ expect((result.colorsConfig as any)?.backgroundPrimary).toBe('#fff');
484
319
  });
485
320
  });
486
321
 
487
322
  describe('v3FrontendConfigCleanup edge cases', () => {
488
- it('returns config as-is when uiConfigs is absent', async () => {
489
- makeOkFetch({ ...baseV3Config, ui_configs: undefined });
490
-
491
- const result = await fetchUnifiedGraphQLConfig(
492
- 'https://api.example.com',
493
- 'key',
494
- 'user_1',
495
- [],
496
- 'app',
323
+ it('returns config as-is when uiConfigs is absent', () => {
324
+ const result = transformV3ProductsConfig(
325
+ makeProductsConfig({ ...baseV3Config, ui_configs: undefined }),
497
326
  );
498
327
 
499
- expect(result.orgConfig?.frontendConfig).toBeDefined();
500
- expect(result.orgConfig?.colorsConfig).toBeDefined();
328
+ expect(result.frontendConfig).toBeDefined();
329
+ expect(result.colorsConfig).toBeDefined();
501
330
  });
502
331
 
503
- it('returns config as-is when floatingButton is absent', async () => {
504
- makeOkFetch({
505
- ...baseV3Config,
506
- ui_configs: {
507
- look_and_feel: baseV3Config.ui_configs.look_and_feel,
508
- },
509
- });
510
-
511
- const result = await fetchUnifiedGraphQLConfig(
512
- 'https://api.example.com',
513
- 'key',
514
- 'user_1',
515
- [],
516
- 'app',
517
- );
518
-
519
- expect(result.orgConfig?.frontendConfig).toBeDefined();
520
- });
521
-
522
- it('returns config as-is when lookAndFeel is absent', async () => {
523
- makeOkFetch({
524
- ...baseV3Config,
525
- ui_configs: {
526
- floating_button: baseV3Config.ui_configs.floating_button,
527
- },
528
- });
529
-
530
- const result = await fetchUnifiedGraphQLConfig(
531
- 'https://api.example.com',
532
- 'key',
533
- 'user_1',
534
- [],
535
- 'app',
332
+ it('returns config as-is when floatingButton is absent', () => {
333
+ const result = transformV3ProductsConfig(
334
+ makeProductsConfig({
335
+ ...baseV3Config,
336
+ ui_configs: { look_and_feel: baseV3Config.ui_configs.look_and_feel },
337
+ }),
536
338
  );
537
339
 
538
- expect(result.orgConfig?.frontendConfig).toBeDefined();
539
- });
540
- });
541
-
542
- describe('fetch call', () => {
543
- it('sends a POST to the correct GraphQL endpoint with the API key', async () => {
544
- makeOkFetch(null);
545
-
546
- await fetchUnifiedGraphQLConfig('https://api.example.com', 'test-key', 'user_1', [], 'app');
547
-
548
- expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/v1/graphql', {
549
- method: 'POST',
550
- headers: {
551
- 'Content-Type': 'application/json',
552
- Authorization: 'Bearer test-key',
553
- },
554
- body: expect.any(String),
555
- });
340
+ expect(result.frontendConfig).toBeDefined();
556
341
  });
557
- });
558
- });
559
342
 
560
- describe('fetchUnifiedGraphQLConfig', () => {
561
- function makeUnifiedOkFetch(v3RootConfigValues: unknown, resolution?: unknown) {
562
- mockFetch.mockResolvedValueOnce({
563
- ok: true,
564
- json: () =>
565
- Promise.resolve({
566
- data: {
567
- me: {
568
- getWidgetConfig: {
569
- productsConfig: {
570
- v_three_config: { values: v3RootConfigValues },
571
- },
572
- resolution,
573
- },
574
- },
575
- },
343
+ it('returns config as-is when lookAndFeel is absent', () => {
344
+ const result = transformV3ProductsConfig(
345
+ makeProductsConfig({
346
+ ...baseV3Config,
347
+ ui_configs: { floating_button: baseV3Config.ui_configs.floating_button },
576
348
  }),
577
- });
578
- }
579
-
580
- it('issues a getWidgetConfig query (not getProductsConfigByVersion) and forwards userId', async () => {
581
- makeUnifiedOkFetch(null);
582
-
583
- await fetchUnifiedGraphQLConfig('https://api.example.com', 'key', 'user_1', [], 'app');
584
-
585
- const body = JSON.parse(mockFetch.mock.calls[0][1].body);
586
- expect(body.query).toContain('getWidgetConfig');
587
- expect(body.query).not.toContain('getProductsConfigByVersion');
588
- // userId is required by the schema; omitting it 400s. Regression guard.
589
- // version is absent without a URL-param override so the resolver uses
590
- // Statsig bucketing (passing 'deployed' here would bypass Statsig).
591
- expect(body.variables).toEqual({ userId: 'user_1', gateNames: [], source: 'app' });
592
- });
593
-
594
- it('threads a spiffy_config_version URL param through as the version variable', async () => {
595
- window.history.replaceState({}, '', '/?spiffy_config_version=1.2.3');
596
- try {
597
- makeUnifiedOkFetch(null);
598
-
599
- await fetchUnifiedGraphQLConfig('https://api.example.com', 'key', 'user_1', [], 'app');
600
-
601
- const body = JSON.parse(mockFetch.mock.calls[0][1].body);
602
- expect(body.variables).toEqual({
603
- userId: 'user_1',
604
- version: '1.2.3',
605
- gateNames: [],
606
- source: 'app',
607
- });
608
- } finally {
609
- window.history.replaceState({}, '', '/');
610
- }
611
- });
612
-
613
- it('parses productsConfig into the same shape as fetchGraphQLConfig', async () => {
614
- makeUnifiedOkFetch(baseV3Config);
615
-
616
- const result = await fetchUnifiedGraphQLConfig(
617
- 'https://api.example.com',
618
- 'key',
619
- 'user_1',
620
- [],
621
- 'app',
622
- );
623
-
624
- expect(result.orgConfig?.colorsConfig).toBeDefined();
625
- expect(result.orgConfig?.frontendConfig).toBeDefined();
626
- expect(result.orgConfig?.orgPageConfig).toBeDefined();
627
- });
628
-
629
- it('returns resolution metadata alongside the config so callers can thread baseVersion into inference', async () => {
630
- makeUnifiedOkFetch(baseV3Config, {
631
- baseVersion: '1.0.670',
632
- baseVersionSource: 'merchant_layer',
633
- });
349
+ );
634
350
 
635
- const result = await fetchUnifiedGraphQLConfig(
636
- 'https://api.example.com',
637
- 'key',
638
- 'user_1',
639
- [],
640
- 'app',
641
- );
642
-
643
- expect(result.orgConfig?.resolution).toEqual({
644
- baseVersion: '1.0.670',
645
- baseVersionSource: 'merchant_layer',
351
+ expect(result.frontendConfig).toBeDefined();
646
352
  });
647
353
  });
648
-
649
- it('selects resolution fields in the query so the server can fill them', async () => {
650
- makeUnifiedOkFetch(null);
651
-
652
- await fetchUnifiedGraphQLConfig('https://api.example.com', 'key', 'user_1', [], 'app');
653
-
654
- const body = JSON.parse(mockFetch.mock.calls[0][1].body);
655
- expect(body.query).toContain('resolution');
656
- expect(body.query).toContain('baseVersion');
657
- expect(body.query).toContain('baseVersionSource');
658
- });
659
-
660
- it('falls back to DEFAULT_PAGE_VARIANTS when productsConfig has no page_variants array', async () => {
661
- // Reuses the no-colors fallback path: when productsConfig is missing
662
- // colors, DEFAULT_PAGE_VARIANTS is returned as the orgPageConfig.
663
- makeUnifiedOkFetch(null);
664
-
665
- const result = await fetchUnifiedGraphQLConfig(
666
- 'https://api.example.com',
667
- 'key',
668
- 'user_1',
669
- [],
670
- 'app',
671
- );
672
-
673
- expect(result.orgConfig?.orgPageConfig?.pageVariants).toEqual(DEFAULT_PAGE_VARIANTS);
674
- });
675
-
676
- it('propagates the error on network failure', async () => {
677
- mockFetch.mockRejectedValueOnce(new Error('boom'));
678
-
679
- await expect(
680
- fetchUnifiedGraphQLConfig('https://api.example.com', 'key', 'user_1', [], 'app'),
681
- ).rejects.toThrow('boom');
682
- });
683
354
  });