@doswiftly/storefront-sdk 4.0.0

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 (206) hide show
  1. package/README.md +430 -0
  2. package/dist/__tests__/unit/test-helpers.d.ts +46 -0
  3. package/dist/__tests__/unit/test-helpers.d.ts.map +1 -0
  4. package/dist/__tests__/unit/test-helpers.js +72 -0
  5. package/dist/core/auth/auth-client.d.ts +46 -0
  6. package/dist/core/auth/auth-client.d.ts.map +1 -0
  7. package/dist/core/auth/auth-client.js +82 -0
  8. package/dist/core/auth/cookie-config.d.ts +18 -0
  9. package/dist/core/auth/cookie-config.d.ts.map +1 -0
  10. package/dist/core/auth/cookie-config.js +18 -0
  11. package/dist/core/auth/handlers.d.ts +32 -0
  12. package/dist/core/auth/handlers.d.ts.map +1 -0
  13. package/dist/core/auth/handlers.js +127 -0
  14. package/dist/core/auth/routes.d.ts +21 -0
  15. package/dist/core/auth/routes.d.ts.map +1 -0
  16. package/dist/core/auth/routes.js +14 -0
  17. package/dist/core/auth/token-client.d.ts +26 -0
  18. package/dist/core/auth/token-client.d.ts.map +1 -0
  19. package/dist/core/auth/token-client.js +42 -0
  20. package/dist/core/auth/types.d.ts +53 -0
  21. package/dist/core/auth/types.d.ts.map +1 -0
  22. package/dist/core/auth/types.js +4 -0
  23. package/dist/core/cache.d.ts +54 -0
  24. package/dist/core/cache.d.ts.map +1 -0
  25. package/dist/core/cache.js +82 -0
  26. package/dist/core/cart/cart-client.d.ts +57 -0
  27. package/dist/core/cart/cart-client.d.ts.map +1 -0
  28. package/dist/core/cart/cart-client.js +89 -0
  29. package/dist/core/cart/types.d.ts +110 -0
  30. package/dist/core/cart/types.d.ts.map +1 -0
  31. package/dist/core/cart/types.js +6 -0
  32. package/dist/core/client/compose.d.ts +9 -0
  33. package/dist/core/client/compose.d.ts.map +1 -0
  34. package/dist/core/client/compose.js +9 -0
  35. package/dist/core/client/create-client.d.ts +15 -0
  36. package/dist/core/client/create-client.d.ts.map +1 -0
  37. package/dist/core/client/create-client.js +85 -0
  38. package/dist/core/client/dedupe.d.ts +7 -0
  39. package/dist/core/client/dedupe.d.ts.map +1 -0
  40. package/dist/core/client/dedupe.js +16 -0
  41. package/dist/core/client/execute.d.ts +20 -0
  42. package/dist/core/client/execute.d.ts.map +1 -0
  43. package/dist/core/client/execute.js +48 -0
  44. package/dist/core/client/hash.d.ts +7 -0
  45. package/dist/core/client/hash.d.ts.map +1 -0
  46. package/dist/core/client/hash.js +21 -0
  47. package/dist/core/client/operation-name.d.ts +7 -0
  48. package/dist/core/client/operation-name.d.ts.map +1 -0
  49. package/dist/core/client/operation-name.js +10 -0
  50. package/dist/core/client/types.d.ts +126 -0
  51. package/dist/core/client/types.d.ts.map +1 -0
  52. package/dist/core/client/types.js +26 -0
  53. package/dist/core/errors.d.ts +43 -0
  54. package/dist/core/errors.d.ts.map +1 -0
  55. package/dist/core/errors.js +43 -0
  56. package/dist/core/format.d.ts +92 -0
  57. package/dist/core/format.d.ts.map +1 -0
  58. package/dist/core/format.js +216 -0
  59. package/dist/core/helpers/assert-no-user-errors.d.ts +10 -0
  60. package/dist/core/helpers/assert-no-user-errors.d.ts.map +1 -0
  61. package/dist/core/helpers/assert-no-user-errors.js +16 -0
  62. package/dist/core/helpers/normalize-connection.d.ts +36 -0
  63. package/dist/core/helpers/normalize-connection.d.ts.map +1 -0
  64. package/dist/core/helpers/normalize-connection.js +21 -0
  65. package/dist/core/helpers/sanitize-html.d.ts +8 -0
  66. package/dist/core/helpers/sanitize-html.d.ts.map +1 -0
  67. package/dist/core/helpers/sanitize-html.js +35 -0
  68. package/dist/core/index.d.ts +59 -0
  69. package/dist/core/index.d.ts.map +1 -0
  70. package/dist/core/index.js +68 -0
  71. package/dist/core/middleware/auth.d.ts +16 -0
  72. package/dist/core/middleware/auth.d.ts.map +1 -0
  73. package/dist/core/middleware/auth.js +22 -0
  74. package/dist/core/middleware/currency.d.ts +15 -0
  75. package/dist/core/middleware/currency.d.ts.map +1 -0
  76. package/dist/core/middleware/currency.js +21 -0
  77. package/dist/core/middleware/errors.d.ts +24 -0
  78. package/dist/core/middleware/errors.d.ts.map +1 -0
  79. package/dist/core/middleware/errors.js +77 -0
  80. package/dist/core/middleware/retry.d.ts +22 -0
  81. package/dist/core/middleware/retry.d.ts.map +1 -0
  82. package/dist/core/middleware/retry.js +58 -0
  83. package/dist/core/middleware/timeout.d.ts +19 -0
  84. package/dist/core/middleware/timeout.d.ts.map +1 -0
  85. package/dist/core/middleware/timeout.js +51 -0
  86. package/dist/core/operations/auth.d.ts +11 -0
  87. package/dist/core/operations/auth.d.ts.map +1 -0
  88. package/dist/core/operations/auth.js +112 -0
  89. package/dist/core/operations/cart.d.ts +15 -0
  90. package/dist/core/operations/cart.d.ts.map +1 -0
  91. package/dist/core/operations/cart.js +169 -0
  92. package/dist/index.d.ts +24 -0
  93. package/dist/index.d.ts.map +1 -0
  94. package/dist/index.js +24 -0
  95. package/dist/react/cookies.d.ts +28 -0
  96. package/dist/react/cookies.d.ts.map +1 -0
  97. package/dist/react/cookies.js +49 -0
  98. package/dist/react/helpers/create-store-context.d.ts +37 -0
  99. package/dist/react/helpers/create-store-context.d.ts.map +1 -0
  100. package/dist/react/helpers/create-store-context.js +47 -0
  101. package/dist/react/hooks/use-auth.d.ts +65 -0
  102. package/dist/react/hooks/use-auth.d.ts.map +1 -0
  103. package/dist/react/hooks/use-auth.js +168 -0
  104. package/dist/react/hooks/use-cart-manager.d.ts +30 -0
  105. package/dist/react/hooks/use-cart-manager.d.ts.map +1 -0
  106. package/dist/react/hooks/use-cart-manager.js +223 -0
  107. package/dist/react/hooks/use-currency.d.ts +11 -0
  108. package/dist/react/hooks/use-currency.d.ts.map +1 -0
  109. package/dist/react/hooks/use-currency.js +19 -0
  110. package/dist/react/hooks/use-debounced-value.d.ts +15 -0
  111. package/dist/react/hooks/use-debounced-value.d.ts.map +1 -0
  112. package/dist/react/hooks/use-debounced-value.js +25 -0
  113. package/dist/react/hooks/use-hydrated.d.ts +9 -0
  114. package/dist/react/hooks/use-hydrated.d.ts.map +1 -0
  115. package/dist/react/hooks/use-hydrated.js +14 -0
  116. package/dist/react/hooks/use-storefront-client.d.ts +6 -0
  117. package/dist/react/hooks/use-storefront-client.d.ts.map +1 -0
  118. package/dist/react/hooks/use-storefront-client.js +8 -0
  119. package/dist/react/index.d.ts +30 -0
  120. package/dist/react/index.d.ts.map +1 -0
  121. package/dist/react/index.js +34 -0
  122. package/dist/react/providers/currency-provider.d.ts +14 -0
  123. package/dist/react/providers/currency-provider.d.ts.map +1 -0
  124. package/dist/react/providers/currency-provider.js +20 -0
  125. package/dist/react/providers/storefront-client-provider.d.ts +33 -0
  126. package/dist/react/providers/storefront-client-provider.d.ts.map +1 -0
  127. package/dist/react/providers/storefront-client-provider.js +57 -0
  128. package/dist/react/providers/storefront-provider.d.ts +42 -0
  129. package/dist/react/providers/storefront-provider.d.ts.map +1 -0
  130. package/dist/react/providers/storefront-provider.js +40 -0
  131. package/dist/react/server/get-storefront-client.d.ts +42 -0
  132. package/dist/react/server/get-storefront-client.d.ts.map +1 -0
  133. package/dist/react/server/get-storefront-client.js +44 -0
  134. package/dist/react/server/index.d.ts +2 -0
  135. package/dist/react/server/index.d.ts.map +1 -0
  136. package/dist/react/server/index.js +1 -0
  137. package/dist/react/stores/auth.store.d.ts +48 -0
  138. package/dist/react/stores/auth.store.d.ts.map +1 -0
  139. package/dist/react/stores/auth.store.js +67 -0
  140. package/dist/react/stores/currency.store.d.ts +29 -0
  141. package/dist/react/stores/currency.store.d.ts.map +1 -0
  142. package/dist/react/stores/currency.store.js +76 -0
  143. package/dist/react/stores/index.d.ts +8 -0
  144. package/dist/react/stores/index.d.ts.map +1 -0
  145. package/dist/react/stores/index.js +10 -0
  146. package/dist/react/stores/store-context.d.ts +27 -0
  147. package/dist/react/stores/store-context.d.ts.map +1 -0
  148. package/dist/react/stores/store-context.js +62 -0
  149. package/package.json +71 -0
  150. package/src/__tests__/contract/storefront-api.contract.test.ts +450 -0
  151. package/src/__tests__/unit/auth-client.test.ts +210 -0
  152. package/src/__tests__/unit/cart-client.test.ts +233 -0
  153. package/src/__tests__/unit/create-client.test.ts +356 -0
  154. package/src/__tests__/unit/helpers.test.ts +377 -0
  155. package/src/__tests__/unit/middleware.test.ts +374 -0
  156. package/src/__tests__/unit/test-helpers.ts +103 -0
  157. package/src/core/auth/auth-client.ts +123 -0
  158. package/src/core/auth/cookie-config.ts +23 -0
  159. package/src/core/auth/handlers.ts +168 -0
  160. package/src/core/auth/routes.ts +26 -0
  161. package/src/core/auth/token-client.ts +51 -0
  162. package/src/core/auth/types.ts +54 -0
  163. package/src/core/cache.ts +102 -0
  164. package/src/core/cart/cart-client.ts +150 -0
  165. package/src/core/cart/types.ts +104 -0
  166. package/src/core/client/compose.ts +15 -0
  167. package/src/core/client/create-client.ts +129 -0
  168. package/src/core/client/dedupe.ts +19 -0
  169. package/src/core/client/execute.ts +70 -0
  170. package/src/core/client/hash.ts +21 -0
  171. package/src/core/client/operation-name.ts +12 -0
  172. package/src/core/client/types.ts +171 -0
  173. package/src/core/errors.ts +67 -0
  174. package/src/core/format.ts +254 -0
  175. package/src/core/helpers/assert-no-user-errors.ts +21 -0
  176. package/src/core/helpers/normalize-connection.ts +48 -0
  177. package/src/core/helpers/sanitize-html.ts +42 -0
  178. package/src/core/index.ts +148 -0
  179. package/src/core/middleware/auth.ts +27 -0
  180. package/src/core/middleware/currency.ts +26 -0
  181. package/src/core/middleware/errors.ts +86 -0
  182. package/src/core/middleware/retry.ts +75 -0
  183. package/src/core/middleware/timeout.ts +61 -0
  184. package/src/core/operations/auth.ts +123 -0
  185. package/src/core/operations/cart.ts +185 -0
  186. package/src/index.ts +25 -0
  187. package/src/react/cookies.ts +54 -0
  188. package/src/react/helpers/create-store-context.ts +56 -0
  189. package/src/react/hooks/use-auth.ts +218 -0
  190. package/src/react/hooks/use-cart-manager.ts +236 -0
  191. package/src/react/hooks/use-currency.ts +23 -0
  192. package/src/react/hooks/use-debounced-value.ts +30 -0
  193. package/src/react/hooks/use-hydrated.ts +20 -0
  194. package/src/react/hooks/use-storefront-client.ts +12 -0
  195. package/src/react/index.ts +45 -0
  196. package/src/react/providers/currency-provider.tsx +30 -0
  197. package/src/react/providers/storefront-client-provider.tsx +90 -0
  198. package/src/react/providers/storefront-provider.tsx +71 -0
  199. package/src/react/server/get-storefront-client.ts +60 -0
  200. package/src/react/server/index.ts +1 -0
  201. package/src/react/stores/auth.store.ts +112 -0
  202. package/src/react/stores/currency.store.ts +113 -0
  203. package/src/react/stores/index.ts +17 -0
  204. package/src/react/stores/store-context.tsx +82 -0
  205. package/tsconfig.json +20 -0
  206. package/vitest.config.ts +14 -0
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Unit tests for helper utilities.
3
+ *
4
+ * Tests: assertNoUserErrors, hashQuery, getOperationName,
5
+ * cache strategies, StorefrontError, TypedDocumentString.
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+ import { assertNoUserErrors } from '../../core/helpers/assert-no-user-errors';
10
+ import { hashQuery } from '../../core/client/hash';
11
+ import { getOperationName } from '../../core/client/operation-name';
12
+ import { compose } from '../../core/client/compose';
13
+ import { StorefrontError, ErrorCodes } from '../../core/errors';
14
+ import { TypedDocumentString } from '../../core/client/types';
15
+ import {
16
+ cacheLong,
17
+ cacheShort,
18
+ cacheNone,
19
+ cachePrivate,
20
+ cacheCustom,
21
+ generateCacheControlHeader,
22
+ } from '../../core/cache';
23
+ import type { ExecuteFn, GraphQLRequest, Middleware } from '../../core/client/types';
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // assertNoUserErrors
27
+ // ---------------------------------------------------------------------------
28
+
29
+ describe('assertNoUserErrors', () => {
30
+ it('should not throw when userErrors is empty', () => {
31
+ expect(() => assertNoUserErrors({ userErrors: [] })).not.toThrow();
32
+ });
33
+
34
+ it('should not throw when userErrors is null', () => {
35
+ expect(() => assertNoUserErrors({ userErrors: null })).not.toThrow();
36
+ });
37
+
38
+ it('should not throw when userErrors is undefined', () => {
39
+ expect(() => assertNoUserErrors({})).not.toThrow();
40
+ });
41
+
42
+ it('should throw StorefrontError with USER_ERROR code', () => {
43
+ try {
44
+ assertNoUserErrors({
45
+ userErrors: [{ message: 'Invalid email', field: ['email'] }],
46
+ });
47
+ expect.fail('Should have thrown');
48
+ } catch (err) {
49
+ expect(err).toBeInstanceOf(StorefrontError);
50
+ const sfErr = err as StorefrontError;
51
+ expect(sfErr.code).toBe(ErrorCodes.USER_ERROR);
52
+ expect(sfErr.message).toBe('Invalid email');
53
+ expect(sfErr.userErrors).toHaveLength(1);
54
+ expect(sfErr.hasUserErrors).toBe(true);
55
+ }
56
+ });
57
+
58
+ it('should use first error message', () => {
59
+ try {
60
+ assertNoUserErrors({
61
+ userErrors: [
62
+ { message: 'First error' },
63
+ { message: 'Second error' },
64
+ ],
65
+ });
66
+ } catch (err) {
67
+ expect((err as StorefrontError).message).toBe('First error');
68
+ expect((err as StorefrontError).userErrors).toHaveLength(2);
69
+ }
70
+ });
71
+ });
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // hashQuery
75
+ // ---------------------------------------------------------------------------
76
+
77
+ describe('hashQuery', () => {
78
+ it('should produce consistent hash for same query', () => {
79
+ const h1 = hashQuery('{ shop { id } }');
80
+ const h2 = hashQuery('{ shop { id } }');
81
+ expect(h1).toBe(h2);
82
+ });
83
+
84
+ it('should produce different hashes for different queries', () => {
85
+ const h1 = hashQuery('{ shop { id } }');
86
+ const h2 = hashQuery('{ product { id } }');
87
+ expect(h1).not.toBe(h2);
88
+ });
89
+
90
+ it('should produce same hash regardless of variable key order', () => {
91
+ const h1 = hashQuery('query Q', { a: 1, b: 2 });
92
+ const h2 = hashQuery('query Q', { b: 2, a: 1 });
93
+ expect(h1).toBe(h2);
94
+ });
95
+
96
+ it('should produce different hashes for different variables', () => {
97
+ const h1 = hashQuery('query Q', { handle: 'a' });
98
+ const h2 = hashQuery('query Q', { handle: 'b' });
99
+ expect(h1).not.toBe(h2);
100
+ });
101
+
102
+ it('should handle nested objects in variables', () => {
103
+ const h1 = hashQuery('query Q', { filter: { a: 1, b: 2 } });
104
+ const h2 = hashQuery('query Q', { filter: { b: 2, a: 1 } });
105
+ expect(h1).toBe(h2);
106
+ });
107
+
108
+ it('should handle undefined variables', () => {
109
+ const h1 = hashQuery('{ shop { id } }');
110
+ const h2 = hashQuery('{ shop { id } }', undefined);
111
+ expect(h1).toBe(h2);
112
+ });
113
+
114
+ it('should trim whitespace in query', () => {
115
+ const h1 = hashQuery(' { shop { id } } ');
116
+ const h2 = hashQuery('{ shop { id } }');
117
+ expect(h1).toBe(h2);
118
+ });
119
+ });
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // getOperationName
123
+ // ---------------------------------------------------------------------------
124
+
125
+ describe('getOperationName', () => {
126
+ it('should extract query name', () => {
127
+ expect(getOperationName('query Product($id: ID!) { product(id: $id) { id } }')).toBe('Product');
128
+ });
129
+
130
+ it('should extract mutation name', () => {
131
+ expect(getOperationName('mutation CartCreate($input: CartInput!) { cartCreate(input: $input) { cart { id } } }')).toBe('CartCreate');
132
+ });
133
+
134
+ it('should extract subscription name', () => {
135
+ expect(getOperationName('subscription OrderUpdated { orderUpdated { id } }')).toBe('OrderUpdated');
136
+ });
137
+
138
+ it('should return "anonymous" for unnamed operations', () => {
139
+ expect(getOperationName('{ shop { id } }')).toBe('anonymous');
140
+ });
141
+
142
+ it('should return "anonymous" for empty string', () => {
143
+ expect(getOperationName('')).toBe('anonymous');
144
+ });
145
+ });
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // compose
149
+ // ---------------------------------------------------------------------------
150
+
151
+ describe('compose', () => {
152
+ it('should execute middleware in order', async () => {
153
+ const order: number[] = [];
154
+
155
+ const m1: Middleware = async (req, next) => { order.push(1); return next(req); };
156
+ const m2: Middleware = async (req, next) => { order.push(2); return next(req); };
157
+ const m3: Middleware = async (req, next) => { order.push(3); return next(req); };
158
+
159
+ const execute: ExecuteFn = async () => ({
160
+ data: {},
161
+ status: 200,
162
+ headers: new Headers(),
163
+ });
164
+
165
+ const pipeline = compose([m1, m2, m3], execute);
166
+ await pipeline({ query: '', headers: {}, isMutation: false });
167
+
168
+ expect(order).toEqual([1, 2, 3]);
169
+ });
170
+
171
+ it('should return execute directly when no middleware', async () => {
172
+ const execute: ExecuteFn = async () => ({
173
+ data: { test: true },
174
+ status: 200,
175
+ headers: new Headers(),
176
+ });
177
+
178
+ const pipeline = compose([], execute);
179
+ const result = await pipeline({ query: '', headers: {}, isMutation: false });
180
+
181
+ expect(result.data).toEqual({ test: true });
182
+ });
183
+
184
+ it('should allow middleware to modify response', async () => {
185
+ const addField: Middleware = async (req, next) => {
186
+ const response = await next(req);
187
+ return { ...response, data: { ...response.data as object, added: true } };
188
+ };
189
+
190
+ const execute: ExecuteFn = async () => ({
191
+ data: { original: true },
192
+ status: 200,
193
+ headers: new Headers(),
194
+ });
195
+
196
+ const pipeline = compose([addField], execute);
197
+ const result = await pipeline({ query: '', headers: {}, isMutation: false });
198
+
199
+ expect(result.data).toEqual({ original: true, added: true });
200
+ });
201
+ });
202
+
203
+ // ---------------------------------------------------------------------------
204
+ // StorefrontError
205
+ // ---------------------------------------------------------------------------
206
+
207
+ describe('StorefrontError', () => {
208
+ it('should be instance of Error', () => {
209
+ const err = new StorefrontError({ code: 'TEST', message: 'test' });
210
+ expect(err).toBeInstanceOf(Error);
211
+ expect(err).toBeInstanceOf(StorefrontError);
212
+ });
213
+
214
+ it('should have correct name', () => {
215
+ const err = new StorefrontError({ code: 'TEST', message: 'test' });
216
+ expect(err.name).toBe('StorefrontError');
217
+ });
218
+
219
+ it('should default status to 0', () => {
220
+ const err = new StorefrontError({ code: 'TEST', message: 'test' });
221
+ expect(err.status).toBe(0);
222
+ });
223
+
224
+ it('should default arrays to empty', () => {
225
+ const err = new StorefrontError({ code: 'TEST', message: 'test' });
226
+ expect(err.graphqlErrors).toEqual([]);
227
+ expect(err.userErrors).toEqual([]);
228
+ });
229
+
230
+ it('should preserve all fields', () => {
231
+ const err = new StorefrontError({
232
+ code: ErrorCodes.GRAPHQL_ERROR,
233
+ message: 'Field not found',
234
+ status: 200,
235
+ graphqlErrors: [{ message: 'Field not found', path: ['product', 'nonexistent'] }],
236
+ userErrors: [{ message: 'Invalid', field: ['email'] }],
237
+ });
238
+
239
+ expect(err.code).toBe(ErrorCodes.GRAPHQL_ERROR);
240
+ expect(err.message).toBe('Field not found');
241
+ expect(err.status).toBe(200);
242
+ expect(err.graphqlErrors).toHaveLength(1);
243
+ expect(err.userErrors).toHaveLength(1);
244
+ });
245
+
246
+ it('hasUserErrors should be true when userErrors exist', () => {
247
+ const err = new StorefrontError({
248
+ code: ErrorCodes.USER_ERROR,
249
+ message: 'test',
250
+ userErrors: [{ message: 'Error' }],
251
+ });
252
+ expect(err.hasUserErrors).toBe(true);
253
+ });
254
+
255
+ it('hasUserErrors should be false when userErrors are empty', () => {
256
+ const err = new StorefrontError({ code: 'TEST', message: 'test' });
257
+ expect(err.hasUserErrors).toBe(false);
258
+ });
259
+
260
+ it('isNetworkError should be true for NETWORK_ERROR code', () => {
261
+ const err = new StorefrontError({ code: ErrorCodes.NETWORK_ERROR, message: 'fetch failed' });
262
+ expect(err.isNetworkError).toBe(true);
263
+ expect(err.isTimeout).toBe(false);
264
+ });
265
+
266
+ it('isTimeout should be true for TIMEOUT code', () => {
267
+ const err = new StorefrontError({ code: ErrorCodes.TIMEOUT, message: 'timeout' });
268
+ expect(err.isTimeout).toBe(true);
269
+ expect(err.isNetworkError).toBe(false);
270
+ });
271
+ });
272
+
273
+ // ---------------------------------------------------------------------------
274
+ // TypedDocumentString
275
+ // ---------------------------------------------------------------------------
276
+
277
+ describe('TypedDocumentString', () => {
278
+ it('should extend String', () => {
279
+ const doc = new TypedDocumentString('query { shop { id } }');
280
+ expect(doc).toBeInstanceOf(String);
281
+ });
282
+
283
+ it('should return query string via toString()', () => {
284
+ const query = 'query Product { product { id } }';
285
+ const doc = new TypedDocumentString(query);
286
+ expect(doc.toString()).toBe(query);
287
+ });
288
+
289
+ it('should return query string via valueOf()', () => {
290
+ const query = 'query Product { product { id } }';
291
+ const doc = new TypedDocumentString(query);
292
+ expect(doc.valueOf()).toBe(query);
293
+ });
294
+ });
295
+
296
+ // ---------------------------------------------------------------------------
297
+ // Cache strategies
298
+ // ---------------------------------------------------------------------------
299
+
300
+ describe('cache strategies', () => {
301
+ describe('cacheNone', () => {
302
+ it('should return no-store mode', () => {
303
+ const cache = cacheNone();
304
+ expect(cache.mode).toBe('no-store');
305
+ expect(cache.maxAge).toBe(0);
306
+ });
307
+
308
+ it('should accept tags override', () => {
309
+ const cache = cacheNone({ tags: ['cart'] });
310
+ expect(cache.tags).toEqual(['cart']);
311
+ });
312
+ });
313
+
314
+ describe('cacheShort', () => {
315
+ it('should default to 1s max-age, 9s swr', () => {
316
+ const cache = cacheShort();
317
+ expect(cache.maxAge).toBe(1);
318
+ expect(cache.staleWhileRevalidate).toBe(9);
319
+ expect(cache.mode).toBe('public');
320
+ });
321
+
322
+ it('should accept overrides', () => {
323
+ const cache = cacheShort({ maxAge: 5, staleWhileRevalidate: 30, tags: ['products'] });
324
+ expect(cache.maxAge).toBe(5);
325
+ expect(cache.staleWhileRevalidate).toBe(30);
326
+ expect(cache.tags).toEqual(['products']);
327
+ });
328
+ });
329
+
330
+ describe('cacheLong', () => {
331
+ it('should default to 1h max-age, 23h swr', () => {
332
+ const cache = cacheLong();
333
+ expect(cache.maxAge).toBe(3600);
334
+ expect(cache.staleWhileRevalidate).toBe(82800);
335
+ expect(cache.mode).toBe('public');
336
+ });
337
+
338
+ it('should accept tags', () => {
339
+ const cache = cacheLong({ tags: ['product', 'cool-shirt'] });
340
+ expect(cache.tags).toEqual(['product', 'cool-shirt']);
341
+ });
342
+ });
343
+
344
+ describe('cachePrivate', () => {
345
+ it('should return private mode', () => {
346
+ const cache = cachePrivate();
347
+ expect(cache.mode).toBe('private');
348
+ expect(cache.maxAge).toBe(1);
349
+ });
350
+ });
351
+
352
+ describe('cacheCustom', () => {
353
+ it('should pass through all options', () => {
354
+ const cache = cacheCustom({ maxAge: 300, staleWhileRevalidate: 600, mode: 'public', tags: ['custom'] });
355
+ expect(cache.maxAge).toBe(300);
356
+ expect(cache.staleWhileRevalidate).toBe(600);
357
+ expect(cache.mode).toBe('public');
358
+ expect(cache.tags).toEqual(['custom']);
359
+ });
360
+ });
361
+
362
+ describe('generateCacheControlHeader', () => {
363
+ it('should generate no-store for cacheNone', () => {
364
+ expect(generateCacheControlHeader(cacheNone())).toBe('no-store, no-cache, must-revalidate');
365
+ });
366
+
367
+ it('should generate public max-age for cacheLong', () => {
368
+ const header = generateCacheControlHeader(cacheLong());
369
+ expect(header).toBe('public, max-age=3600, stale-while-revalidate=82800');
370
+ });
371
+
372
+ it('should generate private max-age for cachePrivate', () => {
373
+ const header = generateCacheControlHeader(cachePrivate());
374
+ expect(header).toBe('private, max-age=1, stale-while-revalidate=9');
375
+ });
376
+ });
377
+ });