Fix TypeScript matcher typing issue
CI Pipeline / test (pull_request) Successful in 7m5s
CI Pipeline / lint (pull_request) Has been cancelled
CI Pipeline / build (pull_request) Has been cancelled
CI Pipeline / e2e (webkit) (pull_request) Has been cancelled
CI Pipeline / e2e (chromium) (pull_request) Successful in 54m11s
CI Pipeline / e2e (firefox) (pull_request) Failing after 22m9s
CI Pipeline / visual-regression (pull_request) Successful in 11m50s
CI Pipeline / performance (pull_request) Successful in 13m59s

This commit is contained in:
adilallo
2026-01-28 15:57:47 -07:00
parent 2652015e80
commit 9cb89162ab
28 changed files with 69 additions and 90 deletions
-13
View File
@@ -444,19 +444,6 @@ jobs:
.next/test-results
retention-days: 30
storybook:
runs-on: [self-hosted, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "${{ env.NODE_VERSION }}"
cache: npm
- run: npm ci --no-audit --fund=false
- run: npm run storybook:build:github
# Storybook is used for component documentation only.
# Component tests (tests/components/*.test.tsx) provide all test coverage.
lint:
runs-on: [self-hosted, macos-latest]
steps:
-15
View File
@@ -1,15 +0,0 @@
[
{
"name": "maxNumericValue",
"expected": 5000,
"actual": 6316.213500000003,
"values": [6352.161499999999, 6316.213500000003, 6810.3611],
"operator": "<=",
"passed": false,
"auditId": "interactive",
"level": "warn",
"url": "http://127.0.0.1:3010/",
"auditTitle": "Time to Interactive",
"auditDocumentationLink": "https://developer.chrome.com/docs/lighthouse/performance/interactive/"
}
]
+18
View File
@@ -0,0 +1,18 @@
.next/
out/
build/
dist/
node_modules/
# Test/build artifacts
coverage/
playwright-report/
test-results/
lhci-results/
.lighthouseci/
# Storybook build output
storybook-static/
# Misc generated
*.log
+3 -1
View File
@@ -169,7 +169,9 @@ const ContentLockup = memo<ContentLockupProps>(
</div>
{/* Subtitle */}
{subtitle ? <h2 className={styles.subtitle}>{subtitle}</h2> : null}
{subtitle ? (
<h2 className={styles.subtitle}>{subtitle}</h2>
) : null}
</div>
{/* Description */}
+1 -3
View File
@@ -73,9 +73,7 @@ const FeatureGrid = memo<FeatureGridProps>(
</div>
{/* MiniCard Grid */}
<div
className="grid grid-cols-2 md:grid-cols-4 gap-[var(--spacing-scale-012)] mt-[var(--spacing-scale-048)] lg:mt-0 lg:flex-grow lg:shrink-0"
>
<div className="grid grid-cols-2 md:grid-cols-4 gap-[var(--spacing-scale-012)] mt-[var(--spacing-scale-048)] lg:mt-0 lg:flex-grow lg:shrink-0">
{features.map((feature, index) => (
<MiniCard
key={index}
+3
View File
@@ -18,6 +18,7 @@ This directory contains project documentation organized by topic.
### For Testing
Start with **[TESTING_GUIDE.md](./TESTING_GUIDE.md)** for:
- Testing philosophy and approach
- Component testing with `componentTestSuite`
- E2E testing with Playwright
@@ -27,6 +28,7 @@ Start with **[TESTING_GUIDE.md](./TESTING_GUIDE.md)** for:
### For Custom Hooks
See **[CUSTOM_HOOKS.md](./CUSTOM_HOOKS.md)** for:
- Available custom hooks
- Usage examples
- API documentation
@@ -34,6 +36,7 @@ See **[CUSTOM_HOOKS.md](./CUSTOM_HOOKS.md)** for:
### For Content Creation
See **[content-creation.md](./guides/content-creation.md)** for:
- How to create blog articles
- Content guidelines
- Contribution workflow
+3 -2
View File
@@ -74,7 +74,9 @@ const config: ComponentTestSuiteConfig<Props> = {
error: {
props: { error: true } as Partial<Props>,
assert: (el) => {
expect(el).toHaveClass("border-[var(--color-border-default-utility-negative)]");
expect(el).toHaveClass(
"border-[var(--color-border-default-utility-negative)]",
);
},
},
},
@@ -214,4 +216,3 @@ Accessibility is tested at two levels:
- Tests complete user journeys for accessibility barriers
This two-tier approach ensures both individual components and full page experiences meet accessibility standards.
+3 -1
View File
@@ -47,7 +47,9 @@ describe("AskOrganizer (behavioral tests)", () => {
it("renders subtitle when provided", () => {
render(<AskOrganizer title="Test" subtitle="Subtitle" />);
expect(screen.getByRole("heading", { name: "Subtitle" })).toBeInTheDocument();
expect(
screen.getByRole("heading", { name: "Subtitle" }),
).toBeInTheDocument();
});
it("renders button with default text", () => {
-1
View File
@@ -63,4 +63,3 @@ describe("Button (behavioral tests)", () => {
expect(link).toHaveAttribute("href", "/learn");
});
});
-1
View File
@@ -26,4 +26,3 @@ componentTestSuite<CheckboxProps>({
disabledProps: { disabled: true },
},
});
+2 -1
View File
@@ -13,7 +13,8 @@ vi.mock("next/link", () => ({
}));
vi.mock("../../lib/assetUtils", async (importOriginal) => {
const actual = (await importOriginal()) as typeof import("../../lib/assetUtils");
const actual =
(await importOriginal()) as typeof import("../../lib/assetUtils");
return {
...actual,
getAssetPath: vi.fn((asset: string) => `/assets/${asset}`),
+1 -6
View File
@@ -9,11 +9,7 @@ componentTestSuite<ContextMenuProps>({
component: ContextMenu,
name: "ContextMenu",
props: {
children: (
<ContextMenuItem>
Item
</ContextMenuItem>
),
children: <ContextMenuItem>Item</ContextMenuItem>,
} as ContextMenuProps,
requiredProps: [],
primaryRole: "menu",
@@ -25,4 +21,3 @@ componentTestSuite<ContextMenuProps>({
errorState: false,
},
});
@@ -23,4 +23,3 @@ componentTestSuite<ContextMenuItemProps>({
disabledProps: { disabled: true },
},
});
+15 -5
View File
@@ -60,14 +60,24 @@ describe("Footer (behavioral tests)", () => {
it("renders navigation links", () => {
render(<Footer />);
expect(screen.getAllByRole("link", { name: "Use cases" }).length).toBeGreaterThan(0);
expect(screen.getAllByRole("link", { name: "Learn" }).length).toBeGreaterThan(0);
expect(screen.getAllByRole("link", { name: "About" }).length).toBeGreaterThan(0);
expect(
screen.getAllByRole("link", { name: "Use cases" }).length,
).toBeGreaterThan(0);
expect(
screen.getAllByRole("link", { name: "Learn" }).length,
).toBeGreaterThan(0);
expect(
screen.getAllByRole("link", { name: "About" }).length,
).toBeGreaterThan(0);
});
it("renders legal links", () => {
render(<Footer />);
expect(screen.getAllByRole("link", { name: "Privacy Policy" }).length).toBeGreaterThan(0);
expect(screen.getAllByRole("link", { name: "Terms of Service" }).length).toBeGreaterThan(0);
expect(
screen.getAllByRole("link", { name: "Privacy Policy" }).length,
).toBeGreaterThan(0);
expect(
screen.getAllByRole("link", { name: "Terms of Service" }).length,
).toBeGreaterThan(0);
});
});
-1
View File
@@ -19,4 +19,3 @@ componentTestSuite<HeaderProps>({
errorState: false,
},
});
+4 -4
View File
@@ -45,7 +45,9 @@ describe("HeroBanner (behavioral tests)", () => {
it("renders subtitle when provided", () => {
render(<HeroBanner title="Test" subtitle="Subtitle" />);
expect(screen.getByRole("heading", { name: "Subtitle" })).toBeInTheDocument();
expect(
screen.getByRole("heading", { name: "Subtitle" }),
).toBeInTheDocument();
});
it("renders hero image", () => {
@@ -56,9 +58,7 @@ describe("HeroBanner (behavioral tests)", () => {
});
it("renders CTA button when provided", () => {
render(
<HeroBanner title="Test" ctaText="Get Started" ctaHref="/start" />,
);
render(<HeroBanner title="Test" ctaText="Get Started" ctaHref="/start" />);
expect(
screen.getAllByRole("button", { name: "Get Started" }).length,
).toBeGreaterThan(0);
-1
View File
@@ -27,4 +27,3 @@ componentTestSuite<InputProps>({
errorProps: { error: true },
},
});
+2 -6
View File
@@ -37,9 +37,7 @@ describe("Logo (behavioral tests)", () => {
it("renders logo icon", () => {
render(<Logo />);
expect(
screen.getByAltText("CommunityRule Logo Icon"),
).toBeInTheDocument();
expect(screen.getByAltText("CommunityRule Logo Icon")).toBeInTheDocument();
});
it("renders text by default", () => {
@@ -50,9 +48,7 @@ describe("Logo (behavioral tests)", () => {
it("hides text when showText is false", () => {
render(<Logo showText={false} />);
expect(screen.queryByText("CommunityRule")).not.toBeInTheDocument();
expect(
screen.getByAltText("CommunityRule Logo Icon"),
).toBeInTheDocument();
expect(screen.getByAltText("CommunityRule Logo Icon")).toBeInTheDocument();
});
it("renders with different size variants", () => {
-1
View File
@@ -27,4 +27,3 @@ componentTestSuite<RadioButtonProps>({
disabledProps: { disabled: true },
},
});
-1
View File
@@ -25,4 +25,3 @@ componentTestSuite<RadioGroupProps>({
errorState: false,
},
});
+3 -12
View File
@@ -50,19 +50,13 @@ const mockPosts: BlogPost[] = [
describe("RelatedArticles", () => {
it("renders without crashing", () => {
render(
<RelatedArticles
relatedPosts={mockPosts}
currentPostSlug="current"
/>,
<RelatedArticles relatedPosts={mockPosts} currentPostSlug="current" />,
);
});
it("renders related articles", () => {
render(
<RelatedArticles
relatedPosts={mockPosts}
currentPostSlug="current"
/>,
<RelatedArticles relatedPosts={mockPosts} currentPostSlug="current" />,
);
expect(screen.getByTestId("thumbnail-article-1")).toBeInTheDocument();
expect(screen.getByTestId("thumbnail-article-2")).toBeInTheDocument();
@@ -70,10 +64,7 @@ describe("RelatedArticles", () => {
it("filters out current post", () => {
render(
<RelatedArticles
relatedPosts={mockPosts}
currentPostSlug="article-1"
/>,
<RelatedArticles relatedPosts={mockPosts} currentPostSlug="article-1" />,
);
expect(screen.queryByTestId("thumbnail-article-1")).not.toBeInTheDocument();
expect(screen.getByTestId("thumbnail-article-2")).toBeInTheDocument();
-1
View File
@@ -21,4 +21,3 @@ componentTestSuite<SectionHeaderProps>({
errorState: false,
},
});
-1
View File
@@ -32,4 +32,3 @@ componentTestSuite<SelectProps>({
errorProps: { error: true },
},
});
-1
View File
@@ -26,4 +26,3 @@ componentTestSuite<SwitchProps>({
disabledProps: { disabled: true },
},
});
-1
View File
@@ -28,4 +28,3 @@ componentTestSuite<TextAreaProps>({
errorProps: { error: true },
},
});
-1
View File
@@ -24,4 +24,3 @@ componentTestSuite<ToggleProps>({
disabledProps: { disabled: true },
},
});
-1
View File
@@ -20,4 +20,3 @@ componentTestSuite<ToggleGroupProps>({
errorState: false,
},
});
+10 -7
View File
@@ -2,9 +2,7 @@ import React from "react";
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { axe, toHaveNoViolations } from "jest-axe";
expect.extend(toHaveNoViolations);
import { axe } from "jest-axe";
type TestCases = {
renders?: boolean;
@@ -134,9 +132,14 @@ export function componentTestSuite<TProps>(
// Render again with optional props omitted to ensure no runtime error
const { unmount } = render(
<Component {...({ ...props, ...Object.fromEntries(
<Component
{...({
...props,
...Object.fromEntries(
Object.keys(optionalProps).map((k) => [k, undefined]),
) } as TProps)} />,
),
} as TProps)}
/>,
);
// Basic sanity check: component is mounted
@@ -149,7 +152,8 @@ export function componentTestSuite<TProps>(
it("has no obvious accessibility violations (axe)", async () => {
const { container } = render(<Component {...props} />);
const results = await axe(container);
expect(results).toHaveNoViolations();
// Avoid relying on Jest matcher typings in Vitest/Next typecheck context.
expect(results.violations).toHaveLength(0);
});
}
@@ -218,4 +222,3 @@ export function componentTestSuite<TProps>(
}
});
}