@envive-ai/react-hooks 0.3.57 → 0.3.59

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 (108) hide show
  1. package/dist/application/commerce-api.cjs +1 -1
  2. package/dist/application/commerce-api.js +1 -1
  3. package/dist/application/models/graphql/index.cjs +0 -4
  4. package/dist/application/models/graphql/index.d.cts +2 -3
  5. package/dist/application/models/graphql/index.d.ts +2 -3
  6. package/dist/application/models/graphql/index.js +1 -3
  7. package/dist/application/models/graphql/queries/getMerchantColorsQuery.d.cts +2 -3
  8. package/dist/application/models/graphql/queries/getMerchantColorsQuery.d.ts +2 -3
  9. package/dist/application/models/graphql/queries/getWidgetConfigQuery.cjs +22 -3
  10. package/dist/application/models/graphql/queries/getWidgetConfigQuery.js +22 -3
  11. package/dist/application/models/index.cjs +0 -4
  12. package/dist/application/models/index.d.cts +2 -3
  13. package/dist/application/models/index.d.ts +2 -3
  14. package/dist/application/models/index.js +1 -3
  15. package/dist/application/utils/elementObserver.d.cts +2 -2
  16. package/dist/application/utils/index.cjs +1 -1
  17. package/dist/application/utils/index.js +1 -1
  18. package/dist/atoms/app/index.d.cts +7 -7
  19. package/dist/atoms/app/index.d.ts +7 -7
  20. package/dist/atoms/app/variant.d.cts +6 -6
  21. package/dist/atoms/chat/chatState.d.cts +19 -19
  22. package/dist/atoms/chat/chatState.d.ts +19 -19
  23. package/dist/atoms/chat/form.d.cts +2 -2
  24. package/dist/atoms/chat/form.d.ts +2 -2
  25. package/dist/atoms/chat/index.d.cts +2 -2
  26. package/dist/atoms/chat/index.d.ts +2 -2
  27. package/dist/atoms/chat/lastMessage.d.cts +2 -2
  28. package/dist/atoms/chat/lastMessage.d.ts +2 -2
  29. package/dist/atoms/chat/messageQueue.d.cts +6 -6
  30. package/dist/atoms/chat/messageQueue.d.ts +6 -6
  31. package/dist/atoms/chat/performanceMetrics.d.cts +6 -6
  32. package/dist/atoms/chat/performanceMetrics.d.ts +6 -6
  33. package/dist/atoms/chat/renderedWidgetRefs.d.cts +2 -2
  34. package/dist/atoms/chat/renderedWidgetRefs.d.ts +2 -2
  35. package/dist/atoms/chat/replies.d.cts +3 -3
  36. package/dist/atoms/chat/replies.d.ts +3 -3
  37. package/dist/atoms/chat/suggestions.d.cts +2 -2
  38. package/dist/atoms/chat/suggestions.d.ts +2 -2
  39. package/dist/atoms/envive/enviveConfig.d.cts +14 -14
  40. package/dist/atoms/envive/enviveConfig.d.ts +13 -13
  41. package/dist/atoms/globalSearch/globalSearch.d.ts +5 -5
  42. package/dist/atoms/org/customerService.d.cts +6 -6
  43. package/dist/atoms/org/customerService.d.ts +6 -6
  44. package/dist/atoms/org/graphqlConfig.d.cts +4 -4
  45. package/dist/atoms/org/graphqlConfig.d.ts +4 -4
  46. package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
  47. package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
  48. package/dist/atoms/org/orgAnalyticsConfig.d.cts +4 -4
  49. package/dist/atoms/org/orgAnalyticsConfig.d.ts +4 -4
  50. package/dist/atoms/search/chatSearch.d.cts +17 -17
  51. package/dist/atoms/search/chatSearch.d.ts +17 -17
  52. package/dist/atoms/search/searchAPI.d.cts +13 -13
  53. package/dist/atoms/search/searchAPI.d.ts +13 -13
  54. package/dist/atoms/widget/chatPreviewLoading.d.cts +2 -2
  55. package/dist/atoms/widget/chatPreviewLoading.d.ts +2 -2
  56. package/dist/contexts/pageContext/pageContext.cjs +1 -1
  57. package/dist/contexts/pageContext/pageContext.js +1 -1
  58. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.cts +2 -2
  59. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.ts +2 -2
  60. package/dist/contexts/types.d.cts +1 -1
  61. package/dist/contexts/types.d.ts +1 -1
  62. package/dist/contexts/typesV3.d.cts +1 -1
  63. package/dist/contexts/typesV3.d.ts +1 -1
  64. package/dist/contexts/widgetConfigContext/widgetConfigContext.cjs +22 -31
  65. package/dist/contexts/widgetConfigContext/widgetConfigContext.js +22 -31
  66. package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.cts +2 -2
  67. package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.ts +2 -2
  68. package/dist/hooks/Intersection/useIntersection.cjs +14 -3
  69. package/dist/hooks/Intersection/useIntersection.js +14 -3
  70. package/dist/hooks/PageViewedEvent/usePageViewedEvent.cjs +1 -1
  71. package/dist/hooks/PageViewedEvent/usePageViewedEvent.js +1 -1
  72. package/dist/hooks/ShopifyUrlOperations/useShopifyUrlOperations.d.cts +2 -2
  73. package/dist/hooks/ShopifyUrlOperations/useShopifyUrlOperations.d.ts +2 -2
  74. package/dist/hooks/SystemSettingsContext/useSystemSettingsContext.d.cts +2 -2
  75. package/dist/hooks/utils.d.ts +1 -1
  76. package/dist/services/enviveConfigService/enviveConfigService.cjs +3 -24
  77. package/dist/services/enviveConfigService/enviveConfigService.js +4 -25
  78. package/dist/services/enviveConfigService/fetchGraphQLConfig.cjs +59 -54
  79. package/dist/services/enviveConfigService/fetchGraphQLConfig.js +60 -54
  80. package/dist/services/hardcopyService/hardcopyService.cjs +1 -1
  81. package/dist/services/hardcopyService/hardcopyService.js +1 -1
  82. package/dist/types/config-versions.cjs +1 -3
  83. package/dist/types/config-versions.js +2 -3
  84. package/package.json +1 -1
  85. package/src/application/models/graphql/index.ts +0 -1
  86. package/src/application/models/graphql/queries/getMerchantColorsQuery.ts +4 -15
  87. package/src/application/models/graphql/queries/getWidgetConfigQuery.ts +35 -14
  88. package/src/application/models/index.ts +0 -1
  89. package/src/contexts/pageContext/__tests__/pageContext.test.tsx +1 -2
  90. package/src/contexts/pageContext/pageContext.tsx +1 -2
  91. package/src/contexts/widgetConfigContext/__tests__/widgetConfigContext.test.tsx +52 -0
  92. package/src/contexts/widgetConfigContext/widgetConfigContext.tsx +39 -36
  93. package/src/hooks/Intersection/__tests__/useIntersection.test.ts +134 -0
  94. package/src/hooks/Intersection/useIntersection.ts +20 -4
  95. package/src/services/enviveConfigService/__tests__/enviveConfigService.test.ts +51 -75
  96. package/src/services/enviveConfigService/__tests__/fetchGraphQLConfig.test.ts +242 -90
  97. package/src/services/enviveConfigService/enviveConfigService.ts +15 -41
  98. package/src/services/enviveConfigService/fetchGraphQLConfig.ts +106 -68
  99. package/dist/application/models/graphql/queries/getColorsAndFrontendQuery.cjs +0 -18
  100. package/dist/application/models/graphql/queries/getColorsAndFrontendQuery.js +0 -18
  101. package/dist/application/models/graphql/queries/getMerchantColorsQuery.cjs +0 -18
  102. package/dist/application/models/graphql/queries/getMerchantColorsQuery.js +0 -18
  103. package/dist/application/models/graphql/queries/getMerchantFrontendConfigQuery.cjs +0 -18
  104. package/dist/application/models/graphql/queries/getMerchantFrontendConfigQuery.d.cts +0 -75
  105. package/dist/application/models/graphql/queries/getMerchantFrontendConfigQuery.d.ts +0 -75
  106. package/dist/application/models/graphql/queries/getMerchantFrontendConfigQuery.js +0 -18
  107. package/src/application/models/graphql/queries/getColorsAndFrontendQuery.ts +0 -13
  108. package/src/application/models/graphql/queries/getMerchantFrontendConfigQuery.ts +0 -103
@@ -0,0 +1,134 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { RefObject } from 'react';
3
+ import { useIntersection } from '../useIntersection';
4
+
5
+ class MockIntersectionObserver {
6
+ static instances: MockIntersectionObserver[] = [];
7
+
8
+ observedTargets: Element[] = [];
9
+
10
+ disconnected = false;
11
+
12
+ private callback: IntersectionObserverCallback;
13
+
14
+ constructor(callback: IntersectionObserverCallback) {
15
+ this.callback = callback;
16
+ MockIntersectionObserver.instances.push(this);
17
+ }
18
+
19
+ observe(target: Element) {
20
+ this.observedTargets.push(target);
21
+ }
22
+
23
+ disconnect() {
24
+ this.disconnected = true;
25
+ }
26
+
27
+ unobserve() {}
28
+
29
+ fire(isIntersecting: boolean, target: Element) {
30
+ this.callback(
31
+ [
32
+ {
33
+ isIntersecting,
34
+ intersectionRatio: isIntersecting ? 1 : 0,
35
+ boundingClientRect: {} as DOMRectReadOnly,
36
+ target,
37
+ } as IntersectionObserverEntry,
38
+ ],
39
+ this as unknown as IntersectionObserver,
40
+ );
41
+ }
42
+ }
43
+
44
+ const lastObserver = () =>
45
+ MockIntersectionObserver.instances[MockIntersectionObserver.instances.length - 1];
46
+
47
+ describe('useIntersection', () => {
48
+ beforeEach(() => {
49
+ MockIntersectionObserver.instances = [];
50
+ vi.stubGlobal('IntersectionObserver', MockIntersectionObserver);
51
+ });
52
+
53
+ afterEach(() => {
54
+ vi.unstubAllGlobals();
55
+ });
56
+
57
+ it('observes the node and reports visibility when the observer fires', () => {
58
+ const node = document.createElement('span');
59
+ const ref: RefObject<HTMLElement> = { current: node };
60
+
61
+ const { result } = renderHook(() => useIntersection(ref, '0px'));
62
+
63
+ expect(MockIntersectionObserver.instances).toHaveLength(1);
64
+ expect(lastObserver().observedTargets).toEqual([node]);
65
+ expect(result.current).toBe(false);
66
+
67
+ act(() => {
68
+ lastObserver().fire(true, node);
69
+ });
70
+ expect(result.current).toBe(true);
71
+ });
72
+
73
+ it('attaches late when the ref starts out null', () => {
74
+ const ref = { current: null as HTMLElement | null };
75
+
76
+ const { rerender } = renderHook(() => useIntersection(ref as RefObject<HTMLElement>, '0px'));
77
+ expect(MockIntersectionObserver.instances).toHaveLength(0);
78
+
79
+ const node = document.createElement('span');
80
+ ref.current = node;
81
+ rerender();
82
+
83
+ expect(MockIntersectionObserver.instances).toHaveLength(1);
84
+ expect(lastObserver().observedTargets).toEqual([node]);
85
+ });
86
+
87
+ it('does not re-attach across rerenders while the node is unchanged', () => {
88
+ const node = document.createElement('span');
89
+ const ref: RefObject<HTMLElement> = { current: node };
90
+
91
+ const { rerender } = renderHook(() => useIntersection(ref, '0px'));
92
+ rerender();
93
+ rerender();
94
+
95
+ expect(MockIntersectionObserver.instances).toHaveLength(1);
96
+ expect(lastObserver().observedTargets).toEqual([node]);
97
+ });
98
+
99
+ it('re-attaches to the new node when the node under the ref is replaced', () => {
100
+ // Mount churn (e.g. a portal re-creating the widget subtree) can swap the DOM node
101
+ // while the hook instance survives. The old implementation kept observing the detached
102
+ // node forever, so the widget read as permanently not-visible and never fetched.
103
+ const nodeA = document.createElement('span');
104
+ const ref: RefObject<HTMLElement> = { current: nodeA };
105
+
106
+ const { result, rerender } = renderHook(() => useIntersection(ref, '0px'));
107
+ const firstObserver = lastObserver();
108
+ expect(firstObserver.observedTargets).toEqual([nodeA]);
109
+
110
+ const nodeB = document.createElement('span');
111
+ (ref as { current: HTMLElement | null }).current = nodeB;
112
+ rerender();
113
+
114
+ expect(firstObserver.disconnected).toBe(true);
115
+ expect(MockIntersectionObserver.instances).toHaveLength(2);
116
+ expect(lastObserver().observedTargets).toEqual([nodeB]);
117
+
118
+ // Visibility of the replacement node is now detected
119
+ act(() => {
120
+ lastObserver().fire(true, nodeB);
121
+ });
122
+ expect(result.current).toBe(true);
123
+ });
124
+
125
+ it('disconnects the observer on unmount', () => {
126
+ const node = document.createElement('span');
127
+ const ref: RefObject<HTMLElement> = { current: node };
128
+
129
+ const { unmount } = renderHook(() => useIntersection(ref, '0px'));
130
+ unmount();
131
+
132
+ expect(lastObserver().disconnected).toBe(true);
133
+ });
134
+ });
@@ -7,23 +7,37 @@ const logger = new Logger('useIntersection');
7
7
  export const useIntersection = (element: RefObject<HTMLElement>, rootMargin: string) => {
8
8
  const [isVisible, setIsVisible] = useState(false);
9
9
  const observerRef = useRef<IntersectionObserver | null>(null);
10
+ const observedNodeRef = useRef<HTMLElement | null>(null);
10
11
 
11
- // Runs after every render so it catches when element.current transitions from null
12
- // to a mounted DOM node (e.g. when a conditionally-rendered element first appears).
13
- // Bails immediately once the observer is attached, keeping per-render cost O(1).
12
+ // Runs after every render so it catches when element.current transitions from null to a
13
+ // mounted DOM node, AND when the node under the ref is replaced. The replacement case is
14
+ // load-bearing: mount churn (e.g. the shadow-root portal re-creating the widget subtree)
15
+ // can swap the DOM node while this hook instance survives, and an observer left on the
16
+ // detached old node never fires again — the widget then reads as permanently not-visible
17
+ // and never starts loading. Bails once the current node is the one being observed,
18
+ // keeping per-render cost O(1).
14
19
  useEffect(() => {
15
20
  const current = element?.current;
16
21
  logger.logDebug('effect ran', {
17
22
  elementCurrent: current,
18
23
  rootMargin,
19
24
  alreadyObserving: !!observerRef.current,
25
+ observingCurrentNode: observedNodeRef.current === current,
20
26
  });
21
27
 
22
28
  if (!current) {
23
29
  logger.logDebug('element.current is null — observer NOT attached');
24
30
  return;
25
31
  }
26
- if (observerRef.current) return; // already observing
32
+ if (observedNodeRef.current === current) return; // already observing this exact node
33
+
34
+ if (observerRef.current) {
35
+ logger.logDebug('observed node was replaced — re-attaching observer', {
36
+ previousNode: observedNodeRef.current,
37
+ newNode: current,
38
+ });
39
+ observerRef.current.disconnect();
40
+ }
27
41
 
28
42
  const observer = new IntersectionObserver(
29
43
  ([entry]) => {
@@ -40,6 +54,7 @@ export const useIntersection = (element: RefObject<HTMLElement>, rootMargin: str
40
54
 
41
55
  observer.observe(current);
42
56
  observerRef.current = observer;
57
+ observedNodeRef.current = current;
43
58
  logger.logDebug('observer attached to element', current);
44
59
  });
45
60
 
@@ -48,6 +63,7 @@ export const useIntersection = (element: RefObject<HTMLElement>, rootMargin: str
48
63
  return () => {
49
64
  observerRef.current?.disconnect();
50
65
  observerRef.current = null;
66
+ observedNodeRef.current = null;
51
67
  };
52
68
  }, []);
53
69
 
@@ -3,35 +3,30 @@ import { EnviveConfigService } from '../enviveConfigService';
3
3
  const mockFetch = vi.fn();
4
4
  globalThis.fetch = mockFetch;
5
5
 
6
- const ORG_CONFIG_RESPONSE = {
7
- org: {
8
- org: {
9
- id: 'org-1',
10
- short_name: 'test-org',
11
- display_name: 'Test Org',
12
- domain: 'test.com',
13
- status: 'active',
14
- created_at: '2024-01-01T00:00:00.000Z',
15
- updated_at: '2024-01-01T00:00:00.000Z',
16
- },
17
- },
18
- gates: [],
19
- };
20
-
21
- function makeOkOrgConfigFetch(overrides: Record<string, unknown> = {}) {
22
- mockFetch.mockResolvedValueOnce({
23
- ok: true,
24
- json: () => Promise.resolve({ ...ORG_CONFIG_RESPONSE, ...overrides }),
25
- });
26
- }
27
-
28
- // productsConfig has no colors, so fetchGraphQLConfig falls back to its mock
29
- // config (see fetchGraphQLConfig.test.ts "no colors fallback") - fine here
30
- // since these tests only care about caching/fetch-count behaviour.
31
- function makeOkGraphqlFetch() {
6
+ // getWidgetConfig is the single config call now: it returns org identity +
7
+ // gates + experiment assignments alongside productsConfig (the separate
8
+ // GET /v1/org/config fetch was removed). productsConfig has no colors, so
9
+ // fetchUnifiedGraphQLConfig falls back to its mock config — fine here since
10
+ // these tests only care about caching / fetch-count behaviour.
11
+ function makeOkConfigFetch() {
32
12
  mockFetch.mockResolvedValueOnce({
33
13
  ok: true,
34
- json: () => Promise.resolve({ data: { me: { getProductsConfigByVersion: {} } } }),
14
+ json: () =>
15
+ Promise.resolve({
16
+ data: {
17
+ me: {
18
+ org: {
19
+ id: 'org-1',
20
+ shortName: 'test-org',
21
+ displayName: 'Test Org',
22
+ domain: 'test.com',
23
+ createdAt: '2024-01-01T00:00:00.000Z',
24
+ updatedAt: '2024-01-01T00:00:00.000Z',
25
+ },
26
+ getWidgetConfig: { productsConfig: {}, gates: [], experimentAssignments: [] },
27
+ },
28
+ },
29
+ }),
35
30
  });
36
31
  }
37
32
 
@@ -60,27 +55,25 @@ afterEach(() => {
60
55
  });
61
56
 
62
57
  describe('EnviveConfigService caching', () => {
63
- it('fetches org config + GraphQL config on a cold cache', async () => {
64
- makeOkOrgConfigFetch();
65
- makeOkGraphqlFetch();
58
+ it('fetches the unified config once on a cold cache', async () => {
59
+ makeOkConfigFetch();
66
60
 
67
61
  const result = await makeService().getEnviveConfig();
68
62
 
69
- expect(mockFetch).toHaveBeenCalledTimes(2);
63
+ expect(mockFetch).toHaveBeenCalledTimes(1);
70
64
  expect(result.org.org.id).toBe('org-1');
71
65
  });
72
66
 
73
67
  it('serves a warm cache to a new instance with identical params without hitting the network', async () => {
74
- makeOkOrgConfigFetch();
75
- makeOkGraphqlFetch();
68
+ makeOkConfigFetch();
76
69
  await makeService().getEnviveConfig();
77
- expect(mockFetch).toHaveBeenCalledTimes(2);
70
+ expect(mockFetch).toHaveBeenCalledTimes(1);
78
71
 
79
72
  // A fresh instance (as happens on the next page navigation) should read
80
73
  // from the sessionStorage cache instead of refetching.
81
74
  const result = await makeService().getEnviveConfig();
82
75
 
83
- expect(mockFetch).toHaveBeenCalledTimes(2);
76
+ expect(mockFetch).toHaveBeenCalledTimes(1);
84
77
  expect(result.org.org.id).toBe('org-1');
85
78
  });
86
79
 
@@ -88,18 +81,16 @@ describe('EnviveConfigService caching', () => {
88
81
  vi.useFakeTimers();
89
82
  vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
90
83
 
91
- makeOkOrgConfigFetch();
92
- makeOkGraphqlFetch();
84
+ makeOkConfigFetch();
93
85
  await makeService().getEnviveConfig();
94
- expect(mockFetch).toHaveBeenCalledTimes(2);
86
+ expect(mockFetch).toHaveBeenCalledTimes(1);
95
87
 
96
88
  vi.setSystemTime(new Date('2026-01-01T00:05:01.000Z'));
97
89
 
98
- makeOkOrgConfigFetch();
99
- makeOkGraphqlFetch();
90
+ makeOkConfigFetch();
100
91
  await makeService().getEnviveConfig();
101
92
 
102
- expect(mockFetch).toHaveBeenCalledTimes(4);
93
+ expect(mockFetch).toHaveBeenCalledTimes(2);
103
94
  });
104
95
 
105
96
  it.each(['envive_config_version', 'spiffy_config_version'])(
@@ -107,59 +98,51 @@ describe('EnviveConfigService caching', () => {
107
98
  async param => {
108
99
  window.history.replaceState({}, '', `/?${param}=1.2.3`);
109
100
 
110
- makeOkOrgConfigFetch();
111
- makeOkGraphqlFetch();
101
+ makeOkConfigFetch();
112
102
  await makeService().getEnviveConfig();
113
- expect(mockFetch).toHaveBeenCalledTimes(2);
103
+ expect(mockFetch).toHaveBeenCalledTimes(1);
114
104
 
115
- makeOkOrgConfigFetch();
116
- makeOkGraphqlFetch();
105
+ makeOkConfigFetch();
117
106
  await makeService().getEnviveConfig();
118
107
 
119
108
  // No cache read/write happened, so a second instance refetches instead
120
109
  // of reusing anything the first instance stored.
121
- expect(mockFetch).toHaveBeenCalledTimes(4);
110
+ expect(mockFetch).toHaveBeenCalledTimes(2);
122
111
  },
123
112
  );
124
113
 
125
114
  it('does not read a version-pinned cache entry after the override is removed', async () => {
126
115
  window.history.replaceState({}, '', '/?envive_config_version=1.2.3');
127
- makeOkOrgConfigFetch();
128
- makeOkGraphqlFetch();
116
+ makeOkConfigFetch();
129
117
  await makeService().getEnviveConfig();
130
118
 
131
119
  window.history.replaceState({}, '', '/');
132
- makeOkOrgConfigFetch();
133
- makeOkGraphqlFetch();
120
+ makeOkConfigFetch();
134
121
  await makeService().getEnviveConfig();
135
122
 
136
- expect(mockFetch).toHaveBeenCalledTimes(4);
123
+ expect(mockFetch).toHaveBeenCalledTimes(2);
137
124
  });
138
125
 
139
126
  it('keeps separate cache entries per userId', async () => {
140
- makeOkOrgConfigFetch();
141
- makeOkGraphqlFetch();
127
+ makeOkConfigFetch();
142
128
  await makeService({ userId: 'user-1' }).getEnviveConfig();
143
- expect(mockFetch).toHaveBeenCalledTimes(2);
129
+ expect(mockFetch).toHaveBeenCalledTimes(1);
144
130
 
145
- makeOkOrgConfigFetch();
146
- makeOkGraphqlFetch();
131
+ makeOkConfigFetch();
147
132
  await makeService({ userId: 'user-2' }).getEnviveConfig();
148
133
 
149
- expect(mockFetch).toHaveBeenCalledTimes(4);
134
+ expect(mockFetch).toHaveBeenCalledTimes(2);
150
135
  });
151
136
 
152
137
  it('keeps separate cache entries per namespace/source/baseUrl/apiKey', async () => {
153
- makeOkOrgConfigFetch();
154
- makeOkGraphqlFetch();
138
+ makeOkConfigFetch();
155
139
  await makeService({ namespace: 'ns-a' }).getEnviveConfig();
156
- expect(mockFetch).toHaveBeenCalledTimes(2);
140
+ expect(mockFetch).toHaveBeenCalledTimes(1);
157
141
 
158
- makeOkOrgConfigFetch();
159
- makeOkGraphqlFetch();
142
+ makeOkConfigFetch();
160
143
  await makeService({ namespace: 'ns-b' }).getEnviveConfig();
161
144
 
162
- expect(mockFetch).toHaveBeenCalledTimes(4);
145
+ expect(mockFetch).toHaveBeenCalledTimes(2);
163
146
  });
164
147
 
165
148
  it('fails open and still returns config when sessionStorage.setItem throws', async () => {
@@ -167,8 +150,7 @@ describe('EnviveConfigService caching', () => {
167
150
  throw new Error('quota exceeded');
168
151
  });
169
152
 
170
- makeOkOrgConfigFetch();
171
- makeOkGraphqlFetch();
153
+ makeOkConfigFetch();
172
154
 
173
155
  const result = await makeService().getEnviveConfig();
174
156
 
@@ -180,8 +162,7 @@ describe('EnviveConfigService caching', () => {
180
162
  throw new Error('storage disabled');
181
163
  });
182
164
 
183
- makeOkOrgConfigFetch();
184
- makeOkGraphqlFetch();
165
+ makeOkConfigFetch();
185
166
 
186
167
  const result = await makeService().getEnviveConfig();
187
168
 
@@ -190,17 +171,12 @@ describe('EnviveConfigService caching', () => {
190
171
 
191
172
  it('fails open and refetches when a cached entry is corrupted JSON', async () => {
192
173
  const service = makeService();
193
- // Seed a corrupted entry under whatever key the service reads from by
194
- // populating every session storage key with garbage after a real write,
195
- // then corrupting it directly.
196
- makeOkOrgConfigFetch();
197
- makeOkGraphqlFetch();
174
+ makeOkConfigFetch();
198
175
  await service.getEnviveConfig();
199
176
  const [key] = Object.keys(sessionStorage);
200
177
  sessionStorage.setItem(key, 'not json');
201
178
 
202
- makeOkOrgConfigFetch();
203
- makeOkGraphqlFetch();
179
+ makeOkConfigFetch();
204
180
  const result = await makeService().getEnviveConfig();
205
181
 
206
182
  expect(result.org.org.id).toBe('org-1');