Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ jobs:
cache-dependency-path: app/package-lock.json

- name: Install dependencies
run: npm ci
run: npm ci --legacy-peer-deps

- name: Install accessibility testing dependencies
run: npm install --save-dev vitest-axe axe-core --legacy-peer-deps

- name: Run lint
run: npm run lint
Expand Down
49 changes: 45 additions & 4 deletions app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"zod": "^3.25.0"
},
"devDependencies": {
"@playwright/test": "^1.49.0",
"@storybook/addon-essentials": "^8.0.0",
"@storybook/addon-interactions": "^8.0.0",
"@storybook/nextjs-vite": "^9.0.0",
Expand All @@ -64,6 +65,7 @@
"@types/react-dom": "^19",
"@types/react-slick": "^0.23.13",
"@vitejs/plugin-react": "^4.4.1",
"axe-core": "^4.13.0",
"babel-plugin-react-compiler": "1.0.0",
"baseline-browser-mapping": "^2.9.18",
"chromatic": "^10.0.0",
Expand All @@ -77,6 +79,6 @@
"tailwindcss": "^4",
"typescript": "^5",
"vitest": "^3.0.0",
"@playwright/test": "^1.49.0"
"vitest-axe": "^0.1.0"
}
}
75 changes: 75 additions & 0 deletions app/src/__tests__/SearchModal.accessibility.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Accessibility audit for SearchModal component (issue #166).
*
* Ensures the global search interface is fully accessible with proper
* keyboard navigation, ARIA labels, and no critical WCAG violations.
*/

import { describe, it, expect, vi, beforeEach } from "vitest";
import { render } from "@testing-library/react";
import { axe } from "vitest-axe";
import SearchModal from "@/components/SearchModal";

// ── Shared mocks ─────────────────────────────────────────────────────────────

vi.mock("next/navigation", () => ({
useRouter: () => ({ push: vi.fn(), replace: vi.fn(), refresh: vi.fn() }),
}));

vi.mock("@tanstack/react-query", () => ({
useQuery: () => ({ data: undefined, isLoading: false, error: null }),
}));

vi.mock("@/services/albumService", () => ({
default: () => ({
useGetAlbums: () => ({ data: undefined, isLoading: false }),
}),
}));

vi.mock("@/services/eventsService", () => ({
default: () => ({
useGetEvents: () => ({ data: undefined, isLoading: false }),
}),
}));

vi.mock("@/services/merchService", () => ({
default: () => ({
useGetMerches: () => ({ data: undefined, isLoading: false }),
}),
}));

// ── SearchModal accessibility ────────────────────────────────────────────────

describe("SearchModal – axe audit", () => {
beforeEach(() => {
vi.clearAllMocks();
});

it("closed modal has no accessibility violations", async () => {
const { container } = render(
<SearchModal isOpen={false} onClose={vi.fn()} />
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});

it("open modal has no critical axe violations", async () => {
const { container } = render(
<SearchModal isOpen={true} onClose={vi.fn()} />
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});

it("modal with search results has no violations", async () => {
const { container, rerender } = render(
<SearchModal isOpen={true} onClose={vi.fn()} />
);

// Simulate search results by re-rendering
rerender(<SearchModal isOpen={true} onClose={vi.fn()} />);

const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
27 changes: 2 additions & 25 deletions app/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "slick-carousel/slick/slick-theme.css";
import Provider from "@/context/provider";
import OfflineIndicator from "@/components/OfflineIndicator";
import { Toaster } from "sonner";
import { defaultMetadata } from "@/utils/metadata";

const geistSans = Geist({
variable: "--font-geist-sans",
Expand All @@ -23,31 +24,7 @@ export const viewport: Viewport = {
maximumScale: 5,
};

export const metadata: Metadata = {
title: "AudioBlocks - Artist Dashboard",
description: "AudioBlocks artist dashboard for managing music, earnings, and fan engagement",
openGraph: {
title: "AudioBlocks - Artist Dashboard",
description: "AudioBlocks artist dashboard for managing music, earnings, and fan engagement",
url: "https://audioblocks.io",
siteName: "AudioBlocks",
images: [
{
url: "/og-default.png",
width: 1200,
height: 630,
alt: "AudioBlocks – Artist Dashboard",
},
],
type: "website",
},
twitter: {
card: "summary_large_image",
title: "AudioBlocks - Artist Dashboard",
description: "AudioBlocks artist dashboard for managing music, earnings, and fan engagement",
images: ["/og-default.png"],
},
};
export const metadata: Metadata = defaultMetadata;

const REQUIRED_ENV_VARS = ["NEXT_PUBLIC_API_BASE_URL", "NEXT_PUBLIC_API_URL"];

Expand Down
8 changes: 5 additions & 3 deletions app/src/app/login/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { Metadata } from "next";
import { generateMetadata } from "@/utils/metadata";

export const metadata: Metadata = {
title: "Log in β€” AudioBlocks",
export const metadata: Metadata = generateMetadata({
title: "Log in",
description:
"Log in to your AudioBlocks artist account to manage your music, earnings, and fan engagement.",
};
url: "/login",
});

export default function LoginLayout({ children }: { children: React.ReactNode }) {
return children;
Expand Down
11 changes: 4 additions & 7 deletions app/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@ import ArtistUpgrade from "@/components/common/artist-hub/ArtistUpgrade";
import Navbar from "@/layouts/navbar";
import Footer from "@/layouts/footer";
import GoToTopButton from "@/components/common/home/GoToTopButton";
import { generateMetadata } from "@/utils/metadata";

export const metadata: Metadata = {
export const metadata: Metadata = generateMetadata({
title: "AudioBlocks for Artists β€” Mint, Distribute & Earn Royalties on Stellar",
description:
"AudioBlocks is the artist-first platform for minting music NFTs, distributing tracks, and earning transparent on-chain royalties on the Stellar network.",
openGraph: {
title: "AudioBlocks for Artists",
description: "Mint your music, distribute globally, and earn transparent royalties on Stellar.",
type: "website",
},
};
url: "/",
});

export default function Home() {
return (
Expand Down
11 changes: 7 additions & 4 deletions app/src/app/signup/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import type { Metadata } from "next";
import { generateMetadata } from "@/utils/metadata";

export const metadata: Metadata = {
title: "Sign up β€” AudioBlocks",
description: "Create your AudioBlocks artist account to upload and manage your music.",
};
export const metadata: Metadata = generateMetadata({
title: "Sign up",
description:
"Create your AudioBlocks artist account to upload and manage your music. Join the Web3 music revolution.",
url: "/signup",
});

export default function SignupLayout({ children }: { children: React.ReactNode }) {
return children;
Expand Down
Loading
Loading