Skip to content

Commit 91ba443

Browse files
authored
fix(mock): enable arrayItems option for MSW-only mock configs (#3576) (#3577)
1 parent 8563fcd commit 91ba443

12 files changed

Lines changed: 397 additions & 16 deletions

File tree

docs/content/docs/guides/faker.mdx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,13 @@ Requires `output.schemas` to be configured (the consolidated file is written int
108108

109109
### Array Item Factories
110110

111-
Set `arrayItems: true` to emit reusable mock factories for **object-like array item schemas** found in operation responses. This covers array elements that are inlined in the response body (not just entries under `components/schemas`):
111+
Set `arrayItems: true` on any mock generator entry to emit reusable mock factories for **object-like array item schemas** found in operation responses. This covers array elements that are inlined in the response body (not just entries under `components/schemas`). Works with both `faker` and `msw` generators:
112112

113113
```ts title="orval.config.ts"
114114
mock: {
115115
generators: [
116116
{
117-
type: 'faker',
117+
type: 'msw',
118118
arrayItems: true,
119119
},
120120
],
@@ -146,7 +146,7 @@ When `schemas: true` is also enabled, `$ref` items delegate to the consolidated
146146

147147
## Options
148148

149-
Set faker-specific options on the generator entry:
149+
Shared mock options apply to both `faker` and `msw` generator entries. Faker-only options are listed separately below.
150150

151151
```ts title="orval.config.ts"
152152
mock: {
@@ -157,6 +157,7 @@ mock: {
157157
generateEachHttpStatus: true,
158158
locale: 'en_GB',
159159
preferredContentType: 'application/json',
160+
arrayItems: true,
160161
},
161162
],
162163
}
@@ -168,9 +169,14 @@ mock: {
168169
| `generateEachHttpStatus` | `boolean` | `false` | Emit a separate factory per HTTP status code defined in the spec (not just the success response). |
169170
| `locale` | `keyof typeof allLocales` || Faker locale. Switches the import to `@faker-js/faker/locale/<x>` (e.g. `'en_GB'`, `'fr'`, `'ja'`). |
170171
| `preferredContentType` | `string` || When an operation has multiple response content types, mock the one matching this MIME type. |
172+
| `arrayItems` | `boolean` | `false` | Emit reusable mock factories for object-like array item schemas in operation responses. See [Array Item Factories](#array-item-factories). |
173+
174+
Faker-only options:
175+
176+
| Option | Type | Default | Description |
177+
|---|---|---|---|
171178
| `schemas` | `boolean` | `false` | Emit a consolidated `get<SchemaName>Mock` factory per `components/schemas` entry into `<schemas-dir>/index.faker.ts`. See [Schema Factories](#schema-factories). |
172179
| `operationResponses` | `boolean` | `true` | Emit per-operation `get<OperationId>ResponseMock` factories. Set to `false` (typically with `schemas: true`) to skip operation-level factories. |
173-
| `arrayItems` | `boolean` | `false` | Emit reusable mock factories for object-like array item schemas in operation responses. See [Array Item Factories](#array-item-factories). |
174180

175181
## Customizing Mock Values
176182

docs/content/docs/guides/msw.mdx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,21 @@ export default defineConfig({
9595

9696
`preferredContentType` accepts common MIME literals and any custom string (via a loose `(string & {})` fallback), so vendor-specific types are supported too.
9797

98+
### Array Item Factories
99+
100+
Set `arrayItems: true` on the MSW generator entry to emit reusable mock factories for object-like array item schemas in operation responses (for example, `getTenantResponseModelDtoMock` for `value: TenantResponseModelDto[]`). The factories are written into the same `.msw.ts` / endpoints mock file as the handlers and response mocks. See the [Faker guide — Array Item Factories](/docs/guides/faker#array-item-factories) for configuration details and supported shapes.
101+
102+
```ts title="orval.config.ts"
103+
mock: {
104+
generators: [
105+
{
106+
type: 'msw',
107+
arrayItems: true,
108+
},
109+
],
110+
}
111+
```
112+
98113
### 2. Aggregated Handlers
99114

100115
Functions that combine all handlers for easy setup:

packages/core/src/types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,10 @@ export interface CommonMockOptions {
470470
locale?: keyof typeof allLocales;
471471
// Selects which response schema is mocked when multiple content types exist
472472
preferredContentType?: string;
473+
// Emit reusable mock factories for object-like array item schemas found in
474+
// operation responses (e.g. `getTenantResponseModelDtoMock` for
475+
// `value: TenantResponseModelDto[]`). Defaults to `false`.
476+
arrayItems?: boolean;
473477
}
474478

475479
export interface MswMockOptions extends CommonMockOptions {
@@ -497,10 +501,6 @@ export interface FakerMockOptions extends CommonMockOptions {
497501
// Defaults to `true`. Set to `false` together with `schemas: true` to get
498502
// only the consolidated schema factories.
499503
operationResponses?: boolean;
500-
// Emit reusable mock factories for object-like array item schemas found in
501-
// operation responses (e.g. `getTenantResponseModelDtoMock` for
502-
// `value: TenantResponseModelDto[]`). Defaults to `false`.
503-
arrayItems?: boolean;
504504
// Custom output directory for faker mock files. Overrides the shared
505505
// `OutputMocksConfig.path` when set. When provided in `single` or `tags`
506506
// modes, mock code is written to separate files instead of being inlined

packages/mock/src/faker/getters/array-item-factory.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@ describe('shouldExtractArrayItemFactories', () => {
7373
);
7474
});
7575

76+
it('returns true when arrayItems is enabled on an MSW-only generator', () => {
77+
const context = {
78+
output: {
79+
mock: {
80+
generators: [{ type: 'msw', arrayItems: true }],
81+
},
82+
},
83+
} as unknown as ContextSpec;
84+
85+
expect(shouldExtractArrayItemFactories(context)).toBe(true);
86+
});
87+
7688
it('returns false when arrayItems is not enabled', () => {
7789
expect(shouldExtractArrayItemFactories(contextWithoutArrayItems)).toBe(
7890
false,
@@ -107,6 +119,50 @@ describe('extractArrayItemMock', () => {
107119
expect(imports).toEqual([{ name: 'TenantResponseModelDto' }]);
108120
});
109121

122+
it('extracts a reusable factory for $ref array items with an MSW generator', () => {
123+
const splitMockImplementations: string[] = [];
124+
const context = {
125+
output: {
126+
mock: {
127+
generators: [{ type: 'msw', arrayItems: true }],
128+
},
129+
override: {
130+
components: { schemas: { suffix: '', itemSuffix: 'Item' } },
131+
},
132+
},
133+
spec: {
134+
openapi: '3.0.3',
135+
components: {
136+
schemas: {
137+
TenantResponseModelDto: {
138+
type: 'object',
139+
properties: {
140+
id: { type: 'string' },
141+
name: { type: 'string' },
142+
},
143+
},
144+
},
145+
},
146+
},
147+
} as unknown as ContextSpec;
148+
149+
const call = extractArrayItemMock({
150+
items: { $ref: '#/components/schemas/TenantResponseModelDto' },
151+
propertyName: 'value',
152+
operationId: 'getTenantsByRef',
153+
tags: [],
154+
mapValue,
155+
context,
156+
splitMockImplementations,
157+
imports: [],
158+
});
159+
160+
expect(call).toBe('{...getTenantResponseModelDtoMock()}');
161+
expect(splitMockImplementations[0]).toContain(
162+
'export const getTenantResponseModelDtoMock',
163+
);
164+
});
165+
110166
it('extracts a reusable factory for inline object array items', () => {
111167
const splitMockImplementations: string[] = [];
112168

packages/mock/src/faker/getters/array-item-factory.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,13 @@ function getFileLevelExtractedFactories(
5959
}
6060

6161
/**
62-
* True when the active faker generator entry opts into reusable array-item
63-
* mock factories for object-like array item schemas in operation responses.
62+
* True when any mock generator entry opts into reusable array-item mock
63+
* factories for object-like array item schemas in operation responses.
6464
*/
6565
export function shouldExtractArrayItemFactories(context: ContextSpec): boolean {
66-
const fakerEntry = context.output.mock.generators.find(
67-
(g) =>
68-
!isFunction(g) &&
69-
g.type === OutputMockType.FAKER &&
70-
g.arrayItems === true,
66+
return context.output.mock.generators.some(
67+
(g) => !isFunction(g) && g.arrayItems === true,
7168
);
72-
return !!fakerEntry;
7369
}
7470

7571
/**

packages/mock/src/msw/index.test.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,6 +1399,147 @@ describe('generateMSW', () => {
13991399
});
14001400
});
14011401

1402+
describe('arrayItems option', () => {
1403+
const tenantListResponseType = {
1404+
key: '200',
1405+
value: 'TenantListResponse',
1406+
contentType: 'application/json',
1407+
originalSchema: { $ref: '#/components/schemas/TenantListResponse' },
1408+
imports: [{ name: 'TenantListResponse' }],
1409+
schemas: [],
1410+
type: 'object',
1411+
isEnum: false,
1412+
isRef: true,
1413+
hasReadonlyProps: false,
1414+
};
1415+
1416+
const tenantsByRefVerbOptions = {
1417+
operationId: 'getTenantsByRef',
1418+
operationName: 'getTenantsByRef',
1419+
verb: 'get',
1420+
tags: ['tenants'],
1421+
response: {
1422+
imports: [{ name: 'TenantListResponse' }],
1423+
definition: { success: 'TenantListResponse' },
1424+
types: { success: [tenantListResponseType] },
1425+
contentTypes: ['application/json'],
1426+
},
1427+
} as unknown as GeneratorVerbOptions;
1428+
1429+
const arrayItemsContext = {
1430+
target: 'test',
1431+
workspace: '',
1432+
spec: {
1433+
openapi: '3.0.3',
1434+
info: { title: 'Test', version: '1.0.0' },
1435+
paths: {},
1436+
components: {
1437+
schemas: {
1438+
TenantListResponse: {
1439+
type: 'object',
1440+
required: ['value', 'count'],
1441+
properties: {
1442+
value: {
1443+
type: 'array',
1444+
items: {
1445+
$ref: '#/components/schemas/TenantResponseModelDto',
1446+
},
1447+
},
1448+
count: { type: 'integer' },
1449+
},
1450+
},
1451+
TenantResponseModelDto: {
1452+
type: 'object',
1453+
required: ['id', 'name'],
1454+
properties: {
1455+
id: { type: 'string', format: 'uuid' },
1456+
name: { type: 'string' },
1457+
},
1458+
},
1459+
},
1460+
},
1461+
},
1462+
output: {
1463+
target: 'test',
1464+
namingConvention: 'camelCase',
1465+
fileExtension: '.ts',
1466+
schemaFileExtension: '.ts',
1467+
mode: 'single',
1468+
mock: {
1469+
indexMockFiles: false,
1470+
generators: [
1471+
{ type: OutputMockType.MSW, arrayItems: true, delay: false },
1472+
],
1473+
},
1474+
override: {
1475+
operations: {},
1476+
tags: {},
1477+
components: { schemas: { suffix: '', itemSuffix: 'Item' } },
1478+
},
1479+
client: 'axios-functions',
1480+
httpClient: 'fetch',
1481+
clean: false,
1482+
docs: false,
1483+
formatter: undefined,
1484+
headers: false,
1485+
indexFiles: true,
1486+
allParamsOptional: false,
1487+
urlEncodeParameters: false,
1488+
unionAddMissingProperties: false,
1489+
optionsParamRequired: false,
1490+
propertySortOrder: 'specification',
1491+
},
1492+
};
1493+
1494+
const arrayItemsOptions = {
1495+
route: '/tenants-by-ref',
1496+
pathRoute: '/tenants-by-ref',
1497+
output: 'test',
1498+
override: { operations: {}, tags: {} } as NormalizedOverrideOutput,
1499+
context: arrayItemsContext,
1500+
mock: {
1501+
type: OutputMockType.MSW,
1502+
arrayItems: true,
1503+
delay: false,
1504+
},
1505+
} as unknown as GeneratorOptions;
1506+
1507+
it('extracts reusable array item factories when arrayItems is enabled on the MSW generator', () => {
1508+
const result = generateMSW(tenantsByRefVerbOptions, arrayItemsOptions);
1509+
1510+
expect(result.implementation.function).toContain(
1511+
'export const getTenantResponseModelDtoMock',
1512+
);
1513+
expect(result.implementation.function).toContain(
1514+
'getGetTenantsByRefResponseMock',
1515+
);
1516+
expect(result.implementation.function).toContain(
1517+
'...getTenantResponseModelDtoMock()',
1518+
);
1519+
});
1520+
1521+
it('does not extract array item factories when arrayItems is disabled', () => {
1522+
const result = generateMSW(tenantsByRefVerbOptions, {
1523+
...arrayItemsOptions,
1524+
mock: { type: OutputMockType.MSW, delay: false },
1525+
context: {
1526+
...arrayItemsContext,
1527+
output: {
1528+
...arrayItemsContext.output,
1529+
mock: {
1530+
indexMockFiles: false,
1531+
generators: [{ type: OutputMockType.MSW, delay: false }],
1532+
},
1533+
},
1534+
},
1535+
} as unknown as GeneratorOptions);
1536+
1537+
expect(result.implementation.function).not.toContain(
1538+
'export const getTenantResponseModelDtoMock',
1539+
);
1540+
});
1541+
});
1542+
14021543
describe('strict mock types (#3525)', () => {
14031544
const petResponseType = {
14041545
key: '200',

0 commit comments

Comments
 (0)