feat(www): landing page (#25)
* design: landing page spec + app icon asset * chore(www): add anime.js, fonts, and landing page screenshot assets * feat(www): add design tokens, fonts, shared layout * feat(www): add landing page sections and styling * feat(www): add theme toggle and anime.js viewport animations * feat(www): feedback updates — no hero card, os-aware download, footer email, lowercase colored heading * feat(www): remove screenshot borders, move macos hint below ctas, mobile github fallback * feat(www): keep hero CTAs side by side * feat(www): set initial nav border to transparent * feat(www): add Rybbit prod-only, sitemap, SEO head, OG image, robots.txt, 404 page * docs(AGENTS): include www landing page version in release sync checklist * chore(www): add railpack.json for Dokploy deploy (force node provider, TZ) * chore(www): use bun for Railpack deploy (packageManager + standalone www/bun.lock) * docs(AGENTS): note www/bun.lock sync for Railpack deploys * chore(www): add vitest test tooling * feat(www): add FAQ data source * feat(www): add SEO JSON-LD builders * feat(www): add Seo JSON-LD component * feat(www): wire Seo component and social/meta tags into Layout * feat(www): add homepage JSON-LD, keyword H1, data-driven FAQ * fix(www): point Roadmap footer link to GitHub * feat(www): add lastmod to sitemap * perf(www): self-host anime.js * refactor(www): move nav and footer into shared Layout * feat(www): add changelog page * fix(www): make shared component styles global in Layout * perf(www): optimize screenshots with Astro Image (webp/avif + srcset) * feat(www): add web app manifest * feat(www): add helpful links to 404 page * fix(www): shorten homepage H1 copy * fix(www): OS-aware navbar download + footer layout * feat(www): build full changelog from v0.7.0 to v0.7.10
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
buildSoftwareApplicationJsonLd,
|
||||
buildFaqJsonLd,
|
||||
buildOrganizationJsonLd,
|
||||
buildWebSiteJsonLd,
|
||||
buildBreadcrumbJsonLd,
|
||||
buildTitle,
|
||||
} from "./seo";
|
||||
|
||||
describe("seo builders", () => {
|
||||
it("buildSoftwareApplicationJsonLd returns valid schema", () => {
|
||||
const ld = buildSoftwareApplicationJsonLd({
|
||||
name: "Gridline",
|
||||
url: "https://getgridline.app/",
|
||||
description: "Open-source database GUI",
|
||||
image: "https://getgridline.app/og.png",
|
||||
operatingSystem: "macOS, Windows, Linux",
|
||||
applicationCategory: "DeveloperApplication",
|
||||
offersPrice: 0,
|
||||
offersCurrency: "USD",
|
||||
downloadUrl: "https://github.com/AdrianBonpin/gridline/releases",
|
||||
});
|
||||
expect(ld["@type"]).toBe("SoftwareApplication");
|
||||
expect(ld.name).toBe("Gridline");
|
||||
expect((ld.offers as { price: number }).price).toBe(0);
|
||||
});
|
||||
|
||||
it("buildFaqJsonLd maps questions to mainEntity", () => {
|
||||
const ld = buildFaqJsonLd([
|
||||
{ question: "Q1", answer: "A1" },
|
||||
{ question: "Q2", answer: "A2" },
|
||||
]);
|
||||
expect(ld["@type"]).toBe("FAQPage");
|
||||
const main = ld.mainEntity as { name: string }[];
|
||||
expect(main).toHaveLength(2);
|
||||
expect(main[0].name).toBe("Q1");
|
||||
});
|
||||
|
||||
it("buildOrganizationJsonLd includes sameAs", () => {
|
||||
const ld = buildOrganizationJsonLd({
|
||||
name: "Gridline",
|
||||
url: "https://getgridline.app/",
|
||||
logo: "https://getgridline.app/gridline-icon.svg",
|
||||
sameAs: ["https://github.com/AdrianBonpin/gridline"],
|
||||
});
|
||||
expect(ld["@type"]).toBe("Organization");
|
||||
expect(ld.sameAs).toContain("https://github.com/AdrianBonpin/gridline");
|
||||
});
|
||||
|
||||
it("buildWebSiteJsonLd includes SearchAction", () => {
|
||||
const ld = buildWebSiteJsonLd({
|
||||
name: "Gridline",
|
||||
url: "https://getgridline.app/",
|
||||
searchUrl: "https://getgridline.app/?q={search_term_string}",
|
||||
});
|
||||
expect(ld["@type"]).toBe("WebSite");
|
||||
expect(ld.potentialAction).toBeDefined();
|
||||
});
|
||||
|
||||
it("buildBreadcrumbJsonLd builds itemListElement", () => {
|
||||
const ld = buildBreadcrumbJsonLd([
|
||||
{ name: "Home", url: "https://getgridline.app/" },
|
||||
{ name: "Changelog", url: "https://getgridline.app/changelog" },
|
||||
]);
|
||||
expect(ld["@type"]).toBe("BreadcrumbList");
|
||||
expect((ld.itemListElement as unknown[]).length).toBe(2);
|
||||
});
|
||||
|
||||
it("buildTitle appends the brand", () => {
|
||||
expect(buildTitle("PostgreSQL GUI")).toBe("PostgreSQL GUI | Gridline");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user