diff --git a/tests/e2e/BlogNavigation.e2e.test.jsx b/tests/e2e/BlogNavigation.e2e.test.jsx
new file mode 100644
index 0000000..c2aabb6
--- /dev/null
+++ b/tests/e2e/BlogNavigation.e2e.test.jsx
@@ -0,0 +1,201 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { render, screen, fireEvent } from "@testing-library/react";
+import ContentThumbnailTemplate from "../../app/components/ContentThumbnailTemplate";
+import RelatedArticles from "../../app/components/RelatedArticles";
+
+// Mock Next.js navigation
+const mockPush = vi.fn();
+vi.mock("next/navigation", () => ({
+ useRouter: () => ({ push: mockPush }),
+ notFound: vi.fn(),
+ usePathname: vi.fn(() => "/"),
+}));
+
+// Mock Next.js Link to trigger navigation
+vi.mock("next/link", () => ({
+ default: ({ children, href, ...props }) => (
+ {
+ e.preventDefault();
+ mockPush(href);
+ }}
+ >
+ {children}
+
+ ),
+}));
+
+// Mock asset utils
+vi.mock("../../lib/assetUtils", () => ({
+ getAssetPath: vi.fn((asset) => `/assets/${asset}`),
+ ASSETS: {
+ CONTENT_THUMBNAIL_1: "Content_Thumbnail_1.svg",
+ CONTENT_THUMBNAIL_2: "Content_Thumbnail_2.svg",
+ CONTENT_THUMBNAIL_3: "Content_Thumbnail_3.svg",
+ CONTENT_ICON_1: "Content_Icon_1.svg",
+ CONTENT_ICON_2: "Content_Icon_2.svg",
+ CONTENT_ICON_3: "Content_Icon_3.svg",
+ },
+}));
+
+const mockBlogPost = {
+ slug: "resolving-active-conflicts",
+ frontmatter: {
+ title: "Resolving Active Conflicts",
+ description:
+ "Practical steps for resolving conflicts while maintaining trust",
+ author: "Test Author",
+ date: "2025-04-15",
+ },
+};
+
+const mockRelatedPosts = [
+ {
+ slug: "operational-security-mutual-aid",
+ frontmatter: {
+ title: "Operational Security for Mutual Aid",
+ description: "Tactics to protect members, secure communication",
+ author: "Test Author",
+ date: "2025-04-14",
+ },
+ },
+ {
+ slug: "making-decisions-without-hierarchy",
+ frontmatter: {
+ title: "Making Decisions Without Hierarchy",
+ description:
+ "A brief guide to collaborative nonhierarchical decision making",
+ author: "Test Author",
+ date: "2025-04-13",
+ },
+ },
+];
+
+describe("Blog Navigation E2E", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe("ContentThumbnailTemplate Navigation", () => {
+ it("should navigate to blog post when thumbnail is clicked", () => {
+ render();
+
+ // Find the thumbnail link
+ const thumbnailLink = screen.getByRole("link");
+ expect(thumbnailLink).toBeInTheDocument();
+ expect(thumbnailLink).toHaveAttribute(
+ "href",
+ "/blog/resolving-active-conflicts"
+ );
+
+ // Click the thumbnail
+ fireEvent.click(thumbnailLink);
+
+ // Verify navigation was called
+ expect(mockPush).toHaveBeenCalledWith("/blog/resolving-active-conflicts");
+ });
+
+ it("should display correct post information", () => {
+ render();
+
+ // Verify post content is displayed
+ expect(
+ screen.getByText("Resolving Active Conflicts")
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText(
+ "Practical steps for resolving conflicts while maintaining trust"
+ )
+ ).toBeInTheDocument();
+ expect(screen.getByText("Test Author")).toBeInTheDocument();
+ expect(screen.getByText("April 2025")).toBeInTheDocument();
+ });
+
+ it("should render with correct variant based on screen size", () => {
+ render();
+
+ // Verify the thumbnail container exists
+ const thumbnailContainer = screen.getByRole("link").closest("div");
+ expect(thumbnailContainer).toBeInTheDocument();
+ });
+ });
+
+ describe("RelatedArticles Navigation", () => {
+ it("should display related articles with correct links", () => {
+ render();
+
+ // Verify related articles are displayed
+ expect(
+ screen.getByText("Operational Security for Mutual Aid")
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText("Making Decisions Without Hierarchy")
+ ).toBeInTheDocument();
+
+ // Verify links are present
+ const relatedLinks = screen.getAllByRole("link");
+ expect(relatedLinks).toHaveLength(2);
+ expect(relatedLinks[0]).toHaveAttribute(
+ "href",
+ "/blog/operational-security-mutual-aid"
+ );
+ expect(relatedLinks[1]).toHaveAttribute(
+ "href",
+ "/blog/making-decisions-without-hierarchy"
+ );
+ });
+
+ it("should navigate to related article when clicked", () => {
+ render();
+
+ // Find and click first related article
+ const firstRelatedLink = screen
+ .getByText("Operational Security for Mutual Aid")
+ .closest("a");
+ expect(firstRelatedLink).toBeInTheDocument();
+
+ fireEvent.click(firstRelatedLink);
+
+ // Verify navigation was called
+ expect(mockPush).toHaveBeenCalledWith(
+ "/blog/operational-security-mutual-aid"
+ );
+ });
+
+ it("should handle empty related posts gracefully", () => {
+ const { container } = render();
+
+ // Should not crash and should render nothing (component returns null)
+ expect(container.firstChild).toBeNull();
+ });
+ });
+
+ describe("Navigation Flow", () => {
+ it("should complete navigation flow: thumbnail → related article", () => {
+ // Render thumbnail
+ const { rerender } = render(
+
+ );
+
+ // Click thumbnail
+ const thumbnailLink = screen.getByRole("link");
+ fireEvent.click(thumbnailLink);
+ expect(mockPush).toHaveBeenCalledWith("/blog/resolving-active-conflicts");
+
+ // Clear mocks and render related articles
+ vi.clearAllMocks();
+ rerender();
+
+ // Click related article
+ const relatedLink = screen
+ .getByText("Operational Security for Mutual Aid")
+ .closest("a");
+ fireEvent.click(relatedLink);
+ expect(mockPush).toHaveBeenCalledWith(
+ "/blog/operational-security-mutual-aid"
+ );
+ });
+ });
+});
diff --git a/tests/e2e/ContentPageRendering.e2e.test.jsx b/tests/e2e/ContentPageRendering.e2e.test.jsx
new file mode 100644
index 0000000..168f3e3
--- /dev/null
+++ b/tests/e2e/ContentPageRendering.e2e.test.jsx
@@ -0,0 +1,194 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { render, screen } from "@testing-library/react";
+import ContentBanner from "../../app/components/ContentBanner";
+import AskOrganizer from "../../app/components/AskOrganizer";
+
+// Mock Next.js navigation
+vi.mock("next/navigation", () => ({
+ useRouter: () => ({ push: vi.fn() }),
+ notFound: vi.fn(),
+ usePathname: vi.fn(() => "/blog/test-post"),
+}));
+
+// Mock asset utils
+vi.mock("../../lib/assetUtils", () => ({
+ getAssetPath: vi.fn((asset) => `/assets/${asset}`),
+ ASSETS: {
+ CONTENT_BANNER_1: "Content_Banner_1.svg",
+ CONTENT_BANNER_2: "Content_Banner_2.svg",
+ CONTENT_SHAPE_1: "Content_Shape_1.svg",
+ CONTENT_SHAPE_2: "Content_Shape_2.svg",
+ },
+}));
+
+const mockBlogPost = {
+ slug: "test-article",
+ frontmatter: {
+ title: "Test Article Title",
+ description: "This is a test article description",
+ author: "Test Author",
+ date: "2025-04-15",
+ },
+ htmlContent:
+ "
This is the main content of the test article.
It has multiple paragraphs.
",
+};
+
+const mockRelatedPosts = [
+ {
+ slug: "related-article-1",
+ frontmatter: {
+ title: "Related Article 1",
+ description: "First related article",
+ author: "Test Author",
+ date: "2025-04-14",
+ },
+ },
+ {
+ slug: "related-article-2",
+ frontmatter: {
+ title: "Related Article 2",
+ description: "Second related article",
+ author: "Test Author",
+ date: "2025-04-13",
+ },
+ },
+];
+
+describe("Content Page Rendering E2E", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe("ContentBanner Component", () => {
+ it("should render blog post banner with correct information", () => {
+ render();
+
+ // Verify banner content
+ expect(screen.getByText("Test Article Title")).toBeInTheDocument();
+ expect(
+ screen.getByText("This is a test article description")
+ ).toBeInTheDocument();
+ expect(screen.getByText("Test Author")).toBeInTheDocument();
+ expect(screen.getByText("April 2025")).toBeInTheDocument();
+ });
+
+ it("should render with proper semantic structure", () => {
+ render();
+
+ // Verify semantic HTML structure - ContentBanner doesn't have role="banner"
+ const container = screen.getByText("Test Article Title").closest("div");
+ expect(container).toBeInTheDocument();
+
+ // Verify headings hierarchy
+ const h3 = screen.getByRole("heading", { level: 3 });
+ expect(h3).toHaveTextContent("Test Article Title");
+ });
+
+ it("should handle different blog posts with different content", () => {
+ const differentPost = {
+ ...mockBlogPost,
+ frontmatter: {
+ ...mockBlogPost.frontmatter,
+ title: "Different Article Title",
+ description: "Different description",
+ },
+ };
+
+ render();
+
+ // Verify different content is rendered
+ expect(screen.getByText("Different Article Title")).toBeInTheDocument();
+ expect(screen.getByText("Different description")).toBeInTheDocument();
+
+ // Verify old content is not present
+ expect(screen.queryByText("Test Article Title")).not.toBeInTheDocument();
+ });
+ });
+
+ describe("AskOrganizer Component", () => {
+ it("should render ask organizer with correct content", () => {
+ render(
+
+ );
+
+ // Verify ask organizer content
+ expect(screen.getByText("Still have questions?")).toBeInTheDocument();
+ expect(
+ screen.getByText("Get help from our community organizers")
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole("link", { name: /ask an organizer/i })
+ ).toBeInTheDocument();
+ });
+
+ it("should render with inverse variant", () => {
+ render(
+
+ );
+
+ // Verify ask organizer content is still present
+ expect(screen.getByText("Still have questions?")).toBeInTheDocument();
+ expect(
+ screen.getByText("Get help from our community organizers")
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole("link", { name: /ask an organizer/i })
+ ).toBeInTheDocument();
+ });
+
+ it("should have proper accessibility attributes", () => {
+ render();
+
+ // Verify link is accessible (Button component renders as a link)
+ const link = screen.getByRole("link", { name: /ask an organizer/i });
+ expect(link).toBeInTheDocument();
+ expect(link).toHaveAttribute("href", "#");
+ });
+ });
+
+ describe("Component Integration", () => {
+ it("should render multiple components together", () => {
+ render(
+
+ );
+
+ // Verify both components are rendered
+ expect(screen.getByText("Test Article Title")).toBeInTheDocument();
+ expect(screen.getByText("Still have questions?")).toBeInTheDocument();
+ });
+
+ it("should maintain proper semantic structure when combined", () => {
+ render(
+
+
+
+
+ );
+
+ // Verify semantic structure
+ expect(screen.getByRole("main")).toBeInTheDocument();
+ expect(screen.getByRole("region")).toBeInTheDocument(); // AskOrganizer has role="region"
+
+ // Verify headings hierarchy
+ const h3 = screen.getByRole("heading", { level: 3 });
+ expect(h3).toHaveTextContent("Test Article Title");
+ });
+ });
+});
diff --git a/tests/e2e/LogoNavigation.e2e.test.jsx b/tests/e2e/LogoNavigation.e2e.test.jsx
new file mode 100644
index 0000000..7e311c1
--- /dev/null
+++ b/tests/e2e/LogoNavigation.e2e.test.jsx
@@ -0,0 +1,57 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { render, screen, fireEvent } from "@testing-library/react";
+import Logo from "../../app/components/Logo";
+
+// Mock Next.js Link component
+vi.mock("next/link", () => ({
+ default: ({ children, href, ...props }) => (
+
+ {children}
+
+ ),
+}));
+
+// Mock asset utils
+vi.mock("../../lib/assetUtils", () => ({
+ getAssetPath: vi.fn((asset) => `/assets/${asset}`),
+ ASSETS: {
+ LOGO: "CommunityRule_Logo.svg",
+ },
+}));
+
+describe("Logo Navigation E2E", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("should navigate to homepage when logo is clicked", () => {
+ render();
+
+ // Find the logo link
+ const logoLink = screen.getByRole("link", { name: /communityrule logo/i });
+ expect(logoLink).toBeInTheDocument();
+ expect(logoLink).toHaveAttribute("href", "/");
+
+ // Verify the link is clickable (Next.js Link renders as tag)
+ expect(logoLink.tagName).toBe("A");
+ });
+
+ it("should have proper accessibility attributes", () => {
+ render();
+
+ const logoLink = screen.getByRole("link", { name: /communityrule logo/i });
+ expect(logoLink).toHaveAttribute("aria-label", "CommunityRule Logo");
+ expect(logoLink).toHaveAttribute("href", "/");
+ });
+
+ it("should render logo image correctly", () => {
+ render();
+
+ // The image has aria-hidden="true" so we need to find it by alt text
+ const logoImage = screen.getByAltText("CommunityRule Logo Icon");
+ expect(logoImage).toBeInTheDocument();
+ expect(logoImage).toHaveAttribute("src", "/assets/CommunityRule_Logo.svg");
+ expect(logoImage).toHaveAttribute("alt", "CommunityRule Logo Icon");
+ expect(logoImage).toHaveAttribute("aria-hidden", "true");
+ });
+});
diff --git a/vitest.config.mjs b/vitest.config.mjs
index 72715bd..83c0e50 100644
--- a/vitest.config.mjs
+++ b/vitest.config.mjs
@@ -16,6 +16,7 @@ export default defineConfig({
"tests/unit/**/*.test.{js,jsx,ts,tsx}",
"tests/integration/**/*.test.{js,jsx,ts,tsx}",
"tests/accessibility/**/*.test.{js,jsx,ts,tsx}",
+ "tests/e2e/**/*.test.{js,jsx,ts,tsx}",
],
css: true,
coverage: {