Skip to content

Commit d95a137

Browse files
TakehiroTadaclaude
andcommitted
test: improve branch coverage to 91.62% (above 90% threshold)
Add tests for uncovered branches in generate.mts (axios client, enums, noOperationId, noSchemas, schemaType options), service.mts edge cases (non-exported vars, non-arrow functions, JSDoc, deprecated detection), createImports.mts (axios client), and format.mts (prettier/eslint). Replace @ts-ignore with proper type assertions in createImports tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9751deb commit d95a137

5 files changed

Lines changed: 305 additions & 7 deletions

File tree

tests/__snapshots__/generate.test.ts.snap

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,3 +612,100 @@ export const useFindPaginatedPetsSuspense = <
612612
});
613613
"
614614
`;
615+
616+
exports[`generate - axios client with enums, noOperationId, schemaType > queries.ts 1`] = `
617+
"// generated with @7nohe/openapi-react-query-codegen@1.0.0
618+
619+
import { useMutation, UseMutationOptions, useQuery, UseQueryOptions } from "@tanstack/react-query";
620+
import { AxiosError } from "axios";
621+
import { type Options } from "../requests/client";
622+
import { deletePetsById, getNotDefined, getPaginatedPets, getPets, getPetsById, postNotDefined, postPets } from "../requests/sdk.gen";
623+
import { DeletePetsByIdData, DeletePetsByIdError, GetNotDefinedData, GetPaginatedPetsData, GetPetsByIdData, GetPetsByIdError, GetPetsData, GetPetsError, PostNotDefinedData, PostPetsData, PostPetsError } from "../requests/types.gen";
624+
import * as Common from "./common";
625+
/**
626+
* Returns all pets from the system that the user has access to
627+
* Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia.
628+
*
629+
* Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien.
630+
*
631+
*/
632+
export const useGetPets = <TData = Common.GetPetsDefaultResponse, TError = AxiosError<GetPetsError>, TQueryKey extends Array<unknown> = unknown[]>(clientOptions: Options<GetPetsData, true> = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseGetPetsKeyFn(clientOptions, queryKey), queryFn: () => getPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
633+
/**
634+
* This path is not fully defined.
635+
*
636+
* @deprecated
637+
*/
638+
export const useGetNotDefined = <TData = Common.GetNotDefinedDefaultResponse, TError = AxiosError<unknown>, TQueryKey extends Array<unknown> = unknown[]>(clientOptions: Options<GetNotDefinedData, true> = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
639+
/**
640+
* Returns a user based on a single ID, if the user does not have access to the pet
641+
*/
642+
export const useGetPetsById = <TData = Common.GetPetsByIdDefaultResponse, TError = AxiosError<GetPetsByIdError>, TQueryKey extends Array<unknown> = unknown[]>(clientOptions: Options<GetPetsByIdData, true>, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseGetPetsByIdKeyFn(clientOptions, queryKey), queryFn: () => getPetsById({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
643+
/**
644+
* Returns paginated pets from the system that the user has access to
645+
*
646+
*/
647+
export const useGetPaginatedPets = <TData = Common.GetPaginatedPetsDefaultResponse, TError = AxiosError<unknown>, TQueryKey extends Array<unknown> = unknown[]>(clientOptions: Options<GetPaginatedPetsData, true> = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseGetPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => getPaginatedPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
648+
/**
649+
* Creates a new pet in the store. Duplicates are allowed
650+
*/
651+
export const usePostPets = <TData = Common.PostPetsMutationResult, TError = AxiosError<PostPetsError>, TQueryKey extends Array<unknown> = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit<UseMutationOptions<TData, TError, Options<PostPetsData, true>, TContext>, "mutationKey" | "mutationFn">) => useMutation<TData, TError, Options<PostPetsData, true>, TContext>({ mutationKey: Common.UsePostPetsKeyFn(mutationKey), mutationFn: clientOptions => postPets(clientOptions) as unknown as Promise<TData>, ...options });
652+
/**
653+
* This path is not defined at all.
654+
*
655+
* @deprecated
656+
*/
657+
export const usePostNotDefined = <TData = Common.PostNotDefinedMutationResult, TError = AxiosError<unknown>, TQueryKey extends Array<unknown> = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit<UseMutationOptions<TData, TError, Options<PostNotDefinedData, true>, TContext>, "mutationKey" | "mutationFn">) => useMutation<TData, TError, Options<PostNotDefinedData, true>, TContext>({ mutationKey: Common.UsePostNotDefinedKeyFn(mutationKey), mutationFn: clientOptions => postNotDefined(clientOptions) as unknown as Promise<TData>, ...options });
658+
/**
659+
* deletes a single pet based on the ID supplied
660+
*/
661+
export const useDeletePetsById = <TData = Common.DeletePetsByIdMutationResult, TError = AxiosError<DeletePetsByIdError>, TQueryKey extends Array<unknown> = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit<UseMutationOptions<TData, TError, Options<DeletePetsByIdData, true>, TContext>, "mutationKey" | "mutationFn">) => useMutation<TData, TError, Options<DeletePetsByIdData, true>, TContext>({ mutationKey: Common.UseDeletePetsByIdKeyFn(mutationKey), mutationFn: clientOptions => deletePetsById(clientOptions) as unknown as Promise<TData>, ...options });
662+
"
663+
`;
664+
665+
exports[`generate - noSchemas option > queries.ts 1`] = `
666+
"// generated with @7nohe/openapi-react-query-codegen@1.0.0
667+
668+
import { useMutation, UseMutationOptions, useQuery, UseQueryOptions } from "@tanstack/react-query";
669+
import { type Options } from "../requests/client";
670+
import { addPet, deletePet, findPaginatedPets, findPetById, findPets, getNotDefined, postNotDefined } from "../requests/sdk.gen";
671+
import { AddPetData, AddPetError, DeletePetData, DeletePetError, FindPaginatedPetsData, FindPetByIdData, FindPetByIdError, FindPetsData, FindPetsError, GetNotDefinedData, PostNotDefinedData } from "../requests/types.gen";
672+
import * as Common from "./common";
673+
/**
674+
* Returns all pets from the system that the user has access to
675+
* Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia.
676+
*
677+
* Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien.
678+
*
679+
*/
680+
export const useFindPets = <TData = Common.FindPetsDefaultResponse, TError = FindPetsError, TQueryKey extends Array<unknown> = unknown[]>(clientOptions: Options<FindPetsData, true> = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => findPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
681+
/**
682+
* This path is not fully defined.
683+
*
684+
* @deprecated
685+
*/
686+
export const useGetNotDefined = <TData = Common.GetNotDefinedDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>(clientOptions: Options<GetNotDefinedData, true> = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
687+
/**
688+
* Returns a user based on a single ID, if the user does not have access to the pet
689+
*/
690+
export const useFindPetById = <TData = Common.FindPetByIdDefaultResponse, TError = FindPetByIdError, TQueryKey extends Array<unknown> = unknown[]>(clientOptions: Options<FindPetByIdData, true>, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
691+
/**
692+
* Returns paginated pets from the system that the user has access to
693+
*
694+
*/
695+
export const useFindPaginatedPets = <TData = Common.FindPaginatedPetsDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>(clientOptions: Options<FindPaginatedPetsData, true> = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => findPaginatedPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
696+
/**
697+
* Creates a new pet in the store. Duplicates are allowed
698+
*/
699+
export const useAddPet = <TData = Common.AddPetMutationResult, TError = AddPetError, TQueryKey extends Array<unknown> = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit<UseMutationOptions<TData, TError, Options<AddPetData, true>, TContext>, "mutationKey" | "mutationFn">) => useMutation<TData, TError, Options<AddPetData, true>, TContext>({ mutationKey: Common.UseAddPetKeyFn(mutationKey), mutationFn: clientOptions => addPet(clientOptions) as unknown as Promise<TData>, ...options });
700+
/**
701+
* This path is not defined at all.
702+
*
703+
* @deprecated
704+
*/
705+
export const usePostNotDefined = <TData = Common.PostNotDefinedMutationResult, TError = unknown, TQueryKey extends Array<unknown> = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit<UseMutationOptions<TData, TError, Options<PostNotDefinedData, true>, TContext>, "mutationKey" | "mutationFn">) => useMutation<TData, TError, Options<PostNotDefinedData, true>, TContext>({ mutationKey: Common.UsePostNotDefinedKeyFn(mutationKey), mutationFn: clientOptions => postNotDefined(clientOptions) as unknown as Promise<TData>, ...options });
706+
/**
707+
* deletes a single pet based on the ID supplied
708+
*/
709+
export const useDeletePet = <TData = Common.DeletePetMutationResult, TError = DeletePetError, TQueryKey extends Array<unknown> = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit<UseMutationOptions<TData, TError, Options<DeletePetData, true>, TContext>, "mutationKey" | "mutationFn">) => useMutation<TData, TError, Options<DeletePetData, true>, TContext>({ mutationKey: Common.UseDeletePetKeyFn(mutationKey), mutationFn: clientOptions => deletePet(clientOptions) as unknown as Promise<TData>, ...options });
710+
"
711+
`;

tests/createImports.test.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import path from "node:path";
22
import { Project } from "ts-morph";
3-
import { describe, expect, test } from "vitest";
3+
import type ts from "typescript";
4+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
45
import { createImports } from "../src/createImports.mts";
56
import { cleanOutputs, generateTSClients, outputPath } from "./utils";
67

78
const fileName = "createImports";
89

910
describe(fileName, () => {
11+
beforeAll(async () => await generateTSClients(fileName));
12+
afterAll(async () => await cleanOutputs(fileName));
13+
1014
test("createImports", async () => {
11-
await generateTSClients(fileName);
1215
const project = new Project({
1316
skipAddingFilesFromTsConfig: true,
1417
});
@@ -17,15 +20,31 @@ describe(fileName, () => {
1720
project,
1821
});
1922

20-
// @ts-ignore
21-
const moduleNames = imports.map((i) => i.moduleSpecifier.text);
23+
const moduleNames = imports.map(
24+
(i) => (i.moduleSpecifier as ts.StringLiteral).text,
25+
);
2226
expect(moduleNames).toStrictEqual([
2327
"../requests/client",
2428
"@tanstack/react-query",
2529
"../requests/sdk.gen",
2630
"../requests/types.gen",
2731
]);
28-
await cleanOutputs(fileName);
32+
});
33+
34+
test("createImports with axios client", async () => {
35+
const project = new Project({
36+
skipAddingFilesFromTsConfig: true,
37+
});
38+
project.addSourceFilesAtPaths(path.join(outputPath(fileName), "**", "*"));
39+
const imports = createImports({
40+
project,
41+
client: "@hey-api/client-axios",
42+
});
43+
44+
const moduleNames = imports.map(
45+
(i) => (i.moduleSpecifier as ts.StringLiteral).text,
46+
);
47+
expect(moduleNames).toContain("axios");
2948
});
3049

3150
// Skip: no-models.yaml causes upstream @hey-api/openapi-ts error
@@ -40,8 +59,9 @@ describe(fileName, () => {
4059
project,
4160
});
4261

43-
// @ts-ignore
44-
const moduleNames = imports.map((i) => i.moduleSpecifier.text);
62+
const moduleNames = imports.map(
63+
(i) => (i.moduleSpecifier as ts.StringLiteral).text,
64+
);
4565
expect(moduleNames).toStrictEqual([
4666
"../requests/client",
4767
"@tanstack/react-query",

tests/format.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { beforeEach, describe, expect, test, vi } from "vitest";
2+
import { processOutput } from "../src/format.mjs";
3+
4+
vi.mock("cross-spawn", () => ({
5+
sync: vi.fn(),
6+
}));
7+
8+
describe("processOutput", () => {
9+
beforeEach(() => {
10+
vi.clearAllMocks();
11+
});
12+
13+
test("runs prettier formatter", async () => {
14+
const { sync } = await import("cross-spawn");
15+
await processOutput({ output: "/tmp/test", format: "prettier" });
16+
expect(sync).toHaveBeenCalledWith("prettier", [
17+
"--ignore-unknown",
18+
"/tmp/test",
19+
"--write",
20+
"--ignore-path",
21+
"./.prettierignore",
22+
]);
23+
});
24+
25+
test("runs eslint linter", async () => {
26+
const { sync } = await import("cross-spawn");
27+
await processOutput({ output: "/tmp/test", lint: "eslint" });
28+
expect(sync).toHaveBeenCalledWith("eslint", ["/tmp/test", "--fix"]);
29+
});
30+
31+
test("runs biome formatter", async () => {
32+
const { sync } = await import("cross-spawn");
33+
await processOutput({ output: "/tmp/test", format: "biome" });
34+
expect(sync).toHaveBeenCalledWith("biome", [
35+
"format",
36+
"--write",
37+
"/tmp/test",
38+
]);
39+
});
40+
41+
test("runs biome linter", async () => {
42+
const { sync } = await import("cross-spawn");
43+
await processOutput({ output: "/tmp/test", lint: "biome" });
44+
expect(sync).toHaveBeenCalledWith("biome", [
45+
"lint",
46+
"--write",
47+
"/tmp/test",
48+
]);
49+
});
50+
51+
test("does nothing without format or lint", async () => {
52+
const { sync } = await import("cross-spawn");
53+
await processOutput({ output: "/tmp/test" });
54+
expect(sync).not.toHaveBeenCalled();
55+
});
56+
});

tests/generate.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,71 @@ describe("generate", () => {
6464
expect(readOutput("ensureQueryData.ts")).toMatchSnapshot();
6565
});
6666
});
67+
68+
describe("generate - axios client with enums, noOperationId, schemaType", () => {
69+
const outputDir = "outputs-generate-axios";
70+
const readAxiosOutput = (fileName: string) => {
71+
return readFileSync(
72+
path.join(__dirname, outputDir, "queries", fileName),
73+
"utf-8",
74+
);
75+
};
76+
77+
beforeAll(async () => {
78+
const options: LimitedUserConfig = {
79+
input: path.join(__dirname, "inputs", "petstore.yaml"),
80+
output: path.join("tests", outputDir),
81+
client: "@hey-api/client-axios",
82+
enums: "javascript",
83+
noOperationId: true,
84+
schemaType: "json",
85+
pageParam: "page",
86+
nextPageParam: "meta.next",
87+
initialPageParam: "initial",
88+
};
89+
await generate(options, "1.0.0");
90+
});
91+
92+
afterAll(async () => {
93+
if (existsSync(path.join(__dirname, outputDir))) {
94+
await rm(path.join(__dirname, outputDir), { recursive: true });
95+
}
96+
});
97+
98+
test("queries.ts", () => {
99+
expect(readAxiosOutput("queries.ts")).toMatchSnapshot();
100+
});
101+
});
102+
103+
describe("generate - noSchemas option", () => {
104+
const outputDir = "outputs-generate-noschemas";
105+
const readNoSchemasOutput = (fileName: string) => {
106+
return readFileSync(
107+
path.join(__dirname, outputDir, "queries", fileName),
108+
"utf-8",
109+
);
110+
};
111+
112+
beforeAll(async () => {
113+
const options: LimitedUserConfig = {
114+
input: path.join(__dirname, "inputs", "petstore.yaml"),
115+
output: path.join("tests", outputDir),
116+
client: "@hey-api/client-fetch",
117+
noSchemas: true,
118+
pageParam: "page",
119+
nextPageParam: "meta.next",
120+
initialPageParam: "initial",
121+
};
122+
await generate(options, "1.0.0");
123+
});
124+
125+
afterAll(async () => {
126+
if (existsSync(path.join(__dirname, outputDir))) {
127+
await rm(path.join(__dirname, outputDir), { recursive: true });
128+
}
129+
});
130+
131+
test("queries.ts", () => {
132+
expect(readNoSchemasOutput("queries.ts")).toMatchSnapshot();
133+
});
134+
});

0 commit comments

Comments
 (0)