Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/mock/src/msw/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { generateMSW } from './index';
describe('generateMSW', () => {
const mockVerbOptions = {
operationId: 'getUser',
operationName: 'getUser',
verb: 'get',
tags: [],
response: {
Expand Down Expand Up @@ -1422,6 +1423,7 @@ describe('strict mock types (#3525)', () => {

const petVerbOptions = {
operationId: 'getPet',
operationName: 'getPet',
verb: 'get',
tags: [],
response: {
Expand Down Expand Up @@ -1495,6 +1497,34 @@ describe('strict mock types (#3525)', () => {
expect(result.implementation.function).not.toContain(', null]');
});

it('derives responseMock and handler names from operationName (splitByContentType)', () => {
// splitByContentType keeps one operationId across variants but suffixes
// operationName (e.g. *WithJson / *WithFormData). The MSW responseMock and
// handler names must follow operationName, otherwise sibling variants emit
// duplicate declarations and tsc fails with TS2451. See #3342.
const result = generateMSW(
{
...petVerbOptions,
operationId: 'getPet',
operationName: 'getPetWithFormData',
} as unknown as GeneratorVerbOptions,
{ ...baseOptions, mock: { type: OutputMockType.MSW } },
);

expect(result.implementation.handlerName).toBe(
'getGetPetWithFormDataMockHandler',
);
expect(result.implementation.function).toContain(
'export const getGetPetWithFormDataResponseMock',
);
expect(result.implementation.handler).toContain(
'export const getGetPetWithFormDataMockHandler',
);
expect(result.implementation.function).not.toContain(
'getGetPetResponseMock',
);
});

it('keeps the loose return type when strict flags are unset', () => {
const looseOverride = {
operations: {},
Expand Down Expand Up @@ -1541,6 +1571,7 @@ describe('recursion guards for cyclic allOf schemas', () => {
const run = (schemas: Record<string, unknown>, root: string) => {
const verbOptions = {
operationId: 'getRoot',
operationName: 'getRoot',
verb: 'get',
tags: [],
response: {
Expand Down
10 changes: 7 additions & 3 deletions packages/mock/src/msw/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ export function generateMSW(
generatorOptions: GeneratorOptions,
): ClientMockGeneratorBuilder {
const { pathRoute, override, mock } = generatorOptions;
const { operationId, response } = generatorVerbOptions;
const { operationName, response } = generatorVerbOptions;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

operationName is a required field on GeneratorVerbOptions (packages/core/src/types.ts), not optional, and buildVerbOption always populates it in the generation pipeline. Both production call sites — packages/mock/src/index.ts and packages/mock/src/faker/index.ts — pass pipeline-built verb options, so it's never undefined here. The client generators already derive their function names from operationName; this change only aligns the MSW side with them. A ?? operationId fallback would guard a state the type system already prevents.

Minor: pascal(undefined) returns '' via its default parameter, so a missing name would yield getMockHandler, not getUndefinedMockHandler.


const overrideBaseUrl =
override.mock && 'baseUrl' in override.mock
Expand All @@ -444,8 +444,12 @@ export function generateMSW(
const mockBaseUrl = mock && isMswMock(mock) ? mock.baseUrl : undefined;
const route = getRouteMSW(pathRoute, overrideBaseUrl ?? mockBaseUrl);

const handlerName = `get${pascal(operationId)}MockHandler`;
const getResponseMockFunctionName = `get${pascal(operationId)}ResponseMock`;
// Derive names from operationName (not operationId): splitByContentType keeps
// one operationId across variants but suffixes operationName (e.g. *WithJson /
// *WithFormData), and the client side already names functions from it. Using
// operationId here would emit duplicate handler names and break tsc. See #3342.
const handlerName = `get${pascal(operationName)}MockHandler`;
const getResponseMockFunctionName = `get${pascal(operationName)}ResponseMock`;
Comment on lines +447 to +452

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the line 438 thread: operationName is a required field on GeneratorVerbOptions and is always populated by buildVerbOption in the pipeline, so no fallback is needed here. This only mirrors how the client generators already name their functions.


const splitMockImplementations: string[] = [];

Expand Down
Loading
Loading