Skip to content

Commit 143afa0

Browse files
authored
Merge pull request #53 from codeforjapan/revert-29-fix/remove-temporal-pagination
Revert "fix: remove pagination temporal logic"
2 parents d98b12c + 3da3496 commit 143afa0

File tree

3 files changed

+142
-4
lines changed

3 files changed

+142
-4
lines changed

app/feature/search/components/SearchPagination.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Fa6SolidAngleLeft from "~icons/fa6-solid/angle-left";
99
import Fa6SolidAngleRight from "~icons/fa6-solid/angle-right";
1010

1111
import type { PaginationMeta } from "../../../generated/api/schemas/paginationMeta";
12+
import { buildPaginationMeta } from "../pagination";
1213
import type { noteSearchParamSchema } from "../validation";
1314

1415
type PaginationProps = {
@@ -28,16 +29,18 @@ export const SearchPagination = ({
2829
visibleItemCount,
2930
...groupProps
3031
}: PaginationProps) => {
32+
const pagination = buildPaginationMeta(meta, currentQuery);
33+
3134
const pageFirstItemIndex = currentQuery.offset + 1;
3235
const totalDisplayedItems = currentQuery.offset + visibleItemCount;
3336

3437
const prevTo = useMemo(
35-
() => (meta?.prev ? withQuery("/", getQuery(meta.prev)) : null),
36-
[meta?.prev],
38+
() => (pagination?.prev ? withQuery("/", getQuery(pagination.prev)) : null),
39+
[pagination?.prev],
3740
);
3841
const nextTo = useMemo(
39-
() => (meta?.next ? withQuery("/", getQuery(meta.next)) : null),
40-
[meta?.next],
42+
() => (pagination?.next ? withQuery("/", getQuery(pagination.next)) : null),
43+
[pagination?.next],
4144
);
4245

4346
const [clickedButton, setClickedButton] = useState<"prev" | "next">();
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { getQuery } from "ufo";
2+
import { describe, expect, test } from "vitest";
3+
import type { z } from "zod";
4+
5+
import type { PaginationMeta } from "../../generated/api/schemas/paginationMeta";
6+
import { buildPaginationMeta } from "./pagination";
7+
import type { noteSearchParamSchema } from "./validation";
8+
9+
describe("buildPaginationMeta", () => {
10+
test("API 修正前ロジック: 現在のクエリパラメータから PaginationMeta を生成できる", () => {
11+
const currentQuery = {
12+
post_includes_text: "地震",
13+
limit: 10,
14+
offset: 10,
15+
} satisfies z.infer<typeof noteSearchParamSchema>;
16+
17+
// API が limit, offset 以外のクエリパラメータを削除してしまう挙動を再現
18+
const currentBrokenMeta = {
19+
next: "https://example.com/api/v1/data/search?offset=20&limit=10",
20+
prev: "https://example.com/api/v1/data/search?offset=0&limit=10",
21+
} satisfies PaginationMeta;
22+
23+
const paginationMeta = buildPaginationMeta(currentBrokenMeta, currentQuery);
24+
const prevQuery = paginationMeta.prev
25+
? getQuery(paginationMeta.prev)
26+
: null;
27+
const nextQuery = paginationMeta.next
28+
? getQuery(paginationMeta.next)
29+
: null;
30+
31+
expect(prevQuery).toStrictEqual({
32+
limit: "10",
33+
offset: "0",
34+
post_includes_text: "地震",
35+
});
36+
expect(nextQuery).toStrictEqual({
37+
limit: "10",
38+
offset: "20",
39+
post_includes_text: "地震",
40+
});
41+
});
42+
43+
test("API 修正前ロジック: offset が 0 より大きい AND limit より小さい場合も prev を生成できる", () => {
44+
const currentQuery = {
45+
post_includes_text: "地震",
46+
limit: 15,
47+
offset: 10,
48+
} satisfies z.infer<typeof noteSearchParamSchema>;
49+
50+
// API が limit, offset 以外のクエリパラメータを削除してしまう挙動を再現
51+
const currentBrokenMeta = {
52+
next: "https://example.com/api/v1/data/search?offset=25&limit=15",
53+
prev: "https://example.com/api/v1/data/search?offset=0&limit=15",
54+
} satisfies PaginationMeta;
55+
56+
const fixedMeta = buildPaginationMeta(currentBrokenMeta, currentQuery);
57+
const prevQuery = fixedMeta.prev ? getQuery(fixedMeta.prev) : null;
58+
59+
expect(prevQuery).toStrictEqual({
60+
limit: "15",
61+
offset: "0",
62+
post_includes_text: "地震",
63+
});
64+
});
65+
66+
test("API 修正前ロジック: 前のページが存在しない場合に prev が null になる", () => {
67+
const currentQuery = {
68+
post_includes_text: "地震",
69+
limit: 10,
70+
offset: 0,
71+
} satisfies z.infer<typeof noteSearchParamSchema>;
72+
73+
// API が limit, offset 以外のクエリパラメータを削除してしまう挙動を再現
74+
const currentBrokenMeta = {
75+
next: "https://example.com/api/v1/data/search?offset=10&limit=10",
76+
prev: null,
77+
} satisfies PaginationMeta;
78+
79+
const fixedMeta = buildPaginationMeta(currentBrokenMeta, currentQuery);
80+
81+
const prevQuery = fixedMeta.prev ? getQuery(fixedMeta.prev) : null;
82+
83+
expect(prevQuery).toBe(null);
84+
});
85+
});

app/feature/search/pagination.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { parseURL, stringifyParsedURL, withQuery } from "ufo";
2+
import type { z } from "zod";
3+
4+
import type { PaginationMeta } from "../../generated/api/schemas/paginationMeta";
5+
import type { noteSearchParamSchema } from "./validation";
6+
7+
/**
8+
* API 側のページネーション情報が正しい情報を返すようになるまで、現在のパラメータから次のページと前のページの URL を生成する
9+
* @param meta
10+
* API が返したページネーション情報。前後ページが存在するかどうかの確認にのみ使用する
11+
* @param currentQuery
12+
* 現在のクエリパラメータ
13+
* @returns
14+
* 修正後のページネーション情報
15+
*/
16+
// API が正常な PaginationMeta を返すようになったらこの巻数で計算する必要はない
17+
export const buildPaginationMeta = (
18+
meta: PaginationMeta,
19+
currentQuery: z.infer<typeof noteSearchParamSchema>,
20+
): PaginationMeta => {
21+
const { limit, offset, ...rest } = currentQuery;
22+
23+
if (meta.next == null && meta.prev == null) {
24+
return {
25+
next: null,
26+
prev: null,
27+
};
28+
}
29+
30+
const nextOffset = offset + limit;
31+
32+
// offset が 0: 前のページが存在しない
33+
// offset が 10, limit が 15: 前のページが存在する
34+
const prevOffset = Math.max(offset - limit, 0);
35+
36+
const isFirstPage = offset === 0;
37+
38+
// 必ず prev か next が存在する
39+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
40+
const url = parseURL((meta.prev ?? meta.next)!);
41+
url.search = "";
42+
const baseUrl = stringifyParsedURL(url);
43+
44+
return {
45+
next: withQuery(baseUrl, { ...rest, limit, offset: nextOffset }),
46+
prev: isFirstPage
47+
? null
48+
: withQuery(baseUrl, { ...rest, limit, offset: prevOffset }),
49+
};
50+
};

0 commit comments

Comments
 (0)