- {filterByStatus(filters.status)
+ {filterByStatus(filter.status)
.sort(compareByClassTitleAndDescriptionFn)
.map(scenario => (
{
+ jest.resetAllMocks();
+});
+
+const onExpansionCallback = jest.fn();
+const onCollapsionCallback = jest.fn();
+
+describe("Scenario", () => {
+ it("displays single scenario case", () => {
+ const className = "my custom class name";
+ const scenarioCases = [createScenarioCaseModel()];
+ const model = createScenarioModel({ className, scenarioCases });
+
+ render(
+
+ );
+
+ expect(screen.getByText(className)).toBeVisible();
+ });
+
+ describe("Scenario accordion behavior", () => {
+ it("accordion details are not visible when globalExpansionState is COLLAPSED", async () => {
+ const details = "some details";
+ const model = createScenarioModel({
+ scenarioCases: [
+ createScenarioCaseModel({
+ steps: [createStepModel({ words: [createWord({ value: details })] })]
+ })
+ ]
+ });
+ render(
+
+ );
+ const accordion = screen.getByLabelText("Scenario Overview");
+ expect(accordion.attributes.getNamedItem("aria-expanded")?.value).toBe("false");
+ expect(screen.queryByText(details)).not.toBeVisible();
+ });
+
+ it("accordion details are visible when globalExpansionState is EXPANDED", async () => {
+ const details = "some details";
+ const model = createScenarioModel({
+ scenarioCases: [
+ createScenarioCaseModel({
+ steps: [createStepModel({ words: [createWord({ value: details })] })]
+ })
+ ]
+ });
+ render(
+
+ );
+ const accordion = screen.getByLabelText("Scenario Overview");
+ expect(accordion.attributes.getNamedItem("aria-expanded")?.value).toBe("true");
+ expect(screen.queryByText(details)).toBeVisible();
+ });
+
+ it("onExpansionCallback is invoked when clicking on the header of a collapsed scenario", async () => {
+ render(
+
+ );
+ const scenarioOverview = await screen.findByLabelText("Scenario Overview");
+ userEvent.click(scenarioOverview);
+ expect(onExpansionCallback).toHaveBeenCalled();
+ });
+
+ it("onCollapsionCallback is invoked when clicking on the header of an expanded scenario", async () => {
+ render(
+
+ );
+ const scenarioOverview = await screen.findByLabelText("Scenario Overview");
+ userEvent.click(scenarioOverview);
+ expect(onCollapsionCallback).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/new/src/components/Scenarios/__test__/ScenarioCase.test.tsx b/new/src/components/Scenarios/__test__/ScenarioCase.test.tsx
new file mode 100644
index 00000000..10384ba7
--- /dev/null
+++ b/new/src/components/Scenarios/__test__/ScenarioCase.test.tsx
@@ -0,0 +1,26 @@
+import { render, screen } from "@testing-library/react";
+import { ScenarioCase } from "../ScenarioCase";
+import { createScenarioCaseModel, createStepModel, createWord } from "./scenarioTestData";
+
+describe("ScenarioCase", () => {
+ it("should display class name", () => {
+ const className = "name.of.my.class";
+ render();
+
+ expect(screen.getByText(className)).toBeInTheDocument();
+ });
+
+ it("should display all scenario steps", () => {
+ const singleWordScenarioDescriptions = ["marine", "debug", "grind", "trivial", "timetable"];
+
+ const steps = singleWordScenarioDescriptions.map(description =>
+ createStepModel({ words: [createWord({ value: description })] })
+ );
+
+ render();
+
+ singleWordScenarioDescriptions.forEach(description => {
+ expect(screen.getByText(description)).toBeVisible();
+ });
+ });
+});
diff --git a/new/src/components/Scenarios/__test__/ScenarioHead.test.tsx b/new/src/components/Scenarios/__test__/ScenarioHead.test.tsx
new file mode 100644
index 00000000..a99fedbf
--- /dev/null
+++ b/new/src/components/Scenarios/__test__/ScenarioHead.test.tsx
@@ -0,0 +1,48 @@
+import { render } from "@testing-library/react";
+import { ScenarioHead } from "../ScenarioHead";
+import { createScenarioModel } from "./scenarioTestData";
+import { screen } from "../../../testUtils/enhancedScreen";
+
+describe("Scenario Head", () => {
+ it("displays class title", () => {
+ const classTitle = "The class title";
+ const model = createScenarioModel({ classTitle });
+
+ render();
+ expect(screen.getByText(classTitle)).toBeVisible();
+ });
+
+ it("displays capitalized title", () => {
+ const description = "scenario description";
+ const expectedDisplayValue = "Scenario description";
+
+ const model = createScenarioModel({ description });
+ render();
+
+ expect(screen.getByText(expectedDisplayValue)).toBeVisible();
+ });
+
+ it("displays checkbox icon if scenario has executionStatus SUCCESS", () => {
+ const model = createScenarioModel({ executionStatus: "SUCCESS" });
+ render();
+
+ expect(screen.getAllIcons()).toHaveLength(1);
+ expect(screen.getCheckboxIcon()).toBeVisible();
+ });
+
+ it("displays error icon if scenario has executionStatus FAILED", () => {
+ const model = createScenarioModel({ executionStatus: "FAILED" });
+ render();
+
+ expect(screen.getAllIcons()).toHaveLength(1);
+ expect(screen.getErrorIcon()).toBeVisible();
+ });
+
+ it("displays pending icon if scenario has executionStatus PENDING", () => {
+ const model = createScenarioModel({ executionStatus: "PENDING" });
+ render();
+
+ expect(screen.getAllIcons()).toHaveLength(1);
+ expect(screen.getPendingIcon()).toBeVisible();
+ });
+});
diff --git a/new/src/components/Scenarios/__test__/ScenarioStep.test.tsx b/new/src/components/Scenarios/__test__/ScenarioStep.test.tsx
new file mode 100644
index 00000000..b2a669a2
--- /dev/null
+++ b/new/src/components/Scenarios/__test__/ScenarioStep.test.tsx
@@ -0,0 +1,57 @@
+import { render, screen, within } from "@testing-library/react";
+import { createStepModel, createWord } from "./scenarioTestData";
+import { ScenarioStep } from "../ScenarioStep";
+
+describe("ScenarioStep", () => {
+ it("should display words in scenario step description separated by space", () => {
+ const words = [
+ createWord({ value: "cower" }),
+ createWord({ value: "comfortable" }),
+ createWord({ value: "front" }),
+ createWord({ value: "pony" })
+ ];
+ const expectedDisplayValue = "cower comfortable front pony";
+
+ render();
+
+ expect(screen.getByText(expectedDisplayValue)).toBeVisible();
+ });
+
+ it.each([
+ [1e7 + 1, "(0.010s)"],
+ [1e9, "(1.000s)"],
+ [234123455532, "(234.123s)"]
+ ])(
+ "should display the runtime in seconds if durationInNanos = %s",
+ (durationInNanos, expectedDisplayValue) => {
+ const word = "some word";
+ render(
+
+ );
+
+ expect(screen.getByText(expectedDisplayValue)).toBeVisible();
+ }
+ );
+
+ it.each([[-1e16], [0], [100], [1e7]])(
+ "should not display the duration if durationInNanos = %s",
+ durationInNanos => {
+ const word = "some word";
+ render(
+
+ );
+
+ expect(within(screen.getByText(word)).getByText("")).toBeInTheDocument();
+ }
+ );
+});
diff --git a/new/src/components/Scenarios/__test__/scenarioTestData.ts b/new/src/components/Scenarios/__test__/scenarioTestData.ts
new file mode 100644
index 00000000..640baa8a
--- /dev/null
+++ b/new/src/components/Scenarios/__test__/scenarioTestData.ts
@@ -0,0 +1,57 @@
+import { ScenarioCaseModel, ScenarioModel, StepModel, Word } from "../../../reportModel";
+
+export function createWord(props?: Partial): Word {
+ return {
+ value: props?.value ?? "word value",
+ isIntroWord: props?.isIntroWord,
+ argumentInfo: props?.argumentInfo
+ };
+}
+
+export function createStepModel(props?: Partial): StepModel {
+ return {
+ name: props?.name ?? "step name",
+ words: props?.words ?? [],
+ status: props?.status ?? "PASSED",
+ durationInNanos: props?.durationInNanos ?? 0,
+ depth: props?.depth ?? 0,
+ parentFailed: props?.parentFailed ?? false,
+ nestedSteps: props?.nestedSteps,
+ extendedDescription: props?.extendedDescription,
+ attachments: props?.attachments,
+ isSectionTitle: props?.isSectionTitle,
+ comment: props?.comment
+ };
+}
+
+export function createScenarioCaseModel(props?: Partial): ScenarioCaseModel {
+ return {
+ caseNr: props?.caseNr ?? 0,
+ steps: props?.steps ?? [],
+ explicitArguments: props?.explicitArguments ?? [],
+ derivedArguments: props?.derivedArguments ?? [],
+ status: props?.status ?? "SUCCESS",
+ errorMessage: props?.errorMessage,
+ stackTrace: props?.stackTrace,
+ durationInNanos: props?.durationInNanos ?? 0,
+ description: props?.description
+ };
+}
+
+export function createScenarioModel(props?: Partial): ScenarioModel {
+ return {
+ className: props?.className ?? "class name",
+ classTitle: props?.classTitle ?? "class title",
+ testMethodName: props?.testMethodName ?? "test method name",
+ description: props?.description ?? "scenario description",
+ extendedDescription: props?.extendedDescription,
+ tagIds: props?.tagIds ?? [],
+ explicitParameters: props?.explicitParameters ?? [],
+ derivedParameters: props?.derivedParameters ?? [],
+ scenarioCases: props?.scenarioCases ?? [],
+ casesAsTable: props?.casesAsTable ?? false,
+ durationInNanos: props?.durationInNanos ?? 0,
+ executionStatus: props?.executionStatus ?? "SUCCESS",
+ tags: props?.tags ?? []
+ };
+}
diff --git a/new/src/hooks/useFilters.ts b/new/src/hooks/useFilters.ts
index b0325a29..24999502 100644
--- a/new/src/hooks/useFilters.ts
+++ b/new/src/hooks/useFilters.ts
@@ -1,15 +1,15 @@
import { SetURLSearchParams, useSearchParams } from "react-router-dom";
import { ScenarioStatusFilter } from "../components/ScenarioOverview/ScenarioCollectionHead";
-export interface Filters {
+export interface Filter {
status: ScenarioStatusFilter | undefined;
}
-export function useFilters(): [Filters, SetURLSearchParams] {
+export function useFilters(): { filter: Filter; setUrlSearchParams: SetURLSearchParams } {
const [searchParams, setSearchParams] = useSearchParams();
const status = searchParams.get("status");
- return [{ status: parseScenarioStatus(status) }, setSearchParams];
+ return { filter: { status: parseScenarioStatus(status) }, setUrlSearchParams: setSearchParams };
}
function parseScenarioStatus(status: string | null): ScenarioStatusFilter | undefined {
diff --git a/new/src/testUtils/enhancedScreen.ts b/new/src/testUtils/enhancedScreen.ts
new file mode 100644
index 00000000..464aa12c
--- /dev/null
+++ b/new/src/testUtils/enhancedScreen.ts
@@ -0,0 +1,83 @@
+import { buildQueries, screen as classicScreen } from "@testing-library/react";
+function queryAllIcons(container: HTMLElement): HTMLElement[] {
+ const svgElements: NodeListOf =
+ container.querySelectorAll("svg.MuiSvgIcon-root");
+ return Array.from(svgElements);
+}
+
+function getMultipleIconsErrorText(iconDescription: string): string {
+ return `Found multiple ${iconDescription}`;
+}
+
+function getMissingIconsErrorText(iconDescription: string): string {
+ return `Found no ${iconDescription}`;
+}
+
+const [, getAllIcons] = buildQueries<[]>(
+ queryAllIcons,
+ () => getMultipleIconsErrorText("icons"),
+ () => getMissingIconsErrorText("icons")
+);
+
+const iconQueries = {
+ getAllIcons: () => getAllIcons(document.body)
+};
+
+function queryAllCheckboxIcons(container: HTMLElement): HTMLElement[] {
+ const svgElements: NodeListOf = container.querySelectorAll(
+ `svg.MuiSvgIcon-root[data-testid="CheckBoxIcon"]`
+ );
+ return Array.from(svgElements);
+}
+
+const [, , getCheckboxIcon] = buildQueries<[]>(
+ queryAllCheckboxIcons,
+ () => getMultipleIconsErrorText("CheckBox icons"),
+ () => getMissingIconsErrorText("CheckBox icons")
+);
+
+const checkboxIconQueries = {
+ getCheckboxIcon: () => getCheckboxIcon(document.body)
+};
+
+function queryAllErrorIcons(container: HTMLElement): HTMLElement[] {
+ const svgElements: NodeListOf = container.querySelectorAll(
+ `svg.MuiSvgIcon-root[data-testid="ErrorIcon"]`
+ );
+ return Array.from(svgElements);
+}
+
+const [, , getErrorIcon] = buildQueries<[]>(
+ queryAllErrorIcons,
+ () => getMultipleIconsErrorText("Error icons"),
+ () => getMissingIconsErrorText("Error icons")
+);
+
+const errorIconQueries = {
+ getErrorIcon: () => getErrorIcon(document.body)
+};
+
+function queryAllPendingIcons(container: HTMLElement): HTMLElement[] {
+ const svgElements: NodeListOf = container.querySelectorAll(
+ `svg.MuiSvgIcon-root[data-testid="DoNotDisturbAltIcon"]`
+ );
+ return Array.from(svgElements);
+}
+
+const [, , getPendingIcon] = buildQueries<[]>(
+ queryAllPendingIcons,
+ () => getMultipleIconsErrorText("Pending icons"),
+ () => getMissingIconsErrorText("Pending icons")
+);
+
+const pendingIconQueries = {
+ getPendingIcon: () => getPendingIcon(document.body)
+};
+
+export const screen = {
+ ...classicScreen,
+ ...iconQueries,
+ ...checkboxIconQueries,
+ ...errorIconQueries,
+ ...pendingIconQueries
+};
diff --git a/new/src/wordProcessor.ts b/new/src/wordProcessor.ts
index dab08717..64e73dab 100644
--- a/new/src/wordProcessor.ts
+++ b/new/src/wordProcessor.ts
@@ -14,7 +14,7 @@ export function processWords(words: Word[] | string | Word | undefined) {
function processArray(array: Word[]) {
if (array.length > 0) {
- return processWordArray(array as Word[]);
+ return processWordArray(array);
}
return "";
}