Skip to content

Commit 1134ba5

Browse files
authored
fix(mock): inline enum values in mocks for union enumGenerationType (#3694)
1 parent 8d9d8b3 commit 1134ba5

9 files changed

Lines changed: 267 additions & 3 deletions

File tree

packages/mock/src/faker/getters/scalar.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable unicorn/no-null */
22
import type { ContextSpec, OpenApiSchemaObjectType } from '@orval/core';
3+
import { EnumGeneration } from '@orval/core';
34
import { describe, expect, it } from 'vitest';
45

56
import { createTestContextSpec } from '../../../../core/src/test-utils/context';
@@ -1166,3 +1167,66 @@ describe('getMockScalar (schema-scoped overrides)', () => {
11661167
expect(result.value).toBe("'red'");
11671168
});
11681169
});
1170+
1171+
describe('getMockScalar (referenced string enum by enumGenerationType #3690)', () => {
1172+
const baseArg = {
1173+
imports: [],
1174+
operationId: 'test-operation',
1175+
tags: [],
1176+
existingReferencedProperties: [],
1177+
splitMockImplementations: [],
1178+
};
1179+
1180+
const enumRefItem = {
1181+
type: 'string' as OpenApiSchemaObjectType,
1182+
enum: ['ONE', 'TWO', 'THREE'],
1183+
name: 'MyEnum',
1184+
isRef: true,
1185+
};
1186+
1187+
it('inlines the enum values for `union` (a union type has no runtime value)', () => {
1188+
const result = getMockScalar({
1189+
...baseArg,
1190+
item: { ...enumRefItem },
1191+
context: scalarContext({ enumGenerationType: EnumGeneration.UNION }),
1192+
});
1193+
1194+
expect(result.value).toBe(
1195+
"faker.helpers.arrayElement(['ONE','TWO','THREE'] as const)",
1196+
);
1197+
// No value import: the union type must not be referenced as a value.
1198+
expect(result.imports).not.toContainEqual(
1199+
expect.objectContaining({ name: 'MyEnum', values: true }),
1200+
);
1201+
});
1202+
1203+
it('uses Object.values for `enum` (a native enum is a runtime object)', () => {
1204+
const result = getMockScalar({
1205+
...baseArg,
1206+
item: { ...enumRefItem },
1207+
context: scalarContext({ enumGenerationType: EnumGeneration.ENUM }),
1208+
});
1209+
1210+
expect(result.value).toBe(
1211+
'faker.helpers.arrayElement(Object.values(MyEnum))',
1212+
);
1213+
expect(result.imports).toContainEqual(
1214+
expect.objectContaining({ name: 'MyEnum', values: true }),
1215+
);
1216+
});
1217+
1218+
it('uses Object.values for `const` (a const object is a runtime value)', () => {
1219+
const result = getMockScalar({
1220+
...baseArg,
1221+
item: { ...enumRefItem },
1222+
context: scalarContext({ enumGenerationType: EnumGeneration.CONST }),
1223+
});
1224+
1225+
expect(result.value).toBe(
1226+
'faker.helpers.arrayElement(Object.values(MyEnum))',
1227+
);
1228+
expect(result.imports).toContainEqual(
1229+
expect.objectContaining({ name: 'MyEnum', values: true }),
1230+
);
1231+
});
1232+
});

packages/mock/src/faker/getters/scalar.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -635,8 +635,16 @@ function getEnum(
635635
enumValue += ' as const';
636636
}
637637

638-
// But if the value is a reference, we can use the object directly via the imports and using Object.values.
639-
if (item.isRef && type === 'string') {
638+
// But if the value is a reference to a schema that emits a runtime value
639+
// (a native `enum` object or a `const` object), we can use it directly via
640+
// the imports and `Object.values`. A `union` reference is a pure type with
641+
// no runtime value, so `Object.values` would fail (TS2693) — keep the
642+
// inlined values in that case (#3690).
643+
if (
644+
item.isRef &&
645+
type === 'string' &&
646+
context.output.override.enumGenerationType !== EnumGeneration.UNION
647+
) {
640648
enumValue = `Object.values(${item.name})`;
641649
imports.push({
642650
name: item.name,

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import type {
1010
NormalizedOverrideOutput,
1111
OpenApiSchemaObject,
1212
} from '@orval/core';
13-
import { isFakerMock, isMswMock, OutputMockType } from '@orval/core';
13+
import {
14+
EnumGeneration,
15+
isFakerMock,
16+
isMswMock,
17+
OutputMockType,
18+
} from '@orval/core';
1419
import { describe, expect, expectTypeOf, it } from 'vitest';
1520

1621
import { createTestContextSpec } from '../../../core/src/test-utils/context';
@@ -534,7 +539,11 @@ describe('oneOf split helpers forward body imports (#3656)', () => {
534539
// mutating that array suppresses the caller-side merge of the returned
535540
// imports, so forwarding only the variant's type import loses the enum
536541
// value import and the generated mock fails tsc with TS2304.
542+
// `enum` generation makes the $ref'd enum a runtime object, so the helper
543+
// renders it as `Object.values(ReasonEnum)` (see #3690 — `union` inlines the
544+
// values instead and forwards no value import).
537545
const context = createTestContextSpec({
546+
override: { enumGenerationType: EnumGeneration.ENUM },
538547
spec: {
539548
components: {
540549
schemas: {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* Generated by orval v8.20.0 🍺
3+
* Do not edit manually.
4+
* Union enum ref mock
5+
* A parent schema references a string enum component via $ref while
6+
* enumGenerationType is 'union'. The union renders the enum as a pure type
7+
* (no runtime value), so the generated faker/msw mock must inline the enum
8+
* values instead of calling Object.values(EnumName) — which would fail tsc
9+
* with TS2693 (#3690).
10+
*
11+
* OpenAPI spec version: 1.0.0
12+
*/
13+
import axios from 'axios';
14+
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
15+
16+
import type { DisplayValueDto } from './model';
17+
18+
import { faker } from '@faker-js/faker';
19+
20+
import { HttpResponse, http } from 'msw';
21+
import type { RequestHandlerOptions } from 'msw';
22+
23+
export const getUnionEnumRefMock = (axiosInstance: AxiosInstance = axios) => {
24+
const getDisplay = (
25+
options?: AxiosRequestConfig,
26+
): Promise<AxiosResponse<DisplayValueDto>> => {
27+
return axiosInstance.get(`/display`, options);
28+
};
29+
30+
return { getDisplay };
31+
};
32+
export type GetDisplayResult = AxiosResponse<DisplayValueDto>;
33+
34+
export const getGetDisplayResponseMock = (
35+
overrideResponse: Partial<Extract<DisplayValueDto, object>> = {},
36+
): DisplayValueDto => ({
37+
color: faker.helpers.arrayElement([
38+
faker.helpers.arrayElement([
39+
'TEXT_01',
40+
'TEXT_04',
41+
'TEXT_CURRENCY_GAIN',
42+
] as const),
43+
undefined,
44+
]),
45+
value: faker.string.alpha({ length: { min: 10, max: 20 } }),
46+
...overrideResponse,
47+
});
48+
49+
export const getGetDisplayMockHandler = (
50+
overrideResponse?:
51+
| DisplayValueDto
52+
| ((
53+
info: Parameters<Parameters<typeof http.get>[1]>[0],
54+
) => Promise<DisplayValueDto> | DisplayValueDto),
55+
options?: RequestHandlerOptions,
56+
) => {
57+
return http.get(
58+
'*/display',
59+
async (info: Parameters<Parameters<typeof http.get>[1]>[0]) => {
60+
return HttpResponse.json(
61+
overrideResponse !== undefined
62+
? typeof overrideResponse === 'function'
63+
? await overrideResponse(info)
64+
: overrideResponse
65+
: getGetDisplayResponseMock(),
66+
{ status: 200 },
67+
);
68+
},
69+
options,
70+
);
71+
};
72+
export const getUnionEnumRefMockMock = () => [getGetDisplayMockHandler()];
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Generated by orval v8.20.0 🍺
3+
* Do not edit manually.
4+
* Union enum ref mock
5+
* A parent schema references a string enum component via $ref while
6+
* enumGenerationType is 'union'. The union renders the enum as a pure type
7+
* (no runtime value), so the generated faker/msw mock must inline the enum
8+
* values instead of calling Object.values(EnumName) — which would fail tsc
9+
* with TS2693 (#3690).
10+
*
11+
* OpenAPI spec version: 1.0.0
12+
*/
13+
14+
/**
15+
* A text color that can be used for display purposes.
16+
*/
17+
export type DisplayColor = 'TEXT_01' | 'TEXT_04' | 'TEXT_CURRENCY_GAIN';
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Generated by orval v8.20.0 🍺
3+
* Do not edit manually.
4+
* Union enum ref mock
5+
* A parent schema references a string enum component via $ref while
6+
* enumGenerationType is 'union'. The union renders the enum as a pure type
7+
* (no runtime value), so the generated faker/msw mock must inline the enum
8+
* values instead of calling Object.values(EnumName) — which would fail tsc
9+
* with TS2693 (#3690).
10+
*
11+
* OpenAPI spec version: 1.0.0
12+
*/
13+
import type { DisplayColor } from './displayColor';
14+
15+
export interface DisplayValueDto {
16+
color?: DisplayColor;
17+
value: string;
18+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Generated by orval v8.20.0 🍺
3+
* Do not edit manually.
4+
* Union enum ref mock
5+
* A parent schema references a string enum component via $ref while
6+
* enumGenerationType is 'union'. The union renders the enum as a pure type
7+
* (no runtime value), so the generated faker/msw mock must inline the enum
8+
* values instead of calling Object.values(EnumName) — which would fail tsc
9+
* with TS2693 (#3690).
10+
*
11+
* OpenAPI spec version: 1.0.0
12+
*/
13+
14+
export * from './displayColor';
15+
export * from './displayValueDto';

tests/configs/mock.config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,27 @@ export default defineConfig({
545545
target: '../specifications/faker-schemas-string-enum-ref.yaml',
546546
},
547547
},
548+
// #3690: with `enumGenerationType: 'union'` a $ref'd string enum is a pure
549+
// type (no runtime value), so the msw/faker mock must inline the enum values
550+
// instead of calling `Object.values(EnumName)` (which fails tsc with TS2693).
551+
unionEnumRefMsw: {
552+
output: {
553+
target: '../generated/mock/union-enum-ref-msw/endpoints.ts',
554+
schemas: '../generated/mock/union-enum-ref-msw/model',
555+
client: 'axios',
556+
mock: {
557+
generators: [{ type: 'msw' }],
558+
},
559+
override: {
560+
enumGenerationType: 'union',
561+
},
562+
clean: true,
563+
formatter: 'prettier',
564+
},
565+
input: {
566+
target: '../specifications/union-enum-ref-mock.yaml',
567+
},
568+
},
548569
issue3200: {
549570
output: {
550571
target: '../generated/mock/issue-3200/endpoints.ts',
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Union enum ref mock
4+
description: |
5+
A parent schema references a string enum component via $ref while
6+
enumGenerationType is 'union'. The union renders the enum as a pure type
7+
(no runtime value), so the generated faker/msw mock must inline the enum
8+
values instead of calling Object.values(EnumName) — which would fail tsc
9+
with TS2693 (#3690).
10+
version: 1.0.0
11+
paths:
12+
/display:
13+
get:
14+
operationId: getDisplay
15+
responses:
16+
'200':
17+
description: OK
18+
content:
19+
application/json:
20+
schema:
21+
$ref: '#/components/schemas/DisplayValueDto'
22+
components:
23+
schemas:
24+
DisplayColor:
25+
description: A text color that can be used for display purposes.
26+
type: string
27+
enum:
28+
- TEXT_01
29+
- TEXT_04
30+
- TEXT_CURRENCY_GAIN
31+
DisplayValueDto:
32+
type: object
33+
required:
34+
- value
35+
properties:
36+
color:
37+
allOf:
38+
- $ref: '#/components/schemas/DisplayColor'
39+
value:
40+
type: string

0 commit comments

Comments
 (0)