Skip to content

Commit

Permalink
Merge pull request #288 from NIAEFEUP/hotfix/array-query-params
Browse files Browse the repository at this point in the history
Fixed passing array values in query
  • Loading branch information
Naapperas authored Dec 10, 2022
2 parents b2b0152 + c0486cd commit 3035406
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 12 deletions.
6 changes: 4 additions & 2 deletions src/components/HomePage/SearchArea/SearchArea.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import useOffersSearcher from "../SearchResultsArea/SearchResultsWidget/useOffer

import useSearchParams from "./useUrlSearchParams";

import { ensureArray } from "../../../utils";

export const AdvancedSearchControllerContext = React.createContext({});

export const AdvancedSearchController = ({
Expand Down Expand Up @@ -97,8 +99,8 @@ export const AdvancedSearchController = ({
]);

setJobType(queryParams.jobType);
setFields(queryParams.fields || []);
setTechs(queryParams.technologies || []);
setFields(ensureArray(queryParams.fields ?? []));
setTechs(ensureArray(queryParams.technologies ?? []));

setSearchValue(queryParams.searchValue);
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down
4 changes: 4 additions & 0 deletions src/components/HomePage/SearchArea/SearchArea.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ describe("SearchArea", () => {
const filterJobDuration = false;
props.setShowJobDurationSlider(filterJobDuration);
expect(dispatch).toHaveBeenCalledWith(setShowJobDurationSlider(false));

dispatch.mockClear();
props.resetAdvancedSearchFields();
expect(dispatch).toHaveBeenCalled();
});
});
});
21 changes: 17 additions & 4 deletions src/components/HomePage/SearchArea/useUrlSearchParams.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { useLocation, useHistory } from "react-router-dom";

import { throttle } from "lodash";

import { ensureArray } from "../../../utils";

const HISTORY_REPLACE_THROTTLE_DELAY_MS = 350;

/**
Expand Down Expand Up @@ -55,6 +57,7 @@ export default ({
const actualSetJobType = useCallback(({ target: { value: jobType } }) => {
changeURLFilters(location, queryParams, { jobType });

/* istanbul ignore else */
if (setJobType)
setJobType(jobType);

Expand All @@ -66,6 +69,7 @@ export default ({

changeURLFilters(location, queryParams, { jobMinDuration, jobMaxDuration });

/* istanbul ignore else */
if (setJobDuration)
setJobDuration(unused, duration);

Expand All @@ -75,33 +79,41 @@ export default ({
if (!showJobDurationSlider)
changeURLFilters(location, queryParams, { jobMinDuration: null, jobMaxDuration: null });

/* istanbul ignore else */
if (setShowJobDurationSlider)
setShowJobDurationSlider(showJobDurationSlider);

}, [changeURLFilters, location, queryParams, setShowJobDurationSlider]);

const actualSetFields = useCallback((fields) => {

changeURLFilters(location, queryParams, { fields });
const sanitizedFields = ensureArray(fields);

changeURLFilters(location, queryParams, { fields: sanitizedFields });

/* istanbul ignore else */
if (setFields)
setFields(fields);
setFields(sanitizedFields);

}, [changeURLFilters, location, queryParams, setFields]);

const actualSetTechs = useCallback((technologies) => {

changeURLFilters(location, queryParams, { technologies });
const sanitizedTechnologies = ensureArray(technologies);

changeURLFilters(location, queryParams, { technologies: sanitizedTechnologies });

/* istanbul ignore else */
if (setTechs)
setTechs(technologies);
setTechs(sanitizedTechnologies);

}, [changeURLFilters, location, queryParams, setTechs]);

const actualSetSearchValue = useCallback((searchValue) => {

changeURLFilters(location, queryParams, { searchValue });

/* istanbul ignore else */
if (setSearchValue)
setSearchValue(searchValue);

Expand All @@ -110,6 +122,7 @@ export default ({
const actualResetAdvancedSearchFields = useCallback(() => {
clearURLFilters(location);

/* istanbul ignore else */
if (resetAdvancedSearchFields)
resetAdvancedSearchFields();

Expand Down
50 changes: 47 additions & 3 deletions src/components/HomePage/SearchArea/useUrlSearchParams.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import useUrlSearchParams from "./useUrlSearchParams";
import { testHook } from "../../../test-utils";
import { act } from "react-dom/test-utils";

const capitalizeFirstLetter = ([first, ...rest], locale = navigator.language) =>
first === undefined ? "" : first.toLocaleUpperCase(locale) + rest.join("");
import { capitalize } from "../../../utils";

describe("useUrlSearchParams", () => {

Expand Down Expand Up @@ -46,7 +45,7 @@ describe("useUrlSearchParams", () => {

let location, searchParamsResult;

const functionName = `set${capitalizeFirstLetter(fieldName)}`;
const functionName = `set${capitalize(fieldName)}`;
const testFunction = jest.fn();

testHook(() => {
Expand Down Expand Up @@ -94,6 +93,51 @@ describe("useUrlSearchParams", () => {
expect(location).toHaveProperty("search", expectedLocationSearch);
});

it.each([
["fields", "TEST-FIELD", ["TEST-FIELD"]],
["techs", "TEST-TECH", ["TEST-TECH"]],
])("should parse array-like query value '%s' correctly", async (fieldName, fieldValue, expectedValue) => {

let location, searchParamsResult;

const functionName = `set${capitalize(fieldName)}`;
const testFunction = jest.fn();

testHook(() => {
searchParamsResult = useUrlSearchParams({
[functionName]: testFunction,
});

location = useLocation();
}, MemoryRouter);

const hookedInTestFunction = searchParamsResult[functionName];

// using fail(...) is not recommended as Jasmine globals will be removed from Jest
if (!hookedInTestFunction) throw Error(`No hooked-in test function found for ${functionName}`);

expect(location).toHaveProperty("search", "");

await act(() => {
hookedInTestFunction(fieldValue);
});

expect(testFunction).toHaveBeenCalled();

let params = {
[fieldName]: expectedValue,
};

if (fieldName === "techs")
params = {
"technologies": expectedValue,
};

const expectedLocationSearch = `?${qs.stringify(params, { skipNulls: true, arrayFormat: "brackets" })}`;

expect(location).toHaveProperty("search", expectedLocationSearch);
});

it("should clear the Location's search property", async () => {
let location, searchParamsResult;

Expand Down
6 changes: 4 additions & 2 deletions src/components/HomePage/URLSearchParamsParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { useSelector } from "react-redux";
import useOffersSearcher from "./SearchResultsArea/SearchResultsWidget/useOffersSearcher";
import { SearchResultsConstants } from "./SearchResultsArea/SearchResultsWidget/SearchResultsUtils";

import { ensureArray } from "../../utils";

// offerSearch params that should NOT trigger an auto-submit
const invalidParams = ["error", "offers", "loading", "filterJobDuration"];

Expand All @@ -31,8 +33,8 @@ const URLSearchParamsParser = ({ showSearchResults }) => {
jobMaxDuration: queryParams.jobMaxDuration,
jobMinDuration: queryParams.jobMinDuration,
jobType: queryParams.jobType,
fields: queryParams.fields,
technologies: queryParams.technologies,
fields: ensureArray(queryParams.fields),
technologies: ensureArray(queryParams.technologies),
});

// we specifically want this to only run once to avoid infinite re-renders
Expand Down
6 changes: 6 additions & 0 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,9 @@ export const generalParseRequestErrors = (err, getHumanError) => {
generalErrors,
};
};

export const ensureArray = (val) => {
if (Array.isArray(val)) return val;

else return [val];
};
13 changes: 12 additions & 1 deletion src/utils/index.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useContext } from "react";
import { createMemoryHistory } from "history";
import { Switch, Link, Router } from "react-router-dom";
import { smoothScrollToRef, capitalize, Wrap, Route, ProtectedRoute } from ".";
import { smoothScrollToRef, capitalize, Wrap, Route, ProtectedRoute, ensureArray } from ".";
import { render, renderWithStore, screen, act, fireEvent } from "../test-utils";
import useSession from "../hooks/useSession";

Expand Down Expand Up @@ -262,4 +262,15 @@ describe("utils", () => {
expect(screen.getByText("No match")).toBeInTheDocument();
});
});

describe("ensureArray", () => {
it("should return array value when receiving array-like value", () => {
expect(ensureArray([1])).toEqual([1]);
expect(ensureArray(Array.of(1, 2))).toEqual([1, 2]);
});
it("should return array value when receiving non-array-like value", () => {
expect(ensureArray(1)).toEqual([1]);
expect(ensureArray("test")).toEqual(["test"]);
});
});
});

0 comments on commit 3035406

Please sign in to comment.