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
12 changes: 0 additions & 12 deletions .husky/pre-commit

This file was deleted.

4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
"build": "next build",
"start": "next start",
"lint": "eslint .",
"test:e2e": "playwright test",
"prepare": "husky"
"test:e2e": "playwright test"
},
"devDependencies": {
"@playwright/test": "^1.55.0",
Expand All @@ -26,7 +25,6 @@
"eslint": "^9.39.2",
"eslint-config-next": "^16.1.0",
"eslint-config-prettier": "^10.1.8",
"husky": "^9.1.7",
"npm-run-all": "^4.1.5",
"postcss": "^8.5.6",
"prettier": "^3.8.0",
Expand Down
10 changes: 0 additions & 10 deletions pnpm-lock.yaml

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

7 changes: 5 additions & 2 deletions src/i18n/routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ export const routing = defineRouting({

// Used when no locale matches
defaultLocale: 'en',

// The prefix for the default locale when it's in the path
localePrefix: 'as-needed'
localePrefix: 'as-needed',

// Keep existing unprefixed URLs stable instead of redirecting based on browser language.
localeDetection: false,
});

// Lightweight wrappers around Next.js' navigation APIs
Expand Down
9 changes: 6 additions & 3 deletions tests/e2e/code-highlighting.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ import { test, expect } from '@playwright/test';

test.describe('Code highlighting', () => {
test('render production-like syntax colors for fenced code blocks', async ({ page }) => {
await page.goto('/posts/2026-03-12-java-modules-encapsulation-internal-packages');
await page.goto('/posts/2023-04-18-detect-null-errors-with-static-analysis', {
waitUntil: 'domcontentloaded',
});
await expect(page.locator('main')).toBeVisible();

const articleBody = page.locator('main .prose').first();
const codeBlock = articleBody.locator('.highlight pre').first();
const keywordToken = codeBlock.locator('.token.keyword').first();
const punctuationToken = codeBlock.locator('.token.punctuation').first();
const operatorToken = codeBlock.locator('.token.operator').first();

await expect(codeBlock).toBeVisible();
await expect(codeBlock).toHaveCSS('background-color', 'rgb(39, 40, 34)');
await expect(keywordToken).toHaveCSS('color', 'rgb(102, 217, 239)');
await expect(punctuationToken).toHaveCSS('color', 'rgb(249, 38, 114)');
await expect(operatorToken).toHaveCSS('color', 'rgb(249, 38, 114)');
});
});
36 changes: 22 additions & 14 deletions tests/e2e/markdown-shortcode-links.spec.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,40 @@
import { test, expect } from '@playwright/test';

test.describe('Markdown shortcode links', () => {
test('render relref links inside markdown link syntax', async ({ page }) => {
await page.goto('/posts/2026-03-12-java-modules-encapsulation-internal-packages');
test('render relref and ref links to post and top-level routes', async ({ page }) => {
await page.goto('/posts/2026-02-10-review-2025', {
waitUntil: 'domcontentloaded',
});
await expect(page.locator('main')).toBeVisible();

const articleBody = page.locator('main .prose').first();
const firstArticleLink = articleBody.locator(
'a[href="/posts/2026-01-27-java-modules-maven4-basics"]',
'a[href="/posts/2025-01-16-open-elements-in-2024"]',
);
const homeworkLink = articleBody.locator(
'a[href="/posts/2026-02-26-java-modules-maven4-basics-homework"]',
const supportCareLink = articleBody.locator(
'a[href="/support-care-maven"]',
);

await expect(firstArticleLink).toBeVisible();
await expect(firstArticleLink).toContainText('first article');
await expect(homeworkLink).toBeVisible();
await expect(homeworkLink).toContainText('homework extension');
await expect(firstArticleLink).toContainText('growth trajectory from 2024');
await expect(supportCareLink.first()).toBeVisible();
});

test('preserve relref fragments inside markdown links', async ({ page }) => {
await page.goto('/posts/2026-02-26-java-modules-maven4-basics-homework');
test('render relref links to nested routes', async ({ page }) => {
await page.goto('/posts/2026-02-10-review-2025', {
waitUntil: 'domcontentloaded',
});
await expect(page.locator('main')).toBeVisible();

const articleBody = page.locator('main .prose').first();
const fragmentLink = articleBody.locator(
'a[href="/posts/2026-01-27-java-modules-maven4-basics#the-module-source-hierarchy"]',
const articleLink = articleBody.locator(
'a[href="/articles/what-is-maven"]',
);
const employeeLink = articleBody.locator('a[href="/employees/jessie"]');

await expect(fragmentLink).toBeVisible();
await expect(fragmentLink).toContainText('module source hierarchy');
await expect(articleLink).toBeVisible();
await expect(articleLink).toContainText('work on Apache Maven');
await expect(employeeLink).toBeVisible();
await expect(employeeLink).toContainText('Jessy Ssebuliba');
});
});
13 changes: 8 additions & 5 deletions tests/e2e/markdown-tables.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ import { test, expect } from '@playwright/test';

test.describe('Markdown tables', () => {
test('render GFM tables in blog posts', async ({ page }) => {
await page.goto('/posts/2026-03-12-java-modules-encapsulation-internal-packages');
await page.goto('/posts/2020-02-21-adopt-tests', {
waitUntil: 'domcontentloaded',
});
await expect(page.locator('main')).toBeVisible();

const articleBody = page.locator('main .prose').first();
const table = articleBody.locator('table').first();

await expect(table).toBeVisible();
await expect(table).toContainText('Aspect');
await expect(table).toContainText('Classpath');
await expect(table).toContainText('Module Path');
await expect(table.locator('tr')).toHaveCount(5);
await expect(table).toContainText('name');
await expect(table).toContainText('openjdk');
await expect(table).toContainText('functional');
await expect(table.locator('tr')).toHaveCount(7);
});
});
19 changes: 12 additions & 7 deletions tests/e2e/post-heading-anchors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,28 @@ import { test, expect } from '@playwright/test';

test.describe('Post heading anchors', () => {
test('render Hugo-style anchor links for post headlines', async ({ page }) => {
await page.goto('/posts/2026-03-12-java-modules-encapsulation-internal-packages');
await page.goto('/posts/2026-03-05-oss-ai-slop', {
waitUntil: 'domcontentloaded',
});
await expect(page.locator('main')).toBeVisible();

const articleBody = page.locator('main .prose').first();
const punctuatedHeading = articleBody.locator(
'h2#controlling-visibility-with-module-infojava',
'h2#case-study-an-unusual-contribution-trend-in-the-context-of-major-projects',
);
const whyItMattersAnchor = articleBody.locator(
'h2#why-this-ai-slop-matters a[href="#why-this-ai-slop-matters"]',
);
const sourceCodeAnchor = articleBody.locator('h2#source-code a[href="#source-code"]');

await expect(punctuatedHeading).toBeVisible();
await expect(
punctuatedHeading.locator(
'a[href="#controlling-visibility-with-module-infojava"] [data-icon="mdi-link-variant"]',
'a[href="#case-study-an-unusual-contribution-trend-in-the-context-of-major-projects"] [data-icon="mdi-link-variant"]',
),
).toHaveCount(1);

await expect(sourceCodeAnchor).toBeVisible();
await sourceCodeAnchor.click();
await expect(page).toHaveURL(/#source-code$/);
await expect(whyItMattersAnchor).toBeVisible();
await whyItMattersAnchor.click();
await expect(page).toHaveURL(/#why-this-ai-slop-matters$/);
});
});
19 changes: 11 additions & 8 deletions tests/e2e/post-images-centering.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,19 @@ async function expectImageToBeCentered(page: Page, imageSelector: string) {

test.describe('Post images', () => {
test('center standalone images in blog posts', async ({ page }) => {
await page.goto('/posts/2025-01-16-open-elements-in-2024');
await page.goto('/posts/2020-02-21-adopt-tests', {
waitUntil: 'domcontentloaded',
});
await expect(page.locator('main')).toBeVisible();
await expectImageToBeCentered(page, 'img[alt="ci pipeline"]');

await page.goto('/posts/2026-03-12-agentic-wallets', {
waitUntil: 'domcontentloaded',
});
await expect(page.locator('main')).toBeVisible();
await expectImageToBeCentered(
page,
'img[alt="Open Elements continues to focus on open source and Java"]',
'img[alt="AI agents are blocked by traditional payment infrastructure that requires human identity verification"]',
);

await page.goto('/posts/2026-01-27-java-modules-maven4-basics');
await expectImageToBeCentered(page, 'img[alt="Module dependencies"]');

await page.goto('/posts/2026-03-05-oss-ai-slop');
await expectImageToBeCentered(page, 'img[alt="Symbolic image of open source maintenance"]');
});
});
16 changes: 16 additions & 0 deletions tests/e2e/post-language-switch.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import { test, expect } from '@playwright/test';

test.describe('Blog post language switch', () => {
test('keeps direct English post URLs stable for German browser preferences', async ({ browser }) => {
const context = await browser.newContext({
extraHTTPHeaders: {
'Accept-Language': 'de-DE,de',
},
});
const page = await context.newPage();

await page.goto('/posts/2026/03/12/agentic-wallets-when-ai-agents-need-to-pay');

await expect(page).toHaveURL(/\/posts\/2026\/03\/12\/agentic-wallets-when-ai-agents-need-to-pay\/?$/);
await expect(page.getByRole('heading', { level: 1 })).toContainText('Agentic Wallets');

await context.close();
});

test('hides the language switch on an English-only post', async ({ page }) => {
await page.goto('/posts/2026-03-12-agentic-wallets');

Expand Down
Loading