Integration tests added
This commit is contained in:
@@ -0,0 +1,353 @@
|
|||||||
|
import { render, screen, cleanup } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { vi, describe, test, expect, afterEach } from "vitest";
|
||||||
|
import HeroBanner from "../../app/components/HeroBanner";
|
||||||
|
import NumberedCards from "../../app/components/NumberedCards";
|
||||||
|
import RuleStack from "../../app/components/RuleStack";
|
||||||
|
import FeatureGrid from "../../app/components/FeatureGrid";
|
||||||
|
import QuoteBlock from "../../app/components/QuoteBlock";
|
||||||
|
import AskOrganizer from "../../app/components/AskOrganizer";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Component Interactions Integration", () => {
|
||||||
|
test("hero banner and numbered cards work together to explain the process", () => {
|
||||||
|
const heroData = {
|
||||||
|
title: "Collaborate",
|
||||||
|
subtitle: "with clarity",
|
||||||
|
description:
|
||||||
|
"Help your community make important decisions in a way that reflects its unique values.",
|
||||||
|
ctaText: "Learn how CommunityRule works",
|
||||||
|
ctaHref: "#",
|
||||||
|
};
|
||||||
|
|
||||||
|
const numberedCardsData = {
|
||||||
|
title: "How CommunityRule works",
|
||||||
|
subtitle: "Here's a quick overview of the process, from start to finish.",
|
||||||
|
cards: [
|
||||||
|
{
|
||||||
|
text: "Document how your community makes decisions",
|
||||||
|
iconShape: "blob",
|
||||||
|
iconColor: "green",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Build an operating manual for a successful community",
|
||||||
|
iconShape: "gear",
|
||||||
|
iconColor: "purple",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Get a link to your manual for your group to review and evolve",
|
||||||
|
iconShape: "star",
|
||||||
|
iconColor: "orange",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<HeroBanner {...heroData} />
|
||||||
|
<NumberedCards {...numberedCardsData} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hero introduces the concept
|
||||||
|
expect(
|
||||||
|
screen.getByText(/Help your community make important decisions/)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Numbered cards explain the process
|
||||||
|
expect(screen.getByText("How CommunityRule works")).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText("Document how your community makes decisions")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText("Build an operating manual for a successful community")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
"Get a link to your manual for your group to review and evolve"
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rule stack and feature grid complement each other", () => {
|
||||||
|
const featureGridData = {
|
||||||
|
title: "We've got your back, every step of the way",
|
||||||
|
subtitle:
|
||||||
|
"Use our toolkit to improve, document, and evolve your organization.",
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<RuleStack />
|
||||||
|
<FeatureGrid {...featureGridData} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rule stack shows governance options
|
||||||
|
expect(screen.getByText("Consensus clusters")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Elected Board")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Consensus")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Petition")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Feature grid provides support context
|
||||||
|
expect(
|
||||||
|
screen.getByText("We've got your back, every step of the way")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
"Use our toolkit to improve, document, and evolve your organization."
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("quote block provides social proof for the entire application", () => {
|
||||||
|
render(<QuoteBlock />);
|
||||||
|
|
||||||
|
// Quote provides credibility and social proof
|
||||||
|
expect(
|
||||||
|
screen.getByText(/The rules of decision-making must be open/)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Should have proper attribution
|
||||||
|
expect(screen.getByText("Jo Freeman")).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText("The Tyranny of Structurelessness")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ask organizer provides help context for all components", () => {
|
||||||
|
const askOrganizerData = {
|
||||||
|
title: "Still have questions?",
|
||||||
|
subtitle: "Get answers from an experienced organizer",
|
||||||
|
buttonText: "Ask an organizer",
|
||||||
|
buttonHref: "#contact",
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<AskOrganizer {...askOrganizerData} />);
|
||||||
|
|
||||||
|
// Provides help for users who need assistance
|
||||||
|
expect(screen.getByText("Still have questions?")).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText("Get answers from an experienced organizer")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("link", { name: /Ask an organizer/i })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("all components maintain consistent styling and branding", () => {
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<HeroBanner
|
||||||
|
title="Test"
|
||||||
|
subtitle="Test"
|
||||||
|
description="Test description"
|
||||||
|
ctaText="Test CTA"
|
||||||
|
/>
|
||||||
|
<NumberedCards
|
||||||
|
title="Test Cards"
|
||||||
|
subtitle="Test subtitle"
|
||||||
|
cards={[{ text: "Test card", iconShape: "blob", iconColor: "green" }]}
|
||||||
|
/>
|
||||||
|
<RuleStack />
|
||||||
|
<FeatureGrid title="Test Features" subtitle="Test subtitle" />
|
||||||
|
<QuoteBlock />
|
||||||
|
<AskOrganizer
|
||||||
|
title="Test Help"
|
||||||
|
subtitle="Test help subtitle"
|
||||||
|
buttonText="Test Help Button"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// All components should render without errors
|
||||||
|
expect(screen.getAllByText("Test").length).toBeGreaterThan(0);
|
||||||
|
expect(screen.getByText("Test Cards")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Consensus clusters")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Test Features")).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(/The rules of decision-making must be open/)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Test Help")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("components handle data flow and prop passing correctly", () => {
|
||||||
|
const testData = {
|
||||||
|
hero: {
|
||||||
|
title: "Test Hero",
|
||||||
|
subtitle: "Test Subtitle",
|
||||||
|
description: "Test description",
|
||||||
|
ctaText: "Test CTA",
|
||||||
|
ctaHref: "/test",
|
||||||
|
},
|
||||||
|
cards: {
|
||||||
|
title: "Test Cards",
|
||||||
|
subtitle: "Test subtitle",
|
||||||
|
cards: [
|
||||||
|
{ text: "Card 1", iconShape: "blob", iconColor: "green" },
|
||||||
|
{ text: "Card 2", iconShape: "gear", iconColor: "purple" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
title: "Test Features",
|
||||||
|
subtitle: "Test features subtitle",
|
||||||
|
},
|
||||||
|
help: {
|
||||||
|
title: "Test Help",
|
||||||
|
subtitle: "Test help subtitle",
|
||||||
|
buttonText: "Test Help Button",
|
||||||
|
buttonHref: "/help",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<HeroBanner {...testData.hero} />
|
||||||
|
<NumberedCards {...testData.cards} />
|
||||||
|
<FeatureGrid {...testData.features} />
|
||||||
|
<AskOrganizer {...testData.help} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify all data is passed correctly
|
||||||
|
expect(screen.getByText("Test Hero")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Test Subtitle")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Test description")).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getAllByRole("button", { name: "Test CTA" }).length
|
||||||
|
).toBeGreaterThan(0);
|
||||||
|
expect(screen.getByText("Test Cards")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Card 1")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Card 2")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Test Features")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Test Help")).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("link", { name: /Test Help Button/i })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("components work together to create a cohesive user experience", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<HeroBanner
|
||||||
|
title="Collaborate"
|
||||||
|
subtitle="with clarity"
|
||||||
|
description="Help your community make important decisions."
|
||||||
|
ctaText="Learn more"
|
||||||
|
ctaHref="#learn"
|
||||||
|
/>
|
||||||
|
<NumberedCards
|
||||||
|
title="How it works"
|
||||||
|
subtitle="Simple steps to get started"
|
||||||
|
cards={[
|
||||||
|
{ text: "Step 1", iconShape: "blob", iconColor: "green" },
|
||||||
|
{ text: "Step 2", iconShape: "gear", iconColor: "purple" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<RuleStack />
|
||||||
|
<FeatureGrid title="Features" subtitle="Everything you need" />
|
||||||
|
<QuoteBlock />
|
||||||
|
<AskOrganizer
|
||||||
|
title="Need help?"
|
||||||
|
subtitle="We're here to help"
|
||||||
|
buttonText="Contact us"
|
||||||
|
buttonHref="#contact"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test interaction flow
|
||||||
|
const learnButtons = screen.getAllByRole("button", { name: "Learn more" });
|
||||||
|
await user.click(learnButtons[0]);
|
||||||
|
|
||||||
|
const createButtons = screen.getAllByRole("button", {
|
||||||
|
name: "Create CommunityRule",
|
||||||
|
});
|
||||||
|
if (createButtons.length > 0) {
|
||||||
|
await user.click(createButtons[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contactButton = screen.getByRole("link", { name: /Contact us/i });
|
||||||
|
await user.click(contactButton);
|
||||||
|
|
||||||
|
// All components should remain functional
|
||||||
|
expect(screen.getByText("Collaborate")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("How it works")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Consensus clusters")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Features")).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(/The rules of decision-making must be open/)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Need help?")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("components handle edge cases and missing data gracefully", () => {
|
||||||
|
// Test with minimal data
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<HeroBanner title="Minimal Hero" />
|
||||||
|
<NumberedCards title="Minimal Cards" cards={[]} />
|
||||||
|
<FeatureGrid title="Minimal Features" />
|
||||||
|
<AskOrganizer title="Minimal Help" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Components should render without crashing
|
||||||
|
expect(screen.getByText("Minimal Hero")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Minimal Cards")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Minimal Features")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Minimal Help")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("components maintain accessibility when used together", () => {
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<HeroBanner
|
||||||
|
title="Accessible Hero"
|
||||||
|
subtitle="Accessible Subtitle"
|
||||||
|
description="Accessible description"
|
||||||
|
ctaText="Accessible CTA"
|
||||||
|
/>
|
||||||
|
<NumberedCards
|
||||||
|
title="Accessible Cards"
|
||||||
|
subtitle="Accessible subtitle"
|
||||||
|
cards={[
|
||||||
|
{ text: "Accessible card", iconShape: "blob", iconColor: "green" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<RuleStack />
|
||||||
|
<FeatureGrid
|
||||||
|
title="Accessible Features"
|
||||||
|
subtitle="Accessible features subtitle"
|
||||||
|
/>
|
||||||
|
<QuoteBlock />
|
||||||
|
<AskOrganizer
|
||||||
|
title="Accessible Help"
|
||||||
|
subtitle="Accessible help subtitle"
|
||||||
|
buttonText="Accessible Help Button"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for proper heading hierarchy
|
||||||
|
const headings = screen.getAllByRole("heading");
|
||||||
|
expect(headings.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Check for proper button roles
|
||||||
|
const buttons = screen.getAllByRole("button");
|
||||||
|
buttons.forEach((button) => {
|
||||||
|
expect(button).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for proper link roles
|
||||||
|
const links = screen.getAllByRole("link");
|
||||||
|
links.forEach((link) => {
|
||||||
|
expect(link).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
import { render, screen, cleanup } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { vi, describe, test, expect, afterEach } from "vitest";
|
||||||
|
import Header from "../../app/components/Header";
|
||||||
|
import Footer from "../../app/components/Footer";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Layout Integration", () => {
|
||||||
|
test("header and footer provide consistent branding", () => {
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that CommunityRule branding appears in both header and footer
|
||||||
|
const headerLogos = screen.getAllByAltText("CommunityRule Logo Icon");
|
||||||
|
expect(headerLogos.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Footer should have the organization name
|
||||||
|
expect(screen.getByText("Media Economies Design Lab")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("navigation is consistent between header and footer", () => {
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header navigation items
|
||||||
|
expect(
|
||||||
|
screen.getAllByRole("menuitem", { name: "Navigate to Use cases page" })
|
||||||
|
.length
|
||||||
|
).toBeGreaterThan(0);
|
||||||
|
expect(
|
||||||
|
screen.getAllByRole("menuitem", { name: "Navigate to Learn page" }).length
|
||||||
|
).toBeGreaterThan(0);
|
||||||
|
expect(
|
||||||
|
screen.getAllByRole("menuitem", { name: "Navigate to About page" }).length
|
||||||
|
).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Footer navigation items (should be present in footer as well)
|
||||||
|
const useCasesLinks = screen.getAllByRole("link", { name: "Use cases" });
|
||||||
|
const learnLinks = screen.getAllByRole("link", { name: "Learn" });
|
||||||
|
const aboutLinks = screen.getAllByRole("link", { name: "About" });
|
||||||
|
|
||||||
|
expect(useCasesLinks.length).toBeGreaterThan(0);
|
||||||
|
expect(learnLinks.length).toBeGreaterThan(0);
|
||||||
|
expect(aboutLinks.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("header navigation is interactive", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<Header />);
|
||||||
|
|
||||||
|
// Test navigation links
|
||||||
|
const useCasesLinks = screen.getAllByRole("menuitem", {
|
||||||
|
name: "Navigate to Use cases page",
|
||||||
|
});
|
||||||
|
const learnLinks = screen.getAllByRole("menuitem", {
|
||||||
|
name: "Navigate to Learn page",
|
||||||
|
});
|
||||||
|
const aboutLinks = screen.getAllByRole("menuitem", {
|
||||||
|
name: "Navigate to About page",
|
||||||
|
});
|
||||||
|
|
||||||
|
const useCasesLink = useCasesLinks[0];
|
||||||
|
const learnLink = learnLinks[0];
|
||||||
|
const aboutLink = aboutLinks[0];
|
||||||
|
|
||||||
|
expect(useCasesLink).toHaveAttribute("href", "#");
|
||||||
|
expect(learnLink).toHaveAttribute("href", "#");
|
||||||
|
expect(aboutLink).toHaveAttribute("href", "#");
|
||||||
|
|
||||||
|
// Test button interactions
|
||||||
|
const createRuleButtons = screen.getAllByRole("button", {
|
||||||
|
name: "Create a new rule with avatar decoration",
|
||||||
|
});
|
||||||
|
await user.click(createRuleButtons[0]);
|
||||||
|
expect(createRuleButtons[0]).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("footer provides contact and social information", () => {
|
||||||
|
render(<Footer />);
|
||||||
|
|
||||||
|
// Contact information
|
||||||
|
expect(screen.getByText("medlab@colorado.edu")).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("link", { name: "medlab@colorado.edu" })
|
||||||
|
).toHaveAttribute("href", "mailto:medlab@colorado.edu");
|
||||||
|
|
||||||
|
// Social media links
|
||||||
|
expect(
|
||||||
|
screen.getByRole("link", { name: "Follow us on Bluesky" })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("link", { name: "Follow us on GitLab" })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Legal links
|
||||||
|
expect(
|
||||||
|
screen.getByRole("link", { name: "Privacy Policy" })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("link", { name: "Terms of Service" })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("header provides proper authentication options", () => {
|
||||||
|
render(<Header />);
|
||||||
|
|
||||||
|
// Login button should be present
|
||||||
|
const loginButtons = screen.getAllByRole("menuitem", {
|
||||||
|
name: "Log in to your account",
|
||||||
|
});
|
||||||
|
const loginButton = loginButtons[0];
|
||||||
|
expect(loginButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Create rule button should be present
|
||||||
|
const createRuleButtons = screen.getAllByRole("button", {
|
||||||
|
name: "Create a new rule with avatar decoration",
|
||||||
|
});
|
||||||
|
expect(createRuleButtons.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("layout maintains proper semantic structure", () => {
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header should have banner role
|
||||||
|
const header = screen.getByRole("banner");
|
||||||
|
expect(header).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Navigation should be present
|
||||||
|
const navigation = screen.getByRole("navigation");
|
||||||
|
expect(navigation).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Footer should be present
|
||||||
|
const footer = screen.getByRole("contentinfo");
|
||||||
|
expect(footer).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("responsive design elements are present", () => {
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header should have responsive navigation elements
|
||||||
|
const headerContainer = screen.getByRole("banner");
|
||||||
|
expect(headerContainer).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Footer should have responsive layout
|
||||||
|
const footerContainer = screen.getByRole("contentinfo");
|
||||||
|
expect(footerContainer).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("all interactive elements have proper focus management", () => {
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get all interactive elements
|
||||||
|
const buttons = screen.getAllByRole("button");
|
||||||
|
const links = screen.getAllByRole("link");
|
||||||
|
|
||||||
|
// All buttons should be focusable
|
||||||
|
buttons.forEach((button) => {
|
||||||
|
expect(button).not.toHaveAttribute("tabindex", "-1");
|
||||||
|
});
|
||||||
|
|
||||||
|
// All links should be focusable
|
||||||
|
links.forEach((link) => {
|
||||||
|
expect(link).not.toHaveAttribute("tabindex", "-1");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("layout provides consistent user experience", () => {
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header provides main navigation
|
||||||
|
expect(screen.getByRole("navigation")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Footer provides additional navigation and contact info
|
||||||
|
expect(screen.getByText("Media Economies Design Lab")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("medlab@colorado.edu")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Both header and footer should have CommunityRule branding
|
||||||
|
const logos = screen.getAllByAltText("CommunityRule Logo Icon");
|
||||||
|
expect(logos.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("header and footer work together for complete navigation", () => {
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Main navigation in header
|
||||||
|
const headerNav = screen.getByRole("navigation");
|
||||||
|
expect(headerNav).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Additional navigation in footer
|
||||||
|
const footerLinks = screen.getAllByRole("link");
|
||||||
|
const navigationLinks = footerLinks.filter(
|
||||||
|
(link) =>
|
||||||
|
link.textContent?.includes("Use cases") ||
|
||||||
|
link.textContent?.includes("Learn") ||
|
||||||
|
link.textContent?.includes("About")
|
||||||
|
);
|
||||||
|
expect(navigationLinks.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Contact information in footer
|
||||||
|
expect(
|
||||||
|
screen.getByRole("link", { name: "medlab@colorado.edu" })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
import { render, screen, cleanup } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { vi, describe, test, expect, afterEach } from "vitest";
|
||||||
|
import Page from "../../app/page";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Page Flow Integration", () => {
|
||||||
|
test("renders complete page with all sections in correct order", () => {
|
||||||
|
render(<Page />);
|
||||||
|
|
||||||
|
// Hero Banner section
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", { name: "Collaborate" })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", { name: "with clarity" })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
"Help your community make important decisions in a way that reflects its unique values."
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
// Check that CTA button exists (multiple sizes for responsive design)
|
||||||
|
const ctaButtons = screen.getAllByRole("button", {
|
||||||
|
name: "Learn how CommunityRule works",
|
||||||
|
});
|
||||||
|
expect(ctaButtons.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Logo Wall section - check for partner logos
|
||||||
|
expect(screen.getByAltText("Food Not Bombs")).toBeInTheDocument();
|
||||||
|
expect(screen.getByAltText("Start COOP")).toBeInTheDocument();
|
||||||
|
expect(screen.getByAltText("Metagov")).toBeInTheDocument();
|
||||||
|
expect(screen.getByAltText("Open Civics")).toBeInTheDocument();
|
||||||
|
expect(screen.getByAltText("Mutual Aid CO")).toBeInTheDocument();
|
||||||
|
expect(screen.getByAltText("CU Boulder")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Numbered Cards section
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", { name: /How CommunityRule works/ })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
"Here's a quick overview of the process, from start to finish."
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText("Document how your community makes decisions")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText("Build an operating manual for a successful community")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
"Get a link to your manual for your group to review and evolve"
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Rule Stack section
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", { name: "Consensus clusters" })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", { name: "Elected Board" })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", { name: "Consensus" })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", { name: "Petition" })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Feature Grid section
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", {
|
||||||
|
name: "We've got your back, every step of the way",
|
||||||
|
})
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
"Use our toolkit to improve, document, and evolve your organization."
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Quote Block section
|
||||||
|
expect(
|
||||||
|
screen.getByText(/The rules of decision-making must be open/)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Ask Organizer section
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", { name: "Still have questions?" })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText("Get answers from an experienced organizer")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("link", { name: /Ask an organizer/i })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("hero banner CTA button is interactive", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<Page />);
|
||||||
|
|
||||||
|
// Get the first CTA button (multiple sizes for responsive design)
|
||||||
|
const ctaButtons = screen.getAllByRole("button", {
|
||||||
|
name: "Learn how CommunityRule works",
|
||||||
|
});
|
||||||
|
const ctaButton = ctaButtons[0];
|
||||||
|
expect(ctaButton).toBeInTheDocument();
|
||||||
|
// Button should be clickable (no href needed for button elements)
|
||||||
|
expect(ctaButton).toBeEnabled();
|
||||||
|
|
||||||
|
// Test button interaction
|
||||||
|
await user.click(ctaButton);
|
||||||
|
// Button should remain visible after click
|
||||||
|
expect(ctaButton).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("numbered cards display with correct icons and colors", () => {
|
||||||
|
render(<Page />);
|
||||||
|
|
||||||
|
// Check that all three cards are rendered
|
||||||
|
const cards = screen.getAllByText(
|
||||||
|
/Document how your community|Build an operating manual|Get a link to your manual/
|
||||||
|
);
|
||||||
|
expect(cards).toHaveLength(3);
|
||||||
|
|
||||||
|
// Check that section numbers are present
|
||||||
|
const sectionNumbers = screen.getAllByText(/1|2|3/);
|
||||||
|
expect(sectionNumbers.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rule stack displays all four governance types", () => {
|
||||||
|
render(<Page />);
|
||||||
|
|
||||||
|
// Check all four rule types are present
|
||||||
|
expect(screen.getByText("Consensus clusters")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Elected Board")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Consensus")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Petition")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Check that create rule button is present
|
||||||
|
const createButton = screen.getByRole("button", {
|
||||||
|
name: "Create CommunityRule",
|
||||||
|
});
|
||||||
|
expect(createButton).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ask organizer section has proper call-to-action", () => {
|
||||||
|
render(<Page />);
|
||||||
|
|
||||||
|
const askLink = screen.getByRole("link", { name: /Ask an organizer/i });
|
||||||
|
expect(askLink).toBeInTheDocument();
|
||||||
|
expect(askLink).toHaveAttribute("href", "#contact");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("page maintains proper semantic structure", () => {
|
||||||
|
render(<Page />);
|
||||||
|
|
||||||
|
// Check for proper heading hierarchy
|
||||||
|
const headings = screen.getAllByRole("heading");
|
||||||
|
expect(headings.length).toBeGreaterThan(5); // Should have multiple headings
|
||||||
|
|
||||||
|
// Check that main content is properly structured
|
||||||
|
const mainContent = screen.getByText(
|
||||||
|
/Help your community make important decisions/
|
||||||
|
);
|
||||||
|
expect(mainContent).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("all interactive elements are accessible", () => {
|
||||||
|
render(<Page />);
|
||||||
|
|
||||||
|
// Check all buttons have proper roles
|
||||||
|
const buttons = screen.getAllByRole("button");
|
||||||
|
buttons.forEach((button) => {
|
||||||
|
expect(button).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check all links have proper roles
|
||||||
|
const links = screen.getAllByRole("link");
|
||||||
|
links.forEach((link) => {
|
||||||
|
expect(link).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("page content flows logically from top to bottom", () => {
|
||||||
|
render(<Page />);
|
||||||
|
|
||||||
|
// Verify the logical flow of information
|
||||||
|
// 1. Hero introduces the concept
|
||||||
|
expect(
|
||||||
|
screen.getByText(/Help your community make important decisions/)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// 2. How it works section explains the process
|
||||||
|
expect(screen.getByText("How CommunityRule works")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// 3. Rule types show different governance options
|
||||||
|
expect(screen.getByText("Consensus clusters")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// 4. Features highlight benefits
|
||||||
|
expect(
|
||||||
|
screen.getByText("We've got your back, every step of the way")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// 5. Quote provides social proof
|
||||||
|
expect(
|
||||||
|
screen.getByText(/The rules of decision-making must be open/)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// 6. Call to action for help
|
||||||
|
expect(screen.getByText("Still have questions?")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
import { render, screen, cleanup } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { vi, describe, test, expect, afterEach } from "vitest";
|
||||||
|
import Page from "../../app/page";
|
||||||
|
import Header from "../../app/components/Header";
|
||||||
|
import Footer from "../../app/components/Footer";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("User Journey Integration", () => {
|
||||||
|
test("new user discovers the application through hero section", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<Page />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// User sees the main value proposition
|
||||||
|
expect(
|
||||||
|
screen.getByText(/Help your community make important decisions/)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// User clicks the main CTA to learn more
|
||||||
|
const learnButtons = screen.getAllByRole("button", {
|
||||||
|
name: "Learn how CommunityRule works",
|
||||||
|
});
|
||||||
|
const learnButton = learnButtons[0];
|
||||||
|
await user.click(learnButton);
|
||||||
|
|
||||||
|
// User should see the "How it works" section
|
||||||
|
expect(screen.getByText("How CommunityRule works")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user explores different governance types", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<Page />);
|
||||||
|
|
||||||
|
// User sees all four governance options
|
||||||
|
expect(screen.getByText("Consensus clusters")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Elected Board")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Consensus")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Petition")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// User clicks on a governance type to create a rule
|
||||||
|
const createButtons = screen.getAllByRole("button", {
|
||||||
|
name: "Create CommunityRule",
|
||||||
|
});
|
||||||
|
expect(createButtons.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
await user.click(createButtons[0]);
|
||||||
|
// Button should remain interactive
|
||||||
|
expect(createButtons[0]).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user navigates through the application using header navigation", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<Page />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// User clicks on navigation links in header (check that they exist and are clickable)
|
||||||
|
const navigationLinks = screen.getAllByRole("link");
|
||||||
|
const headerNavLinks = navigationLinks.filter(
|
||||||
|
(link) =>
|
||||||
|
link.textContent?.includes("Use Cases") ||
|
||||||
|
link.textContent?.includes("Learn") ||
|
||||||
|
link.textContent?.includes("About")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test that navigation links are present and clickable
|
||||||
|
for (const link of headerNavLinks) {
|
||||||
|
await user.click(link);
|
||||||
|
expect(link).toBeInTheDocument();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user seeks help through ask organizer section", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<Page />);
|
||||||
|
|
||||||
|
// User scrolls to the bottom and sees the help section
|
||||||
|
expect(screen.getByText("Still have questions?")).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText("Get answers from an experienced organizer")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// User clicks the ask organizer button (it's actually a link, not a button)
|
||||||
|
const askLink = screen.getByRole("link", { name: /Ask an organizer/i });
|
||||||
|
await user.click(askLink);
|
||||||
|
expect(askLink).toHaveAttribute("href", "#contact");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user explores the process through numbered cards", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<Page />);
|
||||||
|
|
||||||
|
// User reads through the process steps
|
||||||
|
expect(
|
||||||
|
screen.getByText("Document how your community makes decisions")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText("Build an operating manual for a successful community")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
"Get a link to your manual for your group to review and evolve"
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// User sees the step numbers
|
||||||
|
const stepNumbers = screen.getAllByText(/1|2|3/);
|
||||||
|
expect(stepNumbers.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user accesses contact information through footer", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<Page />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// User finds contact email in footer
|
||||||
|
const emailLink = screen.getByRole("link", { name: "medlab@colorado.edu" });
|
||||||
|
expect(emailLink).toHaveAttribute("href", "mailto:medlab@colorado.edu");
|
||||||
|
|
||||||
|
// User finds social media links
|
||||||
|
const blueskyLink = screen.getByRole("link", {
|
||||||
|
name: "Follow us on Bluesky",
|
||||||
|
});
|
||||||
|
const gitlabLink = screen.getByRole("link", {
|
||||||
|
name: "Follow us on GitLab",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(blueskyLink).toBeInTheDocument();
|
||||||
|
expect(gitlabLink).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user explores features and benefits", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<Page />);
|
||||||
|
|
||||||
|
// User sees the features section
|
||||||
|
expect(
|
||||||
|
screen.getByText("We've got your back, every step of the way")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
"Use our toolkit to improve, document, and evolve your organization."
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// User sees the testimonial/quote (check for the actual quote content)
|
||||||
|
expect(
|
||||||
|
screen.getByText(/The rules of decision-making must be open/)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user interacts with logo wall and social proof", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Page />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// User sees the logo wall with partner logos (check for any logo images)
|
||||||
|
const logoImages = screen.getAllByRole("img");
|
||||||
|
const partnerLogos = logoImages.filter(
|
||||||
|
(img) =>
|
||||||
|
img.alt?.includes("Food Not Bombs") ||
|
||||||
|
img.alt?.includes("Start COOP") ||
|
||||||
|
img.alt?.includes("Metagov") ||
|
||||||
|
img.alt?.includes("Open Civics") ||
|
||||||
|
img.alt?.includes("Mutual Aid CO") ||
|
||||||
|
img.alt?.includes("CU Boulder")
|
||||||
|
);
|
||||||
|
expect(partnerLogos.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Social links should be present in footer
|
||||||
|
const blueskyLink = screen.getByRole("link", { name: /Bluesky/i });
|
||||||
|
const gitlabLink = screen.getByRole("link", { name: /GitLab/i });
|
||||||
|
expect(blueskyLink).toBeInTheDocument();
|
||||||
|
expect(gitlabLink).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user completes the full journey from discovery to action", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<Page />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// 1. User discovers the application
|
||||||
|
expect(screen.getByText("Collaborate")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("with clarity")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// 2. User learns how it works
|
||||||
|
expect(screen.getByText("How CommunityRule works")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// 3. User sees governance options
|
||||||
|
expect(screen.getByText("Consensus clusters")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// 4. User sees features and benefits
|
||||||
|
expect(
|
||||||
|
screen.getByText("We've got your back, every step of the way")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// 5. User sees social proof
|
||||||
|
expect(
|
||||||
|
screen.getByText(/The rules of decision-making must be open/)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// 6. User can take action
|
||||||
|
const createButtons = screen.getAllByRole("button", {
|
||||||
|
name: "Create CommunityRule",
|
||||||
|
});
|
||||||
|
expect(createButtons.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// 7. User can get help if needed
|
||||||
|
expect(screen.getByText("Still have questions?")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user can access all navigation options consistently", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<Page />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header navigation
|
||||||
|
const headerNav = screen.getByRole("navigation");
|
||||||
|
expect(headerNav).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Footer navigation
|
||||||
|
const footerLinks = screen.getAllByRole("link");
|
||||||
|
const navigationLinks = footerLinks.filter(
|
||||||
|
(link) =>
|
||||||
|
link.textContent?.includes("Use cases") ||
|
||||||
|
link.textContent?.includes("Learn") ||
|
||||||
|
link.textContent?.includes("About")
|
||||||
|
);
|
||||||
|
expect(navigationLinks.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// All navigation links should be accessible
|
||||||
|
navigationLinks.forEach((link) => {
|
||||||
|
expect(link).not.toHaveAttribute("tabindex", "-1");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user can complete actions without errors", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<Page />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test all interactive elements
|
||||||
|
const buttons = screen.getAllByRole("button");
|
||||||
|
const links = screen.getAllByRole("link");
|
||||||
|
|
||||||
|
// All buttons should be clickable
|
||||||
|
for (const button of buttons) {
|
||||||
|
await user.click(button);
|
||||||
|
expect(button).toBeInTheDocument();
|
||||||
|
}
|
||||||
|
|
||||||
|
// All links should be accessible
|
||||||
|
for (const link of links) {
|
||||||
|
expect(link).toBeInTheDocument();
|
||||||
|
expect(link).not.toHaveAttribute("tabindex", "-1");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user