Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Basic tests describing the "search" feature #3566

Merged
merged 4 commits into from
Aug 27, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { FullStore, useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import { act } from "react-dom/test-utils";
import { setup } from "testUtils";
import { axe } from "vitest-axe";

import { ExternalPortalList } from "./ExternalPortalList";

const { getState, setState } = useStore;

let initialState: FullStore;

beforeAll(() => (initialState = getState()));
afterEach(() => act(() => setState(initialState)));

const externalPortals: FullStore["externalPortals"] = {
abc: { name: "Portal 1", href: "myTeam/portalOne" },
def: { name: "Portal 2", href: "myTeam/portalTwo" },
};

it("does not display if there are no external portals in the flow", () => {
const { container } = setup(<ExternalPortalList />);

expect(container).toBeEmptyDOMElement();
});

it("displays a list of external portals if present in the flow", () => {
act(() => setState({ externalPortals }));
const { container, getAllByRole } = setup(<ExternalPortalList />);

expect(container).not.toBeEmptyDOMElement();
expect(getAllByRole("listitem")).toHaveLength(2);
});

it("allows users to navigate to the external portals", () => {
act(() => setState({ externalPortals }));
const { container, getAllByRole } = setup(<ExternalPortalList />);

expect(container).not.toBeEmptyDOMElement();
const [first, second] = getAllByRole("link") as HTMLAnchorElement[];
expect(first).toHaveAttribute("href", "../myTeam/portalOne");
expect(second).toHaveAttribute("href", "../myTeam/portalTwo");
});

it("should not have any accessibility violations on initial load", async () => {
act(() => setState({ externalPortals }));
const { container } = setup(<ExternalPortalList />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from "react";
import { setup } from "testUtils";
import { FONT_WEIGHT_BOLD } from "theme";
import { axe } from "vitest-axe";

import { Headline } from "./Headline";

const sampleText = "The quick fox jumps...";
const foxIndices: [number, number][] = [[10, 13]];

const DEFAULT_FONT_WEIGHT = "400";

it("displays matches from the headline in bold", () => {
const { getByText } = setup(
<Headline text={sampleText} matchIndices={foxIndices} variant="data" />,
);

// Input text is split into characters in order to highlight a substring
const tStyle = window.getComputedStyle(getByText("T"));
const hStyle = window.getComputedStyle(getByText("h"));
const eStyle = window.getComputedStyle(getByText("e"));

// Non matching text is not in bold
expect(tStyle.fontWeight).toEqual(DEFAULT_FONT_WEIGHT);
expect(hStyle.fontWeight).toEqual(DEFAULT_FONT_WEIGHT);
expect(eStyle.fontWeight).toEqual(DEFAULT_FONT_WEIGHT);

const fStyle = window.getComputedStyle(getByText("f"));
const oStyle = window.getComputedStyle(getByText("o"));
const xStyle = window.getComputedStyle(getByText("x"));

// Matching text is in bold
expect(fStyle.fontWeight).toEqual(FONT_WEIGHT_BOLD);
expect(oStyle.fontWeight).toEqual(FONT_WEIGHT_BOLD);
expect(xStyle.fontWeight).toEqual(FONT_WEIGHT_BOLD);
});

it("should not have any accessibility violations on initial load", async () => {
const { container } = setup(
<Headline text={sampleText} matchIndices={foxIndices} variant="data" />,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import { setup } from "testUtils";
import { axe } from "vitest-axe";

import { flow, results } from "./mocks/simple";
import { NodeSearchResults } from "./NodeSearchResults";

beforeAll(() => useStore.setState({ flow }));

it("Displays a warning if no results are returned", () => {
const { getByText, getByRole } = setup(<NodeSearchResults results={[]} />);
expect(getByText("No matches found")).toBeInTheDocument();
expect(getByRole("list")).toBeEmptyDOMElement();
});

it("Displays the count for a single result", () => {
const { getByText, getByRole, getAllByRole } = setup(
<NodeSearchResults results={[results[0]]} />,
);
expect(getByText("1 result:")).toBeInTheDocument();
expect(getByRole("list")).not.toBeEmptyDOMElement();
expect(getAllByRole("listitem")).toHaveLength(1);
});

it("Displays the count for multiple results", () => {
const { getByText, getByRole, getAllByRole } = setup(
<NodeSearchResults results={results} />,
);
expect(getByText("2 results:")).toBeInTheDocument();
expect(getByRole("list")).not.toBeEmptyDOMElement();
expect(getAllByRole("listitem")).toHaveLength(2);
});

it("should not have any accessibility violations on initial load", async () => {
const { container } = setup(<NodeSearchResults results={results} />);
const axeResults = await axe(container);
expect(axeResults).toHaveNoViolations();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as planxCore from "@opensystemslab/planx-core";
import { waitFor } from "@testing-library/react";
import { FullStore, useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import { act } from "react-dom/test-utils";
import { setup } from "testUtils";
import { vi } from "vitest";
import { axe } from "vitest-axe";

import Search from ".";
import { flow } from "./mocks/simple";

const { setState, getState } = useStore;

let initialState: FullStore;

beforeAll(() => (initialState = getState()));

beforeEach(() => setState({ flow }));
afterEach(() => act(() => setState(initialState)));

vi.mock("@opensystemslab/planx-core", async (originalModule) => {
const actualModule = await originalModule<typeof planxCore>();
return {
...actualModule,
// Spy on sortFlow while keeping its original implementation
sortFlow: vi.fn(actualModule.sortFlow),
};
});

test("data field checkbox is checked and disabled", () => {
const { getByLabelText } = setup(<Search />);
const checkbox = getByLabelText("Search only data fields");

expect(checkbox).toBeInTheDocument();
expect(checkbox).toBeChecked();
expect(checkbox).toBeDisabled();
});

test("entering a search term displays a series of cards", async () => {
const { user, queryByRole, getByRole, getAllByRole, getByLabelText } = setup(
<Search />,
);

expect(queryByRole("list")).not.toBeInTheDocument();

const searchInput = getByLabelText("Search this flow and internal portals");
user.type(searchInput, "ind");

await waitFor(() => expect(getByRole("list")).toBeInTheDocument());
await waitFor(() => expect(getAllByRole("listitem")).toHaveLength(2));
});

test.todo("cards link to their associated nodes", async () => {
const { user, getAllByRole, getByLabelText } = setup(<Search />);

const searchInput = getByLabelText("Search this flow and internal portals");
user.type(searchInput, "ind");

await waitFor(() => expect(getAllByRole("listitem")).toHaveLength(2));

const [first, second] = getAllByRole("listitem");
// TODO!
expect(first).toHaveAttribute("href", "link to tR9tdaWOvF (India)");
expect(second).toHaveAttribute("href", "link to tvUxd2IoPo (Indonesia)");
});

it("orderedFlow is set in the store on render of Search", async () => {
expect(getState().orderedFlow).toBeUndefined();

setup(<Search />);

expect(getState().orderedFlow).toBeDefined();
});

test("setOrderedFlow is only called once on initial render", async () => {
const sortFlowSpy = vi.spyOn(planxCore, "sortFlow");
expect(sortFlowSpy).not.toHaveBeenCalled();

const { user, getAllByRole, getByLabelText } = setup(<Search />);

const searchInput = getByLabelText("Search this flow and internal portals");
user.type(searchInput, "ind");

await waitFor(() => expect(getAllByRole("listitem")).toHaveLength(2));

expect(sortFlowSpy).toHaveBeenCalledTimes(1);
});

it("should not have any accessibility violations on initial load", async () => {
const { container } = setup(<Search />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const Search: React.FC = () => {
Search this flow and internal portals
</Typography>
<Input
id="search"
name="search"
value={formik.values.input}
onChange={(e) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { FlowGraph, IndexedNode } from "@opensystemslab/planx-core/types";
import { SearchResults } from "hooks/useSearch";

export const flow: FlowGraph = {
_root: {
edges: ["Ej0xpn4l8u"],
},
Ej0xpn4l8u: {
type: 100,
data: {
fn: "country",
text: "Pick a country",
},
edges: ["VhSydY2fTe", "tR9tdaWOvF", "tvUxd2IoPo"],
},
VhSydY2fTe: {
type: 200,
data: {
text: "Spain",
val: "spain",
},
},
tR9tdaWOvF: {
type: 200,
data: {
text: "India",
val: "india",
},
},
tvUxd2IoPo: {
type: 200,
data: {
text: "Indonesia",
val: "indonesia",
},
},
};

export const results: SearchResults<IndexedNode> = [
{
item: {
id: "tR9tdaWOvF",
parentId: "Ej0xpn4l8u",
type: 200,
data: {
text: "India",
val: "india",
},
},
key: "data.val",
matchIndices: [[0, 2]],
},
{
item: {
id: "tvUxd2IoPo",
parentId: "Ej0xpn4l8u",
type: 200,
data: {
text: "Indonesia",
val: "indonesia",
},
},
key: "data.val",
matchIndices: [[0, 2]],
},
];
Loading