Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,33 @@ import EncounterFormStore from "../../../pages/SearchPages/stores/EncounterFormS

describe("EncounterFormStore", () => {
let store;
let mockStorage = {};

beforeAll(() => {
jest
.spyOn(Storage.prototype, "getItem")
.mockImplementation((key) => mockStorage[key] || null);
jest
.spyOn(Storage.prototype, "setItem")
.mockImplementation((key, value) => {
mockStorage[key] = value ? value.toString() : "";
});
jest.spyOn(Storage.prototype, "removeItem").mockImplementation((key) => {
delete mockStorage[key];
});
jest.spyOn(Storage.prototype, "clear").mockImplementation(() => {
mockStorage = {};
});
});

beforeEach(() => {
store = new EncounterFormStore();
mockStorage = {};
jest.clearAllMocks();
});

afterAll(() => {
jest.restoreAllMocks();
});

test("initializes with empty formFilters", () => {
Expand Down Expand Up @@ -72,5 +96,92 @@ describe("EncounterFormStore", () => {
store.resetFilters();

expect(store.formFilters).toEqual([]);
expect(store.appliedFilters).toEqual([]);
expect(window.sessionStorage.removeItem).toHaveBeenCalledWith("formData");
});

test("applyFilters deep copies formFilters to appliedFilters and saves to storage", () => {
const mockFilter = {
filterId: "f1",
clause: "AND",
query: "q1",
filterKey: "k1",
path: "",
};
store.addFilter(
mockFilter.filterId,
mockFilter.clause,
mockFilter.query,
mockFilter.filterKey,
);

store.applyFilters();

expect(store.appliedFilters).toHaveLength(1);
expect(store.appliedFilters[0]).toEqual(mockFilter);

expect(store.appliedFilters).not.toBe(store.formFilters);

expect(window.sessionStorage.setItem).toHaveBeenCalledWith(
store.FILTER_STORAGE_KEY,
JSON.stringify([mockFilter]),
);
});

test("getFiltersFromStorage loads valid JSON into formFilters and appliedFilters", () => {
const mockFilter = {
filterId: "f1",
clause: "AND",
query: "q1",
filterKey: "k1",
path: "",
};
const savedData = JSON.stringify([mockFilter]);
window.sessionStorage.setItem("formData", savedData);

store.getFiltersFromStorage();

expect(window.sessionStorage.getItem).toHaveBeenCalledTimes(1);

expect(store.formFilters).toHaveLength(1);
expect(store.appliedFilters).toHaveLength(1);
});

test("getFiltersFromStorage ignores strings/non-arrays and clears storage", () => {
window.sessionStorage.setItem("formData", JSON.stringify("abc"));
store.getFiltersFromStorage();

expect(store.formFilters).toEqual([]);
expect(window.sessionStorage.removeItem).toHaveBeenCalledWith("formData");
});

test("setFiltersInSessionStorage clears the storage data when called with no data", () => {
const mockFilter = {
filterId: "f1",
clause: "AND",
query: "q1",
filterKey: "k1",
path: "",
};
window.sessionStorage.setItem("formData", JSON.stringify(mockFilter));
store.setFiltersInSessionStorage();

expect(store.formFilters).toEqual([]);
expect(window.sessionStorage.removeItem).toHaveBeenCalledWith("formData");
});

test("getFiltersFromStorage handles malformed JSON gracefully and clears storage", () => {
const consoleSpy = jest
.spyOn(console, "error")
.mockImplementation(() => {});
window.sessionStorage.setItem("formData", "{ bad_json ]");

store.getFiltersFromStorage();

expect(store.formFilters).toEqual([]);
expect(consoleSpy).toHaveBeenCalled();
expect(window.sessionStorage.removeItem).toHaveBeenCalledWith("formData");

consoleSpy.mockRestore();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from "@testing-library/react";
import EncounterSearch from "../../../pages/SearchPages/EncounterSearch";
import { MemoryRouter } from "react-router-dom";
import * as useFilterEncountersHook from "../../../models/encounters/useFilterEncounters";
import useFilterEncounters, * as useFilterEncountersHook from "../../../models/encounters/useFilterEncounters";
import * as useFilterEncountersWithMediaAssetsHook from "../../../models/encounters/useFilterEncountersWithMediaAssets";
import * as useEncounterSearchSchemasHook from "../../../models/encounters/useEncounterSearchSchemas";
import * as getAllSearchParams from "../../../pages/SearchPages/getAllSearchParamsAndParse";
Expand All @@ -18,6 +18,7 @@ import axios from "axios";
jest.mock("../../../pages/SearchPages/stores/EncounterFormStore", () => ({
globalEncounterFormStore: {
formFilters: [],
appliedFilters: [],
mediaAssetsSearchQuery: [],
pageSize: 20,
start: 0,
Expand All @@ -32,6 +33,7 @@ jest.mock("../../../pages/SearchPages/stores/EncounterFormStore", () => ({
setGalleryLoading: jest.fn(),
setLoadingAll: jest.fn(),
setGalleryExhausted: jest.fn(),
getFiltersFromStorage: jest.fn(),
},
}));

Expand Down Expand Up @@ -120,6 +122,7 @@ describe("EncounterSearch", () => {
jest.clearAllMocks();

globalEncounterFormStore.formFilters = [];
globalEncounterFormStore.appliedFilters = [];
globalEncounterFormStore.mediaAssetsSearchQuery = [];
globalEncounterFormStore.pageSize = 20;
globalEncounterFormStore.start = 0;
Expand Down Expand Up @@ -677,4 +680,42 @@ describe("EncounterSearch", () => {
expect(globalEncounterFormStore.setAssetOffset).toHaveBeenCalled();
});
});

it("calls getFiltersFromStorage on mount if no queryID is present", () => {
const params = {
from: 0,
size: 20,
sort: "date",
sortOrder: "desc",
};
const mockFilter = [
{
filterId: "f1",
clause: "AND",
query: "q1",
filterKey: "k1",
path: "",
},
];

globalEncounterFormStore.appliedFilters = mockFilter;

renderWithProviders("/search");

expect(
globalEncounterFormStore.getFiltersFromStorage,
).toHaveBeenCalledTimes(1);
expect(useFilterEncounters).toHaveBeenCalledWith({
queries: mockFilter,
params: params,
});
});

it("does NOT call getFiltersFromStorage if a queryID is present in the URL", () => {
renderWithProviders("/search?searchQueryId=abc123");

expect(
globalEncounterFormStore.getFiltersFromStorage,
).not.toHaveBeenCalled();
});
});
7 changes: 1 addition & 6 deletions frontend/src/components/FilterPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export default function FilterPanel({
style = {},
handleSearch = () => {},
refetch = () => {},
setTempFormFilters = () => {},
store,
}) {
const { data } = useSiteSettings();
Expand Down Expand Up @@ -143,16 +142,12 @@ export default function FilterPanel({
backgroundColor={theme.primaryColors.primary700}
borderColor={theme.primaryColors.primary700}
onClick={() => {
setTempFormFilters([...store.formFilters]);
refetch().then(({ data }) => {
console.log("Refetched data:", data);
});
store.resetGallery();
setSearchParams(new URLSearchParams());
sessionStorage.setItem(
"formData",
JSON.stringify(store.formFilters),
);
store.applyFilters();
setFilterPanel(false);
handleSearch();
store.setActiveStep(0);
Expand Down
47 changes: 27 additions & 20 deletions frontend/src/components/filterFields/SideBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useSearchParams } from "react-router-dom";
import { observer } from "mobx-react-lite";

const Sidebar = observer(
({ setFilterPanel, searchQueryId, queryID, tempFormFilters = [], store }) => {
({ setFilterPanel, searchQueryId, queryID = false, store }) => {
const theme = React.useContext(ThemeContext);
const [show, setShow] = useState(false);
const sidebarWidth = 400;
Expand All @@ -18,14 +18,17 @@ const Sidebar = observer(
const handleShow = () => setShow(true);
const [, setSearchParams] = useSearchParams();

const num = () => (queryID ? 1 : tempFormFilters.length);
const num = () => (queryID ? 1 : store.appliedFilters.length);

const handleCopy = () => {
// When viewing a result loaded from a shared query ID, copy that ID;
// otherwise copy the ID freshly generated for the current filter search.
const idToCopy = queryID || searchQueryId;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard
.writeText(searchQueryId)
.writeText(idToCopy)
.then(() => {
alert(`Query ID: ${searchQueryId} copied to clipboard!`);
alert(`Query ID: ${idToCopy} copied to clipboard!`);
})
.catch((err) => {
console.error("Failed to copy text: ", err);
Expand Down Expand Up @@ -111,7 +114,7 @@ const Sidebar = observer(
</div>
) : (
<div style={{ overflowY: "auto" }}>
{tempFormFilters.map((filter, index) => (
{store.appliedFilters.map((filter, index) => (
<Chip key={index}>{filter}</Chip>
))}
</div>
Expand All @@ -135,21 +138,25 @@ const Sidebar = observer(
>
<FormattedMessage id="FILTER_COPY" defaultMessage={"Copy"} />
</BrutalismButton>
<BrutalismButton
onClick={() => {
setFilterPanel(true);
handleClose();
}}
backgroundColor={theme.primaryColors.primary700}
borderColor={theme.primaryColors.primary700}
color="white"
noArrow={true}
>
<FormattedMessage
id="FILTER_EDIT_FILTER"
defaultMessage={"Edit"}
/>
</BrutalismButton>

{!queryID && (
<BrutalismButton
onClick={() => {
setFilterPanel(true);
handleClose();
}}
backgroundColor={theme.primaryColors.primary700}
borderColor={theme.primaryColors.primary700}
color="white"
noArrow={true}
>
<FormattedMessage
id="FILTER_EDIT_FILTER"
defaultMessage={"Edit"}
/>
</BrutalismButton>
)}

<BrutalismButton
borderColor={theme.primaryColors.primary700}
color={theme.primaryColors.primary700}
Expand Down
Loading