@envive-ai/react-hooks 0.3.32 → 0.3.33

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 (170) hide show
  1. package/dist/application/commerce-api.d.cts +1 -1
  2. package/dist/application/commerce-api.d.ts +1 -1
  3. package/dist/atoms/app/index.d.cts +9 -9
  4. package/dist/atoms/app/index.d.ts +9 -9
  5. package/dist/atoms/app/variant.d.cts +8 -8
  6. package/dist/atoms/app/variant.d.ts +8 -8
  7. package/dist/atoms/chat/chatState.d.cts +20 -20
  8. package/dist/atoms/chat/chatState.d.ts +20 -20
  9. package/dist/atoms/chat/form.d.cts +2 -2
  10. package/dist/atoms/chat/form.d.ts +2 -2
  11. package/dist/atoms/chat/index.cjs +1 -1
  12. package/dist/atoms/chat/index.d.cts +3 -3
  13. package/dist/atoms/chat/index.d.ts +2 -2
  14. package/dist/atoms/chat/index.js +1 -1
  15. package/dist/atoms/chat/lastMessage.d.ts +2 -2
  16. package/dist/atoms/chat/messageQueue.d.cts +6 -6
  17. package/dist/atoms/chat/messageQueue.d.ts +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.ts +3 -3
  22. package/dist/atoms/chat/suggestions.d.cts +2 -2
  23. package/dist/atoms/chat/suggestions.d.ts +2 -2
  24. package/dist/atoms/envive/enviveConfig.d.cts +13 -13
  25. package/dist/atoms/envive/enviveConfig.d.ts +13 -13
  26. package/dist/atoms/globalSearch/globalSearch.d.cts +5 -5
  27. package/dist/atoms/globalSearch/globalSearch.d.ts +6 -6
  28. package/dist/atoms/org/customerService.d.cts +6 -6
  29. package/dist/atoms/org/customerService.d.ts +6 -6
  30. package/dist/atoms/org/graphqlConfig.d.cts +5 -5
  31. package/dist/atoms/org/graphqlConfig.d.ts +5 -5
  32. package/dist/atoms/org/index.cjs +2 -3
  33. package/dist/atoms/org/index.d.cts +2 -2
  34. package/dist/atoms/org/index.d.ts +2 -2
  35. package/dist/atoms/org/index.js +3 -3
  36. package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
  37. package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
  38. package/dist/atoms/org/orgAnalyticsConfig.cjs +1 -3
  39. package/dist/atoms/org/orgAnalyticsConfig.d.cts +7 -8
  40. package/dist/atoms/org/orgAnalyticsConfig.d.ts +7 -8
  41. package/dist/atoms/org/orgAnalyticsConfig.js +2 -3
  42. package/dist/atoms/search/chatSearch.cjs +1 -1
  43. package/dist/atoms/search/chatSearch.d.cts +17 -17
  44. package/dist/atoms/search/chatSearch.d.ts +17 -17
  45. package/dist/atoms/search/chatSearch.js +1 -1
  46. package/dist/atoms/search/searchAPI.cjs +1 -1
  47. package/dist/atoms/search/searchAPI.d.cts +13 -13
  48. package/dist/atoms/search/searchAPI.d.ts +13 -13
  49. package/dist/atoms/search/searchAPI.js +1 -1
  50. package/dist/atoms/widget/chatPreviewLoading.d.ts +2 -2
  51. package/dist/contexts/amplitudeContext/amplitudeContext.cjs +1 -5
  52. package/dist/contexts/amplitudeContext/amplitudeContext.js +1 -5
  53. package/dist/contexts/enviveConfigContext/enviveConfigContext.cjs +18 -23
  54. package/dist/contexts/enviveConfigContext/enviveConfigContext.d.cts +10 -12
  55. package/dist/contexts/enviveConfigContext/enviveConfigContext.d.ts +10 -12
  56. package/dist/contexts/enviveConfigContext/enviveConfigContext.js +18 -23
  57. package/dist/contexts/enviveContext/enviveContext.cjs +41 -74
  58. package/dist/contexts/enviveContext/enviveContext.d.cts +15 -32
  59. package/dist/contexts/enviveContext/enviveContext.d.ts +15 -32
  60. package/dist/contexts/enviveContext/enviveContext.js +41 -74
  61. package/dist/contexts/enviveContext/index.d.cts +2 -2
  62. package/dist/contexts/enviveContext/index.d.ts +2 -2
  63. package/dist/contexts/enviveCssContext/enviveCssContext.cjs +31 -16
  64. package/dist/contexts/enviveCssContext/enviveCssContext.js +31 -16
  65. package/dist/contexts/graphqlContext/graphqlContext.cjs +3 -212
  66. package/dist/contexts/graphqlContext/graphqlContext.d.cts +2 -10
  67. package/dist/contexts/graphqlContext/graphqlContext.d.ts +2 -10
  68. package/dist/contexts/graphqlContext/graphqlContext.js +3 -212
  69. package/dist/contexts/graphqlContext/mockV3Config.cjs +31 -16
  70. package/dist/contexts/graphqlContext/mockV3Config.js +31 -16
  71. package/dist/contexts/hardcopyContext/hardcopyContext.cjs +9 -238
  72. package/dist/contexts/hardcopyContext/hardcopyContext.d.cts +5 -17
  73. package/dist/contexts/hardcopyContext/hardcopyContext.d.ts +5 -17
  74. package/dist/contexts/hardcopyContext/hardcopyContext.js +9 -238
  75. package/dist/contexts/hardcopyContext/index.d.cts +3 -2
  76. package/dist/contexts/hardcopyContext/index.d.ts +3 -2
  77. package/dist/contexts/newOrgConfigContext/newOrgConfigContext.cjs +10 -32
  78. package/dist/contexts/newOrgConfigContext/newOrgConfigContext.d.cts +2 -2
  79. package/dist/contexts/newOrgConfigContext/newOrgConfigContext.d.ts +2 -2
  80. package/dist/contexts/newOrgConfigContext/newOrgConfigContext.js +12 -34
  81. package/dist/contexts/pageContext/mapping.d.cts +1 -1
  82. package/dist/contexts/pageContext/mapping.d.ts +1 -1
  83. package/dist/contexts/pageContext/types.d.cts +1 -1
  84. package/dist/contexts/salesAgentContext/chatAPI.cjs +2 -2
  85. package/dist/contexts/salesAgentContext/chatAPI.js +2 -2
  86. package/dist/contexts/salesAgentContext/salesAgentService.cjs +1 -1
  87. package/dist/contexts/salesAgentContext/salesAgentService.js +1 -1
  88. package/dist/contexts/searchContext/searchContext.cjs +1 -1
  89. package/dist/contexts/searchContext/searchContext.js +1 -1
  90. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.cts +2 -2
  91. package/dist/contexts/types.d.cts +2 -2
  92. package/dist/contexts/types.d.ts +2 -2
  93. package/dist/contexts/typesV3.cjs +31 -16
  94. package/dist/contexts/typesV3.d.cts +62 -31
  95. package/dist/contexts/typesV3.d.ts +62 -31
  96. package/dist/contexts/typesV3.js +31 -16
  97. package/dist/contexts/userIdentityContext/userIdentityContext.cjs +1 -1
  98. package/dist/contexts/userIdentityContext/userIdentityContext.d.cts +3 -3
  99. package/dist/contexts/userIdentityContext/userIdentityContext.d.ts +3 -3
  100. package/dist/contexts/userIdentityContext/userIdentityContext.js +1 -1
  101. package/dist/hooks/ChatToggle/useChatToggle.cjs +1 -1
  102. package/dist/hooks/ChatToggle/useChatToggle.js +1 -1
  103. package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.cts +2 -2
  104. package/dist/hooks/GraphQLConfig/index.cjs +0 -1
  105. package/dist/hooks/GraphQLConfig/index.d.cts +2 -2
  106. package/dist/hooks/GraphQLConfig/index.d.ts +2 -2
  107. package/dist/hooks/GraphQLConfig/index.js +2 -2
  108. package/dist/hooks/GraphQLConfig/useGraphQLConfig.cjs +7 -66
  109. package/dist/hooks/GraphQLConfig/useGraphQLConfig.d.cts +5 -13
  110. package/dist/hooks/GraphQLConfig/useGraphQLConfig.d.ts +5 -13
  111. package/dist/hooks/GraphQLConfig/useGraphQLConfig.js +8 -66
  112. package/dist/hooks/Search/useSearch.cjs +3 -3
  113. package/dist/hooks/Search/useSearch.js +3 -3
  114. package/dist/services/amplitudeService/amplitudeService.cjs +4 -3
  115. package/dist/services/amplitudeService/amplitudeService.d.cts +1 -2
  116. package/dist/services/amplitudeService/amplitudeService.d.ts +1 -2
  117. package/dist/services/amplitudeService/amplitudeService.js +4 -3
  118. package/dist/services/enviveConfigService/enviveConfigService.cjs +25 -5
  119. package/dist/services/enviveConfigService/enviveConfigService.d.cts +24 -5
  120. package/dist/services/enviveConfigService/enviveConfigService.d.ts +24 -5
  121. package/dist/services/enviveConfigService/enviveConfigService.js +25 -6
  122. package/dist/services/enviveConfigService/fetchGraphQLConfig.cjs +181 -0
  123. package/dist/services/enviveConfigService/fetchGraphQLConfig.js +180 -0
  124. package/dist/services/enviveConfigService/index.cjs +2 -1
  125. package/dist/services/enviveConfigService/index.d.cts +2 -2
  126. package/dist/services/enviveConfigService/index.d.ts +2 -2
  127. package/dist/services/enviveConfigService/index.js +2 -2
  128. package/dist/services/hardcopyService/hardcopyService.cjs +232 -0
  129. package/dist/services/hardcopyService/hardcopyService.d.cts +39 -0
  130. package/dist/services/hardcopyService/hardcopyService.d.ts +39 -0
  131. package/dist/services/hardcopyService/hardcopyService.js +229 -0
  132. package/dist/services/hardcopyService/index.cjs +5 -0
  133. package/dist/services/hardcopyService/index.d.cts +2 -0
  134. package/dist/services/hardcopyService/index.d.ts +2 -0
  135. package/dist/services/hardcopyService/index.js +3 -0
  136. package/dist/services/userIdentityService/index.cjs +1 -0
  137. package/dist/services/userIdentityService/index.d.cts +2 -2
  138. package/dist/services/userIdentityService/index.d.ts +2 -2
  139. package/dist/services/userIdentityService/index.js +2 -2
  140. package/dist/services/userIdentityService/userIdentityService.cjs +13 -1
  141. package/dist/services/userIdentityService/userIdentityService.d.cts +12 -2
  142. package/dist/services/userIdentityService/userIdentityService.d.ts +12 -2
  143. package/dist/services/userIdentityService/userIdentityService.js +13 -2
  144. package/dist/types/enviveConfig.d.cts +1 -1
  145. package/dist/types/enviveConfig.d.ts +1 -1
  146. package/package.json +5 -1
  147. package/src/atoms/org/orgAnalyticsConfig.ts +0 -5
  148. package/src/contexts/amplitudeContext/amplitudeContext.tsx +0 -4
  149. package/src/contexts/enviveConfigContext/enviveConfigContext.tsx +37 -49
  150. package/src/contexts/enviveContext/enviveContext.tsx +72 -134
  151. package/src/contexts/enviveCssContext/enviveCssContext.tsx +32 -17
  152. package/src/contexts/graphqlContext/__tests__/graphqlContext.test.tsx +3 -36
  153. package/src/contexts/graphqlContext/graphqlContext.tsx +4 -304
  154. package/src/contexts/graphqlContext/mockV3Config.ts +30 -15
  155. package/src/contexts/hardcopyContext/hardcopyContext.tsx +12 -270
  156. package/src/contexts/newOrgConfigContext/__tests__/newOrgConfigContext.test.tsx +54 -478
  157. package/src/contexts/newOrgConfigContext/newOrgConfigContext.tsx +9 -26
  158. package/src/contexts/typesV3.ts +61 -30
  159. package/src/contexts/userIdentityContext/userIdentityContext.tsx +2 -2
  160. package/src/hooks/GraphQLConfig/useGraphQLConfig.ts +2 -62
  161. package/src/hooks/Search/__tests__/useSearch.test.tsx +2 -2
  162. package/src/services/amplitudeService/__tests__/amplitudeService.test.ts +3 -5
  163. package/src/services/amplitudeService/amplitudeService.ts +5 -3
  164. package/src/services/enviveConfigService/__tests__/fetchGraphQLConfig.test.ts +425 -0
  165. package/src/services/enviveConfigService/enviveConfigService.ts +41 -13
  166. package/src/services/enviveConfigService/fetchGraphQLConfig.ts +225 -0
  167. package/src/services/hardcopyService/__tests__/hardcopyService.test.ts +367 -0
  168. package/src/services/hardcopyService/hardcopyService.ts +271 -0
  169. package/src/services/hardcopyService/index.ts +1 -0
  170. package/src/services/userIdentityService/userIdentityService.ts +18 -0
@@ -0,0 +1,425 @@
1
+ import { fetchGraphQLConfig, DEFAULT_PAGE_VARIANTS } from '../fetchGraphQLConfig';
2
+ import { mockV3ColorsConfig, mockV3FrontendConfig } from 'src/contexts/graphqlContext/mockV3Config';
3
+ import { FloatingButtonLocation } from '@envive-ai/react-toolkit-v3/FloatingButton';
4
+
5
+ const { MockLogger } = vi.hoisted(() => {
6
+ const MockLogger = vi.fn(function () {
7
+ return { logDebug: vi.fn(), logError: vi.fn() };
8
+ });
9
+ return { MockLogger };
10
+ });
11
+
12
+ vi.mock('src/application/logging/logger', () => ({ default: MockLogger }));
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
+ getProductsConfigByVersion: {
25
+ v_three_config: { values: v3RootConfigValues },
26
+ frontend: frontendValues == null ? undefined : { values: frontendValues },
27
+ },
28
+ },
29
+ },
30
+ }),
31
+ });
32
+ }
33
+
34
+ // Minimal valid raw v3 config with all fields present
35
+ const baseV3Config = {
36
+ colors: { values: { text_primary: '#000000' } },
37
+ ui_configs: {
38
+ floating_button: { position: 'bottom-right' },
39
+ look_and_feel: {
40
+ typography: {
41
+ font_size: { b1: '16px', h1: '24px' },
42
+ line_height: { lh_114: '1.14', lh_120: '1.20' },
43
+ },
44
+ chat_header_logo_src: 'logo.png',
45
+ chat_header_logo_dark_src: 'logo-dark.png',
46
+ chat_header_logo_light_src: 'logo-light.png',
47
+ },
48
+ },
49
+ mounting_configs: [],
50
+ widget_configs: [],
51
+ page_variants: [],
52
+ };
53
+
54
+ beforeEach(() => {
55
+ vi.clearAllMocks();
56
+ });
57
+
58
+ describe('fetchGraphQLConfig', () => {
59
+ describe('error handling', () => {
60
+ it('returns empty config when the HTTP response is not ok', async () => {
61
+ mockFetch.mockResolvedValueOnce({ ok: false, statusText: 'Unauthorized' });
62
+
63
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
64
+
65
+ expect(result).toEqual({ colorsConfig: undefined, frontendConfig: undefined });
66
+ });
67
+
68
+ it('returns empty config when the response contains GraphQL errors', async () => {
69
+ mockFetch.mockResolvedValueOnce({
70
+ ok: true,
71
+ json: () => Promise.resolve({ errors: [{ message: 'Not authorized' }], data: null }),
72
+ });
73
+
74
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
75
+
76
+ expect(result).toEqual({ colorsConfig: undefined, frontendConfig: undefined });
77
+ });
78
+
79
+ it('returns empty config when fetch throws', async () => {
80
+ mockFetch.mockRejectedValueOnce(new Error('Network failure'));
81
+
82
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
83
+
84
+ expect(result).toEqual({ colorsConfig: undefined, frontendConfig: undefined });
85
+ });
86
+ });
87
+
88
+ describe('no colors fallback', () => {
89
+ it('returns mock config when v3RootConfig has no colors field', async () => {
90
+ makeOkFetch({ ui_configs: {} });
91
+
92
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
93
+
94
+ expect(result).toEqual({
95
+ colorsConfig: mockV3ColorsConfig,
96
+ frontendConfig: mockV3FrontendConfig,
97
+ orgPageConfig: {
98
+ pageVariants: DEFAULT_PAGE_VARIANTS,
99
+ widgetConfigs: {},
100
+ mountingConfigs: {},
101
+ },
102
+ });
103
+ });
104
+
105
+ it('returns mock config when v3RootConfig is absent entirely', async () => {
106
+ mockFetch.mockResolvedValueOnce({
107
+ ok: true,
108
+ json: () =>
109
+ Promise.resolve({
110
+ data: { me: { getProductsConfigByVersion: {} } },
111
+ }),
112
+ });
113
+
114
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
115
+
116
+ expect(result).toEqual({
117
+ colorsConfig: mockV3ColorsConfig,
118
+ frontendConfig: mockV3FrontendConfig,
119
+ orgPageConfig: {
120
+ pageVariants: DEFAULT_PAGE_VARIANTS,
121
+ widgetConfigs: {},
122
+ mountingConfigs: {},
123
+ },
124
+ });
125
+ });
126
+ });
127
+
128
+ describe('position mapping', () => {
129
+ it.each([
130
+ ['bottom-left', FloatingButtonLocation.BOTTOM_LEFT],
131
+ ['middle-left', FloatingButtonLocation.MIDDLE_LEFT],
132
+ ['middle-right', FloatingButtonLocation.MIDDLE_RIGHT],
133
+ ['bottom-right', FloatingButtonLocation.BOTTOM_RIGHT],
134
+ ])('maps position "%s" to the correct enum value', async (position, expected) => {
135
+ makeOkFetch({
136
+ ...baseV3Config,
137
+ ui_configs: { ...baseV3Config.ui_configs, floating_button: { position } },
138
+ });
139
+
140
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
141
+
142
+ expect((result.frontendConfig as any)?.uiConfigs?.floatingButton?.position).toBe(expected);
143
+ });
144
+
145
+ it('returns empty config for an unrecognised position string', async () => {
146
+ makeOkFetch({
147
+ ...baseV3Config,
148
+ ui_configs: { ...baseV3Config.ui_configs, floating_button: { position: 'top-center' } },
149
+ });
150
+
151
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
152
+
153
+ expect(result).toEqual({ colorsConfig: undefined, frontendConfig: undefined });
154
+ });
155
+ });
156
+
157
+ describe('typography normalization', () => {
158
+ it('uses underscore-separated font size keys (b_1) when camelCase keys are absent', async () => {
159
+ makeOkFetch({
160
+ ...baseV3Config,
161
+ ui_configs: {
162
+ ...baseV3Config.ui_configs,
163
+ look_and_feel: {
164
+ ...baseV3Config.ui_configs.look_and_feel,
165
+ typography: {
166
+ ...baseV3Config.ui_configs.look_and_feel.typography,
167
+ font_size: { b_1: '14px', h_1: '20px' },
168
+ },
169
+ },
170
+ },
171
+ });
172
+
173
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
174
+ const fontSize = (result.frontendConfig as any)?.uiConfigs?.lookAndFeel?.typography?.fontSize;
175
+
176
+ expect(fontSize?.b1).toBe('14px');
177
+ expect(fontSize?.h1).toBe('20px');
178
+ });
179
+
180
+ it('normalises lh_N line height keys to numeric string keys', async () => {
181
+ makeOkFetch({
182
+ ...baseV3Config,
183
+ ui_configs: {
184
+ ...baseV3Config.ui_configs,
185
+ look_and_feel: {
186
+ ...baseV3Config.ui_configs.look_and_feel,
187
+ typography: {
188
+ ...baseV3Config.ui_configs.look_and_feel.typography,
189
+ line_height: { lh_114: '1.14', lh_128: '1.28' },
190
+ },
191
+ },
192
+ },
193
+ });
194
+
195
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
196
+ const lineHeight = (result.frontendConfig as any)?.uiConfigs?.lookAndFeel?.typography
197
+ ?.lineHeight;
198
+
199
+ expect(lineHeight?.['114']).toBe('1.14');
200
+ expect(lineHeight?.['128']).toBe('1.28');
201
+ });
202
+ });
203
+
204
+ describe('chat header logo fallback', () => {
205
+ it('falls back chatHeaderLogoDarkSrc to chatHeaderLogoSrc when not set', async () => {
206
+ makeOkFetch({
207
+ ...baseV3Config,
208
+ ui_configs: {
209
+ ...baseV3Config.ui_configs,
210
+ look_and_feel: {
211
+ ...baseV3Config.ui_configs.look_and_feel,
212
+ chat_header_logo_src: 'logo.png',
213
+ chat_header_logo_dark_src: null,
214
+ chat_header_logo_light_src: 'logo-light.png',
215
+ },
216
+ },
217
+ });
218
+
219
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
220
+ const lookAndFeel = (result.frontendConfig as any)?.uiConfigs?.lookAndFeel;
221
+
222
+ expect(lookAndFeel?.chatHeaderLogoDarkSrc).toBe('logo.png');
223
+ });
224
+
225
+ it('falls back chatHeaderLogoLightSrc to chatHeaderLogoSrc when not set', async () => {
226
+ makeOkFetch({
227
+ ...baseV3Config,
228
+ ui_configs: {
229
+ ...baseV3Config.ui_configs,
230
+ look_and_feel: {
231
+ ...baseV3Config.ui_configs.look_and_feel,
232
+ chat_header_logo_src: 'logo.png',
233
+ chat_header_logo_dark_src: 'logo-dark.png',
234
+ chat_header_logo_light_src: null,
235
+ },
236
+ },
237
+ });
238
+
239
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
240
+ const lookAndFeel = (result.frontendConfig as any)?.uiConfigs?.lookAndFeel;
241
+
242
+ expect(lookAndFeel?.chatHeaderLogoLightSrc).toBe('logo.png');
243
+ });
244
+
245
+ it('uses explicit dark/light src when both are set', async () => {
246
+ makeOkFetch(baseV3Config);
247
+
248
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
249
+ const lookAndFeel = (result.frontendConfig as any)?.uiConfigs?.lookAndFeel;
250
+
251
+ expect(lookAndFeel?.chatHeaderLogoDarkSrc).toBe('logo-dark.png');
252
+ expect(lookAndFeel?.chatHeaderLogoLightSrc).toBe('logo-light.png');
253
+ });
254
+ });
255
+
256
+ describe('merchantOverrideCss', () => {
257
+ it('uses merchantOverrideCss from the v3 frontend config when present', async () => {
258
+ makeOkFetch({
259
+ ...baseV3Config,
260
+ ui_configs: {
261
+ ...baseV3Config.ui_configs,
262
+ merchant_override_css: 'body { color: red; }',
263
+ },
264
+ });
265
+
266
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
267
+
268
+ expect((result.frontendConfig as any)?.merchantOverrideCss).toBe('body { color: red; }');
269
+ });
270
+
271
+ it('falls back to frontendValues.merchant_override_css when absent from v3 config', async () => {
272
+ makeOkFetch(baseV3Config, { merchant_override_css: 'body { color: blue; }' });
273
+
274
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
275
+
276
+ expect((result.frontendConfig as any)?.merchantOverrideCss).toBe('body { color: blue; }');
277
+ });
278
+ });
279
+
280
+ describe('page variants', () => {
281
+ it('uses DEFAULT_PAGE_VARIANTS when page_variants is not an array', async () => {
282
+ makeOkFetch({ ...baseV3Config, page_variants: null });
283
+
284
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
285
+
286
+ expect(result.orgPageConfig?.pageVariants).toEqual(DEFAULT_PAGE_VARIANTS);
287
+ });
288
+
289
+ it('transforms each page variant entry from the API response', async () => {
290
+ const rawVariant = {
291
+ variant_id: 'plp',
292
+ variant_type: 'plp',
293
+ plp_id_extractor: 'url-resolver-plp-id',
294
+ variant_checks: [{ check_type: 'UrlResolver' }],
295
+ widget_mounting: [],
296
+ };
297
+ makeOkFetch({ ...baseV3Config, page_variants: [rawVariant] });
298
+
299
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
300
+
301
+ expect(result.orgPageConfig?.pageVariants).toHaveLength(1);
302
+ expect(result.orgPageConfig?.pageVariants[0].variantId).toBe('plp');
303
+ expect(result.orgPageConfig?.pageVariants[0].variantTests?.[0].testType).toBe('UrlResolver');
304
+ });
305
+ });
306
+
307
+ describe('widget configs', () => {
308
+ it('keys widget configs by widgetConfigId', async () => {
309
+ const rawWidgets = [
310
+ { widget_config_id: 'widget-1', widget_type: 'floating_button' },
311
+ { widget_config_id: 'widget-2', widget_type: 'search_bar' },
312
+ ];
313
+ makeOkFetch({ ...baseV3Config, widget_configs: rawWidgets });
314
+
315
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
316
+
317
+ expect(Object.keys(result.orgPageConfig?.widgetConfigs ?? {})).toEqual([
318
+ 'widget-1',
319
+ 'widget-2',
320
+ ]);
321
+ expect((result.orgPageConfig?.widgetConfigs as any)?.['widget-1']?.widgetConfigId).toBe(
322
+ 'widget-1',
323
+ );
324
+ });
325
+
326
+ it('also exposes widget configs on frontendConfig', async () => {
327
+ const rawWidgets = [{ widget_config_id: 'widget-1', widget_type: 'floating_button' }];
328
+ makeOkFetch({ ...baseV3Config, widget_configs: rawWidgets });
329
+
330
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
331
+
332
+ expect((result.frontendConfig as any)?.widgetConfigs?.['widget-1']).toBeDefined();
333
+ });
334
+ });
335
+
336
+ describe('mounting configs', () => {
337
+ it('keys mounting configs by their key field', async () => {
338
+ const rawMountingConfigs = [
339
+ {
340
+ key: 'plp-mount',
341
+ config: {
342
+ mounting_config_id: 'mc-1',
343
+ container_id: 'container-1',
344
+ test_id: 'test-1',
345
+ insertion_point: { selector: '.grid', insertion_type: 'after' },
346
+ },
347
+ },
348
+ ];
349
+ makeOkFetch({ ...baseV3Config, mounting_configs: rawMountingConfigs });
350
+
351
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
352
+
353
+ expect(result.orgPageConfig?.mountingConfigs?.['plp-mount']).toBeDefined();
354
+ expect(result.orgPageConfig?.mountingConfigs?.['plp-mount']?.mountingConfigId).toBe('mc-1');
355
+ });
356
+ });
357
+
358
+ describe('colors config', () => {
359
+ it('camelCases the colors values from the API response', async () => {
360
+ makeOkFetch({
361
+ ...baseV3Config,
362
+ colors: { values: { text_primary: '#111', background_primary: '#fff' } },
363
+ });
364
+
365
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
366
+
367
+ expect((result.colorsConfig as any)?.textPrimary).toBe('#111');
368
+ expect((result.colorsConfig as any)?.backgroundPrimary).toBe('#fff');
369
+ });
370
+ });
371
+
372
+ describe('v3FrontendConfigCleanup edge cases', () => {
373
+ it('returns config as-is when uiConfigs is absent', async () => {
374
+ makeOkFetch({ ...baseV3Config, ui_configs: undefined });
375
+
376
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
377
+
378
+ expect(result.frontendConfig).toBeDefined();
379
+ expect(result.colorsConfig).toBeDefined();
380
+ });
381
+
382
+ it('returns config as-is when floatingButton is absent', async () => {
383
+ makeOkFetch({
384
+ ...baseV3Config,
385
+ ui_configs: {
386
+ look_and_feel: baseV3Config.ui_configs.look_and_feel,
387
+ },
388
+ });
389
+
390
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
391
+
392
+ expect(result.frontendConfig).toBeDefined();
393
+ });
394
+
395
+ it('returns config as-is when lookAndFeel is absent', async () => {
396
+ makeOkFetch({
397
+ ...baseV3Config,
398
+ ui_configs: {
399
+ floating_button: baseV3Config.ui_configs.floating_button,
400
+ },
401
+ });
402
+
403
+ const result = await fetchGraphQLConfig('https://api.example.com', 'key');
404
+
405
+ expect(result.frontendConfig).toBeDefined();
406
+ });
407
+ });
408
+
409
+ describe('fetch call', () => {
410
+ it('sends a POST to the correct GraphQL endpoint with the API key', async () => {
411
+ makeOkFetch(null);
412
+
413
+ await fetchGraphQLConfig('https://api.example.com', 'test-key');
414
+
415
+ expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/v1/graphql', {
416
+ method: 'POST',
417
+ headers: {
418
+ 'Content-Type': 'application/json',
419
+ Authorization: 'Bearer test-key',
420
+ },
421
+ body: expect.any(String),
422
+ });
423
+ });
424
+ });
425
+ });
@@ -1,5 +1,7 @@
1
1
  import { OrgConfigFeatureGate } from 'src/application/models';
2
2
  import { FeatureGates } from 'src/application/models/featureGates';
3
+ import { GraphQlConfigValues } from 'src/contexts/graphqlContext';
4
+ import { fetchGraphQLConfig } from './fetchGraphQLConfig';
3
5
 
4
6
  type EnviveConfigServiceProps = {
5
7
  baseUrl: string;
@@ -22,12 +24,19 @@ export type EnviveServiceConfig = {
22
24
  };
23
25
  };
24
26
  gates: OrgConfigFeatureGate[];
27
+ orgConfig?: GraphQlConfigValues;
25
28
  };
26
29
 
27
- export class EnviveConfigService {
28
- private readonly baseUrl: string;
30
+ export interface IEnviveConfigService {
31
+ readonly baseUrl: string;
32
+ readonly apiKey: string;
33
+ getEnviveConfig(): Promise<EnviveServiceConfig>;
34
+ }
35
+
36
+ export class EnviveConfigService implements IEnviveConfigService {
37
+ public readonly baseUrl: string;
29
38
 
30
- private readonly apiKey: string;
39
+ public readonly apiKey: string;
31
40
 
32
41
  private readonly userId: string;
33
42
 
@@ -58,15 +67,34 @@ export class EnviveConfigService {
58
67
  .map(featureGate => featureGate.toString())
59
68
  .join(','),
60
69
  });
61
- const response = await fetch(`${this.baseUrl}/v1/org/config?${queryParams.toString()}`, {
62
- method: 'GET',
63
- headers: {
64
- 'Content-Type': 'application/json',
65
- Authorization: `Bearer ${this.apiKey}`,
66
- },
67
- });
68
- const data = (await response.json()) as EnviveServiceConfig;
69
- this.response = data;
70
- return data;
70
+ const [orgConfigResponse, orgConfig] = await Promise.all([
71
+ fetch(`${this.baseUrl}/v1/org/config?${queryParams.toString()}`, {
72
+ method: 'GET',
73
+ headers: {
74
+ 'Content-Type': 'application/json',
75
+ Authorization: `Bearer ${this.apiKey}`,
76
+ },
77
+ }).then(r => r.json() as Promise<EnviveServiceConfig>),
78
+ fetchGraphQLConfig(this.baseUrl, this.apiKey),
79
+ ]);
80
+ this.response = { ...orgConfigResponse, orgConfig };
81
+ return this.response;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Returns static mock data from its constructor arguments. Passes empty baseUrl/apiKey
87
+ * so any code checking isReady does not fire real network requests. The orgConfig field
88
+ * covers colors, frontendConfig, and page/widget/mounting configs without any network calls.
89
+ */
90
+ export class MockEnviveConfigService implements IEnviveConfigService {
91
+ public readonly baseUrl: string = '';
92
+
93
+ public readonly apiKey: string = '';
94
+
95
+ constructor(private readonly mockConfig: EnviveServiceConfig) {}
96
+
97
+ async getEnviveConfig(): Promise<EnviveServiceConfig> {
98
+ return this.mockConfig;
71
99
  }
72
100
  }