Skip to content

Commit

Permalink
test: Basic tests describing the "search" feature (#3566)
Browse files Browse the repository at this point in the history
  • Loading branch information
DafyddLlyr authored Aug 27, 2024
1 parent aa6ecf7 commit bad5a37
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 0 deletions.
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]],
},
];

0 comments on commit bad5a37

Please sign in to comment.