Profile page UI and functionality implemented

This commit is contained in:
adilallo
2026-04-25 17:57:58 -06:00
parent 7dd2562bae
commit 68517796a9
103 changed files with 4439 additions and 1476 deletions
+53 -27
View File
@@ -1,47 +1,73 @@
import { describe, it, expect } from "vitest";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { renderWithProviders as render, screen } from "../utils/test-utils";
import "@testing-library/jest-dom/vitest";
import { CompletedScreen } from "../../app/(app)/create/screens/completed/CompletedScreen";
import { CREATE_FLOW_LAST_PUBLISHED_KEY } from "../../lib/create/lastPublishedRule";
const storedRuleFixture = {
id: "rule-fixture-1",
title: "Fixture Community Rule",
summary: "A short summary for tests.",
document: {
sections: [
{
categoryName: "Values",
entries: [
{
title: "Fixture value title",
body: "Fixture value body text for the test document.",
},
],
},
{
categoryName: "Communication",
entries: [
{
title: "Fixture channel",
body: "How we talk to each other.",
},
],
},
],
},
};
describe("CompletedScreen", () => {
beforeEach(() => {
sessionStorage.removeItem(CREATE_FLOW_LAST_PUBLISHED_KEY);
});
afterEach(() => {
sessionStorage.removeItem(CREATE_FLOW_LAST_PUBLISHED_KEY);
});
it("renders without crashing", () => {
render(<CompletedScreen />);
expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
});
it("renders HeaderLockup with expected title", () => {
it("shows no placeholder title or document when session is empty", () => {
render(<CompletedScreen />);
const h1 = screen.getByRole("heading", { level: 1 });
expect(h1.textContent).toBe("");
expect(screen.queryByText("Values")).not.toBeInTheDocument();
});
it("renders header and document from sessionStorage", () => {
sessionStorage.setItem(
CREATE_FLOW_LAST_PUBLISHED_KEY,
JSON.stringify(storedRuleFixture),
);
render(<CompletedScreen />);
expect(
screen.getByRole("heading", {
name: "Mutual Aid Mondays",
name: "Fixture Community Rule",
}),
).toBeInTheDocument();
});
it("renders HeaderLockup with expected description", () => {
render(<CompletedScreen />);
expect(
screen.getByText(
/Mutual Aid Monday is a grassroots community in Denver, founded in November 2020 by Kelsang Virya, dedicated to supporting neighbors experiencing homelessness./i,
),
).toBeInTheDocument();
});
it("renders Community Rule document with section labels", () => {
render(<CompletedScreen />);
expect(screen.getByText("A short summary for tests.")).toBeInTheDocument();
expect(screen.getByText("Values")).toBeInTheDocument();
expect(screen.getByText("Communication")).toBeInTheDocument();
expect(screen.getByText("Membership")).toBeInTheDocument();
expect(screen.getByText("Decision-making")).toBeInTheDocument();
expect(screen.getByText("Conflict management")).toBeInTheDocument();
});
it("renders document entry titles", () => {
render(<CompletedScreen />);
expect(screen.getByText("Solidarity Forever")).toBeInTheDocument();
expect(screen.getByText("Shared Leadership")).toBeInTheDocument();
expect(screen.getByText("Organizing Offline")).toBeInTheDocument();
expect(screen.getByText("Circular Food Systems")).toBeInTheDocument();
expect(screen.getByText("Fixture value title")).toBeInTheDocument();
});
it("renders toast alert when page loads", () => {
+2 -2
View File
@@ -59,11 +59,11 @@ describe("Create", () => {
}
});
it("uses login yellow backdrop when backdropVariant is loginYellow", () => {
it("uses blurred yellow backdrop when backdropVariant is blurredYellow", () => {
renderWithProviders(
<Create
{...defaultProps}
backdropVariant="loginYellow"
backdropVariant="blurredYellow"
headerContent={<div>Header</div>}
/>,
);
+60
View File
@@ -0,0 +1,60 @@
import React from "react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { screen, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom/vitest";
import { renderWithProviders } from "../utils/test-utils";
import Dialog from "../../app/components/modals/Dialog";
type Props = React.ComponentProps<typeof Dialog>;
describe("Dialog", () => {
const defaultProps: Props = {
isOpen: true,
onClose: vi.fn(),
title: "Confirm action",
description: "This cannot be undone.",
footer: (
<>
<button type="button">Cancel</button>
<button type="button">Confirm</button>
</>
),
};
beforeEach(() => {
vi.clearAllMocks();
});
it("renders when isOpen is true", () => {
renderWithProviders(<Dialog {...defaultProps} />);
expect(screen.getByRole("dialog")).toBeInTheDocument();
expect(screen.getByText("Confirm action")).toBeInTheDocument();
expect(screen.getByText("This cannot be undone.")).toBeInTheDocument();
expect(screen.getByText("Cancel")).toBeInTheDocument();
expect(screen.getByText("Confirm")).toBeInTheDocument();
});
it("does not render when isOpen is false", () => {
renderWithProviders(<Dialog {...defaultProps} isOpen={false} />);
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
it("calls onClose when close control is activated", () => {
const onClose = vi.fn();
renderWithProviders(<Dialog {...defaultProps} onClose={onClose} />);
fireEvent.click(screen.getByLabelText("Close dialog"));
expect(onClose).toHaveBeenCalledTimes(1);
});
it("calls onClose when Escape is pressed", () => {
const onClose = vi.fn();
renderWithProviders(<Dialog {...defaultProps} onClose={onClose} />);
fireEvent.keyDown(document, { key: "Escape" });
expect(onClose).toHaveBeenCalledTimes(1);
});
it("locks body scroll when open", () => {
renderWithProviders(<Dialog {...defaultProps} />);
expect(document.body.style.overflow).toBe("hidden");
});
});
+8
View File
@@ -89,4 +89,12 @@ describe("HeaderLockup (behavioral tests)", () => {
render(<HeaderLockup title="Test Title" justification="left" size="L" />);
expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
});
it("forwards titleId to the h1", () => {
render(<HeaderLockup title="Test Title" titleId="profile-welcome" />);
expect(screen.getByRole("heading", { level: 1 })).toHaveAttribute(
"id",
"profile-welcome",
);
});
});
+7
View File
@@ -46,4 +46,11 @@ describe("TextInput (size tests)", () => {
expect(input).toHaveClass("h-[32px]");
});
it("forwards maxLength to the native input", () => {
const { container } = render(
<TextInput label="Test" maxLength={200} />,
);
const input = container.querySelector("input");
expect(input).toHaveAttribute("maxLength", "200");
});
});
+90
View File
@@ -0,0 +1,90 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi } from "vitest";
import List from "../../../app/components/layout/List";
import {
componentTestSuite,
type ComponentTestSuiteConfig,
} from "../../utils/componentTestSuite";
const items = [
{
id: "a",
title: "First",
description: "First description",
href: "/first",
},
{
id: "b",
title: "Second",
description: "Second description",
onClick: () => {},
},
];
type Props = React.ComponentProps<typeof List>;
const config: ComponentTestSuiteConfig<Props> = {
component: List,
name: "List",
props: { items } as Props,
primaryRole: "list",
testCases: {
renders: true,
accessibility: true,
keyboardNavigation: false,
disabledState: false,
errorState: false,
},
};
describe("List", () => {
componentTestSuite<Props>(config);
it("renders a link row when item has href", () => {
render(
<List
items={[
{
id: "1",
title: "T",
description: "D",
href: "/x",
},
]}
/>,
);
expect(screen.getByRole("link", { name: /T/ })).toHaveAttribute("href", "/x");
});
it("calls onClick for button rows", async () => {
const user = userEvent.setup();
const onClick = vi.fn();
render(
<List
items={[
{
id: "1",
title: "Action",
description: "Desc",
onClick,
},
]}
/>,
);
await user.click(screen.getByRole("button", { name: /Action/ }));
expect(onClick).toHaveBeenCalledTimes(1);
});
it("applies size s and l Figma data attributes on the list root", () => {
const { container: a, rerender } = render(<List items={items} size="s" />);
expect(
a.querySelector('[data-figma-node="21863:45631"]'),
).toBeInTheDocument();
rerender(<List items={items} size="l" />);
expect(
a.querySelector('[data-figma-node="21844:4405"]'),
).toBeInTheDocument();
});
});
@@ -0,0 +1,84 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi } from "vitest";
import ListEntry from "../../../app/components/layout/ListEntry";
import {
componentTestSuite,
type ComponentTestSuiteConfig,
} from "../../utils/componentTestSuite";
type Props = React.ComponentProps<typeof ListEntry>;
const base: Props = {
title: "Item",
description: "Description",
href: "#",
topDivider: false,
bottomDivider: true,
};
const config: ComponentTestSuiteConfig<Props> = {
component: ListEntry,
name: "ListEntry",
props: base,
primaryRole: "link",
testCases: {
renders: true,
accessibility: true,
keyboardNavigation: true,
disabledState: false,
errorState: false,
},
};
describe("ListEntry", () => {
componentTestSuite<Props>(config);
it("uses Base / Interactive Figma id for size m", () => {
const { container } = render(
<ListEntry
title="A"
description="B"
size="m"
href="#"
topDivider={false}
bottomDivider={false}
/>,
);
expect(
container.querySelector('[data-figma-node="21863:45422"]'),
).toBeInTheDocument();
});
it("omits description when showDescription is false", () => {
render(
<ListEntry
title="Only"
description="Hidden"
showDescription={false}
href="#"
topDivider={false}
bottomDivider={false}
/>,
);
expect(screen.getByRole("link", { name: "Only" })).toBeInTheDocument();
expect(screen.queryByText("Hidden")).not.toBeInTheDocument();
});
it("button fires onClick", async () => {
const user = userEvent.setup();
const onClick = vi.fn();
render(
<ListEntry
title="Tap"
description="D"
onClick={onClick}
topDivider={false}
bottomDivider={false}
/>,
);
await user.click(screen.getByRole("button", { name: /Tap/ }));
expect(onClick).toHaveBeenCalledTimes(1);
});
});
+75
View File
@@ -0,0 +1,75 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi } from "vitest";
import "@testing-library/jest-dom/vitest";
import Link from "../../../app/components/navigation/Link";
vi.mock("next/link", () => ({
default: ({
children,
href,
...props
}: {
children?: React.ReactNode;
href?: string;
[key: string]: unknown;
}) => (
<a href={href} {...props}>
{children}
</a>
),
}));
const FIGMA = "21861:21428";
describe("Link (Navigation)", () => {
it("renders an anchor with href and data-figma-node", () => {
const { container } = render(
<Link
href="/rules/1"
variant="paragraph"
type="primary"
theme="light"
>
View
</Link>,
);
const a = screen.getByRole("link", { name: /view/i });
expect(a).toHaveAttribute("href", "/rules/1");
expect(a).toHaveAttribute("data-figma-node", FIGMA);
expect(container.querySelector("a")?.className).toMatch(
/text-\[var\(--color-link-primary\)\]/,
);
});
it("renders a button when href is omitted", async () => {
const user = userEvent.setup();
const onClick = vi.fn();
render(
<Link
variant="paragraph"
type="primary"
theme="light"
onClick={onClick}
>
Delete
</Link>,
);
const btn = screen.getByRole("button", { name: /delete/i });
expect(btn).toHaveAttribute("data-figma-node", FIGMA);
expect(btn).toHaveAttribute("type", "button");
await user.click(btn);
expect(onClick).toHaveBeenCalledTimes(1);
});
it("applies secondary + dark classes for that combination", () => {
const { container } = render(
<Link href="#" variant="paragraph" type="secondary" theme="dark">
More
</Link>,
);
const el = container.querySelector("a");
expect(el?.className).toMatch(/text-\[var\(--color-link-invert-secondary\)\]/);
});
});
+53
View File
@@ -0,0 +1,53 @@
import { describe, it, expect } from "vitest";
import { render } from "@testing-library/react";
import Divider from "../../../app/components/utility/Divider";
import {
componentTestSuite,
type ComponentTestSuiteConfig,
} from "../../utils/componentTestSuite";
type Props = React.ComponentProps<typeof Divider>;
const config: ComponentTestSuiteConfig<Props> = {
component: Divider,
name: "Divider",
props: {} as Props,
testCases: {
renders: true,
accessibility: true,
keyboardNavigation: false,
disabledState: false,
errorState: false,
},
};
describe("Divider", () => {
componentTestSuite<Props>(config);
it("renders horizontal content line with Figma line node", () => {
const { container } = render(
<Divider type="content" orientation="horizontal" />,
);
expect(
container.querySelector('[data-figma-node="6894:22989"]'),
).toBeInTheDocument();
});
it("renders menu horizontal with tertiary line", () => {
const { container } = render(
<Divider type="menu" orientation="horizontal" />,
);
const line = container.querySelector('[data-figma-node="2002:30856"]');
expect(line).toBeInTheDocument();
expect(line).toHaveClass("bg-[var(--color-border-default-tertiary)]");
});
it("renders vertical content bar", () => {
const { container } = render(
<Divider type="content" orientation="vertical" />,
);
expect(
container.querySelector('[data-figma-node="6894:22990"]'),
).toBeInTheDocument();
});
});