Brings the working codebase (Next.js app, auth system, Stripe billing, Docker/deploy config, tests, docs) into version control on top of the placeholder initial commit, and adds account self-deletion (Danger Zone in Settings, password + typed-email confirmation, cascading DB cleanup, Stripe cancellation) per GDPR right-to-erasure. Excludes local build caches, node_modules, and internal agent scratch files; .gitignore hardened to keep those out going forward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
90 lines
3.2 KiB
TypeScript
90 lines
3.2 KiB
TypeScript
/**
|
|
* Locale-specific SEO slugs — public German keyword URLs rewrite onto the
|
|
* shared App Router folders; English slugs under /de/ 301 to the DE slug.
|
|
*/
|
|
|
|
import { describe, test, expect } from "./runner";
|
|
import {
|
|
decideSeoLocalePath,
|
|
localizePathname,
|
|
publicPath,
|
|
} from "../../src/lib/seo/slugs";
|
|
|
|
describe("SEO slugs — public paths", () => {
|
|
test("German blog slugs use keyword URLs", () => {
|
|
expect(publicPath("de", "blog/simple-expense-tracking")).toBe(
|
|
"/de/blog/einfache-ausgabenverfolgung",
|
|
);
|
|
expect(publicPath("en", "blog/simple-expense-tracking")).toBe(
|
|
"/en/blog/simple-expense-tracking",
|
|
);
|
|
});
|
|
|
|
test("German keyword pages use German slugs", () => {
|
|
expect(publicPath("de", "ocr-receipt-scanner")).toBe("/de/beleg-ocr");
|
|
expect(publicPath("de", "expense-tracker-freelancers")).toBe(
|
|
"/de/ausgaben-tracker-freelancer",
|
|
);
|
|
expect(publicPath("de", "expensify-alternative")).toBe("/de/alternative-zu-expensify");
|
|
expect(publicPath("de", "lexoffice-alternative")).toBe("/de/alternative-zu-lexoffice");
|
|
});
|
|
});
|
|
|
|
describe("SEO slugs — middleware decisions", () => {
|
|
test("DE public slug rewrites onto the internal folder", () => {
|
|
expect(decideSeoLocalePath("/de/blog/einfache-ausgabenverfolgung")).toEqual({
|
|
kind: "rewrite",
|
|
pathname: "/de/blog/simple-expense-tracking",
|
|
});
|
|
expect(decideSeoLocalePath("/de/beleg-ocr")).toEqual({
|
|
kind: "rewrite",
|
|
pathname: "/de/ocr-receipt-scanner",
|
|
});
|
|
});
|
|
|
|
test("English slug under /de/ redirects to the German keyword URL", () => {
|
|
expect(decideSeoLocalePath("/de/blog/simple-expense-tracking")).toEqual({
|
|
kind: "redirect",
|
|
pathname: "/de/blog/einfache-ausgabenverfolgung",
|
|
});
|
|
expect(decideSeoLocalePath("/de/ocr-receipt-scanner")).toEqual({
|
|
kind: "redirect",
|
|
pathname: "/de/beleg-ocr",
|
|
});
|
|
});
|
|
|
|
test("German slug under /en/ redirects to the English URL", () => {
|
|
expect(decideSeoLocalePath("/en/blog/einfache-ausgabenverfolgung")).toEqual({
|
|
kind: "redirect",
|
|
pathname: "/en/blog/simple-expense-tracking",
|
|
});
|
|
});
|
|
|
|
test("correct English public slugs pass through", () => {
|
|
expect(decideSeoLocalePath("/en/blog/simple-expense-tracking")).toEqual({
|
|
kind: "none",
|
|
});
|
|
expect(decideSeoLocalePath("/en/ocr-receipt-scanner")).toEqual({ kind: "none" });
|
|
});
|
|
|
|
test("homepage and blog index are untouched", () => {
|
|
expect(decideSeoLocalePath("/de")).toEqual({ kind: "none" });
|
|
expect(decideSeoLocalePath("/en")).toEqual({ kind: "none" });
|
|
expect(decideSeoLocalePath("/de/blog")).toEqual({ kind: "none" });
|
|
expect(decideSeoLocalePath("/en/blog")).toEqual({ kind: "none" });
|
|
});
|
|
});
|
|
|
|
describe("SEO slugs — language switch", () => {
|
|
test("maps the sibling locale slug instead of only swapping /en and /de", () => {
|
|
expect(localizePathname("/en/blog/simple-expense-tracking", "de")).toBe(
|
|
"/de/blog/einfache-ausgabenverfolgung",
|
|
);
|
|
expect(localizePathname("/de/blog/einfache-ausgabenverfolgung", "en")).toBe(
|
|
"/en/blog/simple-expense-tracking",
|
|
);
|
|
expect(localizePathname("/de", "en")).toBe("/en");
|
|
expect(localizePathname("/en/blog", "de")).toBe("/de/blog");
|
|
});
|
|
});
|