From 30c5ae0d8d8858d334cb58856932144ce2703329 Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Mon, 2 Feb 2026 22:31:42 +0300 Subject: [PATCH 1/3] feat: test automation Signed-off-by: Daniel Ntege --- tests/e2e/about.spec.ts | 18 ++++++------------ tests/e2e/accessibility.spec.ts | 26 +++++++++----------------- tests/e2e/contact.spec.ts | 18 ++++++------------ tests/e2e/home.spec.ts | 23 ++++++++--------------- 4 files changed, 29 insertions(+), 56 deletions(-) diff --git a/tests/e2e/about.spec.ts b/tests/e2e/about.spec.ts index c240a444..01d611d2 100644 --- a/tests/e2e/about.spec.ts +++ b/tests/e2e/about.spec.ts @@ -10,14 +10,11 @@ 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 { @@ -27,15 +24,12 @@ test.describe('About Page', () => { 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/); } diff --git a/tests/e2e/accessibility.spec.ts b/tests/e2e/accessibility.spec.ts index 6a62be5c..3227a237 100644 --- a/tests/e2e/accessibility.spec.ts +++ b/tests/e2e/accessibility.spec.ts @@ -10,56 +10,48 @@ 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(); } diff --git a/tests/e2e/contact.spec.ts b/tests/e2e/contact.spec.ts index 2092e488..833fc63f 100644 --- a/tests/e2e/contact.spec.ts +++ b/tests/e2e/contact.spec.ts @@ -10,14 +10,11 @@ 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 { @@ -27,17 +24,14 @@ test.describe('Contact Page', () => { 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/); } diff --git a/tests/e2e/home.spec.ts b/tests/e2e/home.spec.ts index 9ec576a2..ee39cab9 100644 --- a/tests/e2e/home.spec.ts +++ b/tests/e2e/home.spec.ts @@ -10,40 +10,33 @@ 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(); }); From bdf7e07fc120be72e478be941f2f3bb588640ac4 Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Thu, 5 Feb 2026 18:44:34 +0300 Subject: [PATCH 2/3] feat: update pre-commit hook to check for pnpm before running lint Signed-off-by: Daniel Ntege --- .husky/pre-commit | 6 ++- README.md | 95 +++++++++++++++++------------------------------ 2 files changed, 40 insertions(+), 61 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 5bb9572f..32ffa07a 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -3,4 +3,8 @@ set -e # die on error -pnpm run lint \ No newline at end of file +if command -v pnpm >/dev/null 2>&1; then + pnpm run lint +else + echo "pnpm not found. Skipping lint." >&2 +fi \ No newline at end of file diff --git a/README.md b/README.md index 83373343..cfc17140 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,65 @@ # Open Elements Website -This repo contains the website of Open Elements. -The website is still work in progress. -In future the website will be available at https://www.open-elements.de and https://www.open-elements.com. +This repository contains the Open Elements website. -Netlify status of English page: +## Architecture (2026) -[![Netlify status of English page](https://api.netlify.com/api/v1/badges/0a7875a4-d4ba-4358-8616-87200dcbe7c5/deploy-status)](https://app.netlify.com/sites/open-elements-en/deploys) +The project is now a Next.js application with App Router, Tailwind CSS, and `next-intl` for i18n. Legacy Hugo content and templates are still kept in the repo for migration and historical content. -Netlify status of German page: +### Runtime layers -[![Netlify status of German page](https://api.netlify.com/api/v1/badges/935f5408-eef5-4889-9cb6-ee55a0990a0f/deploy-status)](https://app.netlify.com/sites/open-elements-de/deploys) +- **Next.js App (primary)** + - App Router pages and layouts in `src/app`. + - UI components in `src/components`. + - Shared utilities in `src/lib`, data in `src/data`, types in `src/types`. + - Styling via Tailwind CSS and `src/app/globals.css`. +- **Internationalization** + - `next-intl` routing and helpers in `src/i18n`. + - Translation messages in `locales`. -## Building the website +- **Legacy Hugo content (migrating)** + - Markdown content in `content`. + - Hugo templates in `src/layouts`. + - Hugo configuration in `config.toml`. + - Built static artifacts live in `public` (do not edit manually). -Since the page is based on Hugo and React we use `npm-run-all` to execute several dev executions in parallel. -Therefore you need to install `npm-run-all` as a dev dependency: +- **Web components** + - Custom elements live in `react-src` and are bundled via `react-src/build.mjs` into `public/js`. -``` -npm install --save-dev npm-run-all -``` - - -The project is based on [Hugo](https://gohugo.io/) and you need to [install Hugo](https://gohugo.io/installation/) to build the website. -Once Hugo is installed you can host the website on localhost by executing to following command from the root folder of the repository: - -``` -hugo serve -``` +- **E2E tests** + - Playwright specs in `tests/e2e`. -While the process is running the English (default) version of the website can be reached at http://localhost:1313/ and the German can be reached at http://localhost:1314/. +## Development -## Adding Tailwind CSS +### Requirements -### 1-Install Tailwind CSS +- Node.js 22 +- pnpm 10 -Install tailwindcss via npm, and create your tailwind.config.js file in the root folder. +### Install dependencies ``` -npm install -D tailwindcss -npx tailwindcss init +pnpm install ``` -### 2-Configure your template paths - -Add the paths to all of your template files in your tailwind.config.js file. +### Run locally ``` -content: [ - "content/**/*.md", "layouts/**/*.html" -], +pnpm run dev ``` -### 3-Add the Tailwind directives to your CSS -Create 'input.css' file in the root folder and add the @tailwind directives for each of Tailwind’s layers to your input CSS file. - -``` -@tailwind base; -@tailwind components; -@tailwind utilities; -``` +The app is available at http://localhost:3000. -### 4-Code snippet for Package.json - -Add the following code in 'Package.json' +### Build & start ``` - "scripts": { - "dev:css": "npx tailwindcss -i input.css -o assets/css/style.css -w", - "dev:hugo": "hugo server", - "dev": "run-p dev:*", - "build:css": "NODE_ENV=production npx tailwindcss -i input.css -o assets/css/style.css -m", - "build:hugo": "hugo", - "build": "run-s build:*" - }, +pnpm run build +pnpm run start ``` -### 5-Dev environment -For development run the following command in terminal. -``` -npm run dev -``` +### E2E tests -### 6-Production -For production ready css, run the following command in terminal. ``` -npm run build +pnpm run test:e2e ``` From 32dc1553bb7c9345f10d289930a06f07d2df1af0 Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Thu, 5 Feb 2026 18:45:55 +0300 Subject: [PATCH 3/3] feat: update pre-commit hook, fix lint command, and enhance layout script loading Signed-off-by: Daniel Ntege --- .husky/pre-commit | 4 +++- eslint.config.js | 3 ++- package.json | 2 +- react-src/maven-prs.tsx | 2 +- src/app/[locale]/layout.tsx | 3 ++- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 32ffa07a..05fde6f9 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,9 +1,11 @@ #!/bin/sh -. "$(dirname "$0")/_/husky.sh" set -e # die on error +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) + if command -v pnpm >/dev/null 2>&1; then + cd "$REPO_ROOT" pnpm run lint else echo "pnpm not found. Skipping lint." >&2 diff --git a/eslint.config.js b/eslint.config.js index 055f0e4b..7b7549aa 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -11,6 +11,7 @@ export default defineConfig([ "build/**", "next-env.d.ts", "content/**", - "src/react-src/**" + "public/**", + "react-src/**" ]) ]); diff --git a/package.json b/package.json index 4c9b4bbf..9a8ebaa1 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint", + "lint": "eslint .", "test:e2e": "playwright test", "prepare": "husky" }, diff --git a/react-src/maven-prs.tsx b/react-src/maven-prs.tsx index a874d681..97594920 100644 --- a/react-src/maven-prs.tsx +++ b/react-src/maven-prs.tsx @@ -69,7 +69,7 @@ export default function MavenPRs({status}: { status?: string }) { } fetchPRs(); - }, []); + }, [status]); if (!prs) return
Keine Pull Requests gefunden
; diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 2b626fba..fe289d4e 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -4,6 +4,7 @@ import { notFound } from 'next/navigation'; import { routing } from '@/i18n/routing'; import type { Metadata } from 'next' import { Montserrat } from 'next/font/google' +import Script from 'next/script' import '../globals.css' import Navbar from '@/components/Navbar' import Footer from '@/components/Footer' @@ -85,7 +86,7 @@ export default async function LocaleLayout({
{children}