Skip to content

Commit edcc75a

Browse files
authored
Merge pull request #273 from zigzagdev/feat/pin-map-into-search-result-page
Feat/pin map into search result page
2 parents 08d4c5a + 2a1c2a7 commit edcc75a

17 files changed

Lines changed: 562 additions & 10 deletions

File tree

client/package-lock.json

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
"@emotion/styled": "^11.14.1",
2222
"@mui/icons-material": "7.3.7",
2323
"@mui/material": "^7.3.7",
24+
"leaflet": "^1.9.4",
2425
"react": "^19.1.1",
2526
"react-dom": "^19.1.1",
27+
"react-leaflet": "^5.0.0",
2628
"react-router-dom": "^7.9.5",
2729
"vitest": "^4.0.8"
2830
},
@@ -31,6 +33,7 @@
3133
"@testing-library/jest-dom": "^6.9.1",
3234
"@testing-library/react": "^16.3.2",
3335
"@types/jest": "^29.5.14",
36+
"@types/leaflet": "^1.9.21",
3437
"@types/node": "^24.6.0",
3538
"@types/react": "^19.1.16",
3639
"@types/react-dom": "^19.1.9",
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { StudyRegion } from "../../../domain/types.ts";
2+
3+
export const REGION_CENTRES: Record<StudyRegion, [number, number]> = {
4+
Africa: [8.7832, 34.5085],
5+
Asia: [34.0479, 100.6197],
6+
Europe: [54.526, 15.2551],
7+
"North America": [40.4637, -86.6735],
8+
"South America": [-8.7832, -55.4915],
9+
Oceania: [-25.2744, 133.7751],
10+
};

client/src/app/features/heritages/mappers/__tests__/to-world-heritage-vm.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe("toWorldHeritageVm", () => {
3535
officialName: "Shirakami-Sanchi",
3636
name: "Shirakami-Sanchi",
3737
heritageNameJp: "白神山地",
38-
country: "Japan",
38+
country: "日本",
3939
countryNameJp: "日本",
4040
region: "Asia",
4141
stateParty: "JPN",

client/src/app/features/heritages/mappers/to-world-heritage-vm.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type {
2-
ApiWorldHeritageDto,
3-
WorldHeritageVm,
4-
CriteriaCode,
1+
import {
2+
type ApiWorldHeritageDto,
3+
type WorldHeritageVm,
4+
type CriteriaCode,
55
} from "../../../../domain/types.ts";
66
import { statePartyLabels } from "@features/constants/state-party-labels.ts";
77
import { CRITERIA } from "../../../../domain/types.ts";
@@ -66,7 +66,7 @@ export function toWorldHeritageVm(data: ApiWorldHeritageDto): WorldHeritageVm {
6666
name: data.name,
6767
heritageNameJp: data.heritage_name_jp,
6868

69-
country: data.country,
69+
country: data.country_name_jp,
7070
countryNameJp: data.country_name_jp,
7171
region: data.region,
7272
stateParty,
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { describe, it, expect, beforeEach, jest } from "@jest/globals";
2+
import { createRegionCountApi } from "./region-count-api.ts";
3+
import type { RegionCount } from "../../../../domain/types.ts";
4+
5+
type MockResponse = Pick<Response, "ok" | "status" | "json">;
6+
7+
let fetchSpy: jest.MockedFunction<typeof fetch>;
8+
9+
const API_BASE = "http://localhost:8700";
10+
const ENDPOINT = `${API_BASE}/api/v1/heritages/region-count`;
11+
12+
const makeOkResponse = (body: unknown): MockResponse => ({
13+
ok: true,
14+
status: 200,
15+
json: async () => body,
16+
});
17+
18+
const makeNgResponse = (status: number): MockResponse => ({
19+
ok: false,
20+
status,
21+
json: async () => ({}),
22+
});
23+
24+
const makeRegionCountResponse = (data: RegionCount[]) => ({
25+
status: "success",
26+
data,
27+
});
28+
29+
const MOCK_REGION_COUNTS: RegionCount[] = [
30+
{ region: "Africa", count: 92 },
31+
{ region: "Asia", count: 263 },
32+
{ region: "Europe", count: 542 },
33+
{ region: "North America", count: 87 },
34+
{ region: "South America", count: 103 },
35+
{ region: "Oceania", count: 35 },
36+
];
37+
38+
describe("createRegionCountApi", () => {
39+
let api: ReturnType<typeof createRegionCountApi>;
40+
41+
beforeEach(() => {
42+
fetchSpy = jest.fn() as jest.MockedFunction<typeof fetch>;
43+
api = createRegionCountApi({ apiBase: API_BASE, fetchImpl: fetchSpy });
44+
});
45+
46+
describe("fetchRegionCount", () => {
47+
it("calls the correct endpoint", async () => {
48+
fetchSpy.mockResolvedValue(
49+
makeOkResponse(makeRegionCountResponse(MOCK_REGION_COUNTS)) as Response,
50+
);
51+
52+
await api.fetchRegionCount();
53+
54+
expect(fetchSpy).toHaveBeenCalledWith(
55+
ENDPOINT,
56+
expect.objectContaining({
57+
headers: expect.objectContaining({ Accept: "application/json" }),
58+
}),
59+
);
60+
});
61+
62+
it("returns RegionCount[] when status is success", async () => {
63+
fetchSpy.mockResolvedValue(
64+
makeOkResponse(makeRegionCountResponse(MOCK_REGION_COUNTS)) as Response,
65+
);
66+
67+
const result = await api.fetchRegionCount();
68+
69+
expect(result).toEqual(MOCK_REGION_COUNTS);
70+
});
71+
72+
it("passes signal when provided", async () => {
73+
fetchSpy.mockResolvedValue(
74+
makeOkResponse(makeRegionCountResponse(MOCK_REGION_COUNTS)) as Response,
75+
);
76+
77+
const abortController = new AbortController();
78+
await api.fetchRegionCount({ signal: abortController.signal });
79+
80+
expect(fetchSpy).toHaveBeenCalledWith(
81+
ENDPOINT,
82+
expect.objectContaining({ signal: abortController.signal }),
83+
);
84+
});
85+
86+
it("throws on HTTP error", async () => {
87+
fetchSpy.mockResolvedValue(makeNgResponse(500) as Response);
88+
89+
await expect(api.fetchRegionCount()).rejects.toThrow("HTTP 500");
90+
});
91+
92+
it("throws when API status is not success", async () => {
93+
fetchSpy.mockResolvedValue(makeOkResponse({ status: "error", data: [] }) as Response);
94+
95+
await expect(api.fetchRegionCount()).rejects.toThrow("API status is not success: error");
96+
});
97+
});
98+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { createRegionCountApi } from "./region-count-api";
2+
3+
const apiBase = import.meta.env.VITE_API_BASE_URL;
4+
5+
if (!apiBase) {
6+
throw new Error("VITE_API_BASE_URL is not set");
7+
}
8+
9+
const regionCountApi = createRegionCountApi({ apiBase });
10+
11+
export const fetchRegionCount = regionCountApi.fetchRegionCount;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { RegionCount } from "../../../../domain/types.ts";
2+
3+
export type RegionCountApiDeps = {
4+
apiBase: string;
5+
fetchImpl?: typeof fetch;
6+
};
7+
8+
type RegionCountResponse = {
9+
status: string;
10+
data: RegionCount[];
11+
};
12+
13+
const normalizeApiBase = (apiBase: string): string => {
14+
return apiBase.replace(/\/+$/, "");
15+
};
16+
17+
export const createRegionCountApi = ({ apiBase, fetchImpl = fetch }: RegionCountApiDeps) => {
18+
const normalizedApiBase = normalizeApiBase(apiBase);
19+
const endpoint = `${normalizedApiBase}/api/v1/heritages/region-count`;
20+
21+
return {
22+
async fetchRegionCount(init?: RequestInit): Promise<RegionCount[]> {
23+
const res = await fetchImpl(endpoint, {
24+
...init,
25+
headers: {
26+
Accept: "application/json",
27+
...(init?.headers ?? {}),
28+
},
29+
});
30+
31+
if (!res.ok) {
32+
throw new Error(`HTTP ${res.status}`);
33+
}
34+
35+
const json = (await res.json()) as RegionCountResponse;
36+
if (json.status !== "success") {
37+
throw new Error(`API status is not success: ${json.status}`);
38+
}
39+
40+
return json.data;
41+
},
42+
};
43+
};

0 commit comments

Comments
 (0)