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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ dist/

# pnpm
.pnpm-store/
test-results/
3 changes: 3 additions & 0 deletions locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@
"contactUs": "Kontaktieren Sie uns",
"offeringsTitle": "Unsere Support & Care Angebote",
"learnMore": "Mehr erfahren →",
"altTexts": {
"underline": "Unterstreichung"
},
"maven": {
"title": "Support & Care für Apache Maven™",
"description": "Support & Care für Apache Maven™ stärkt die Zukunft des Java-Ökosystems durch nachhaltige Finanzierung und transparente Entwicklung. Sichern Sie die langfristige Stabilität und Weiterentwicklung von Apache Maven."
Expand Down
3 changes: 3 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@
"contactUs": "Contact us",
"offeringsTitle": "Our Support & Care Offerings",
"learnMore": "Learn more →",
"altTexts": {
"underline": "Underline"
},
"maven": {
"title": "Support & Care for Apache Maven™",
"description": "Support & Care for Apache Maven™ strengthens the future of the Java ecosystem through sustainable funding and transparent development. Secure the long-term stability and continued development of Apache Maven."
Expand Down
4 changes: 2 additions & 2 deletions src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ export default async function LocaleLayout({
const messages = await getMessages();

return (
<html lang={locale} className={`${montserrat.variable} scroll-smooth`}>
<html lang={locale} className={`${montserrat.variable} scroll-smooth`} suppressHydrationWarning>
<head>
<link rel="icon" href="/icons/favicon.ico" />
</head>
<body className={montserrat.className}>
<body className={montserrat.className} suppressHydrationWarning>
<NextIntlClientProvider messages={messages}>
<div id="top" className="relative overflow-x-hidden">
<Navbar locale={locale} />
Expand Down
14 changes: 2 additions & 12 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
import type { Metadata } from 'next'
import { Montserrat } from 'next/font/google'
import './globals.css'
import Navbar from '@/components/Navbar'
import Footer from '@/components/Footer'

const montserrat = Montserrat({
subsets: ['latin'],
weight: ['300', '400', '500', '600', '700', '800', '900'],
variable: '--font-montserrat',
display: 'swap',
})

export const metadata: Metadata = {
title: 'Open Elements - Open Source made right',
Expand All @@ -33,8 +23,8 @@ export const metadata: Metadata = {
},
}

// RootLayout is now in [locale]/layout.tsx for i18n support
// This is just a redirect wrapper
// Root layout delegates to [locale]/layout.tsx for i18n support
// The locale layout provides html/body with proper lang attributes
export default function RootLayout({
children,
}: {
Expand Down
1 change: 1 addition & 0 deletions src/types/css.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module '*.css';
44 changes: 44 additions & 0 deletions tests/e2e/about.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { test, expect } from '@playwright/test';

const locales = ['en', 'de'] as const;

function localePath(locale: (typeof locales)[number], path: string = '') {
return locale === 'en' ? `/${path}` : `/${locale}/${path}`;
}

test.describe('About Page', () => {
for (const locale of locales) {
test(`loads about page correctly for ${locale}`, async ({ page }) => {
await page.goto(localePath(locale, 'about'));

// Page should load successfully
await expect(page).toHaveURL(/\/about/);

// Should have main content
await expect(page.locator('main')).toBeVisible();

// Should maintain locale in URL
if (locale === 'de') {
await expect(page).toHaveURL(/\/de\/about/);
} else {
await expect(page).toHaveURL(/^(?!.*\/de\/).*\/about/);
}
});

test(`about page navigation works for ${locale}`, async ({ page }) => {
await page.goto(localePath(locale));

// Find and click about link in navigation
const aboutLink = page.locator('nav a[href*="about"]').first();
await aboutLink.click();

// Should navigate to about page
await expect(page).toHaveURL(/\/about/);

// Should maintain locale
if (locale === 'de') {
await expect(page).toHaveURL(/\/de\/about/);
}
});
}
});
80 changes: 80 additions & 0 deletions tests/e2e/accessibility.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { test, expect } from '@playwright/test';

const locales = ['en', 'de'] as const;

function localePath(locale: (typeof locales)[number], path: string = '') {
return locale === 'en' ? `/${path}` : `/${locale}/${path}`;
}

test.describe('Accessibility', () => {
for (const locale of locales) {
test(`home page has proper heading hierarchy for ${locale}`, async ({ page }) => {
await page.goto(localePath(locale));

// Should have exactly one h1
const h1Count = await page.locator('h1').count();
expect(h1Count).toBe(1);

// H1 should have text content
const h1Text = await page.locator('h1').first().textContent();
expect(h1Text?.trim().length).toBeGreaterThan(0);
});

test(`navigation has proper ARIA landmarks for ${locale}`, async ({ page }) => {
await page.goto(localePath(locale));

// Should have navigation landmark
const nav = page.locator('nav, [role="navigation"]');
await expect(nav).toBeVisible();

// Should have main landmark
const main = page.locator('main, [role="main"]');
await expect(main).toBeVisible();
});

test(`all images have alt text for ${locale}`, async ({ page }) => {
await page.goto(localePath(locale));

// Get all images
const images = page.locator('img');
const count = await images.count();

// Check each image has alt attribute
for (let i = 0; i < count; i++) {
const img = images.nth(i);
const alt = await img.getAttribute('alt');
// Alt can be empty string for decorative images, but must exist
expect(alt).not.toBeNull();
}
});

test(`interactive elements are keyboard accessible for ${locale}`, async ({ page }) => {
await page.goto(localePath(locale));

// All links should be keyboard accessible
const links = page.locator('a[href]');
const linkCount = await links.count();

if (linkCount > 0) {
const firstLink = links.first();
await expect(firstLink).toBeVisible();

// Link should be focusable
await firstLink.focus();
await expect(firstLink).toBeFocused();
}
});

test(`page has valid lang attribute for ${locale}`, async ({ page }) => {
await page.goto(localePath(locale));

const htmlLang = await page.locator('html').getAttribute('lang');

if (locale === 'de') {
expect(htmlLang).toContain('de');
} else {
expect(htmlLang).toContain('en');
}
});
}
});
47 changes: 47 additions & 0 deletions tests/e2e/contact.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { test, expect } from '@playwright/test';

const locales = ['en', 'de'] as const;

function localePath(locale: (typeof locales)[number], path: string = '') {
return locale === 'en' ? `/${path}` : `/${locale}/${path}`;
}

test.describe('Contact Page', () => {
for (const locale of locales) {
test(`loads contact page correctly for ${locale}`, async ({ page }) => {
await page.goto(localePath(locale, 'contact'));

// Page should load successfully
await expect(page).toHaveURL(/\/contact/);

// Should have main content
await expect(page.locator('main')).toBeVisible();

// Should maintain locale in URL
if (locale === 'de') {
await expect(page).toHaveURL(/\/de\/contact/);
} else {
await expect(page).toHaveURL(/^(?!.*\/de\/).*\/contact/);
}
});

test(`contact page navigation works for ${locale}`, async ({ page }) => {
await page.goto(localePath(locale));

// Find and click contact link in navigation
const contactLink = page.locator('nav a[href*="contact"]').first();

if (await contactLink.count() > 0) {
await contactLink.click();

// Should navigate to contact page
await expect(page).toHaveURL(/\/contact/);

// Should maintain locale
if (locale === 'de') {
await expect(page).toHaveURL(/\/de\/contact/);
}
Comment on lines +34 to +43
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This navigation test can pass without validating anything because it only clicks/asserts when contactLink.count() > 0. If the nav link is expected to exist, remove the guard and assert the link is present/visible before clicking so regressions are caught.

Suggested change
if (await contactLink.count() > 0) {
await contactLink.click();
// Should navigate to contact page
await expect(page).toHaveURL(/\/contact/);
// Should maintain locale
if (locale === 'de') {
await expect(page).toHaveURL(/\/de\/contact/);
}
// Contact link is expected to exist and be visible
await expect(contactLink).toBeVisible();
await contactLink.click();
// Should navigate to contact page
await expect(page).toHaveURL(/\/contact/);
// Should maintain locale
if (locale === 'de') {
await expect(page).toHaveURL(/\/de\/contact/);

Copilot uses AI. Check for mistakes.
}
});
}
});
51 changes: 51 additions & 0 deletions tests/e2e/home.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { test, expect } from '@playwright/test';

const locales = ['en', 'de'] as const;

function localePath(locale: (typeof locales)[number]) {
return locale === 'en' ? '/' : `/${locale}/`;
}

test.describe('Home Page', () => {
for (const locale of locales) {
test(`loads home page correctly for ${locale}`, async ({ page }) => {
await page.goto(localePath(locale));

// Page should load successfully
await expect(page).toHaveTitle(/Open Elements/i);

// Should have main navigation
await expect(page.locator('nav')).toBeVisible();

// Should have main content
await expect(page.locator('main')).toBeVisible();
});

test(`home page has proper meta tags for ${locale}`, async ({ page }) => {
await page.goto(localePath(locale));

// Should have meta description
const metaDescription = page.locator('meta[name="description"]');
await expect(metaDescription).toHaveAttribute('content', /.+/);

// Should have viewport meta tag
const viewport = page.locator('meta[name="viewport"]');
await expect(viewport).toHaveAttribute('content', /.+/);
});

test(`home page is responsive for ${locale}`, async ({ page }) => {
// Test mobile view
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(localePath(locale));
await expect(page.locator('nav')).toBeVisible();

// Test tablet view
await page.setViewportSize({ width: 768, height: 1024 });
await expect(page.locator('nav')).toBeVisible();

// Test desktop view
await page.setViewportSize({ width: 1920, height: 1080 });
await expect(page.locator('nav')).toBeVisible();
});
}
});
57 changes: 57 additions & 0 deletions tests/e2e/locale-switching.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { test, expect } from '@playwright/test';

test.describe('Locale Switching', () => {
test('can switch between EN and DE on home page', async ({ page }) => {
// Start with English
await page.goto('/');
await expect(page).not.toHaveURL(/\/de\//);

// Find and click language switcher to German
const languageSwitcher = page.locator('[data-locale-switcher], a[href*="/de/"], button[aria-label*="Deutsch"], button[aria-label*="German"]').first();

if (await languageSwitcher.count() > 0) {
await languageSwitcher.click();

// Should switch to German
await expect(page).toHaveURL(/\/de\//);
}
Comment on lines +10 to +17
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test can pass without actually verifying locale switching because it only clicks/asserts if a candidate switcher is found (if (await languageSwitcher.count() > 0)). If the switcher disappears/regresses, the test becomes a false positive. Make the switcher required (assert it exists/visible) and then perform the click + URL assertion.

Copilot uses AI. Check for mistakes.
});

test('maintains page context when switching locale', async ({ page }) => {
// Go to about page in English
await page.goto('/about');

// Find language switcher
const languageSwitcher = page.locator('[data-locale-switcher], a[href*="/de/about"], button[aria-label*="Deutsch"]').first();

if (await languageSwitcher.count() > 0) {
await languageSwitcher.click();

// Should be on about page in German
await expect(page).toHaveURL(/\/de\/about/);
}
Comment on lines +25 to +32
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above: this test silently succeeds when the locale switcher isn’t found due to the if (await languageSwitcher.count() > 0) guard. If switching locale is expected functionality, assert the switcher exists and fail the test when it doesn’t.

Copilot uses AI. Check for mistakes.
});

test('locale persists across navigation', async ({ page }) => {
// Start with German
await page.goto('/de/');

// Navigate to about page
const aboutLink = page.locator('nav a[href*="about"]').first();
if (await aboutLink.count() > 0) {
await aboutLink.click();

// Should still be in German
await expect(page).toHaveURL(/\/de\/about/);
}

// Navigate to contact page
const contactLink = page.locator('nav a[href*="contact"]').first();
if (await contactLink.count() > 0) {
await contactLink.click();

// Should still be in German
await expect(page).toHaveURL(/\/de\/contact/);
}
Comment on lines +41 to +55
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This navigation assertion is conditional on aboutLink.count() > 0, so a missing/incorrect About link won’t fail the test (false positive). If locale persistence across navigation is required, assert the link exists/visible before clicking.

Suggested change
if (await aboutLink.count() > 0) {
await aboutLink.click();
// Should still be in German
await expect(page).toHaveURL(/\/de\/about/);
}
// Navigate to contact page
const contactLink = page.locator('nav a[href*="contact"]').first();
if (await contactLink.count() > 0) {
await contactLink.click();
// Should still be in German
await expect(page).toHaveURL(/\/de\/contact/);
}
await expect(aboutLink).toBeVisible();
await aboutLink.click();
// Should still be in German
await expect(page).toHaveURL(/\/de\/about/);
// Navigate to contact page
const contactLink = page.locator('nav a[href*="contact"]').first();
await expect(contactLink).toBeVisible();
await contactLink.click();
// Should still be in German
await expect(page).toHaveURL(/\/de\/contact/);

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +55
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue: guarding the click with if (await contactLink.count() > 0) allows the test to pass even if the Contact link is missing/broken. Make the link required (expect it visible) so the test actually validates locale persistence.

Copilot uses AI. Check for mistakes.
});
});
Loading
Loading