Skip to content

Commit 4c8176a

Browse files
committed
fix
1 parent 3047d18 commit 4c8176a

9 files changed

Lines changed: 89 additions & 54 deletions

File tree

oxlint.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,19 @@ export default defineConfig({
135135
"unicorn/switch-case-braces": ["error", "avoid"],
136136

137137
// ── eslint-plugin-vitest (built-in) ──
138+
"jest/prefer-importing-jest-globals": "off",
139+
"jest/prefer-ending-with-an-expect": "off",
140+
138141
"vitest/consistent-test-it": ["error", { fn: "it" }],
142+
"vitest/no-hooks": "off",
139143
"vitest/no-importing-vitest-globals": "off",
140144
"vitest/no-standalone-expect": [
141145
"error",
142146
{ additionalTestBlockFunctions: ["it.for"] },
143147
],
144148
"vitest/prefer-called-once": "off",
145149
"vitest/prefer-importing-vitest-globals": "off",
150+
"vitest/prefer-lowercase-title": "off",
146151
"vitest/prefer-to-be-falsy": "off",
147152
"vitest/prefer-to-be-truthy": "off",
148153
"vitest/require-hook": "off",

src/api/apiClient/apiClient.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { HttpResponse, http } from "msw";
22
import { apiClient } from "./apiClient";
33
import { server } from "../../mocks/server";
4+
import { ResponseError } from "../openapi";
45

56
const BASE_URL = "https://jsonplaceholder.typicode.com";
67

@@ -70,7 +71,9 @@ describe("apiClient", () => {
7071
),
7172
);
7273

73-
await expect(apiClient.postsPostIdGet({ postId: 999 })).rejects.toThrow();
74+
await expect(apiClient.postsPostIdGet({ postId: 999 })).rejects.toThrow(
75+
ResponseError,
76+
);
7477
});
7578
});
7679

@@ -119,7 +122,7 @@ describe("apiClient", () => {
119122

120123
await expect(
121124
apiClient.postsPostIdDelete({ postId: 1 }),
122-
).rejects.toThrow();
125+
).rejects.toThrow(ResponseError);
123126
});
124127
});
125128
});

src/api/queries/post.queries.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { QueryClient } from "@tanstack/react-query";
22
import { HttpResponse, http } from "msw";
33
import { getPostsQueryOptions } from "./post.queries";
44
import { server } from "../../mocks/server";
5+
import { ResponseError } from "../openapi";
56

67
const BASE_URL = "https://jsonplaceholder.typicode.com";
78

@@ -60,13 +61,13 @@ describe("getPostsQueryOptions", () => {
6061
it("queryKeyにリクエストパラメータが含まれること", () => {
6162
const options = getPostsQueryOptions({ userId: 1 });
6263

63-
expect(options.queryKey).toEqual(["getPosts", { userId: 1 }]);
64+
expect(options.queryKey).toStrictEqual(["getPosts", { userId: 1 }]);
6465
});
6566

6667
it("パラメータなしのqueryKeyが正しいこと", () => {
6768
const options = getPostsQueryOptions();
6869

69-
expect(options.queryKey).toEqual(["getPosts", {}]);
70+
expect(options.queryKey).toStrictEqual(["getPosts", {}]);
7071
});
7172

7273
it("APIエラー時に例外がスローされること", async () => {
@@ -81,14 +82,14 @@ describe("getPostsQueryOptions", () => {
8182

8283
await expect(
8384
queryClient.fetchQuery(getPostsQueryOptions()),
84-
).rejects.toThrow();
85+
).rejects.toThrow(ResponseError);
8586
});
8687

8788
it("空配列が返された場合に正しく処理されること", async () => {
8889
server.use(http.get(`${BASE_URL}/posts`, () => HttpResponse.json([])));
8990

9091
const result = await queryClient.fetchQuery(getPostsQueryOptions());
9192

92-
expect(result).toEqual([]);
93+
expect(result).toStrictEqual([]);
9394
});
9495
});

src/hooks/useIsComposing/useIsComposing.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,17 @@ describe(useIsComposing, () => {
3737
bubble: useIsComposing(false),
3838
}));
3939

40-
expect(result.current).toEqual({ capture: false, bubble: false });
40+
expect(result.current).toStrictEqual({ capture: false, bubble: false });
4141

4242
act(() => {
4343
dispatchComposition("compositionstart");
4444
});
45-
expect(result.current).toEqual({ capture: true, bubble: true });
45+
expect(result.current).toStrictEqual({ capture: true, bubble: true });
4646

4747
act(() => {
4848
dispatchComposition("compositionend");
4949
});
50-
expect(result.current).toEqual({ capture: false, bubble: false });
50+
expect(result.current).toStrictEqual({ capture: false, bubble: false });
5151
});
5252
});
5353

src/hooks/useLocalStorage/useLocalStorage.test.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,31 @@ describe(useLocalStorage, () => {
5656
expect(mockStorage.setItem).toHaveBeenCalledWith(key, "updated");
5757
});
5858

59-
it("関数型更新をサポートする(prev は string | null", () => {
59+
it("関数型更新 - 初回は prev として null が渡される", () => {
6060
const key = uniqueKey();
6161
const { result } = renderHook(() => useLocalStorage(key));
62+
const updater = vi.fn<(prev: string | null) => string>(() => "a");
6263

6364
act(() => {
64-
result.current[1]((prev) => `${prev ?? ""}a`);
65+
result.current[1](updater);
6566
});
67+
68+
expect(updater).toHaveBeenCalledWith(null);
6669
expect(result.current[0]).toBe("a");
70+
});
71+
72+
it("関数型更新 - 2回目以降は prev として前回値が渡される", () => {
73+
const key = uniqueKey();
74+
const { result } = renderHook(() => useLocalStorage(key));
6775

6876
act(() => {
69-
result.current[1]((prev) => `${prev ?? ""}b`);
77+
result.current[1]("a");
7078
});
79+
80+
act(() => {
81+
result.current[1]((prev) => `${prev}b`);
82+
});
83+
7184
expect(result.current[0]).toBe("ab");
7285
});
7386

src/hooks/useWebStorage/useWebStorage.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,14 @@ describe(useWebStorage, () => {
110110
it("関数型更新はストアの最新値に基づいて解決される", () => {
111111
const store = createMockStore();
112112
const key = uniqueKey();
113+
store.write(key, "");
113114
const { result } = renderHook(() => useWebStorage(store, key));
114115

115116
act(() => {
116-
result.current[1]((prev) => `${prev ?? ""}a`);
117+
result.current[1]((prev) => `${prev}a`);
117118
});
118119
act(() => {
119-
result.current[1]((prev) => `${prev ?? ""}b`);
120+
result.current[1]((prev) => `${prev}b`);
120121
});
121122

122123
expect(result.current[0]).toBe("ab");
@@ -130,7 +131,7 @@ describe(useWebStorage, () => {
130131

131132
act(() => {
132133
store.write(key, "base");
133-
result.current[1]((prev) => `${prev ?? ""}+1`);
134+
result.current[1]((prev) => `${prev}+1`);
134135
});
135136

136137
expect(result.current[0]).toBe("base+1");
@@ -145,7 +146,7 @@ describe(useWebStorage, () => {
145146
rerender();
146147
rerender();
147148

148-
expect(vi.mocked(store.subscribe).mock.calls.length).toBe(initialCalls);
149+
expect(store.subscribe).toHaveBeenCalledTimes(initialCalls);
149150
});
150151

151152
it("アンマウント時に購読が解除される", () => {

src/utils/debounce/debounce.test.ts

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -176,16 +176,16 @@ describe(debounce, () => {
176176

177177
it("leading コールバック内から再帰呼び出ししても leading が二重発火しないこと", () => {
178178
const calls: string[] = [];
179-
const debounced = debounce(
180-
(x: string) => {
181-
calls.push(x);
182-
if (x === "a") {
183-
debounced("re-entry");
184-
}
185-
},
186-
100,
187-
{ edges: ["leading"] },
188-
);
179+
const callback = vi.fn<(x: string) => void>((x) => {
180+
calls.push(x);
181+
});
182+
const debounced = debounce(callback, 100, { edges: ["leading"] });
183+
184+
// 1 回目の invoke だけ再帰呼び出しする
185+
callback.mockImplementationOnce((x) => {
186+
calls.push(x);
187+
debounced("re-entry");
188+
});
189189

190190
debounced("a");
191191
// 再帰呼び出しは isLeadingInvoked ガードにより leading 発火せず
@@ -428,12 +428,15 @@ describe(debounce, () => {
428428
describe("再帰呼び出し", () => {
429429
it("trailing コールバック内から再帰呼び出しした引数が失われないこと", () => {
430430
const calls: string[] = [];
431-
const debounced = debounce((x: string) => {
431+
const callback = vi.fn<(x: string) => void>((x) => {
432432
calls.push(x);
433-
if (x === "a") {
434-
debounced("re-entry");
435-
}
436-
}, 100);
433+
});
434+
const debounced = debounce(callback, 100);
435+
436+
callback.mockImplementationOnce((x) => {
437+
calls.push(x);
438+
debounced("re-entry");
439+
});
437440

438441
debounced("a");
439442
vi.advanceTimersByTime(100);
@@ -446,16 +449,17 @@ describe(debounce, () => {
446449

447450
it("leading+trailing コールバック内から再帰呼び出しした引数が trailing で実行されること", () => {
448451
const calls: string[] = [];
449-
const debounced = debounce(
450-
(x: string) => {
451-
calls.push(x);
452-
if (x === "a") {
453-
debounced("re-entry");
454-
}
455-
},
456-
100,
457-
{ edges: ["leading", "trailing"] },
458-
);
452+
const callback = vi.fn<(x: string) => void>((x) => {
453+
calls.push(x);
454+
});
455+
const debounced = debounce(callback, 100, {
456+
edges: ["leading", "trailing"],
457+
});
458+
459+
callback.mockImplementationOnce((x) => {
460+
calls.push(x);
461+
debounced("re-entry");
462+
});
459463

460464
debounced("a");
461465
expect(calls).toStrictEqual(["a"]);
@@ -1136,20 +1140,25 @@ describe(debounce, () => {
11361140

11371141
it("再帰呼び出し時に保存した this が失われないこと", () => {
11381142
const spy = vi.fn();
1143+
const callback = vi.fn<(this: { value: number }, step: number) => void>(
1144+
function defaultImpl(this: { value: number }, step) {
1145+
spy(this.value, step);
1146+
},
1147+
);
11391148
const obj = {
11401149
value: 1,
1141-
update: debounce(function update(
1142-
this: { value: number },
1143-
step: number,
1144-
) {
1145-
spy(this.value, step);
1146-
if (step === 1) {
1147-
// trailing 実行中に再帰呼び出し
1148-
obj.update(2);
1149-
}
1150-
}, 100),
1150+
update: debounce(callback, 100),
11511151
};
11521152

1153+
// 1 回目の trailing 実行中だけ再帰呼び出しする
1154+
callback.mockImplementationOnce(function recursingImpl(
1155+
this: { value: number },
1156+
step,
1157+
) {
1158+
spy(this.value, step);
1159+
obj.update(2);
1160+
});
1161+
11531162
obj.update(1);
11541163
vi.advanceTimersByTime(100);
11551164
expect(spy).toHaveBeenNthCalledWith(1, 1, 1);

src/utils/isFunction/isFunction.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,15 @@ describe(isFunction, () => {
3030
});
3131
});
3232

33+
// 型述語の narrowing 挙動そのものがテスト対象のため、各ブランチで expect する
3334
it("ユニオン型を関数成分で narrow する", () => {
3435
const union: string | (() => string) = returnX;
36+
// oxlint-disable jest/no-conditional-in-test,jest/no-conditional-expect
3537
if (isFunction(union)) {
3638
expect(union()).toBe("x");
3739
} else {
3840
expect.unreachable();
3941
}
42+
// oxlint-enable jest/no-conditional-in-test,jest/no-conditional-expect
4043
});
4144
});

vitest.config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ export default defineConfig({
2424
},
2525
testTimeout: 10_000,
2626
hookTimeout: 10_000,
27+
restoreMocks: true,
28+
unstubEnvs: true,
29+
unstubGlobals: true,
2730
projects: [
2831
{
2932
extends: true,
@@ -39,9 +42,6 @@ export default defineConfig({
3942
environment: "happy-dom",
4043
globalSetup: "./vitest.globalSetup.ts",
4144
setupFiles: "./vitest.setup.ts",
42-
restoreMocks: true,
43-
unstubEnvs: true,
44-
unstubGlobals: true,
4545
},
4646
},
4747
{

0 commit comments

Comments
 (0)