diff --git a/app/hooks/useFormValidation.ts b/app/hooks/useFormValidation.ts index c6a5dc1..63982dd 100644 --- a/app/hooks/useFormValidation.ts +++ b/app/hooks/useFormValidation.ts @@ -9,7 +9,7 @@ export type ValidationRule = (value: T) => string | null; * Validation rules for common patterns */ export const validationRules = { - required: (value: T): string | null => { + required: (value: unknown): string | null => { if (value === null || value === undefined || value === "") { return "This field is required"; } diff --git a/tests/unit/Page.test.jsx b/tests/unit/Page.test.jsx index 5e73786..41bc382 100644 --- a/tests/unit/Page.test.jsx +++ b/tests/unit/Page.test.jsx @@ -1,9 +1,9 @@ import { describe, test, expect } from "vitest"; -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import Page from "../../app/page"; describe("Page", () => { - test("renders all main sections", () => { + test("renders all main sections", async () => { render(); // Check that all main sections are rendered (using getAllByText since there are multiple instances) @@ -15,10 +15,13 @@ describe("Page", () => { ).length, ).toBeGreaterThan(0); + // Wait for dynamically imported components to load // Check numbered cards section (using getAllByText since there are multiple instances) - expect( - screen.getAllByText("How CommunityRule works").length, - ).toBeGreaterThan(0); + await waitFor(() => { + expect( + screen.getAllByText("How CommunityRule works").length, + ).toBeGreaterThan(0); + }); expect( screen.getAllByText( "Here's a quick overview of the process, from start to finish.", @@ -60,13 +63,15 @@ describe("Page", () => { ).toBeGreaterThan(0); }); - test("renders numbered cards with correct data", () => { + test("renders numbered cards with correct data", async () => { render(); - // Check numbered cards content (using getAllByText since there are multiple instances) - expect( - screen.getAllByText("How CommunityRule works").length, - ).toBeGreaterThan(0); + // Wait for dynamically imported NumberedCards component to load + await waitFor(() => { + expect( + screen.getAllByText("How CommunityRule works").length, + ).toBeGreaterThan(0); + }); expect( screen.getAllByText( "Here's a quick overview of the process, from start to finish.", @@ -89,13 +94,15 @@ describe("Page", () => { ).toBeGreaterThan(0); }); - test("renders feature grid with correct data", () => { + test("renders feature grid with correct data", async () => { render(); - // Check feature grid content (using getAllByText since there are multiple instances) - expect( - screen.getAllByText("We've got your back, every step of the way").length, - ).toBeGreaterThan(0); + // Wait for dynamically imported FeatureGrid component to load + await waitFor(() => { + expect( + screen.getAllByText("We've got your back, every step of the way").length, + ).toBeGreaterThan(0); + }); expect( screen.getAllByText( "Use our toolkit to improve, document, and evolve your organization.", @@ -116,24 +123,29 @@ describe("Page", () => { expect(screen.getAllByText("Ask an organizer").length).toBeGreaterThan(0); }); - test("renders all component sections", () => { + test("renders all component sections", async () => { render(); // Check that all major components are present by looking for their content // HeroBanner expect(screen.getAllByText("Collaborate").length).toBeGreaterThan(0); + // Wait for dynamically imported components to load // LogoWall - should be present (even if just the component structure) // NumberedCards - expect( - screen.getAllByText("How CommunityRule works").length, - ).toBeGreaterThan(0); + await waitFor(() => { + expect( + screen.getAllByText("How CommunityRule works").length, + ).toBeGreaterThan(0); + }); // RuleStack - should be present // FeatureGrid - expect( - screen.getAllByText("We've got your back, every step of the way").length, - ).toBeGreaterThan(0); + await waitFor(() => { + expect( + screen.getAllByText("We've got your back, every step of the way").length, + ).toBeGreaterThan(0); + }); // QuoteBlock - should be present // AskOrganizer @@ -161,7 +173,7 @@ describe("Page", () => { expect(screen.getAllByText("Ask an organizer").length).toBeGreaterThan(0); }); - test("renders descriptive text content", () => { + test("renders descriptive text content", async () => { render(); // Check main description (using getAllByText since there are multiple instances) @@ -171,19 +183,23 @@ describe("Page", () => { ).length, ).toBeGreaterThan(0); - // Check numbered cards description (using getAllByText since there are multiple instances) - expect( - screen.getAllByText( - "Here's a quick overview of the process, from start to finish.", - ).length, - ).toBeGreaterThan(0); + // Wait for dynamically imported NumberedCards component + await waitFor(() => { + expect( + screen.getAllByText( + "Here's a quick overview of the process, from start to finish.", + ).length, + ).toBeGreaterThan(0); + }); - // Check feature grid description (using getAllByText since there are multiple instances) - expect( - screen.getAllByText( - "Use our toolkit to improve, document, and evolve your organization.", - ).length, - ).toBeGreaterThan(0); + // Wait for dynamically imported FeatureGrid component + await waitFor(() => { + expect( + screen.getAllByText( + "Use our toolkit to improve, document, and evolve your organization.", + ).length, + ).toBeGreaterThan(0); + }); // Check ask organizer description (using getAllByText since there are multiple instances) expect( @@ -191,29 +207,38 @@ describe("Page", () => { ).toBeGreaterThan(0); }); - test("renders section titles correctly", () => { + test("renders section titles correctly", async () => { render(); // Check all section titles (using getAllByText since there are multiple instances) expect(screen.getAllByText("Collaborate").length).toBeGreaterThan(0); - expect( - screen.getAllByText("How CommunityRule works").length, - ).toBeGreaterThan(0); - expect( - screen.getAllByText("We've got your back, every step of the way").length, - ).toBeGreaterThan(0); + + // Wait for dynamically imported components + await waitFor(() => { + expect( + screen.getAllByText("How CommunityRule works").length, + ).toBeGreaterThan(0); + }); + await waitFor(() => { + expect( + screen.getAllByText("We've got your back, every step of the way").length, + ).toBeGreaterThan(0); + }); expect(screen.getAllByText("Still have questions?").length).toBeGreaterThan( 0, ); }); - test("renders numbered card items with correct content", () => { + test("renders numbered card items with correct content", async () => { render(); - // Check all three numbered card items (using getAllByText since there are multiple instances) - expect( - screen.getAllByText("Document how your community makes decisions").length, - ).toBeGreaterThan(0); + // Wait for dynamically imported NumberedCards component + await waitFor(() => { + // Check all three numbered card items (using getAllByText since there are multiple instances) + expect( + screen.getAllByText("Document how your community makes decisions").length, + ).toBeGreaterThan(0); + }); expect( screen.getAllByText( "Build an operating manual for a successful community", @@ -226,21 +251,27 @@ describe("Page", () => { ).toBeGreaterThan(0); }); - test("renders subtitle content correctly", () => { + test("renders subtitle content correctly", async () => { render(); // Check subtitles (using getAllByText since there are multiple instances) expect(screen.getAllByText("with clarity").length).toBeGreaterThan(0); - expect( - screen.getAllByText( - "Here's a quick overview of the process, from start to finish.", - ).length, - ).toBeGreaterThan(0); - expect( - screen.getAllByText( - "Use our toolkit to improve, document, and evolve your organization.", - ).length, - ).toBeGreaterThan(0); + + // Wait for dynamically imported components + await waitFor(() => { + expect( + screen.getAllByText( + "Here's a quick overview of the process, from start to finish.", + ).length, + ).toBeGreaterThan(0); + }); + await waitFor(() => { + expect( + screen.getAllByText( + "Use our toolkit to improve, document, and evolve your organization.", + ).length, + ).toBeGreaterThan(0); + }); expect( screen.getAllByText("Get answers from an experienced organizer").length, ).toBeGreaterThan(0); diff --git a/vitest.setup.ts b/vitest.setup.ts index fad60c7..49ba666 100644 --- a/vitest.setup.ts +++ b/vitest.setup.ts @@ -1,5 +1,5 @@ import "@testing-library/jest-dom/vitest"; -import { afterAll, afterEach, beforeAll } from "vitest"; +import { afterAll, afterEach, beforeAll, vi } from "vitest"; import { cleanup } from "@testing-library/react"; import { server } from "./tests/msw/server"; // Note: Tailwind CSS v4 uses syntax that jsdom can't parse @@ -7,6 +7,41 @@ import { server } from "./tests/msw/server"; // Design tokens are accessible via CSS variables in the DOM // If you need to test CSS, use a CSS transformer or mock the import +// Mock next/dynamic for tests - return components directly instead of lazy loading +vi.mock("next/dynamic", () => { + const React = require("react"); + return { + default: (importFn: () => Promise, options?: any) => { + // In tests, return a component that immediately resolves and renders + return function DynamicComponent(props: any) { + const [Component, setComponent] = React.useState(null); + const [loading, setLoading] = React.useState(true); + + React.useEffect(() => { + importFn() + .then((mod: any) => { + setComponent(mod.default || mod); + setLoading(false); + }) + .catch(() => { + setLoading(false); + }); + }, []); + + if (loading && options?.loading) { + return options.loading(); + } + + if (Component) { + return React.createElement(Component, props); + } + + return null; + }; + }, + }; +}); + // MSW for API integration tests (mock fetch) beforeAll(() => server.listen({ onUnhandledRequest: "bypass" })); afterEach(() => {