- Header Context (Dark Text)
+ Create Flow Context
- Default:
-
-
-
- Medium:
-
-
-
- Large:
-
-
-
- XLarge:
-
+ CreateFlow:
+
@@ -231,38 +164,7 @@ export const HeaderContext = {
docs: {
description: {
story:
- "Header context showing dark text variants. These are used on light backgrounds.",
- },
- },
- },
-};
-
-export const FooterContext = {
- args: {},
- render: () => (
-
-
-
- Footer Context (Larger Sizes)
-
-
-
- Default:
-
-
-
- Large:
-
-
-
-
-
- ),
- parameters: {
- docs: {
- description: {
- story:
- "Footer context showing larger size variants for footer placement.",
+ "Create flow context showing responsive logo. Used in CreateFlowTopNav component.",
},
},
},
diff --git a/stories/utility/CreateFlowFooter.stories.js b/stories/utility/CreateFlowFooter.stories.js
new file mode 100644
index 0000000..4943f23
--- /dev/null
+++ b/stories/utility/CreateFlowFooter.stories.js
@@ -0,0 +1,56 @@
+import CreateFlowFooter from "../../app/components/utility/CreateFlowFooter";
+import Button from "../../app/components/buttons/Button";
+
+export default {
+ title: "Components/Utility/CreateFlowFooter",
+ component: CreateFlowFooter,
+ parameters: {
+ layout: "fullscreen",
+ docs: {
+ description: {
+ component:
+ "Footer component for the create rule flow with progress bar and buttons.",
+ },
+ },
+ },
+ argTypes: {
+ progressBar: {
+ control: "boolean",
+ description: "Whether to show the progress bar",
+ },
+ secondButton: {
+ control: false,
+ description: "The second button (typically Next) to display on the right side",
+ },
+ },
+ tags: ["autodocs"],
+};
+
+export const Default = {
+ args: {
+ progressBar: true,
+ secondButton: (
+
+ Next
+
+ ),
+ },
+};
+
+export const WithoutProgressBar = {
+ args: {
+ progressBar: false,
+ secondButton: (
+
+ Next
+
+ ),
+ },
+};
+
+export const WithoutSecondButton = {
+ args: {
+ progressBar: true,
+ secondButton: undefined,
+ },
+};
diff --git a/stories/utility/CreateFlowTopNav.stories.js b/stories/utility/CreateFlowTopNav.stories.js
new file mode 100644
index 0000000..918876d
--- /dev/null
+++ b/stories/utility/CreateFlowTopNav.stories.js
@@ -0,0 +1,65 @@
+import CreateFlowTopNav from "../../app/components/utility/CreateFlowTopNav";
+
+export default {
+ title: "Components/Utility/CreateFlowTopNav",
+ component: CreateFlowTopNav,
+ parameters: {
+ layout: "fullscreen",
+ docs: {
+ description: {
+ component:
+ "Top navigation bar for the create rule flow. Includes logo and action buttons (Share, Export, Edit, Exit/Save & Exit).",
+ },
+ },
+ },
+ argTypes: {
+ hasShare: {
+ control: "boolean",
+ description: "Whether to show the Share button",
+ },
+ hasExport: {
+ control: "boolean",
+ description: "Whether to show the Export button",
+ },
+ hasEdit: {
+ control: "boolean",
+ description: "Whether to show the Edit button",
+ },
+ loggedIn: {
+ control: "boolean",
+ description: "Whether the user is logged in (affects Exit button text)",
+ },
+ onShare: { action: "share clicked" },
+ onExport: { action: "export clicked" },
+ onEdit: { action: "edit clicked" },
+ onExit: { action: "exit clicked" },
+ },
+ tags: ["autodocs"],
+};
+
+export const Default = {
+ args: {
+ hasShare: false,
+ hasExport: false,
+ hasEdit: false,
+ loggedIn: false,
+ },
+};
+
+export const AllButtons = {
+ args: {
+ hasShare: true,
+ hasExport: true,
+ hasEdit: true,
+ loggedIn: false,
+ },
+};
+
+export const LoggedIn = {
+ args: {
+ hasShare: true,
+ hasExport: true,
+ hasEdit: true,
+ loggedIn: true,
+ },
+};
diff --git a/tests/components/CreateFlowFooter.test.tsx b/tests/components/CreateFlowFooter.test.tsx
new file mode 100644
index 0000000..971c0c8
--- /dev/null
+++ b/tests/components/CreateFlowFooter.test.tsx
@@ -0,0 +1,70 @@
+import React from "react";
+import { describe, it, expect } from "vitest";
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom/vitest";
+import CreateFlowFooter from "../../app/components/utility/CreateFlowFooter";
+import Button from "../../app/components/buttons/Button";
+import {
+ componentTestSuite,
+ ComponentTestSuiteConfig,
+} from "../utils/componentTestSuite";
+
+type CreateFlowFooterProps = React.ComponentProps
;
+
+const baseProps: CreateFlowFooterProps = {};
+
+const config: ComponentTestSuiteConfig = {
+ component: CreateFlowFooter,
+ name: "CreateFlowFooter",
+ props: baseProps,
+ requiredProps: [],
+ optionalProps: {
+ secondButton: Next ,
+ progressBar: true,
+ className: "test-class",
+ },
+ primaryRole: "contentinfo",
+ testCases: {
+ renders: true,
+ accessibility: true,
+ keyboardNavigation: false,
+ disabledState: false,
+ errorState: false,
+ },
+};
+
+componentTestSuite(config);
+
+describe("CreateFlowFooter (behavioral tests)", () => {
+ it("renders Back button", () => {
+ render( );
+ const backButton = screen.getByRole("button", { name: "Back" });
+ expect(backButton).toBeInTheDocument();
+ });
+
+ it("renders progress bar when progressBar is true", () => {
+ render( );
+ const footer = screen.getByRole("contentinfo", { name: "Create Flow Footer" });
+ expect(footer).toBeInTheDocument();
+ });
+
+ it("does not render progress bar when progressBar is false", () => {
+ const { container } = render( );
+ const progressBar = container.querySelector('[role="progressbar"]');
+ expect(progressBar).not.toBeInTheDocument();
+ });
+
+ it("renders secondButton when provided", () => {
+ const secondButton = Next ;
+ render( );
+ const nextButton = screen.getByRole("button", { name: "Next" });
+ expect(nextButton).toBeInTheDocument();
+ });
+
+ it("does not render secondButton when not provided", () => {
+ render( );
+ const buttons = screen.getAllByRole("button");
+ expect(buttons).toHaveLength(1);
+ expect(buttons[0]).toHaveTextContent("Back");
+ });
+});
diff --git a/tests/components/CreateFlowTopNav.test.tsx b/tests/components/CreateFlowTopNav.test.tsx
new file mode 100644
index 0000000..6cd3b2e
--- /dev/null
+++ b/tests/components/CreateFlowTopNav.test.tsx
@@ -0,0 +1,119 @@
+import React from "react";
+import { describe, it, expect, vi } from "vitest";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import "@testing-library/jest-dom/vitest";
+import CreateFlowTopNav from "../../app/components/utility/CreateFlowTopNav";
+import {
+ componentTestSuite,
+ ComponentTestSuiteConfig,
+} from "../utils/componentTestSuite";
+
+// Mock next/navigation
+vi.mock("next/navigation", () => ({
+ useRouter: () => ({
+ push: vi.fn(),
+ replace: vi.fn(),
+ prefetch: vi.fn(),
+ back: vi.fn(),
+ forward: vi.fn(),
+ refresh: vi.fn(),
+ }),
+}));
+
+type CreateFlowTopNavProps = React.ComponentProps;
+
+const baseProps: CreateFlowTopNavProps = {};
+
+const config: ComponentTestSuiteConfig = {
+ component: CreateFlowTopNav,
+ name: "CreateFlowTopNav",
+ props: baseProps,
+ requiredProps: [],
+ optionalProps: {
+ hasShare: true,
+ hasExport: true,
+ hasEdit: true,
+ loggedIn: true,
+ onShare: vi.fn(),
+ onExport: vi.fn(),
+ onEdit: vi.fn(),
+ onExit: vi.fn(),
+ className: "test-class",
+ },
+ primaryRole: "banner",
+ testCases: {
+ renders: true,
+ accessibility: true,
+ keyboardNavigation: false,
+ disabledState: false,
+ errorState: false,
+ },
+};
+
+componentTestSuite(config);
+
+describe("CreateFlowTopNav (behavioral tests)", () => {
+ it("renders Exit button by default", () => {
+ render( );
+ const exitButton = screen.getByRole("button", { name: "Exit" });
+ expect(exitButton).toBeInTheDocument();
+ });
+
+ it("shows Save & Exit when loggedIn is true", () => {
+ render( );
+ const exitButton = screen.getByRole("button", { name: "Save & Exit" });
+ expect(exitButton).toBeInTheDocument();
+ });
+
+ it("shows Exit when loggedIn is false", () => {
+ render( );
+ const exitButton = screen.getByRole("button", { name: "Exit" });
+ expect(exitButton).toBeInTheDocument();
+ });
+
+ it("renders Share button when hasShare is true", () => {
+ render( );
+ const shareButton = screen.getByRole("button", { name: "Share" });
+ expect(shareButton).toBeInTheDocument();
+ });
+
+ it("does not render Share button when hasShare is false", () => {
+ render( );
+ expect(screen.queryByRole("button", { name: "Share" })).not.toBeInTheDocument();
+ });
+
+ it("renders Export button when hasExport is true", () => {
+ render( );
+ const exportButton = screen.getByRole("button", { name: "Export" });
+ expect(exportButton).toBeInTheDocument();
+ });
+
+ it("renders Edit button when hasEdit is true", () => {
+ render( );
+ const editButton = screen.getByRole("button", { name: "Edit" });
+ expect(editButton).toBeInTheDocument();
+ });
+
+ it("calls onExit when Exit button is clicked", async () => {
+ const user = userEvent.setup();
+ const handleExit = vi.fn();
+ render( );
+
+ const exitButton = screen.getByRole("button", { name: "Exit" });
+ await user.click(exitButton);
+
+ expect(handleExit).toHaveBeenCalledTimes(1);
+ });
+
+ it("calls onShare when Share button is clicked", async () => {
+ const user = userEvent.setup();
+ const handleShare = vi.fn();
+ render( );
+
+ const shareButton = screen.getByRole("button", { name: "Share" });
+ await user.click(shareButton);
+
+ expect(handleShare).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/tests/components/Logo.test.tsx b/tests/components/Logo.test.tsx
index 5ade664..ef846d0 100644
--- a/tests/components/Logo.test.tsx
+++ b/tests/components/Logo.test.tsx
@@ -46,19 +46,26 @@ describe("Logo (behavioral tests)", () => {
});
it("hides text when showText is false", () => {
- render( );
- expect(screen.queryByText("CommunityRule")).not.toBeInTheDocument();
+ const { container } = render( );
+ const textElement = container.querySelector('.hidden');
+ expect(textElement).toBeInTheDocument();
expect(screen.getByAltText("CommunityRule Logo Icon")).toBeInTheDocument();
});
it("renders with different size variants", () => {
- const { rerender } = render( );
+ const { rerender } = render( );
expect(screen.getByRole("link")).toBeInTheDocument();
rerender( );
expect(screen.getByRole("link")).toBeInTheDocument();
- rerender( );
+ rerender( );
+ expect(screen.getByRole("link")).toBeInTheDocument();
+
+ rerender( );
+ expect(screen.getByRole("link")).toBeInTheDocument();
+
+ rerender( );
expect(screen.getByRole("link")).toBeInTheDocument();
});
});