@codewithagents/openapi-react-query 3.5.1 → 3.5.2

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.
@@ -1,291 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
- import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
3
- import { tmpdir } from 'node:os'
4
- import { join } from 'node:path'
5
- import { loadConfig, validateConfigPath, validateOutputPath, validateInputPath } from '../config.js'
6
-
7
- describe('loadConfig', () => {
8
- let tmpDir: string
9
-
10
- beforeEach(() => {
11
- tmpDir = mkdtempSync(join(tmpdir(), 'openapi-react-query-config-'))
12
- })
13
-
14
- afterEach(() => {
15
- rmSync(tmpDir, { recursive: true, force: true })
16
- })
17
-
18
- function writeConfig(content: unknown) {
19
- writeFileSync(join(tmpDir, 'openapi-react-query.config.json'), JSON.stringify(content))
20
- }
21
-
22
- it('loads a valid minimal config', async () => {
23
- writeConfig({ input_openapi: 'openapi.json', output: 'src/hooks' })
24
- const config = await loadConfig(tmpDir)
25
- expect(config.input_openapi).toBe('openapi.json')
26
- expect(config.output).toBe('src/hooks')
27
- expect(config.stale_time).toBeUndefined()
28
- expect(config.gc_time).toBeUndefined()
29
- })
30
-
31
- it('loads a full config with all fields', async () => {
32
- writeConfig({
33
- input_openapi: 'api/openapi.json',
34
- output: 'src/generated',
35
- stale_time: 5000,
36
- gc_time: 600000,
37
- })
38
- const config = await loadConfig(tmpDir)
39
- expect(config.input_openapi).toBe('api/openapi.json')
40
- expect(config.output).toBe('src/generated')
41
- expect(config.stale_time).toBe(5000)
42
- expect(config.gc_time).toBe(600000)
43
- })
44
-
45
- it('throws when config file is missing', async () => {
46
- await expect(loadConfig(tmpDir)).rejects.toThrow('Config file not found')
47
- })
48
-
49
- it('throws when config is not valid JSON', async () => {
50
- writeFileSync(join(tmpDir, 'openapi-react-query.config.json'), 'not json {{{')
51
- await expect(loadConfig(tmpDir)).rejects.toThrow('not valid JSON')
52
- })
53
-
54
- it('throws when config is not an object', async () => {
55
- writeFileSync(join(tmpDir, 'openapi-react-query.config.json'), '"just a string"')
56
- await expect(loadConfig(tmpDir)).rejects.toThrow('JSON object')
57
- })
58
-
59
- it('throws when input_openapi is missing', async () => {
60
- writeConfig({ output: 'src/hooks' })
61
- await expect(loadConfig(tmpDir)).rejects.toThrow('input_openapi')
62
- })
63
-
64
- it('throws when output is missing', async () => {
65
- writeConfig({ input_openapi: 'openapi.json' })
66
- await expect(loadConfig(tmpDir)).rejects.toThrow('output')
67
- })
68
-
69
- it('throws when stale_time is not a number', async () => {
70
- writeConfig({ input_openapi: 'openapi.json', output: 'src/hooks', stale_time: 'fast' })
71
- await expect(loadConfig(tmpDir)).rejects.toThrow('stale_time')
72
- })
73
-
74
- it('throws when gc_time is not a number', async () => {
75
- writeConfig({ input_openapi: 'openapi.json', output: 'src/hooks', gc_time: true })
76
- await expect(loadConfig(tmpDir)).rejects.toThrow('gc_time')
77
- })
78
-
79
- it('ignores unknown config fields', async () => {
80
- writeConfig({ input_openapi: 'openapi.json', output: 'src/hooks', unknown_field: 'ignored' })
81
- const config = await loadConfig(tmpDir)
82
- expect(config.input_openapi).toBe('openapi.json')
83
- })
84
-
85
- it('accepts a config loaded via explicit configPath', async () => {
86
- const configFile = join(tmpDir, 'custom.config.json')
87
- writeFileSync(
88
- configFile,
89
- JSON.stringify({ input_openapi: 'openapi.json', output: 'src/hooks' })
90
- )
91
- const config = await loadConfig(tmpDir, configFile)
92
- expect(config.input_openapi).toBe('openapi.json')
93
- expect(config.output).toBe('src/hooks')
94
- })
95
-
96
- it('rejects explicit configPath that is not .json', async () => {
97
- const configFile = join(tmpDir, 'config.ts')
98
- writeFileSync(
99
- configFile,
100
- JSON.stringify({ input_openapi: 'openapi.json', output: 'src/hooks' })
101
- )
102
- await expect(loadConfig(tmpDir, configFile)).rejects.toThrow('Config file must be a .json file')
103
- })
104
-
105
- it('throws when suspense is not a boolean', async () => {
106
- writeConfig({ input_openapi: 'openapi.json', output: 'src/hooks', suspense: 'yes' })
107
- await expect(loadConfig(tmpDir)).rejects.toThrow('"suspense" must be a boolean')
108
- })
109
-
110
- it('throws when auto_invalidate is not a boolean', async () => {
111
- writeConfig({ input_openapi: 'openapi.json', output: 'src/hooks', auto_invalidate: 'yes' })
112
- await expect(loadConfig(tmpDir)).rejects.toThrow('"auto_invalidate" must be a boolean')
113
- })
114
-
115
- it('throws when overrides is not an object', async () => {
116
- writeConfig({ input_openapi: 'openapi.json', output: 'src/hooks', overrides: 'flat' })
117
- await expect(loadConfig(tmpDir)).rejects.toThrow('"overrides" must be an object')
118
- })
119
-
120
- it('throws when overrides is an array', async () => {
121
- writeConfig({ input_openapi: 'openapi.json', output: 'src/hooks', overrides: [] })
122
- await expect(loadConfig(tmpDir)).rejects.toThrow('"overrides" must be an object')
123
- })
124
-
125
- it('throws when an override entry is not an object', async () => {
126
- writeConfig({
127
- input_openapi: 'openapi.json',
128
- output: 'src/hooks',
129
- overrides: { pets: 'wrong' },
130
- })
131
- await expect(loadConfig(tmpDir)).rejects.toThrow('"overrides.pets" must be an object')
132
- })
133
-
134
- it('throws when override stale_time is not a number', async () => {
135
- writeConfig({
136
- input_openapi: 'openapi.json',
137
- output: 'src/hooks',
138
- overrides: { pets: { stale_time: 'fast' } },
139
- })
140
- await expect(loadConfig(tmpDir)).rejects.toThrow('"overrides.pets.stale_time" must be a number')
141
- })
142
-
143
- it('throws when override gc_time is not a number', async () => {
144
- writeConfig({
145
- input_openapi: 'openapi.json',
146
- output: 'src/hooks',
147
- overrides: { pets: { gc_time: true } },
148
- })
149
- await expect(loadConfig(tmpDir)).rejects.toThrow('"overrides.pets.gc_time" must be a number')
150
- })
151
-
152
- it('loads valid overrides', async () => {
153
- writeConfig({
154
- input_openapi: 'openapi.json',
155
- output: 'src/hooks',
156
- overrides: { pets: { stale_time: 1000, gc_time: 5000 } },
157
- })
158
- const config = await loadConfig(tmpDir)
159
- expect(config.overrides).toEqual({ pets: { stale_time: 1000, gc_time: 5000 } })
160
- })
161
- })
162
-
163
- describe('config security validation', () => {
164
- describe('validateConfigPath', () => {
165
- it('rejects non-.json config file extension', () => {
166
- expect(() => validateConfigPath('/project/config.ts')).toThrow(
167
- 'Config file must be a .json file'
168
- )
169
- })
170
-
171
- it('rejects .yaml extension', () => {
172
- expect(() => validateConfigPath('/project/config.yaml')).toThrow(
173
- 'Config file must be a .json file'
174
- )
175
- })
176
-
177
- it('accepts .json extension', () => {
178
- expect(() => validateConfigPath('/project/config.json')).not.toThrow()
179
- })
180
-
181
- it('accepts nested .json path', () => {
182
- expect(() => validateConfigPath('/Users/someone/project/my-tool.config.json')).not.toThrow()
183
- })
184
- })
185
-
186
- describe('validateOutputPath', () => {
187
- it('rejects output path in /etc', () => {
188
- expect(() => validateOutputPath('/etc/generated')).toThrow('system directory')
189
- })
190
-
191
- it('rejects output path in /usr', () => {
192
- expect(() => validateOutputPath('/usr/local/generated')).toThrow('system directory')
193
- })
194
-
195
- it('rejects output path in /bin', () => {
196
- expect(() => validateOutputPath('/bin/generated')).toThrow('system directory')
197
- })
198
-
199
- it('rejects output path in /sys', () => {
200
- expect(() => validateOutputPath('/sys/something')).toThrow('system directory')
201
- })
202
-
203
- it('rejects output path in /proc', () => {
204
- expect(() => validateOutputPath('/proc/1/generated')).toThrow('system directory')
205
- })
206
-
207
- it('rejects output path in /dev', () => {
208
- expect(() => validateOutputPath('/dev/null')).toThrow('system directory')
209
- })
210
-
211
- it('rejects output path in /boot', () => {
212
- expect(() => validateOutputPath('/boot/generated')).toThrow('system directory')
213
- })
214
-
215
- it('rejects output path that is exactly /run', () => {
216
- expect(() => validateOutputPath('/run')).toThrow('system directory')
217
- })
218
-
219
- it('rejects output path under /run/', () => {
220
- expect(() => validateOutputPath('/run/lock/something')).toThrow('system directory')
221
- })
222
-
223
- it('does NOT reject /runner/_work/... (CI runner path)', () => {
224
- expect(() => validateOutputPath('/runner/_work/project/src/generated')).not.toThrow()
225
- })
226
-
227
- it('accepts normal project-relative resolved path', () => {
228
- expect(() => validateOutputPath('/Users/someone/project/generated')).not.toThrow()
229
- })
230
-
231
- it('accepts absolute path within home directory', () => {
232
- expect(() => validateOutputPath('/Users/someone/myproject/src/hooks')).not.toThrow()
233
- })
234
-
235
- it('accepts dist/api style path', () => {
236
- expect(() => validateOutputPath('/home/user/project/dist/hooks')).not.toThrow()
237
- })
238
-
239
- it('accepts GitHub Actions home runner output path', () => {
240
- expect(() =>
241
- validateOutputPath('/home/runner/work/my-repo/my-repo/src/generated')
242
- ).not.toThrow()
243
- })
244
-
245
- it('accepts GitLab runner build output path', () => {
246
- expect(() =>
247
- validateOutputPath('/var/lib/gitlab-runner/builds/project/src/generated')
248
- ).not.toThrow()
249
- })
250
-
251
- it('accepts common CI workspace output path', () => {
252
- expect(() => validateOutputPath('/workspace/project/src/generated')).not.toThrow()
253
- })
254
- })
255
-
256
- describe('validateInputPath', () => {
257
- it('rejects input spec from /proc', () => {
258
- expect(() => validateInputPath('/proc/1/fd/0')).toThrow('system directory')
259
- })
260
-
261
- it('rejects input spec from /etc', () => {
262
- expect(() => validateInputPath('/etc/passwd')).toThrow('system directory')
263
- })
264
-
265
- it('rejects input spec from /dev', () => {
266
- expect(() => validateInputPath('/dev/random')).toThrow('system directory')
267
- })
268
-
269
- it('rejects input spec from /usr', () => {
270
- expect(() => validateInputPath('/usr/share/openapi.json')).toThrow('system directory')
271
- })
272
-
273
- it('accepts input spec in home directory', () => {
274
- expect(() => validateInputPath('/Users/someone/project/openapi.json')).not.toThrow()
275
- })
276
-
277
- it('accepts input spec in /tmp', () => {
278
- expect(() => validateInputPath('/tmp/openapi.json')).not.toThrow()
279
- })
280
-
281
- it('accepts GitHub Actions home runner input path', () => {
282
- expect(() =>
283
- validateInputPath('/home/runner/work/my-repo/my-repo/spec/api.json')
284
- ).not.toThrow()
285
- })
286
-
287
- it('accepts common CI workspace input path', () => {
288
- expect(() => validateInputPath('/workspace/project/spec/openapi.json')).not.toThrow()
289
- })
290
- })
291
- })
@@ -1,310 +0,0 @@
1
- import fc from 'fast-check'
2
- import { describe, expect, it } from 'vitest'
3
- import type { OpenAPIV3_1 } from 'openapi-types'
4
- import { generateHooks } from '../plugins/hooks.js'
5
-
6
- // ── Arbitrary generators ────────────────────────────────────────────────────
7
-
8
- /** Valid JS identifier: starts with letter, letters+digits, length 3–12 */
9
- const arbOperationId = fc.stringMatching(/^[a-z][a-zA-Z0-9]{2,11}$/)
10
-
11
- /** Valid JS identifier for path params: length 2–8 */
12
- const arbPathParam = fc.stringMatching(/^[a-z][a-z0-9]{1,7}$/)
13
-
14
- /** Query param object */
15
- const arbQueryParam = fc.record({
16
- name: arbPathParam,
17
- required: fc.boolean(),
18
- })
19
-
20
- /** Realistic resource names */
21
- const RESOURCES = [
22
- 'items',
23
- 'tasks',
24
- 'users',
25
- 'projects',
26
- 'orders',
27
- 'reports',
28
- 'documents',
29
- 'records',
30
- ]
31
-
32
- /** Generates realistic path strings */
33
- const arbPath: fc.Arbitrary<string> = fc.oneof(
34
- // /resources (no path params)
35
- fc.constantFrom(...RESOURCES).map((r) => `/${r}`),
36
- // /resources/{id} (1 path param)
37
- fc.tuple(fc.constantFrom(...RESOURCES), arbPathParam).map(([r, p]) => `/${r}/{${p}}`),
38
- // /resources/{p1}/sub/{p2} (2 path params)
39
- fc
40
- .tuple(fc.constantFrom(...RESOURCES), arbPathParam, fc.constantFrom(...RESOURCES), arbPathParam)
41
- .map(([r, p1, sub, p2]) => `/${r}/{${p1}}/${sub}/{${p2}}`)
42
- )
43
-
44
- type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'
45
-
46
- const arbHttpMethod: fc.Arbitrary<HttpMethod> = fc.constantFrom(
47
- 'get',
48
- 'post',
49
- 'put',
50
- 'patch',
51
- 'delete'
52
- )
53
-
54
- function extractPathParams(path: string): string[] {
55
- const matches = path.match(/\{([^}]+)\}/g)
56
- if (matches === null) return []
57
- return matches.map((m) => m.slice(1, -1))
58
- }
59
-
60
- interface ArbOp {
61
- operationId: string
62
- method: HttpMethod
63
- path: string
64
- pathParams: string[]
65
- hasBody: boolean
66
- queryParams: { name: string; required: boolean }[]
67
- }
68
-
69
- /** Generates a single operation object */
70
- const arbOperation: fc.Arbitrary<ArbOp> = fc
71
- .tuple(
72
- arbOperationId,
73
- arbHttpMethod,
74
- arbPath,
75
- fc.array(arbQueryParam, { minLength: 0, maxLength: 3 })
76
- )
77
- .map(([operationId, method, path, queryParams]) => {
78
- const pathParams = extractPathParams(path)
79
- const hasBody = method === 'post' || method === 'put' || method === 'patch'
80
- return { operationId, method, path, pathParams, hasBody, queryParams }
81
- })
82
-
83
- /** Build a valid OpenAPI 3.1 document from ArbOp list */
84
- function buildSpec(ops: ArbOp[]): OpenAPIV3_1.Document {
85
- const paths: OpenAPIV3_1.PathsObject = {}
86
- for (const op of ops) {
87
- if (!paths[op.path]) paths[op.path] = {}
88
- const parameters = [
89
- ...op.pathParams.map((p) => ({
90
- name: p,
91
- in: 'path' as const,
92
- required: true,
93
- schema: { type: 'string' as const },
94
- })),
95
- ...op.queryParams.map((q) => ({
96
- name: q.name,
97
- in: 'query' as const,
98
- required: q.required,
99
- schema: { type: 'string' as const },
100
- })),
101
- ]
102
- ;(paths[op.path] as Record<string, unknown>)[op.method] = {
103
- operationId: op.operationId,
104
- parameters,
105
- ...(op.hasBody
106
- ? {
107
- requestBody: {
108
- required: true,
109
- content: { 'application/json': { schema: { type: 'object' } } },
110
- },
111
- }
112
- : {}),
113
- responses: { '200': { description: 'ok' } },
114
- }
115
- }
116
- return { openapi: '3.1.0', info: { title: 'Test', version: '1.0.0' }, paths }
117
- }
118
-
119
- /**
120
- * Generate a spec from 1–8 operations with unique operationIds.
121
- * Ensures no duplicate (path, method) pairs and unique operationIds.
122
- */
123
- const arbSpec: fc.Arbitrary<OpenAPIV3_1.Document> = fc
124
- .array(arbOperation, { minLength: 1, maxLength: 8 })
125
- .map((ops) => {
126
- // Deduplicate by (path, method) — last one wins
127
- const seen = new Map<string, ArbOp>()
128
- for (const op of ops) {
129
- seen.set(`${op.path}:${op.method}`, op)
130
- }
131
- const deduped = Array.from(seen.values())
132
-
133
- // Make operationIds unique by appending index
134
- const uniqueOps = deduped.map((op, i) => ({ ...op, operationId: `${op.operationId}${i}` }))
135
- return buildSpec(uniqueOps)
136
- })
137
-
138
- // ── Property tests ──────────────────────────────────────────────────────────
139
-
140
- describe('property-based tests — generateHooks invariants', () => {
141
- // Property 1: generator never throws
142
- it('never throws on any generated spec', () => {
143
- fc.assert(
144
- fc.property(arbSpec, (spec) => {
145
- expect(() => generateHooks(spec, { staleTime: 0, gcTime: 0 })).not.toThrow()
146
- }),
147
- { numRuns: 500 }
148
- )
149
- })
150
-
151
- // Property 2: every GET produces a useQuery hook
152
- it('every GET produces a useQuery hook named use + PascalCase(operationId)', () => {
153
- fc.assert(
154
- fc.property(arbSpec, (spec) => {
155
- const { content } = generateHooks(spec, { staleTime: 0, gcTime: 0 })
156
- for (const pathItem of Object.values(spec.paths ?? {})) {
157
- const op = (pathItem as Record<string, unknown>)['get'] as
158
- | { operationId?: string }
159
- | undefined
160
- if (!op?.operationId) continue
161
- const hookName = 'use' + op.operationId[0]!.toUpperCase() + op.operationId.slice(1)
162
- expect(content).toContain(`export function ${hookName}(`)
163
- }
164
- }),
165
- { numRuns: 300 }
166
- )
167
- })
168
-
169
- // Property 3: every mutation produces a useMutation hook
170
- it('every POST/PUT/PATCH/DELETE produces a useMutation hook', () => {
171
- fc.assert(
172
- fc.property(arbSpec, (spec) => {
173
- const { content } = generateHooks(spec, { staleTime: 0, gcTime: 0 })
174
- for (const pathItem of Object.values(spec.paths ?? {})) {
175
- for (const method of ['post', 'put', 'patch', 'delete'] as const) {
176
- const op = (pathItem as Record<string, unknown>)[method] as
177
- | { operationId?: string }
178
- | undefined
179
- if (!op?.operationId) continue
180
- const hookName = 'use' + op.operationId[0]!.toUpperCase() + op.operationId.slice(1)
181
- expect(content).toContain(`export function ${hookName}(`)
182
- }
183
- }
184
- }),
185
- { numRuns: 300 }
186
- )
187
- })
188
-
189
- // Property 4: key factory always defines all() when any GET exists
190
- // NOTE: The invariant checks for the definition `all:` in the key factory, not a call site `.all()`.
191
- // A call to `.all()` only appears in mutation onSuccess handlers (autoInvalidate mode); a detail-only
192
- // spec with no mutations correctly defines `all:` without ever calling it.
193
- it('key factory defines all() method whenever there is at least one GET operation', () => {
194
- fc.assert(
195
- fc.property(arbSpec, (spec) => {
196
- const hasGet = Object.values(spec.paths ?? {}).some(
197
- (p) => (p as Record<string, unknown>)?.['get'] !== undefined
198
- )
199
- if (!hasGet) return // skip specs with no GET
200
- const { content } = generateHooks(spec, { staleTime: 0, gcTime: 0 })
201
- // The key factory always includes an `all:` method definition
202
- expect(content).toContain('all:')
203
- }),
204
- { numRuns: 300 }
205
- )
206
- })
207
-
208
- // Property 5: hooks with 2+ path params always use && in enabled guard
209
- it('detail hooks with 2+ path params use && in enabled guard', () => {
210
- fc.assert(
211
- fc.property(arbSpec, (spec) => {
212
- const { content } = generateHooks(spec, { staleTime: 0, gcTime: 0 })
213
- for (const [_path, pathItem] of Object.entries(spec.paths ?? {})) {
214
- const op = (pathItem as Record<string, unknown>)['get'] as
215
- | { operationId?: string; parameters?: Array<{ in: string }> }
216
- | undefined
217
- if (!op?.operationId) continue
218
- const params = (op.parameters ?? []).filter((p) => p.in === 'path')
219
- if (params.length < 2) continue
220
- // The enabled guard should AND all params together
221
- expect(content).toContain('&&')
222
- }
223
- }),
224
- { numRuns: 200 }
225
- )
226
- })
227
-
228
- // Property 6: with autoInvalidate, mutations only reference key factories that are generated.
229
- // Previously buggy: the generator emitted `${resource}Keys.all()` even for mutation-only
230
- // resources (no GET ops), producing a ReferenceError because the key factory was never defined.
231
- // Fixed by gating invalidateInfo on resourceToGetOps.has(resource).
232
- it('with autoInvalidate, mutation hooks only reference key factories that are generated', () => {
233
- fc.assert(
234
- fc.property(arbSpec, (spec) => {
235
- const { content } = generateHooks(spec, { staleTime: 0, gcTime: 0, autoInvalidate: true })
236
-
237
- // Collect all key factory names that ARE generated (only for resources with GET ops)
238
- const resourcesWithGets = new Set<string>()
239
- for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {
240
- const op = (pathItem as Record<string, unknown>)['get']
241
- if (op === undefined) continue
242
- // Extract primary resource (mirrors primaryResource() function)
243
- const stripped = path.replace(/^\/api\/v\d+\//, '').replace(/^\//, '')
244
- const firstSegment = (stripped.split('/')[0] ?? 'resource').replace(/[{}]/g, '')
245
- resourcesWithGets.add(firstSegment)
246
- }
247
-
248
- // For each resource that appears in mutation ops but NOT in GET ops,
249
- // a key factory reference in onSuccess is invalid (undefined reference)
250
- for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {
251
- for (const method of ['post', 'put', 'patch', 'delete'] as const) {
252
- const op = (pathItem as Record<string, unknown>)[method] as
253
- | { operationId?: string }
254
- | undefined
255
- if (!op?.operationId) continue
256
-
257
- const stripped = path.replace(/^\/api\/v\d+\//, '').replace(/^\//, '')
258
- const firstSegment = (stripped.split('/')[0] ?? 'resource').replace(/[{}]/g, '')
259
-
260
- if (!resourcesWithGets.has(firstSegment)) {
261
- // This mutation is on a resource with no GET → generator will still emit
262
- // `${factoryName}.all()` in the mutation hook, but the factory is never defined
263
- const hookName = 'use' + op.operationId[0]!.toUpperCase() + op.operationId.slice(1)
264
- const hookStart = content.indexOf(`export function ${hookName}(`)
265
- if (hookStart === -1) continue
266
- const hookEnd = content.indexOf('\n}', hookStart) + 2
267
- const hookContent = content.slice(hookStart, hookEnd)
268
-
269
- // The generated hook will reference factoryName (e.g. itemKeys)
270
- // but that factory is never exported — check if the factory is defined
271
- const camel = firstSegment.replace(/[-_](.)/g, (_: string, c: string) =>
272
- c.toUpperCase()
273
- )
274
- const singular = camel.endsWith('s') ? camel.slice(0, -1) : camel
275
- const factoryName = `${singular}Keys`
276
-
277
- if (hookContent.includes(`${factoryName}.all()`)) {
278
- // The key factory reference exists in the hook — verify the factory is exported
279
- expect(content).toContain(`export const ${factoryName}`)
280
- }
281
- }
282
- }
283
- }
284
- }),
285
- { numRuns: 200 }
286
- )
287
- })
288
-
289
- // Property 7: generated content always starts with the auto-generated header comment
290
- it('generated output always starts with the auto-generated header comment', () => {
291
- fc.assert(
292
- fc.property(arbSpec, (spec) => {
293
- const { content } = generateHooks(spec, { staleTime: 0, gcTime: 0 })
294
- expect(content).toMatch(/^\/\/ (This file is auto-generated|Generated)/)
295
- }),
296
- { numRuns: 200 }
297
- )
298
- })
299
-
300
- // Property 8: filename is always hooks.ts
301
- it('filename is always hooks.ts', () => {
302
- fc.assert(
303
- fc.property(arbSpec, (spec) => {
304
- const { filename } = generateHooks(spec, { staleTime: 0, gcTime: 0 })
305
- expect(filename).toBe('hooks.ts')
306
- }),
307
- { numRuns: 100 }
308
- )
309
- })
310
- })