Skip to content

Commit

Permalink
test(SILVA-539): adding test to autocomplete
Browse files Browse the repository at this point in the history
  • Loading branch information
paulushcgcj committed Nov 28, 2024
1 parent 499d120 commit 4ae3f22
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 6 deletions.
200 changes: 200 additions & 0 deletions frontend/src/__test__/contexts/AutocompleteProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import React from "react";
import { render, screen, waitFor, act } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, vi, expect, beforeEach, afterEach } from "vitest";
import { AutocompleteProvider, useAutocomplete } from "../../contexts/AutocompleteProvider";

// Mock lodash debounce to run instantly in tests
vi.mock("lodash", () => ({
debounce: (fn: any) => {
fn.cancel = vi.fn();
return fn;
},
}));

// Mock component to consume the context
const MockAutocompleteConsumer = () => {
const { options, loading, error, fetchOptions } = useAutocomplete();
return (
<div>
<button onClick={() => fetchOptions("test-query", "key1")}>Fetch Options</button>
{loading && <div data-testid="loading">Loading...</div>}
{error && <div data-testid="error">{error}</div>}
<div data-testid="options">{JSON.stringify(options)}</div>
</div>
);
};

describe("AutocompleteProvider", () => {
let fetchOptionsMock: vi.Mock;

beforeEach(() => {
fetchOptionsMock = vi.fn((query: string, key: string) => []);
});

afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
});

it("renders children correctly", () => {
render(
<AutocompleteProvider fetchOptions={fetchOptionsMock}>
<div>Child Component</div>
</AutocompleteProvider>
);
expect(screen.getByText("Child Component")).toBeInTheDocument();
});

it("calls fetchOptions and updates state correctly", async () => {
fetchOptionsMock.mockResolvedValueOnce(["option1", "option2"]);

await act(async () =>render(
<AutocompleteProvider fetchOptions={fetchOptionsMock}>
<MockAutocompleteConsumer />
</AutocompleteProvider>
));

await act(async () => userEvent.click(screen.getByText("Fetch Options")));

expect(fetchOptionsMock).toHaveBeenCalledWith("test-query", "key1");

await waitFor(() => {
expect(screen.getByTestId("options")).toHaveTextContent(
JSON.stringify({ key1: ["option1", "option2"] })
);
});
});

it("handles skip conditions", async () => {
const skipConditions = { key1: (query: string) => query === "skip" };
fetchOptionsMock.mockResolvedValueOnce(["option1", "option2"]);

await act(async () =>render(
<AutocompleteProvider fetchOptions={fetchOptionsMock} skipConditions={skipConditions}>
<MockAutocompleteConsumer />
</AutocompleteProvider>
));

await act(async () =>userEvent.click(screen.getByText("Fetch Options")));

// Should fetch for "test-query"
expect(fetchOptionsMock).toHaveBeenCalledWith("test-query", "key1");

userEvent.click(screen.getByText("Fetch Options")); // Attempt to fetch for "skip"
fetchOptionsMock.mockClear();

await waitFor(() => {
expect(fetchOptionsMock).not.toHaveBeenCalled(); // Skip condition met
});
});

it("handles errors during fetching", async () => {
fetchOptionsMock.mockRejectedValueOnce(new Error("Fetch error"));

render(
<AutocompleteProvider fetchOptions={fetchOptionsMock}>
<MockAutocompleteConsumer />
</AutocompleteProvider>
);

userEvent.click(screen.getByText("Fetch Options"));

await waitFor(() => {
expect(screen.getByTestId("error")).toHaveTextContent("Error fetching options");
});
});

it("sets options correctly", () => {
render(
<AutocompleteProvider fetchOptions={fetchOptionsMock}>
<MockAutocompleteConsumer />
</AutocompleteProvider>
);

userEvent.click(screen.getByText("Fetch Options"));

waitFor(() => {
expect(screen.getByTestId("options")).toHaveTextContent(
JSON.stringify({ key1: ["option1", "option2"] })
);
});
});

it("handles multiple keys correctly", async () => {
fetchOptionsMock.mockResolvedValueOnce(["option1", "option2"]);
fetchOptionsMock.mockResolvedValueOnce(["option3", "option4"]);

await act(async () =>render(
<AutocompleteProvider fetchOptions={fetchOptionsMock}>
<MockAutocompleteConsumer />
</AutocompleteProvider>
));

await act(async () =>userEvent.click(screen.getByText("Fetch Options")));
expect(fetchOptionsMock).toHaveBeenCalledWith("test-query", "key1");

await waitFor(() => {
expect(screen.getByTestId("options")).toHaveTextContent(
JSON.stringify({ key1: ["option1", "option2"] })
);
});

await act(async () =>userEvent.click(screen.getByText("Fetch Options")));
expect(fetchOptionsMock).toHaveBeenCalledWith("test-query", "key1");

await waitFor(() => {
expect(screen.getByTestId("options")).toHaveTextContent(
JSON.stringify({ key1: ["option3", "option4"] })
);
});
});

it("updates loading state correctly", async () => {
fetchOptionsMock.mockImplementation(
() => new Promise((resolve) => setTimeout(() => resolve(["option1", "option2"]), 2500))
);

await act(async () =>render(
<AutocompleteProvider fetchOptions={fetchOptionsMock}>
<MockAutocompleteConsumer />
</AutocompleteProvider>
));
expect(screen.queryByTestId("loading")).not.toBeInTheDocument();
await act(async () =>userEvent.click(screen.getByText("Fetch Options")));
expect(screen.getByTestId("loading")).toBeInTheDocument();
await waitFor(() => expect(screen.queryByTestId("loading")).not.toBeInTheDocument(), { timeout: 3000 });
vi.clearAllTimers();
vi.useRealTimers();
});

it("updates error state correctly", async () => {
fetchOptionsMock.mockRejectedValueOnce(new Error("Fetch error"));

render(
<AutocompleteProvider fetchOptions={fetchOptionsMock}>
<MockAutocompleteConsumer />
</AutocompleteProvider>
);

userEvent.click(screen.getByText("Fetch Options"));

await waitFor(() => {
expect(screen.getByTestId("error")).toHaveTextContent("Error fetching options");
});
});

it("does not fetch options if key or query is missing", async () => {
render(
<AutocompleteProvider fetchOptions={fetchOptionsMock}>
<MockAutocompleteConsumer />
</AutocompleteProvider>
);

userEvent.click(screen.getByText("Fetch Options"));

await waitFor(() => {
expect(fetchOptionsMock).not.toHaveBeenCalled();
});
});
});
8 changes: 2 additions & 6 deletions frontend/src/contexts/AutocompleteProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const AutocompleteProvider = ({ fetchOptions, skipConditions, children }:
if (skipConditions && skipConditions[key] && skipConditions[key](query)) {
return;
}
setLoading(true);
setOptions(key, searchingForItems);
const fetchedOptions = await fetchOptions(query, key);
setOptions(key, fetchedOptions.length ? fetchedOptions : noDataFound);
Expand All @@ -56,16 +57,11 @@ export const AutocompleteProvider = ({ fetchOptions, skipConditions, children }:
}, 450);
};

const fetchAndSetOptions = async (query: string, key: string) => {
const fetchAndSetOptions = (query: string, key: string) => {
if(!key || !query) return;
if (skipConditions && skipConditions[key] && skipConditions[key](query)) {
return;
}
/*setLoading(true);
setOptions(key, searchingForItems);
const fetchedOptions = await fetchOptions(query, key);
setOptions(key, fetchedOptions.length ? fetchedOptions : noDataFound);
setLoading(false);*/
if (!debouncedFetchMap.current.has(key)) {
debouncedFetchMap.current.set(key, createDebouncedFetch(key));
}
Expand Down

0 comments on commit 4ae3f22

Please sign in to comment.