diff --git a/features/search/FilterDialog.test.tsx b/features/search/FilterDialog.test.tsx index a6d65a40c9..99c823873d 100644 --- a/features/search/FilterDialog.test.tsx +++ b/features/search/FilterDialog.test.tsx @@ -1,16 +1,6 @@ import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { hostingStatusLabels } from "features/profile/constants"; -import { - APPLY_FILTER, - HOSTING_STATUS, - LAST_ACTIVE, - LAST_WEEK, - LOCATION, - MUST_HAVE_LOCATION, - NUM_GUESTS, - PROFILE_KEYWORDS, -} from "features/search/constants"; import useRouteWithSearchFilters from "features/search/useRouteWithSearchFilters"; import mockRouter from "next-router-mock"; import { HostingStatus } from "proto/api_pb"; @@ -48,19 +38,29 @@ describe("FilterDialog", () => { wrapper, }); - const locationInput = screen.getByLabelText(LOCATION); + const locationInput = screen.getByLabelText( + t("search:form.location_field_label") + ); userEvent.type(locationInput, "tes{enter}"); const locationItem = await screen.findByText("test city, test country"); userEvent.click(locationItem); - const keywordsInput = screen.getByLabelText(PROFILE_KEYWORDS); + const keywordsInput = screen.getByLabelText( + t("search:form.keywords.field_label") + ); userEvent.type(keywordsInput, "keyword1"); - const lastActiveInput = screen.getByLabelText(LAST_ACTIVE); + const lastActiveInput = screen.getByLabelText( + t("search:form.host_filters.last_active_field_label") + ); userEvent.click(lastActiveInput); - userEvent.click(screen.getByText(LAST_WEEK)); + userEvent.click( + screen.getByText(t("search:last_active_options.last_week")) + ); - const hostStatusInput = screen.getByLabelText(HOSTING_STATUS); + const hostStatusInput = screen.getByLabelText( + t("search:form.host_filters.hosting_status_field_label") + ); userEvent.click(hostStatusInput); userEvent.click( screen.getByText( @@ -74,7 +74,9 @@ describe("FilterDialog", () => { ) ); - const numGuestsInput = screen.getByLabelText(NUM_GUESTS); + const numGuestsInput = screen.getByLabelText( + t("search:form.accommodation_filters.guests_field_label") + ); userEvent.type(numGuestsInput, "3"); const expectedFilters = { @@ -87,7 +89,11 @@ describe("FilterDialog", () => { numGuests: 3, }; - userEvent.click(screen.getByRole("button", { name: APPLY_FILTER })); + userEvent.click( + screen.getByRole("button", { + name: t("search:form.submit_button_label"), + }) + ); await waitFor(() => { expect(parsedQueryToSearchFilters(mockRouter.query)).toMatchObject( @@ -106,23 +112,27 @@ describe("FilterDialog", () => { wrapper, }); - const locationInput = screen.getByLabelText(LOCATION) as HTMLInputElement; + const locationInput = screen.getByLabelText( + t("search:form.location_field_label") + ) as HTMLInputElement; const keywordInput = screen.getByLabelText( - PROFILE_KEYWORDS + t("search:form.keywords.field_label") ) as HTMLInputElement; const lastActiveInput = screen.getByLabelText( - LAST_ACTIVE + t("search:form.host_filters.last_active_field_label") ) as HTMLInputElement; const hostStatusInput = screen.getByLabelText( - HOSTING_STATUS + t("search:form.host_filters.hosting_status_field_label") ) as HTMLInputElement; const numGuestsInput = screen.getByLabelText( - NUM_GUESTS + t("search:form.accommodation_filters.guests_field_label") ) as HTMLInputElement; expect(locationInput).toHaveValue("test location"); expect(keywordInput).toHaveValue("keyword1"); - expect(lastActiveInput).toHaveValue(LAST_WEEK); + expect(lastActiveInput).toHaveValue( + t("search:last_active_options.last_week") + ); expect( screen.getByRole("button", { name: hostingStatusLabels(t)[HostingStatus.HOSTING_STATUS_CAN_HOST], @@ -164,11 +174,17 @@ describe("FilterDialog", () => { render(, { wrapper, }); - const lastActiveInput = screen.getByLabelText(LAST_ACTIVE); + const lastActiveInput = screen.getByLabelText( + t("search:form.host_filters.last_active_field_label") + ); userEvent.click(lastActiveInput); - userEvent.click(screen.getByText(LAST_WEEK)); + userEvent.click( + screen.getByText(t("search:last_active_options.last_week")) + ); - const hostStatusInput = screen.getByLabelText(HOSTING_STATUS); + const hostStatusInput = screen.getByLabelText( + t("search:form.host_filters.hosting_status_field_label") + ); userEvent.click(hostStatusInput); userEvent.click( screen.getByText( @@ -182,12 +198,20 @@ describe("FilterDialog", () => { ) ); - const numGuestsInput = screen.getByLabelText(NUM_GUESTS); + const numGuestsInput = screen.getByLabelText( + t("search:form.accommodation_filters.guests_field_label") + ); userEvent.type(numGuestsInput, "3"); - userEvent.click(screen.getByRole("button", { name: APPLY_FILTER })); + userEvent.click( + screen.getByRole("button", { + name: t("search:form.submit_button_label"), + }) + ); await waitFor(() => { - const errors = screen.getAllByText(MUST_HAVE_LOCATION); + const errors = screen.getAllByText( + t("search:form.missing_location_validation_error") + ); expect(errors).toHaveLength(3); expect(parsedQueryToSearchFilters(mockRouter.query)).toMatchObject({}); }); diff --git a/features/search/FilterDialog.tsx b/features/search/FilterDialog.tsx index 1c92643c3f..539b9d5233 100644 --- a/features/search/FilterDialog.tsx +++ b/features/search/FilterDialog.tsx @@ -31,21 +31,7 @@ import { useQueryClient } from "react-query"; import { GeocodeResult } from "utils/hooks"; import SearchFilters from "utils/searchFilters"; -import { - ACCOMODATION_FILTERS, - APPLY_FILTER, - CLEAR_SEARCH, - FILTER_DIALOG_TITLE_DESKTOP, - FILTER_DIALOG_TITLE_MOBILE, - HOST_FILTERS, - HOSTING_STATUS, - LAST_ACTIVE, - lastActiveOptions, - LOCATION, - MUST_HAVE_LOCATION, - NUM_GUESTS, - PROFILE_KEYWORDS, -} from "./constants"; +import { getLastActiveOptions } from "./constants"; const hostingStatusOptions = [ HostingStatus.HOSTING_STATUS_CAN_HOST, @@ -64,7 +50,7 @@ const useStyles = makeStyles((theme) => ({ interface FilterDialogFormData extends Omit { location: GeocodeResult | ""; - lastActive: typeof lastActiveOptions[number]; + lastActive: ReturnType[number]; } export default function FilterDialog({ @@ -135,7 +121,7 @@ export default function FilterDialog({ ) return true; return getValues("location") === "" || !getValues("location") - ? MUST_HAVE_LOCATION + ? t("search:form.missing_location_validation_error") : true; }; @@ -143,6 +129,8 @@ export default function FilterDialog({ theme.breakpoints.down("sm") ); + const lastActiveOptions = getLastActiveOptions(t); + return ( - {isSmDown ? FILTER_DIALOG_TITLE_MOBILE : FILTER_DIALOG_TITLE_DESKTOP} + {isSmDown + ? t("search:filter_dialog.mobile_title") + : t("search:filter_dialog.desktop_title")}
@@ -170,7 +160,7 @@ export default function FilterDialog({ } : "" } - label={LOCATION} + label={t("search:form.location_field_label")} fieldError={errors.location?.message} disableRegions /> @@ -178,7 +168,7 @@ export default function FilterDialog({ fullWidth defaultValue={searchFilters.active.query ?? ""} id="keywords-filter" - label={PROFILE_KEYWORDS} + label={t("search:form.keywords.field_label")} name="query" inputRef={register} variant="standard" @@ -186,7 +176,9 @@ export default function FilterDialog({ endAdornment: ( { setValue("query", ""); }} @@ -202,7 +194,9 @@ export default function FilterDialog({ - {HOST_FILTERS} + + {t("search:form.host_filters.title")} + ( o.label} onChange={(_e, option) => onChange(option)} @@ -235,7 +231,9 @@ export default function FilterDialog({ render={({ onChange, value }) => ( id="host-status-filter" - label={HOSTING_STATUS} + label={t( + "search:form.host_filters.hosting_status_field_label" + )} options={hostingStatusOptions} onChange={(_e, options) => { onChange(options); @@ -255,7 +253,9 @@ export default function FilterDialog({ /> - {ACCOMODATION_FILTERS} + + {t("search:form.accommodation_filters.title")} + - +
diff --git a/features/search/SearchBox.test.tsx b/features/search/SearchBox.test.tsx index 9bffa6651e..93a6ec40e3 100644 --- a/features/search/SearchBox.test.tsx +++ b/features/search/SearchBox.test.tsx @@ -1,19 +1,11 @@ import { render, screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { - APPLY_FILTER, - CLEAR_SEARCH, - FILTER_DIALOG_TITLE_DESKTOP, - LOCATION, - PROFILE_KEYWORDS, - SEARCH_BY_KEYWORD, - SEARCH_BY_LOCATION, -} from "features/search/constants"; import useRouteWithSearchFilters from "features/search/useRouteWithSearchFilters"; import mockRouter from "next-router-mock"; import { useEffect } from "react"; import wrapper from "test/hookWrapper"; import { server } from "test/restMock"; +import { t } from "test/utils"; import SearchFilters from "utils/searchFilters"; import SearchBox from "./SearchBox"; @@ -38,8 +30,10 @@ describe("SearchBox", () => { it("performs a keyword search", async () => { const setActive = jest.fn(); render(, { wrapper }); - userEvent.click(screen.getByLabelText(SEARCH_BY_KEYWORD)); - const input = screen.getByLabelText(PROFILE_KEYWORDS); + userEvent.click( + screen.getByLabelText(t("search:form.by_keyword_filter_label")) + ); + const input = screen.getByLabelText(t("search:form.keywords.field_label")); userEvent.type(input, "test search"); await waitFor(() => { expect(setActive).toBeCalledWith({ query: "test search" }); @@ -56,7 +50,9 @@ describe("SearchBox", () => { it("performs a location search", async () => { const setActive = jest.fn(); render(, { wrapper }); - const input = screen.getByLabelText(LOCATION); + const input = screen.getByLabelText( + t("search:form.location_field_label") + ); userEvent.type(input, "tes{enter}"); userEvent.click(await screen.findByText("test city, test country")); await waitFor(() => { @@ -74,8 +70,10 @@ describe("SearchBox", () => { render(, { wrapper, }); - expect(screen.getByLabelText(SEARCH_BY_KEYWORD)).toBeChecked(); - const input = screen.getByLabelText(PROFILE_KEYWORDS); + expect( + screen.getByLabelText(t("search:form.by_keyword_filter_label")) + ).toBeChecked(); + const input = screen.getByLabelText(t("search:form.keywords.field_label")); expect(input).toHaveValue("default value"); }); @@ -85,8 +83,10 @@ describe("SearchBox", () => { wrapper, }); //not easy to also test lat/lng, just test location text - expect(screen.getByLabelText(SEARCH_BY_LOCATION)).toBeChecked(); - const input = screen.getByLabelText(LOCATION); + expect( + screen.getByLabelText(t("search:form.by_location_filter_label")) + ).toBeChecked(); + const input = screen.getByLabelText(t("search:form.location_field_label")); expect(input).toHaveValue("default value"); }); @@ -96,9 +96,13 @@ describe("SearchBox", () => { render(, { wrapper, }); - const input = screen.getByLabelText(PROFILE_KEYWORDS); + const input = screen.getByLabelText(t("search:form.keywords.field_label")); expect(input).toHaveValue("default value"); - userEvent.click(screen.getByRole("button", { name: CLEAR_SEARCH })); + userEvent.click( + screen.getByRole("button", { + name: t("search:form.keywords.clear_field_action_a11y_label"), + }) + ); await waitFor(() => { expect(input).toHaveValue(""); expect(setActive).toBeCalledWith({}); @@ -113,7 +117,7 @@ describe("SearchBox", () => { render(, { wrapper, }); - const input = screen.getByLabelText(LOCATION); + const input = screen.getByLabelText(t("search:form.location_field_label")); expect(input).toHaveValue("default location"); //button role doesn't seem to work, despite it being there userEvent.click(await screen.findByTitle("Clear")); @@ -126,25 +130,33 @@ describe("SearchBox", () => { it("opens and closes the filter dialog, with changes applied to search box", async () => { const setActive = jest.fn(); render(, { wrapper }); - userEvent.click(screen.getByLabelText(SEARCH_BY_KEYWORD)); - const input = screen.getByLabelText(PROFILE_KEYWORDS); + userEvent.click( + screen.getByLabelText(t("search:form.by_keyword_filter_label")) + ); + const input = screen.getByLabelText(t("search:form.keywords.field_label")); userEvent.type(input, "test search"); await waitFor(() => { expect(setActive).toBeCalledWith({ query: "test search" }); }); userEvent.click( - screen.getByRole("button", { name: FILTER_DIALOG_TITLE_DESKTOP }) + screen.getByRole("button", { + name: t("search:filter_dialog.desktop_title"), + }) ); const dialog = screen.getByRole("dialog", { - name: FILTER_DIALOG_TITLE_DESKTOP, + name: t("search:filter_dialog.desktop_title"), }); expect(dialog).toBeVisible(); - const dialogKeywordsField = within(dialog).getByLabelText(PROFILE_KEYWORDS); + const dialogKeywordsField = within(dialog).getByLabelText( + t("search:form.keywords.field_label") + ); expect(dialogKeywordsField).toHaveValue("test search"); userEvent.clear(dialogKeywordsField); userEvent.type(dialogKeywordsField, "new search"); - userEvent.click(screen.getByRole("button", { name: APPLY_FILTER })); + userEvent.click( + screen.getByRole("button", { name: t("search:form.submit_button_label") }) + ); await waitFor( () => { expect(dialog).not.toBeVisible(); diff --git a/features/search/SearchBox.tsx b/features/search/SearchBox.tsx index da204fbf5f..0c13552d78 100644 --- a/features/search/SearchBox.tsx +++ b/features/search/SearchBox.tsx @@ -18,6 +18,8 @@ import TextField from "components/TextField"; import { searchQueryKey } from "features/queryKeys"; import FilterDialog from "features/search/FilterDialog"; import useRouteWithSearchFilters from "features/search/useRouteWithSearchFilters"; +import { useTranslation } from "i18n"; +import { GLOBAL, SEARCH } from "i18n/namespaces"; import { LngLat } from "maplibre-gl"; import { ChangeEvent, useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; @@ -25,16 +27,6 @@ import { useQueryClient } from "react-query"; import { GeocodeResult } from "utils/hooks"; import makeStyles from "utils/makeStyles"; -import { - CLEAR_SEARCH, - FILTER_DIALOG_TITLE_DESKTOP, - FILTER_DIALOG_TITLE_MOBILE, - LOCATION, - PROFILE_KEYWORDS, - SEARCH_BY_KEYWORD, - SEARCH_BY_LOCATION, -} from "./constants"; - const useStyles = makeStyles((theme) => ({ filterDialogButtonDesktop: { marginInlineStart: "auto", @@ -57,6 +49,7 @@ export default function SearchBox({ className?: string; searchFilters: ReturnType; }) { + const { t } = useTranslation([GLOBAL, SEARCH]); const classes = useStyles(); const [isFiltersOpen, setIsFiltersOpen] = useState(false); const [searchType, setSearchType] = useState<"location" | "keyword">(() => @@ -136,7 +129,7 @@ export default function SearchBox({ variant="contained" size="medium" > - {FILTER_DIALOG_TITLE_MOBILE} + {t("search:filter_dialog.mobile_title")} {filterDialog} @@ -162,7 +155,7 @@ export default function SearchBox({ } : "" } - label={LOCATION} + label={t("search:form.location_field_label")} onChange={handleNewLocation} fieldError={errors.location?.message} disableRegions @@ -177,7 +170,7 @@ export default function SearchBox({ fullWidth id="query" value={value} - label={PROFILE_KEYWORDS} + label={t("search:form.keywords.field_label")} variant="standard" helperText=" " onChange={(event) => { @@ -188,7 +181,9 @@ export default function SearchBox({ endAdornment: ( { setValue("query", ""); handleKeywordsChange(""); @@ -217,14 +212,18 @@ export default function SearchBox({ value="location" control={} label={ - {SEARCH_BY_LOCATION} + + {t("search:form.by_location_filter_label")} + } /> } label={ - {SEARCH_BY_KEYWORD} + + {t("search:form.by_keyword_filter_label")} + } /> @@ -236,7 +235,7 @@ export default function SearchBox({ variant="outlined" size="small" > - {FILTER_DIALOG_TITLE_DESKTOP} + {t("search:filter_dialog.desktop_title")} {filterDialog} diff --git a/features/search/SearchResult.tsx b/features/search/SearchResult.tsx index 2fa10b0dfe..d5ce2b47c1 100644 --- a/features/search/SearchResult.tsx +++ b/features/search/SearchResult.tsx @@ -11,7 +11,7 @@ import { AgeGenderLanguagesLabels, ReferencesLastActiveLabels, } from "features/profile/view/userLabels"; -import { aboutText, getShowUserOnMap } from "features/search/constants"; +import { aboutText } from "features/search/constants"; import { useTranslation } from "i18n"; import { GLOBAL, SEARCH } from "i18n/namespaces"; import { User } from "proto/api_pb"; @@ -135,7 +135,7 @@ export default function SearchResult({