@warpgogol/werkstatt-shared 0.14.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -16
- package/README.uk.md +24 -14
- package/package.json +7 -1
- package/src/checks/result-helpers.ts +21 -4
- package/src/checks/tests/remediation-auto-populate.test.ts +83 -0
- package/src/integration/vitest.config.ts +7 -0
- package/src/ontology/manifest.ts +19 -0
- package/src/ontology/schemas/system/tests/growth.test.ts +22 -0
- package/src/ontology/schemas/system/tests/integrations.test.ts +12 -0
- package/src/ontology/schemas/system/tests/manifest.test.ts +22 -0
- package/src/ontology/schemas/system/tests/page-output.test.ts +13 -0
- package/src/ontology/schemas/system/tests/release.test.ts +22 -0
- package/src/ontology/schemas/system/tests/text.test.ts +18 -0
- package/src/ontology/schemas/system/tests/verification.test.ts +12 -0
- package/src/ontology/schemas/tests/biome.test.ts +19 -0
- package/src/ontology/schemas/tests/capability.test.ts +22 -0
- package/src/ontology/schemas/tests/constellation.test.ts +22 -0
- package/src/ontology/schemas/tests/dns-records.test.ts +13 -0
- package/src/ontology/schemas/tests/manifest-resolver.test.ts +13 -0
- package/src/ontology/schemas/tests/page-entry.test.ts +22 -0
- package/src/ontology/schemas/tests/section-archetype.test.ts +12 -0
- package/src/ontology/schemas/tests/site-family.test.ts +18 -0
- package/src/ontology/schemas/tests/system.test.ts +20 -0
- package/src/share/agent/tests/api-catalog.test.ts +8 -0
- package/src/share/agent/tests/ard-catalog.test.ts +8 -0
- package/src/share/agent/tests/capability.test.ts +14 -0
- package/src/share/agent/tests/fleet-catalog.test.ts +15 -0
- package/src/share/agent/tests/knowledge.test.ts +20 -0
- package/src/share/agent/tests/manifest.test.ts +9 -0
- package/src/share/agent/tests/mcp-card.test.ts +8 -0
- package/src/share/agent/tests/omit-empty.test.ts +33 -0
- package/src/share/agent/tests/openapi.test.ts +8 -0
- package/src/share/agent/tests/proof.test.ts +15 -0
- package/src/share/knowledge/tests/claim.test.ts +11 -0
- package/src/share/knowledge/tests/derivation.test.ts +30 -0
- package/src/share/knowledge/tests/freshness.test.ts +18 -0
- package/src/share/knowledge/tests/ledger.test.ts +35 -0
- package/src/share/knowledge/tests/plan.test.ts +19 -0
- package/src/share/knowledge/tests/source.test.ts +19 -0
- package/src/share/remediation/remediation-catalog.ts +196 -0
- package/src/share/schemas/tests/claim-records.test.ts +52 -0
- package/src/share/schemas/tests/claims.test.ts +23 -0
- package/src/share/schemas/tests/effects.test.ts +116 -0
- package/src/share/schemas/tests/features.test.ts +61 -0
- package/src/share/schemas/tests/horizontal-align.test.ts +16 -0
- package/src/share/schemas/tests/icon-color.test.ts +16 -0
- package/src/share/schemas/tests/live-photo.test.ts +40 -0
- package/src/share/schemas/tests/material-credit.test.ts +51 -0
- package/src/share/schemas/tests/media.test.ts +26 -0
- package/src/share/schemas/tests/navigation.test.ts +58 -0
- package/src/share/schemas/tests/page-base.test.ts +19 -0
- package/src/share/schemas/tests/participant.test.ts +26 -0
- package/src/share/schemas/tests/person.test.ts +12 -0
- package/src/share/schemas/tests/print.test.ts +30 -0
- package/src/share/schemas/tests/section-background.test.ts +31 -0
- package/src/share/schemas/tests/section-body.test.ts +16 -0
- package/src/share/schemas/tests/section-cards.test.ts +17 -0
- package/src/share/schemas/tests/section-cta.test.ts +41 -0
- package/src/share/schemas/tests/section-header.test.ts +31 -0
- package/src/share/schemas/tests/section-image.test.ts +24 -0
- package/src/share/schemas/tests/section-motion.test.ts +29 -0
- package/src/share/schemas/tests/section-shell.test.ts +42 -0
- package/src/share/schemas/tests/section-stats.test.ts +17 -0
- package/src/share/schemas/tests/site-background.test.ts +33 -0
- package/src/share/schemas/tests/standard-list-item.test.ts +16 -0
- package/src/share/scripts/tests/external-link-qr.test.ts +8 -0
- package/src/share/scripts/tests/external-links.test.ts +25 -0
- package/src/share/scripts/tests/gsap-counter.test.ts +8 -0
- package/src/share/scripts/tests/gsap-mountain-journey.test.ts +8 -0
- package/src/share/scripts/tests/gsap-parallax.test.ts +8 -0
- package/src/share/scripts/tests/gsap-reveal.test.ts +8 -0
- package/src/share/scripts/tests/gsap-stagger.test.ts +8 -0
- package/src/share/scripts/tests/inline-number-animation.test.ts +8 -0
- package/src/share/scripts/tests/lenis.test.ts +8 -0
- package/src/share/scripts/tests/live-photos.test.ts +8 -0
- package/src/share/scripts/tests/lordicon.test.ts +8 -0
- package/src/share/scripts/tests/orchestrator.test.ts +8 -0
- package/src/share/scripts/tests/scheduler.test.ts +23 -0
- package/src/share/semantic/jsonld/tests/article.test.ts +89 -0
- package/src/share/semantic/jsonld/tests/breadcrumb.test.ts +62 -0
- package/src/share/semantic/jsonld/tests/collection-list.test.ts +39 -0
- package/src/share/semantic/jsonld/tests/faq.test.ts +60 -0
- package/src/share/semantic/jsonld/tests/initiative.test.ts +56 -0
- package/src/share/semantic/jsonld/tests/organization.test.ts +123 -0
- package/src/share/semantic/jsonld/tests/person.test.ts +89 -0
- package/src/share/semantic/jsonld/tests/service.test.ts +65 -0
- package/src/share/semantic/jsonld/tests/shared.test.ts +41 -0
- package/src/share/semantic/jsonld/tests/video.test.ts +84 -0
- package/src/share/semantic/jsonld/tests/webpage.test.ts +93 -0
- package/src/share/semantic/jsonld/tests/website.test.ts +27 -0
- package/src/share/semantic/jsonld/types.ts +1 -0
- package/src/share/semantic/models.ts +1 -0
- package/src/share/semantic/tests/ai.test.ts +55 -0
- package/src/share/semantic/tests/block-extraction.test.ts +34 -0
- package/src/share/semantic/tests/breadcrumbs.test.ts +127 -0
- package/src/share/semantic/tests/build-page.test.ts +122 -0
- package/src/share/semantic/tests/business-projection.test.ts +188 -0
- package/src/share/semantic/tests/derived-prices-loader.test.ts +46 -0
- package/src/share/semantic/tests/extract.test.ts +126 -0
- package/src/share/semantic/tests/feed.test.ts +113 -0
- package/src/share/semantic/tests/helpers.ts +32 -0
- package/src/share/semantic/tests/ids.test.ts +118 -0
- package/src/share/semantic/tests/image-sitemap.test.ts +109 -0
- package/src/share/semantic/tests/jsonld.test.ts +61 -0
- package/src/share/semantic/tests/llms-policy.test.ts +60 -0
- package/src/share/semantic/tests/llms.test.ts +118 -0
- package/src/share/semantic/tests/markdown-hygiene.test.ts +72 -0
- package/src/share/semantic/tests/markdown-page.test.ts +97 -0
- package/src/share/semantic/tests/markdown-twin-provenance.test.ts +152 -0
- package/src/share/semantic/tests/organization-profile.test.ts +107 -0
- package/src/share/semantic/tests/output-projection.test.ts +77 -0
- package/src/share/semantic/tests/page-markdown.test.ts +98 -0
- package/src/share/semantic/tests/page-utils.test.ts +88 -0
- package/src/share/semantic/tests/price-marker-resolver.test.ts +88 -0
- package/src/share/semantic/tests/robots.test.ts +70 -0
- package/src/share/semantic/tests/update-stamp.test.ts +102 -0
- package/src/share/utility-registry.yaml +14 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
sectionDensitySchema,
|
|
4
|
+
sectionToneSchema,
|
|
5
|
+
sectionContainerVariantSchema,
|
|
6
|
+
sectionShellAnimatedSchema,
|
|
7
|
+
} from "../section-shell.ts";
|
|
8
|
+
|
|
9
|
+
describe("sectionDensitySchema", () => {
|
|
10
|
+
it("accepts valid densities", () => {
|
|
11
|
+
for (const d of ["compact", "normal", "spacious", "flush"]) {
|
|
12
|
+
expect(sectionDensitySchema.parse(d)).toBe(d);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("rejects invalid density", () => {
|
|
17
|
+
expect(() => sectionDensitySchema.parse("loose")).toThrow();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("sectionToneSchema", () => {
|
|
22
|
+
it("accepts valid tones", () => {
|
|
23
|
+
for (const t of ["default", "warning", "success", "muted"]) {
|
|
24
|
+
expect(sectionToneSchema.parse(t)).toBe(t);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("sectionContainerVariantSchema", () => {
|
|
30
|
+
it("accepts valid variants", () => {
|
|
31
|
+
for (const v of ["default", "narrow", "full"]) {
|
|
32
|
+
expect(sectionContainerVariantSchema.parse(v)).toBe(v);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("sectionShellAnimatedSchema", () => {
|
|
38
|
+
it("accepts valid values", () => {
|
|
39
|
+
expect(sectionShellAnimatedSchema.parse("none")).toBe("none");
|
|
40
|
+
expect(sectionShellAnimatedSchema.parse("reveal")).toBe("reveal");
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { statItemSchema } from "../section-stats.ts";
|
|
3
|
+
|
|
4
|
+
describe("statItemSchema", () => {
|
|
5
|
+
it("accepts a valid stat item", () => {
|
|
6
|
+
const result = statItemSchema.safeParse({
|
|
7
|
+
label: "Users",
|
|
8
|
+
value: "10K",
|
|
9
|
+
});
|
|
10
|
+
expect(result.success).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("rejects empty object", () => {
|
|
14
|
+
const result = statItemSchema.safeParse({});
|
|
15
|
+
expect(result.success).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { siteBackgroundLayerSchema } from "../site-background.ts";
|
|
3
|
+
|
|
4
|
+
describe("siteBackgroundLayerSchema", () => {
|
|
5
|
+
it("accepts color kind", () => {
|
|
6
|
+
const result = siteBackgroundLayerSchema.safeParse({ kind: "color" });
|
|
7
|
+
expect(result.success).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("accepts image kind with imageName", () => {
|
|
11
|
+
const result = siteBackgroundLayerSchema.safeParse({
|
|
12
|
+
kind: "image",
|
|
13
|
+
imageName: "bg",
|
|
14
|
+
});
|
|
15
|
+
expect(result.success).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("accepts gradient kind with required fields", () => {
|
|
19
|
+
const result = siteBackgroundLayerSchema.safeParse({
|
|
20
|
+
kind: "gradient",
|
|
21
|
+
direction: "vertical",
|
|
22
|
+
stops: [
|
|
23
|
+
{ at: 0, color: "#000" },
|
|
24
|
+
{ at: 1, color: "#fff" },
|
|
25
|
+
],
|
|
26
|
+
});
|
|
27
|
+
expect(result.success).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("rejects invalid kind", () => {
|
|
31
|
+
expect(() => siteBackgroundLayerSchema.parse({ kind: "video" })).toThrow();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { standardListItemSchema } from "../standard-list-item.ts";
|
|
3
|
+
|
|
4
|
+
describe("standardListItemSchema", () => {
|
|
5
|
+
it("accepts a valid list item", () => {
|
|
6
|
+
const result = standardListItemSchema.safeParse({
|
|
7
|
+
text: "Test item",
|
|
8
|
+
});
|
|
9
|
+
expect(result.success).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("rejects empty object", () => {
|
|
13
|
+
const result = standardListItemSchema.safeParse({});
|
|
14
|
+
expect(result.success).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { applyExternalLinkBehavior } from "../external-links.ts";
|
|
3
|
+
|
|
4
|
+
describe("applyExternalLinkBehavior", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.stubGlobal("document", {
|
|
7
|
+
querySelectorAll: vi.fn(() => []),
|
|
8
|
+
});
|
|
9
|
+
vi.stubGlobal("window", {
|
|
10
|
+
location: { href: "https://example.com/", origin: "https://example.com" },
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
vi.unstubAllGlobals();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("is a function", () => {
|
|
19
|
+
expect(typeof applyExternalLinkBehavior).toBe("function");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("does not throw when no anchors exist", () => {
|
|
23
|
+
expect(() => applyExternalLinkBehavior()).not.toThrow();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { initMountainJourneyAnimation } from "../gsap-mountain-journey.ts";
|
|
3
|
+
|
|
4
|
+
describe("initMountainJourneyAnimation", () => {
|
|
5
|
+
it("is an async function", () => {
|
|
6
|
+
expect(typeof initMountainJourneyAnimation).toBe("function");
|
|
7
|
+
});
|
|
8
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { initInlineNumberAnimation } from "../inline-number-animation.ts";
|
|
3
|
+
|
|
4
|
+
describe("initInlineNumberAnimation", () => {
|
|
5
|
+
it("is an async function", () => {
|
|
6
|
+
expect(typeof initInlineNumberAnimation).toBe("function");
|
|
7
|
+
});
|
|
8
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { runStandardLayoutOrchestration } from "../orchestrator.ts";
|
|
3
|
+
|
|
4
|
+
describe("runStandardLayoutOrchestration", () => {
|
|
5
|
+
it("is an async function", () => {
|
|
6
|
+
expect(typeof runStandardLayoutOrchestration).toBe("function");
|
|
7
|
+
});
|
|
8
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { scheduleTask } from "../scheduler.ts";
|
|
3
|
+
|
|
4
|
+
describe("scheduleTask", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.stubGlobal("window", {
|
|
7
|
+
requestIdleCallback: vi.fn(),
|
|
8
|
+
setTimeout: vi.fn(),
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
vi.unstubAllGlobals();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("is a function", () => {
|
|
17
|
+
expect(typeof scheduleTask).toBe("function");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("does not throw when scheduling a task", () => {
|
|
21
|
+
expect(() => scheduleTask(() => undefined)).not.toThrow();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildArticleNode } from "../article.ts";
|
|
3
|
+
import { createJsonLdContext } from "../context.ts";
|
|
4
|
+
import { makePage } from "../../tests/helpers.ts";
|
|
5
|
+
|
|
6
|
+
describe("buildArticleNode", () => {
|
|
7
|
+
it("returns null when page has no datePublished", () => {
|
|
8
|
+
const page = makePage();
|
|
9
|
+
const ctx = createJsonLdContext(page);
|
|
10
|
+
expect(buildArticleNode(ctx)).toBeNull();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("builds Article/BlogPosting node with datePublished", () => {
|
|
14
|
+
const page = makePage({ datePublished: "2024-01-15" });
|
|
15
|
+
const ctx = createJsonLdContext(page);
|
|
16
|
+
const node = buildArticleNode(ctx);
|
|
17
|
+
expect(node).not.toBeNull();
|
|
18
|
+
expect(node!["@type"]).toEqual(["Article", "BlogPosting"]);
|
|
19
|
+
expect(node!.datePublished).toBe("2024-01-15");
|
|
20
|
+
expect(node!.dateModified).toBe("2024-01-15");
|
|
21
|
+
expect(node!.headline).toBe("Test Heading");
|
|
22
|
+
expect(node!.inLanguage).toBe("de");
|
|
23
|
+
expect(node!.publisher).toEqual({ "@id": ctx.ids.organization });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("uses dateModified when provided", () => {
|
|
27
|
+
const page = makePage({ datePublished: "2024-01-15", dateModified: "2024-06-01" });
|
|
28
|
+
const ctx = createJsonLdContext(page);
|
|
29
|
+
const node = buildArticleNode(ctx);
|
|
30
|
+
expect(node!.dateModified).toBe("2024-06-01");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("emits author as Person when author string is present", () => {
|
|
34
|
+
const page = makePage({ datePublished: "2024-01-15", author: "Jane Doe" });
|
|
35
|
+
const ctx = createJsonLdContext(page);
|
|
36
|
+
const node = buildArticleNode(ctx);
|
|
37
|
+
expect(node!.author).toEqual({ "@type": "Person", name: "Jane Doe" });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("emits structured author from authorRecord", () => {
|
|
41
|
+
const page = makePage({
|
|
42
|
+
datePublished: "2024-01-15",
|
|
43
|
+
authorRecord: { name: "Jane Doe", contactUrl: "https://example.com/contact" },
|
|
44
|
+
});
|
|
45
|
+
const ctx = createJsonLdContext(page);
|
|
46
|
+
const node = buildArticleNode(ctx);
|
|
47
|
+
expect(node!.author).toEqual({
|
|
48
|
+
"@type": "Person",
|
|
49
|
+
name: "Jane Doe",
|
|
50
|
+
url: "https://example.com/contact",
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("emits keywords as comma-separated string", () => {
|
|
55
|
+
const page = makePage({ datePublished: "2024-01-15", keywords: ["foo", "bar"] });
|
|
56
|
+
const ctx = createJsonLdContext(page);
|
|
57
|
+
const node = buildArticleNode(ctx);
|
|
58
|
+
expect(node!.keywords).toBe("foo, bar");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("emits image from primaryImage", () => {
|
|
62
|
+
const page = makePage({
|
|
63
|
+
datePublished: "2024-01-15",
|
|
64
|
+
primaryImage: { url: "https://example.com/img.jpg" },
|
|
65
|
+
});
|
|
66
|
+
const ctx = createJsonLdContext(page);
|
|
67
|
+
const node = buildArticleNode(ctx);
|
|
68
|
+
expect(node!.image).toBe("https://example.com/img.jpg");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("uses page URL as mainEntityOfPage for ratgeber depth-1", () => {
|
|
72
|
+
const page = makePage({
|
|
73
|
+
datePublished: "2024-01-15",
|
|
74
|
+
surfaceId: "ratgeber",
|
|
75
|
+
depth: 1,
|
|
76
|
+
url: "https://example.com/de/ratgeber/article/",
|
|
77
|
+
});
|
|
78
|
+
const ctx = createJsonLdContext(page);
|
|
79
|
+
const node = buildArticleNode(ctx);
|
|
80
|
+
expect(node!.mainEntityOfPage).toBe("https://example.com/de/ratgeber/article/");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("uses webpage @id as mainEntityOfPage for non-ratgeber pages", () => {
|
|
84
|
+
const page = makePage({ datePublished: "2024-01-15" });
|
|
85
|
+
const ctx = createJsonLdContext(page);
|
|
86
|
+
const node = buildArticleNode(ctx);
|
|
87
|
+
expect(node!.mainEntityOfPage).toEqual({ "@id": ctx.webpageId });
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildBreadcrumbNode } from "../breadcrumb.ts";
|
|
3
|
+
import { createJsonLdContext } from "../context.ts";
|
|
4
|
+
import { makePage } from "../../tests/helpers.ts";
|
|
5
|
+
|
|
6
|
+
describe("buildBreadcrumbNode", () => {
|
|
7
|
+
it("returns null when only one breadcrumb", () => {
|
|
8
|
+
const page = makePage({
|
|
9
|
+
breadcrumbs: [{ name: "Home", url: "https://example.com/de/" }],
|
|
10
|
+
});
|
|
11
|
+
const ctx = createJsonLdContext(page);
|
|
12
|
+
expect(buildBreadcrumbNode(ctx)).toBeNull();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("builds BreadcrumbList with multiple crumbs", () => {
|
|
16
|
+
const page = makePage({
|
|
17
|
+
breadcrumbs: [
|
|
18
|
+
{ name: "Home", url: "https://example.com/de/" },
|
|
19
|
+
{ name: "About", url: "https://example.com/de/about/" },
|
|
20
|
+
],
|
|
21
|
+
});
|
|
22
|
+
const ctx = createJsonLdContext(page);
|
|
23
|
+
const node = buildBreadcrumbNode(ctx);
|
|
24
|
+
expect(node).not.toBeNull();
|
|
25
|
+
expect(node!["@type"]).toBe("BreadcrumbList");
|
|
26
|
+
expect(node!.itemListElement).toHaveLength(2);
|
|
27
|
+
expect(node as Record<string, unknown>).toMatchObject({
|
|
28
|
+
itemListElement: [
|
|
29
|
+
{ position: 1, name: "Home" },
|
|
30
|
+
{ position: 2, name: "About" },
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("resolves relative crumb URLs against page URL", () => {
|
|
36
|
+
const page = makePage({
|
|
37
|
+
breadcrumbs: [
|
|
38
|
+
{ name: "Home", url: "/de/" },
|
|
39
|
+
{ name: "About", url: "/de/about/" },
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
const ctx = createJsonLdContext(page);
|
|
43
|
+
const node = buildBreadcrumbNode(ctx);
|
|
44
|
+
const items = (node as Record<string, unknown[]>).itemListElement as Record<string, unknown>[];
|
|
45
|
+
expect(items[0].item).toBe("https://example.com/de/");
|
|
46
|
+
expect(items[1].item).toBe("https://example.com/de/about/");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("assigns position-scoped @id to each crumb", () => {
|
|
50
|
+
const page = makePage({
|
|
51
|
+
breadcrumbs: [
|
|
52
|
+
{ name: "Home", url: "https://example.com/de/" },
|
|
53
|
+
{ name: "About", url: "https://example.com/de/about/" },
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
const ctx = createJsonLdContext(page);
|
|
57
|
+
const node = buildBreadcrumbNode(ctx);
|
|
58
|
+
const items = (node as Record<string, unknown[]>).itemListElement as Record<string, unknown>[];
|
|
59
|
+
expect(items[0]["@id"]).toBe(`${ctx.breadcrumbId}/1`);
|
|
60
|
+
expect(items[1]["@id"]).toBe(`${ctx.breadcrumbId}/2`);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildCollectionListNode } from "../collection-list.ts";
|
|
3
|
+
import { createJsonLdContext } from "../context.ts";
|
|
4
|
+
import { makePage } from "../../tests/helpers.ts";
|
|
5
|
+
|
|
6
|
+
describe("buildCollectionListNode", () => {
|
|
7
|
+
it("returns null when page is not collection type", () => {
|
|
8
|
+
const page = makePage({ type: "content" });
|
|
9
|
+
const ctx = createJsonLdContext(page);
|
|
10
|
+
expect(buildCollectionListNode(ctx)).toBeNull();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns null when collection has no items", () => {
|
|
14
|
+
const page = makePage({ type: "collection", collectionItems: [] });
|
|
15
|
+
const ctx = createJsonLdContext(page);
|
|
16
|
+
expect(buildCollectionListNode(ctx)).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("builds ItemList for collection page with items", () => {
|
|
20
|
+
const page = makePage({
|
|
21
|
+
type: "collection",
|
|
22
|
+
collectionItems: [
|
|
23
|
+
{ url: "https://example.com/de/industry-a/", name: "Industry A" },
|
|
24
|
+
{ url: "https://example.com/de/industry-b/", name: "Industry B" },
|
|
25
|
+
],
|
|
26
|
+
});
|
|
27
|
+
const ctx = createJsonLdContext(page);
|
|
28
|
+
const node = buildCollectionListNode(ctx);
|
|
29
|
+
expect(node).not.toBeNull();
|
|
30
|
+
expect(node!["@type"]).toBe("ItemList");
|
|
31
|
+
expect(node!.itemListElement).toHaveLength(2);
|
|
32
|
+
const items = (node as Record<string, unknown[]>).itemListElement as Record<string, unknown>[];
|
|
33
|
+
expect(items[0]).toMatchObject({
|
|
34
|
+
position: 1,
|
|
35
|
+
url: "https://example.com/de/industry-a/",
|
|
36
|
+
name: "Industry A",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildFaqNodes, buildFaqPageNode } from "../faq.ts";
|
|
3
|
+
import { createJsonLdContext } from "../context.ts";
|
|
4
|
+
import { makePage } from "../../tests/helpers.ts";
|
|
5
|
+
|
|
6
|
+
describe("buildFaqPageNode", () => {
|
|
7
|
+
it("returns null when no faqEntries", () => {
|
|
8
|
+
const page = makePage();
|
|
9
|
+
const ctx = createJsonLdContext(page);
|
|
10
|
+
expect(buildFaqPageNode(ctx)).toBeNull();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns null when faqEntries is empty", () => {
|
|
14
|
+
const page = makePage({ faqEntries: [] });
|
|
15
|
+
const ctx = createJsonLdContext(page);
|
|
16
|
+
expect(buildFaqPageNode(ctx)).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("builds FAQPage with Question nodes", () => {
|
|
20
|
+
const page = makePage({
|
|
21
|
+
faqEntries: [
|
|
22
|
+
{ id: "q1", question: "What is this?", answer: "A test." },
|
|
23
|
+
{ id: "q2", question: "How much?", answer: "Free." },
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
const ctx = createJsonLdContext(page);
|
|
27
|
+
const node = buildFaqPageNode(ctx);
|
|
28
|
+
expect(node).not.toBeNull();
|
|
29
|
+
expect(node!["@type"]).toBe("FAQPage");
|
|
30
|
+
expect(node!.mainEntity).toHaveLength(2);
|
|
31
|
+
const entities = (node as Record<string, unknown[]>).mainEntity as Record<string, unknown>[];
|
|
32
|
+
expect(entities[0]).toEqual({
|
|
33
|
+
"@type": "Question",
|
|
34
|
+
name: "What is this?",
|
|
35
|
+
acceptedAnswer: { "@type": "Answer", text: "A test." },
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("buildFaqNodes", () => {
|
|
41
|
+
it("suppresses FAQPage on ratgeber depth-1 article pages", () => {
|
|
42
|
+
const page = makePage({
|
|
43
|
+
surfaceId: "ratgeber",
|
|
44
|
+
depth: 1,
|
|
45
|
+
faqEntries: [{ id: "q1", question: "Q?", answer: "A." }],
|
|
46
|
+
});
|
|
47
|
+
const ctx = createJsonLdContext(page);
|
|
48
|
+
expect(buildFaqNodes(ctx)).toEqual([]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("emits FAQPage for non-ratgeber pages with FAQ entries", () => {
|
|
52
|
+
const page = makePage({
|
|
53
|
+
faqEntries: [{ id: "q1", question: "Q?", answer: "A." }],
|
|
54
|
+
});
|
|
55
|
+
const ctx = createJsonLdContext(page);
|
|
56
|
+
const nodes = buildFaqNodes(ctx);
|
|
57
|
+
expect(nodes).toHaveLength(1);
|
|
58
|
+
expect(nodes[0]["@type"]).toBe("FAQPage");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildInitiativeNodes, buildInitiativesListNode } from "../initiative.ts";
|
|
3
|
+
import { createJsonLdContext } from "../context.ts";
|
|
4
|
+
import { makePage } from "../../tests/helpers.ts";
|
|
5
|
+
|
|
6
|
+
describe("buildInitiativeNodes", () => {
|
|
7
|
+
it("returns empty array when no initiatives", () => {
|
|
8
|
+
const page = makePage();
|
|
9
|
+
const ctx = createJsonLdContext(page);
|
|
10
|
+
expect(buildInitiativeNodes(ctx)).toEqual([]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("builds Thing/Project nodes for each initiative", () => {
|
|
14
|
+
const page = makePage({
|
|
15
|
+
initiatives: [
|
|
16
|
+
{ id: "i1", name: "Project Alpha", summary: "First project" },
|
|
17
|
+
{ id: "i2", name: "Project Beta", summary: "Second project" },
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
const ctx = createJsonLdContext(page);
|
|
21
|
+
const nodes = buildInitiativeNodes(ctx);
|
|
22
|
+
expect(nodes).toHaveLength(2);
|
|
23
|
+
expect(nodes[0]["@type"]).toEqual(["Thing", "Project"]);
|
|
24
|
+
expect(nodes[0].name).toBe("Project Alpha");
|
|
25
|
+
expect(nodes[0].description).toBe("First project");
|
|
26
|
+
expect(nodes[1].name).toBe("Project Beta");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("buildInitiativesListNode", () => {
|
|
31
|
+
it("returns null when no initiatives", () => {
|
|
32
|
+
const page = makePage();
|
|
33
|
+
const ctx = createJsonLdContext(page);
|
|
34
|
+
expect(buildInitiativesListNode(ctx)).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("builds ItemList with positioned initiatives", () => {
|
|
38
|
+
const page = makePage({
|
|
39
|
+
initiatives: [
|
|
40
|
+
{ id: "i1", name: "Project Alpha", summary: "First" },
|
|
41
|
+
{ id: "i2", name: "Project Beta", summary: "Second" },
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
const ctx = createJsonLdContext(page);
|
|
45
|
+
const node = buildInitiativesListNode(ctx);
|
|
46
|
+
expect(node).not.toBeNull();
|
|
47
|
+
expect(node!["@type"]).toBe("ItemList");
|
|
48
|
+
expect(node!.itemListElement).toHaveLength(2);
|
|
49
|
+
expect(node as Record<string, unknown>).toMatchObject({
|
|
50
|
+
itemListElement: [
|
|
51
|
+
{ position: 1, name: "Project Alpha" },
|
|
52
|
+
{ position: 2, name: "Project Beta" },
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|