Skip to content

Commit 4d858f7

Browse files
authored
Merge pull request #40 from DigitalCommons/29_link_redux_to_api
29 Link Redux to search UI and add mock config
2 parents 33a7f09 + 1991445 commit 4d858f7

25 files changed

+967
-1178
lines changed

.vscode/extensions.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"recommendations": [
3-
"bradlc.vscode-tailwindcss",
43
"esbenp.prettier-vscode",
54
"editorconfig.editorconfig",
6-
"yoavbls.pretty-ts-errors"
5+
"yoavbls.pretty-ts-errors",
6+
"deinsoftware.vitest-snippets"
77
]
88
}

apps/front-end/src/App.test.tsx

+23-97
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,31 @@
1-
import { screen, waitFor } from "@testing-library/react"
2-
import App from "./App"
3-
import { renderWithProviders } from "./utils/test-utils"
1+
import { screen, fireEvent } from "@testing-library/react";
2+
import App from "./App";
3+
import { renderWithProviders } from "./utils/test-utils";
44

5-
test("App should have correct initial render", () => {
6-
renderWithProviders(<App />)
5+
// TODO: enable this once we work out how to mock viewport width
6+
// test("App should have correct initial render on desktop", () => {
7+
// window.innerWidth = 1200;
8+
// fireEvent(window, new Event("resize"));
79

8-
// The app should be rendered correctly
9-
expect(screen.getByText(/learn/i)).toBeInTheDocument()
10+
// renderWithProviders(<App />);
1011

11-
// Initial state: count should be 0, incrementValue should be 2
12-
expect(screen.getByLabelText("Count")).toHaveTextContent("0")
13-
expect(screen.getByLabelText("Set increment amount")).toHaveValue(2)
14-
})
12+
// expect(screen.getByLabelText("Open panel")).toBeInTheDocument();
13+
// });
1514

16-
test("Increment value and Decrement value should work as expected", async () => {
17-
const { user } = renderWithProviders(<App />)
15+
test("App should have correct initial render on mobile", () => {
16+
renderWithProviders(<App />);
1817

19-
// Click on "+" => Count should be 1
20-
await user.click(screen.getByLabelText("Increment value"))
21-
expect(screen.getByLabelText("Count")).toHaveTextContent("1")
18+
expect(screen.getByText("Search")).toBeInTheDocument();
19+
});
2220

23-
// Click on "-" => Count should be 0
24-
await user.click(screen.getByLabelText("Decrement value"))
25-
expect(screen.getByLabelText("Count")).toHaveTextContent("0")
26-
})
21+
// test("Increment value and Decrement value should work as expected", async () => {
22+
// const { user } = renderWithProviders(<App />)
2723

28-
test("Add Amount should work as expected", async () => {
29-
const { user } = renderWithProviders(<App />)
24+
// // Click on "+" => Count should be 1
25+
// await user.click(screen.getByLabelText("Increment value"))
26+
// expect(screen.getByLabelText("Count")).toHaveTextContent("1")
3027

31-
// "Add Amount" button is clicked => Count should be 2
32-
await user.click(screen.getByText("Add Amount"))
33-
expect(screen.getByLabelText("Count")).toHaveTextContent("2")
34-
35-
const incrementValueInput = screen.getByLabelText("Set increment amount")
36-
// incrementValue is 2, click on "Add Amount" => Count should be 4
37-
await user.clear(incrementValueInput)
38-
await user.type(incrementValueInput, "2")
39-
await user.click(screen.getByText("Add Amount"))
40-
expect(screen.getByLabelText("Count")).toHaveTextContent("4")
41-
42-
// [Negative number] incrementValue is -1, click on "Add Amount" => Count should be 3
43-
await user.clear(incrementValueInput)
44-
await user.type(incrementValueInput, "-1")
45-
await user.click(screen.getByText("Add Amount"))
46-
expect(screen.getByLabelText("Count")).toHaveTextContent("3")
47-
})
48-
49-
it("Add Async should work as expected", async () => {
50-
const { user } = renderWithProviders(<App />)
51-
52-
// "Add Async" button is clicked => Count should be 2
53-
await user.click(screen.getByText("Add Async"))
54-
55-
await waitFor(() =>
56-
expect(screen.getByLabelText("Count")).toHaveTextContent("2"),
57-
)
58-
59-
const incrementValueInput = screen.getByLabelText("Set increment amount")
60-
// incrementValue is 2, click on "Add Async" => Count should be 4
61-
await user.clear(incrementValueInput)
62-
await user.type(incrementValueInput, "2")
63-
64-
await user.click(screen.getByText("Add Async"))
65-
await waitFor(() =>
66-
expect(screen.getByLabelText("Count")).toHaveTextContent("4"),
67-
)
68-
69-
// [Negative number] incrementValue is -1, click on "Add Async" => Count should be 3
70-
await user.clear(incrementValueInput)
71-
await user.type(incrementValueInput, "-1")
72-
await user.click(screen.getByText("Add Async"))
73-
await waitFor(() =>
74-
expect(screen.getByLabelText("Count")).toHaveTextContent("3"),
75-
)
76-
})
77-
78-
test("Add If Odd should work as expected", async () => {
79-
const { user } = renderWithProviders(<App />)
80-
81-
// "Add If Odd" button is clicked => Count should stay 0
82-
await user.click(screen.getByText("Add If Odd"))
83-
expect(screen.getByLabelText("Count")).toHaveTextContent("0")
84-
85-
// Click on "+" => Count should be updated to 1
86-
await user.click(screen.getByLabelText("Increment value"))
87-
expect(screen.getByLabelText("Count")).toHaveTextContent("1")
88-
89-
// "Add If Odd" button is clicked => Count should be updated to 3
90-
await user.click(screen.getByText("Add If Odd"))
91-
expect(screen.getByLabelText("Count")).toHaveTextContent("3")
92-
93-
const incrementValueInput = screen.getByLabelText("Set increment amount")
94-
// incrementValue is 1, click on "Add If Odd" => Count should be updated to 4
95-
await user.clear(incrementValueInput)
96-
await user.type(incrementValueInput, "1")
97-
await user.click(screen.getByText("Add If Odd"))
98-
expect(screen.getByLabelText("Count")).toHaveTextContent("4")
99-
100-
// click on "Add If Odd" => Count should stay 4
101-
await user.clear(incrementValueInput)
102-
await user.type(incrementValueInput, "-1")
103-
await user.click(screen.getByText("Add If Odd"))
104-
expect(screen.getByLabelText("Count")).toHaveTextContent("4")
105-
})
28+
// // Click on "-" => Count should be 0
29+
// await user.click(screen.getByLabelText("Decrement value"))
30+
// expect(screen.getByLabelText("Count")).toHaveTextContent("0")
31+
// })

apps/front-end/src/App.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1+
import { useEffect } from "react";
12
import MapWrapper from "./components/map/MapWrapper";
23
import Panel from "./components/panel/Panel";
34
import logo from "./logo.svg";
5+
import { fetchConfig } from "./app/vocabsSlice";
6+
import { useAppDispatch } from "./app/hooks";
47

58
const App = () => {
9+
const dispatch = useAppDispatch();
10+
11+
useEffect(() => {
12+
dispatch(fetchConfig());
13+
}, []);
14+
615
return (
716
<div>
817
<MapWrapper />

apps/front-end/src/app/store.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { searchSlice } from "../components/panel/searchPanel/searchSlice";
66
import { panelSlice } from "../components/panel/panelSlice";
77
import { mapSlice } from "../components/map/mapSlice";
88
import { popUpSlice } from "../components/popUp/popUpSlice";
9+
import { vocabsSlice } from "./vocabsSlice";
910

1011
// `combineSlices` automatically combines the reducers using
1112
// their `reducerPath`s, therefore we no longer need to call `combineReducers`.
@@ -15,6 +16,7 @@ const rootReducer = combineSlices(
1516
mapSlice,
1617
panelSlice,
1718
popUpSlice,
19+
vocabsSlice,
1820
);
1921

2022
// Infer the `RootState` type from the root reducer

apps/front-end/src/app/vocabsSlice.ts

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { createAction } from "@reduxjs/toolkit";
2+
import { createAppSlice } from "./createAppSlice";
3+
import mockConfig from "../data/mockConfig";
4+
import { Config, getConfig } from "../services";
5+
6+
export interface VocabsSliceState {
7+
vocabs: Config["vocabs"];
8+
language: string;
9+
}
10+
11+
const initialState: VocabsSliceState = {
12+
vocabs: {},
13+
language: "en",
14+
};
15+
16+
export const vocabsSlice = createAppSlice({
17+
name: "vocabs",
18+
initialState,
19+
reducers: (create) => ({
20+
fetchConfig: create.asyncThunk(
21+
async (_, thunkApi) => {
22+
const datasetId =
23+
new URLSearchParams(window.location.search).get("datasetId") ?? "";
24+
if (datasetId === "") {
25+
return thunkApi.rejectWithValue(
26+
`No datasetId parameter given, so no dataset config can be retrieved`,
27+
);
28+
}
29+
30+
thunkApi.dispatch(configLoaded(mockConfig));
31+
return mockConfig;
32+
33+
// const response = await getConfig({
34+
// params: { datasetId: datasetId },
35+
// });
36+
// if (response.status === 200) {
37+
// thunkApi.dispatch(configLoaded(response.body));
38+
// return response.body;
39+
// } else {
40+
// return thunkApi.rejectWithValue(
41+
// `Failed get config, status code ${response.status}`,
42+
// );
43+
// }
44+
},
45+
{
46+
fulfilled: (state, action) => {
47+
console.log("Mock config", action.payload);
48+
state.vocabs = action.payload.vocabs;
49+
50+
if (action.payload.languages.length > 0) {
51+
const urlParamLang = new URLSearchParams(
52+
window.location.search,
53+
).get("lang");
54+
if (
55+
urlParamLang &&
56+
action.payload.languages.includes(urlParamLang.toLowerCase())
57+
) {
58+
state.language = urlParamLang.toLowerCase();
59+
} else {
60+
state.language = action.payload.languages[0];
61+
}
62+
}
63+
},
64+
rejected: (state, action) => {
65+
console.error("Error fetching config", action.payload);
66+
},
67+
},
68+
),
69+
}),
70+
selectors: {
71+
selectVocabs: (vocabs) => vocabs.vocabs,
72+
},
73+
});
74+
75+
export const configLoaded = createAction<Config>("configLoaded");
76+
77+
export const { fetchConfig } = vocabsSlice.actions;
78+
79+
export const { selectVocabs } = vocabsSlice.selectors;

apps/front-end/src/components/common/panelToggleButton/panelToggleButton.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const PanelToggleButton = ({
3232
onClick={buttonAction}
3333
aria-label={isOpen ? "Close panel" : "Open panel"}
3434
id="panel-toggle-button"
35-
title="Toggle Button"
35+
title={isOpen ? "Close panel" : "Open panel"}
3636
>
3737
{isOpen ? <ArrowBackIosNewIcon /> : <ArrowForwardIosIcon />}
3838
</StyledIconButton>

apps/front-end/src/components/map/MapWrapper.tsx

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import { useRef, useEffect, useState } from "react";
22
import { createMap } from "./mapLibre";
33
import { useAppDispatch, useAppSelector } from "../../app/hooks";
4-
import { selectText, selectVisibleIds } from "../panel/searchPanel/searchSlice";
4+
import {
5+
selectText,
6+
selectVisibleIndexes,
7+
} from "../panel/searchPanel/searchSlice";
58
import { Map as MapLibreMap, GeoJSONSource } from "maplibre-gl";
6-
import { fetchData, selectFeatures } from "./mapSlice";
9+
import { fetchLocations, selectFeatures } from "./mapSlice";
710

811
const MapWrapper = () => {
912
const searchText = useAppSelector(selectText);
10-
// If there is no search text, visible IDs is undefined to show all features
11-
const visibleIds = useAppSelector(selectVisibleIds);
13+
// If there is no search text, visible indexes is undefined to show all features
14+
const visibleIndexes = useAppSelector(selectVisibleIndexes);
1215
const features = useAppSelector((state) =>
13-
selectFeatures(state, searchText ? visibleIds : undefined),
16+
selectFeatures(state, searchText ? visibleIndexes : undefined),
1417
);
1518
const [sourceLoaded, setSourceLoaded] = useState(false);
1619
const map = useRef<MapLibreMap | null>(null);
@@ -25,7 +28,7 @@ const MapWrapper = () => {
2528
setSourceLoaded(true);
2629
}
2730
});
28-
dispatch(fetchData());
31+
dispatch(fetchLocations());
2932

3033
// Clean up on unmount
3134
return () => map.current?.remove();
@@ -41,7 +44,7 @@ const MapWrapper = () => {
4144

4245
const updateMapData = async () => {
4346
if (searchText) {
44-
console.log(`Found ${visibleIds?.length} features that matched`);
47+
console.log(`Found ${visibleIndexes?.length} features that matched`);
4548
}
4649

4750
console.log("Rendering data in MapLibreGL", features);

0 commit comments

Comments
 (0)