@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.
Files changed (117) hide show
  1. package/README.md +26 -16
  2. package/README.uk.md +24 -14
  3. package/package.json +7 -1
  4. package/src/checks/result-helpers.ts +21 -4
  5. package/src/checks/tests/remediation-auto-populate.test.ts +83 -0
  6. package/src/integration/vitest.config.ts +7 -0
  7. package/src/ontology/manifest.ts +19 -0
  8. package/src/ontology/schemas/system/tests/growth.test.ts +22 -0
  9. package/src/ontology/schemas/system/tests/integrations.test.ts +12 -0
  10. package/src/ontology/schemas/system/tests/manifest.test.ts +22 -0
  11. package/src/ontology/schemas/system/tests/page-output.test.ts +13 -0
  12. package/src/ontology/schemas/system/tests/release.test.ts +22 -0
  13. package/src/ontology/schemas/system/tests/text.test.ts +18 -0
  14. package/src/ontology/schemas/system/tests/verification.test.ts +12 -0
  15. package/src/ontology/schemas/tests/biome.test.ts +19 -0
  16. package/src/ontology/schemas/tests/capability.test.ts +22 -0
  17. package/src/ontology/schemas/tests/constellation.test.ts +22 -0
  18. package/src/ontology/schemas/tests/dns-records.test.ts +13 -0
  19. package/src/ontology/schemas/tests/manifest-resolver.test.ts +13 -0
  20. package/src/ontology/schemas/tests/page-entry.test.ts +22 -0
  21. package/src/ontology/schemas/tests/section-archetype.test.ts +12 -0
  22. package/src/ontology/schemas/tests/site-family.test.ts +18 -0
  23. package/src/ontology/schemas/tests/system.test.ts +20 -0
  24. package/src/share/agent/tests/api-catalog.test.ts +8 -0
  25. package/src/share/agent/tests/ard-catalog.test.ts +8 -0
  26. package/src/share/agent/tests/capability.test.ts +14 -0
  27. package/src/share/agent/tests/fleet-catalog.test.ts +15 -0
  28. package/src/share/agent/tests/knowledge.test.ts +20 -0
  29. package/src/share/agent/tests/manifest.test.ts +9 -0
  30. package/src/share/agent/tests/mcp-card.test.ts +8 -0
  31. package/src/share/agent/tests/omit-empty.test.ts +33 -0
  32. package/src/share/agent/tests/openapi.test.ts +8 -0
  33. package/src/share/agent/tests/proof.test.ts +15 -0
  34. package/src/share/knowledge/tests/claim.test.ts +11 -0
  35. package/src/share/knowledge/tests/derivation.test.ts +30 -0
  36. package/src/share/knowledge/tests/freshness.test.ts +18 -0
  37. package/src/share/knowledge/tests/ledger.test.ts +35 -0
  38. package/src/share/knowledge/tests/plan.test.ts +19 -0
  39. package/src/share/knowledge/tests/source.test.ts +19 -0
  40. package/src/share/remediation/remediation-catalog.ts +196 -0
  41. package/src/share/schemas/tests/claim-records.test.ts +52 -0
  42. package/src/share/schemas/tests/claims.test.ts +23 -0
  43. package/src/share/schemas/tests/effects.test.ts +116 -0
  44. package/src/share/schemas/tests/features.test.ts +61 -0
  45. package/src/share/schemas/tests/horizontal-align.test.ts +16 -0
  46. package/src/share/schemas/tests/icon-color.test.ts +16 -0
  47. package/src/share/schemas/tests/live-photo.test.ts +40 -0
  48. package/src/share/schemas/tests/material-credit.test.ts +51 -0
  49. package/src/share/schemas/tests/media.test.ts +26 -0
  50. package/src/share/schemas/tests/navigation.test.ts +58 -0
  51. package/src/share/schemas/tests/page-base.test.ts +19 -0
  52. package/src/share/schemas/tests/participant.test.ts +26 -0
  53. package/src/share/schemas/tests/person.test.ts +12 -0
  54. package/src/share/schemas/tests/print.test.ts +30 -0
  55. package/src/share/schemas/tests/section-background.test.ts +31 -0
  56. package/src/share/schemas/tests/section-body.test.ts +16 -0
  57. package/src/share/schemas/tests/section-cards.test.ts +17 -0
  58. package/src/share/schemas/tests/section-cta.test.ts +41 -0
  59. package/src/share/schemas/tests/section-header.test.ts +31 -0
  60. package/src/share/schemas/tests/section-image.test.ts +24 -0
  61. package/src/share/schemas/tests/section-motion.test.ts +29 -0
  62. package/src/share/schemas/tests/section-shell.test.ts +42 -0
  63. package/src/share/schemas/tests/section-stats.test.ts +17 -0
  64. package/src/share/schemas/tests/site-background.test.ts +33 -0
  65. package/src/share/schemas/tests/standard-list-item.test.ts +16 -0
  66. package/src/share/scripts/tests/external-link-qr.test.ts +8 -0
  67. package/src/share/scripts/tests/external-links.test.ts +25 -0
  68. package/src/share/scripts/tests/gsap-counter.test.ts +8 -0
  69. package/src/share/scripts/tests/gsap-mountain-journey.test.ts +8 -0
  70. package/src/share/scripts/tests/gsap-parallax.test.ts +8 -0
  71. package/src/share/scripts/tests/gsap-reveal.test.ts +8 -0
  72. package/src/share/scripts/tests/gsap-stagger.test.ts +8 -0
  73. package/src/share/scripts/tests/inline-number-animation.test.ts +8 -0
  74. package/src/share/scripts/tests/lenis.test.ts +8 -0
  75. package/src/share/scripts/tests/live-photos.test.ts +8 -0
  76. package/src/share/scripts/tests/lordicon.test.ts +8 -0
  77. package/src/share/scripts/tests/orchestrator.test.ts +8 -0
  78. package/src/share/scripts/tests/scheduler.test.ts +23 -0
  79. package/src/share/semantic/jsonld/tests/article.test.ts +89 -0
  80. package/src/share/semantic/jsonld/tests/breadcrumb.test.ts +62 -0
  81. package/src/share/semantic/jsonld/tests/collection-list.test.ts +39 -0
  82. package/src/share/semantic/jsonld/tests/faq.test.ts +60 -0
  83. package/src/share/semantic/jsonld/tests/initiative.test.ts +56 -0
  84. package/src/share/semantic/jsonld/tests/organization.test.ts +123 -0
  85. package/src/share/semantic/jsonld/tests/person.test.ts +89 -0
  86. package/src/share/semantic/jsonld/tests/service.test.ts +65 -0
  87. package/src/share/semantic/jsonld/tests/shared.test.ts +41 -0
  88. package/src/share/semantic/jsonld/tests/video.test.ts +84 -0
  89. package/src/share/semantic/jsonld/tests/webpage.test.ts +93 -0
  90. package/src/share/semantic/jsonld/tests/website.test.ts +27 -0
  91. package/src/share/semantic/jsonld/types.ts +1 -0
  92. package/src/share/semantic/models.ts +1 -0
  93. package/src/share/semantic/tests/ai.test.ts +55 -0
  94. package/src/share/semantic/tests/block-extraction.test.ts +34 -0
  95. package/src/share/semantic/tests/breadcrumbs.test.ts +127 -0
  96. package/src/share/semantic/tests/build-page.test.ts +122 -0
  97. package/src/share/semantic/tests/business-projection.test.ts +188 -0
  98. package/src/share/semantic/tests/derived-prices-loader.test.ts +46 -0
  99. package/src/share/semantic/tests/extract.test.ts +126 -0
  100. package/src/share/semantic/tests/feed.test.ts +113 -0
  101. package/src/share/semantic/tests/helpers.ts +32 -0
  102. package/src/share/semantic/tests/ids.test.ts +118 -0
  103. package/src/share/semantic/tests/image-sitemap.test.ts +109 -0
  104. package/src/share/semantic/tests/jsonld.test.ts +61 -0
  105. package/src/share/semantic/tests/llms-policy.test.ts +60 -0
  106. package/src/share/semantic/tests/llms.test.ts +118 -0
  107. package/src/share/semantic/tests/markdown-hygiene.test.ts +72 -0
  108. package/src/share/semantic/tests/markdown-page.test.ts +97 -0
  109. package/src/share/semantic/tests/markdown-twin-provenance.test.ts +152 -0
  110. package/src/share/semantic/tests/organization-profile.test.ts +107 -0
  111. package/src/share/semantic/tests/output-projection.test.ts +77 -0
  112. package/src/share/semantic/tests/page-markdown.test.ts +98 -0
  113. package/src/share/semantic/tests/page-utils.test.ts +88 -0
  114. package/src/share/semantic/tests/price-marker-resolver.test.ts +88 -0
  115. package/src/share/semantic/tests/robots.test.ts +70 -0
  116. package/src/share/semantic/tests/update-stamp.test.ts +102 -0
  117. package/src/share/utility-registry.yaml +14 -0
@@ -0,0 +1,123 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildOrganizationNode } from "../organization.ts";
3
+ import { createJsonLdContext } from "../context.ts";
4
+ import { makePage } from "../../tests/helpers.ts";
5
+
6
+ describe("buildOrganizationNode", () => {
7
+ it("builds Organization node with default schemaType", () => {
8
+ const page = makePage();
9
+ const ctx = createJsonLdContext(page);
10
+ const node = buildOrganizationNode(ctx);
11
+ expect(node["@type"]).toEqual(["Organization", "NGO"]);
12
+ expect(node["@id"]).toBe(ctx.ids.organization);
13
+ expect(node.name).toBe("Warpgogol");
14
+ expect(node.description).toBe("Digital infrastructure for nonprofits");
15
+ expect(node.url).toBe("https://example.com");
16
+ });
17
+
18
+ it("uses custom schemaType when provided", () => {
19
+ const page = makePage({
20
+ organization: makePage().organization,
21
+ });
22
+ page.organization.schemaType = ["Organization", "ProfessionalService"];
23
+ const ctx = createJsonLdContext(page);
24
+ const node = buildOrganizationNode(ctx);
25
+ expect(node["@type"]).toEqual(["Organization", "ProfessionalService"]);
26
+ });
27
+
28
+ it("emits legalName, email, foundingYear, registration when present", () => {
29
+ const page = makePage({
30
+ organization: makePage().organization,
31
+ });
32
+ page.organization.legalName = "Warpgogol GmbH";
33
+ page.organization.email = "info@example.com";
34
+ page.organization.foundingYear = "2020";
35
+ page.organization.registration = "HRB 12345";
36
+ const ctx = createJsonLdContext(page);
37
+ const node = buildOrganizationNode(ctx);
38
+ expect(node.legalName).toBe("Warpgogol GmbH");
39
+ expect(node.email).toBe("info@example.com");
40
+ expect(node.foundingDate).toBe("2020");
41
+ expect(node.identifier).toBe("HRB 12345");
42
+ });
43
+
44
+ it("emits address when present", () => {
45
+ const page = makePage({
46
+ organization: makePage().organization,
47
+ });
48
+ page.organization.address = {
49
+ streetAddress: "Test Str. 1",
50
+ postalCode: "12345",
51
+ addressLocality: "Berlin",
52
+ addressCountry: "DE",
53
+ };
54
+ const ctx = createJsonLdContext(page);
55
+ const node = buildOrganizationNode(ctx);
56
+ expect(node.address).toEqual({
57
+ "@type": "PostalAddress",
58
+ streetAddress: "Test Str. 1",
59
+ postalCode: "12345",
60
+ addressLocality: "Berlin",
61
+ addressCountry: "DE",
62
+ });
63
+ });
64
+
65
+ it("emits contactPoints when present", () => {
66
+ const page = makePage({
67
+ organization: makePage().organization,
68
+ });
69
+ page.organization.contactPoints = [
70
+ { contactType: "customer service", email: "support@example.com" },
71
+ ];
72
+ const ctx = createJsonLdContext(page);
73
+ const node = buildOrganizationNode(ctx);
74
+ expect(node.contactPoint).toHaveLength(1);
75
+ expect((node.contactPoint as unknown[])[0]).toMatchObject({
76
+ "@type": "ContactPoint",
77
+ contactType: "customer service",
78
+ email: "support@example.com",
79
+ });
80
+ });
81
+
82
+ it("emits sameAs links", () => {
83
+ const page = makePage({
84
+ organization: makePage().organization,
85
+ });
86
+ page.organization.sameAs = ["https://twitter.com/example", "https://github.com/example"];
87
+ const ctx = createJsonLdContext(page);
88
+ const node = buildOrganizationNode(ctx);
89
+ expect(node.sameAs).toEqual(["https://twitter.com/example", "https://github.com/example"]);
90
+ });
91
+
92
+ it("emits logo and image", () => {
93
+ const page = makePage({
94
+ organization: makePage().organization,
95
+ });
96
+ page.organization.logo = "https://example.com/logo.png";
97
+ page.organization.image = "https://example.com/image.png";
98
+ const ctx = createJsonLdContext(page);
99
+ const node = buildOrganizationNode(ctx);
100
+ expect(node.logo).toEqual({ "@type": "ImageObject", url: "https://example.com/logo.png" });
101
+ expect(node.image).toBe("https://example.com/image.png");
102
+ });
103
+
104
+ it("emits founders as Person references", () => {
105
+ const page = makePage({
106
+ organization: makePage().organization,
107
+ });
108
+ page.organization.founders = [{ name: "Jane Doe" }];
109
+ const ctx = createJsonLdContext(page);
110
+ const node = buildOrganizationNode(ctx);
111
+ expect(node.founder).toEqual([{ "@id": ctx.ids.person("Jane Doe") }]);
112
+ });
113
+
114
+ it("emits areaServed when present", () => {
115
+ const page = makePage({
116
+ organization: makePage().organization,
117
+ });
118
+ page.organization.areaServed = ["Berlin", "Brandenburg"];
119
+ const ctx = createJsonLdContext(page);
120
+ const node = buildOrganizationNode(ctx);
121
+ expect(node.areaServed).toEqual(["Berlin", "Brandenburg"]);
122
+ });
123
+ });
@@ -0,0 +1,89 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildPersonNodes } from "../person.ts";
3
+ import { createJsonLdContext } from "../context.ts";
4
+ import { makePage } from "../../tests/helpers.ts";
5
+
6
+ describe("buildPersonNodes", () => {
7
+ it("returns empty array when no people, founders, or boardMembers", () => {
8
+ const page = makePage();
9
+ const ctx = createJsonLdContext(page);
10
+ expect(buildPersonNodes(ctx)).toEqual([]);
11
+ });
12
+
13
+ it("builds Person nodes from page.people", () => {
14
+ const page = makePage({
15
+ people: [{ name: "Jane Doe", role: "CEO", description: "A leader" }],
16
+ });
17
+ const ctx = createJsonLdContext(page);
18
+ const nodes = buildPersonNodes(ctx);
19
+ expect(nodes).toHaveLength(1);
20
+ expect(nodes[0]["@type"]).toBe("Person");
21
+ expect(nodes[0].name).toBe("Jane Doe");
22
+ expect(nodes[0].jobTitle).toBe("CEO");
23
+ expect(nodes[0].description).toBe("A leader");
24
+ expect(nodes[0].worksFor).toEqual({ "@id": ctx.ids.organization });
25
+ });
26
+
27
+ it("merges founders, boardMembers, and people with deduplication by name", () => {
28
+ const page = makePage({
29
+ organization: makePage().organization,
30
+ people: [{ name: "Jane Doe", role: "CEO" }],
31
+ });
32
+ page.organization.founders = [{ name: "Jane Doe", role: "Founder" }];
33
+ page.organization.boardMembers = [{ name: "John Smith", role: "Board Member" }];
34
+ const ctx = createJsonLdContext(page);
35
+ const nodes = buildPersonNodes(ctx);
36
+ expect(nodes).toHaveLength(2);
37
+ const names = nodes.map((n) => n.name);
38
+ expect(names).toContain("Jane Doe");
39
+ expect(names).toContain("John Smith");
40
+ });
41
+
42
+ it("emits extended fields (address, knowsAbout, affiliation)", () => {
43
+ const page = makePage({
44
+ people: [
45
+ {
46
+ name: "Jane Doe",
47
+ address: { addressLocality: "Berlin", addressCountry: "DE" },
48
+ knowsAbout: ["AI", "Cloud"],
49
+ affiliation: { name: "Warpgogol", url: "https://example.com" },
50
+ },
51
+ ],
52
+ });
53
+ const ctx = createJsonLdContext(page);
54
+ const nodes = buildPersonNodes(ctx);
55
+ expect(nodes[0].address).toEqual({
56
+ "@type": "PostalAddress",
57
+ addressLocality: "Berlin",
58
+ addressCountry: "DE",
59
+ });
60
+ expect(nodes[0].knowsAbout).toEqual(["AI", "Cloud"]);
61
+ expect(nodes[0].affiliation).toEqual({
62
+ "@type": "Organization",
63
+ name: "Warpgogol",
64
+ url: "https://example.com",
65
+ });
66
+ });
67
+
68
+ it("emits birthDate, deathDate, image, sameAs, profileUrl", () => {
69
+ const page = makePage({
70
+ people: [
71
+ {
72
+ name: "Jane Doe",
73
+ birthDate: "1980",
74
+ deathDate: "2024",
75
+ image: "https://example.com/jane.jpg",
76
+ sameAs: ["https://linkedin.com/in/jane"],
77
+ profileUrl: "https://example.com/de/team/jane/",
78
+ },
79
+ ],
80
+ });
81
+ const ctx = createJsonLdContext(page);
82
+ const nodes = buildPersonNodes(ctx);
83
+ expect(nodes[0].birthDate).toBe("1980");
84
+ expect(nodes[0].deathDate).toBe("2024");
85
+ expect(nodes[0].image).toBe("https://example.com/jane.jpg");
86
+ expect(nodes[0].sameAs).toEqual(["https://linkedin.com/in/jane"]);
87
+ expect(nodes[0].url).toBe("https://example.com/de/team/jane/");
88
+ });
89
+ });
@@ -0,0 +1,65 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildServiceNodes } from "../service.ts";
3
+ import { createJsonLdContext } from "../context.ts";
4
+ import { makePage } from "../../tests/helpers.ts";
5
+
6
+ describe("buildServiceNodes", () => {
7
+ it("returns org-level Service nodes for non-surface pages", () => {
8
+ const page = makePage({
9
+ organization: makePage().organization,
10
+ });
11
+ page.organization.services = [
12
+ { id: "s1", name: "Hosting", description: "Managed hosting" },
13
+ { id: "s2", name: "Security" },
14
+ ];
15
+ const ctx = createJsonLdContext(page);
16
+ const nodes = buildServiceNodes(ctx);
17
+ expect(nodes).toHaveLength(2);
18
+ expect(nodes[0]["@type"]).toBe("Service");
19
+ expect(nodes[0].name).toBe("Hosting");
20
+ expect(nodes[0].description).toBe("Managed hosting");
21
+ expect(nodes[0].provider).toEqual({ "@id": ctx.ids.organization });
22
+ });
23
+
24
+ it("emits industry-specific Service node when industryService is present", () => {
25
+ const page = makePage({
26
+ surfaceId: "website-local",
27
+ depth: 1,
28
+ industryService: {
29
+ serviceType: "Web Development",
30
+ audience: "Nonprofits",
31
+ description: "Custom web development",
32
+ areaServed: "Berlin",
33
+ },
34
+ });
35
+ const ctx = createJsonLdContext(page);
36
+ const nodes = buildServiceNodes(ctx);
37
+ expect(nodes).toHaveLength(1);
38
+ expect(nodes[0]["@type"]).toBe("Service");
39
+ expect(nodes[0].serviceType).toBe("Web Development");
40
+ expect(nodes[0].audience).toEqual({ "@type": "Audience", name: "Nonprofits" });
41
+ expect(nodes[0].areaServed).toEqual({ "@type": "City", name: "Berlin" });
42
+ });
43
+
44
+ it("suppresses Service nodes for surface pages without industryService at prohibited depths", () => {
45
+ const page = makePage({
46
+ surfaceId: "website-local",
47
+ depth: 0,
48
+ });
49
+ const ctx = createJsonLdContext(page);
50
+ expect(buildServiceNodes(ctx)).toEqual([]);
51
+ });
52
+
53
+ it("emits org-level Service nodes for surface depth-1 without industryService", () => {
54
+ const page = makePage({
55
+ surfaceId: "website-local",
56
+ depth: 1,
57
+ organization: makePage().organization,
58
+ });
59
+ page.organization.services = [{ id: "s1", name: "Consulting" }];
60
+ const ctx = createJsonLdContext(page);
61
+ const nodes = buildServiceNodes(ctx);
62
+ expect(nodes).toHaveLength(1);
63
+ expect(nodes[0].name).toBe("Consulting");
64
+ });
65
+ });
@@ -0,0 +1,41 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { dedupeGraph } from "../shared.ts";
3
+ import type { JsonLdNode } from "../types.ts";
4
+
5
+ describe("dedupeGraph", () => {
6
+ it("returns empty array for empty input", () => {
7
+ expect(dedupeGraph([])).toEqual([]);
8
+ });
9
+
10
+ it("removes duplicate nodes by @id", () => {
11
+ const nodes: JsonLdNode[] = [
12
+ { "@id": "org-1", "@type": "Organization", name: "Org" },
13
+ { "@id": "org-1", "@type": "Organization", name: "Duplicate" },
14
+ { "@id": "org-2", "@type": "Organization", name: "Other" },
15
+ ];
16
+ const result = dedupeGraph(nodes);
17
+ expect(result).toHaveLength(2);
18
+ expect(result[0]["@id"]).toBe("org-1");
19
+ expect(result[0].name).toBe("Org");
20
+ expect(result[1]["@id"]).toBe("org-2");
21
+ });
22
+
23
+ it("preserves first occurrence of duplicates", () => {
24
+ const nodes: JsonLdNode[] = [
25
+ { "@id": "a", value: "first" },
26
+ { "@id": "a", value: "second" },
27
+ ];
28
+ const result = dedupeGraph(nodes);
29
+ expect(result).toHaveLength(1);
30
+ expect(result[0].value).toBe("first");
31
+ });
32
+
33
+ it("keeps nodes without @id", () => {
34
+ const nodes: JsonLdNode[] = [
35
+ { "@type": "Thing", name: "No ID" },
36
+ { "@type": "Thing", name: "Also No ID" },
37
+ ];
38
+ const result = dedupeGraph(nodes);
39
+ expect(result).toHaveLength(2);
40
+ });
41
+ });
@@ -0,0 +1,84 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildVideoObjectNodes } from "../video.ts";
3
+ import { createJsonLdContext } from "../context.ts";
4
+ import { makePage } from "../../tests/helpers.ts";
5
+
6
+ describe("buildVideoObjectNodes", () => {
7
+ it("returns empty array when no blocks have video data", () => {
8
+ const page = makePage({ blocks: [{ id: "b1", heading: "Test" }] });
9
+ const ctx = createJsonLdContext(page);
10
+ expect(buildVideoObjectNodes(ctx)).toEqual([]);
11
+ });
12
+
13
+ it("builds VideoObject nodes for blocks with video data", () => {
14
+ const page = makePage({
15
+ blocks: [
16
+ {
17
+ id: "vid-1",
18
+ heading: "Video Section",
19
+ video: {
20
+ seo: { name: "Intro Video", description: "An intro", uploadDate: "2024-01-15" },
21
+ manifest: {
22
+ posterUrl: "https://example.com/poster.jpg",
23
+ contentUrl: "https://example.com/video.mp4",
24
+ durationSec: 90,
25
+ },
26
+ },
27
+ },
28
+ ],
29
+ });
30
+ const ctx = createJsonLdContext(page);
31
+ const nodes = buildVideoObjectNodes(ctx);
32
+ expect(nodes).toHaveLength(1);
33
+ expect(nodes[0]["@type"]).toBe("VideoObject");
34
+ expect(nodes[0].name).toBe("Intro Video");
35
+ expect(nodes[0].description).toBe("An intro");
36
+ expect(nodes[0].uploadDate).toBe("2024-01-15");
37
+ expect(nodes[0].thumbnailUrl).toBe("https://example.com/poster.jpg");
38
+ expect(nodes[0].contentUrl).toBe("https://example.com/video.mp4");
39
+ expect(nodes[0].duration).toBe("PT1M30S");
40
+ });
41
+
42
+ it("formats duration correctly for hours, minutes, seconds", () => {
43
+ const page = makePage({
44
+ blocks: [
45
+ {
46
+ id: "vid-2",
47
+ heading: "Long Video",
48
+ video: {
49
+ seo: { name: "Long", description: "Long", uploadDate: "2024-01-15" },
50
+ manifest: {
51
+ posterUrl: "https://example.com/p.jpg",
52
+ contentUrl: "https://example.com/v.mp4",
53
+ durationSec: 3661,
54
+ },
55
+ },
56
+ },
57
+ ],
58
+ });
59
+ const ctx = createJsonLdContext(page);
60
+ const nodes = buildVideoObjectNodes(ctx);
61
+ expect(nodes[0].duration).toBe("PT1H1M1S");
62
+ });
63
+
64
+ it("omits duration when durationSec is not provided", () => {
65
+ const page = makePage({
66
+ blocks: [
67
+ {
68
+ id: "vid-3",
69
+ heading: "No Duration",
70
+ video: {
71
+ seo: { name: "NoDur", description: "No dur", uploadDate: "2024-01-15" },
72
+ manifest: {
73
+ posterUrl: "https://example.com/p.jpg",
74
+ contentUrl: "https://example.com/v.mp4",
75
+ },
76
+ },
77
+ },
78
+ ],
79
+ });
80
+ const ctx = createJsonLdContext(page);
81
+ const nodes = buildVideoObjectNodes(ctx);
82
+ expect(nodes[0].duration).toBeUndefined();
83
+ });
84
+ });
@@ -0,0 +1,93 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildWebPageNode } from "../webpage.ts";
3
+ import { createJsonLdContext } from "../context.ts";
4
+ import { makePage } from "../../tests/helpers.ts";
5
+
6
+ describe("buildWebPageNode", () => {
7
+ it("builds WebPage node for content type", () => {
8
+ const page = makePage({ type: "content" });
9
+ const ctx = createJsonLdContext(page);
10
+ const node = buildWebPageNode(ctx);
11
+ expect(node["@type"]).toEqual(["WebPage"]);
12
+ expect(node["@id"]).toBe(ctx.webpageId);
13
+ expect(node.url).toBe(page.url);
14
+ expect(node.name).toBe("Test Page");
15
+ expect(node.description).toBe("A test page description");
16
+ expect(node.inLanguage).toBe("de");
17
+ });
18
+
19
+ it("uses heading for name when available", () => {
20
+ const page = makePage({ heading: "Custom Heading" });
21
+ const ctx = createJsonLdContext(page);
22
+ const node = buildWebPageNode(ctx);
23
+ expect(node.headline).toBe("Custom Heading");
24
+ });
25
+
26
+ it("uses title for headline", () => {
27
+ const page = makePage({ title: "Page Title" });
28
+ const ctx = createJsonLdContext(page);
29
+ const node = buildWebPageNode(ctx);
30
+ expect(node.name).toBe("Page Title");
31
+ });
32
+
33
+ it("emits CollectionPage for collection type", () => {
34
+ const page = makePage({ type: "collection" });
35
+ const ctx = createJsonLdContext(page);
36
+ const node = buildWebPageNode(ctx);
37
+ expect(node["@type"]).toEqual(["WebPage", "CollectionPage"]);
38
+ });
39
+
40
+ it("emits AboutPage for about type", () => {
41
+ const page = makePage({ type: "about" });
42
+ const ctx = createJsonLdContext(page);
43
+ const node = buildWebPageNode(ctx);
44
+ expect(node["@type"]).toEqual(["WebPage", "AboutPage"]);
45
+ });
46
+
47
+ it("emits ProfilePage for person type", () => {
48
+ const page = makePage({ type: "person" });
49
+ const ctx = createJsonLdContext(page);
50
+ const node = buildWebPageNode(ctx);
51
+ expect(node["@type"]).toEqual(["WebPage", "ProfilePage"]);
52
+ });
53
+
54
+ it("links to organization via publisher", () => {
55
+ const page = makePage();
56
+ const ctx = createJsonLdContext(page);
57
+ const node = buildWebPageNode(ctx);
58
+ expect(node.publisher).toEqual({ "@id": ctx.ids.organization });
59
+ });
60
+
61
+ it("links to breadcrumb when breadcrumbs > 1", () => {
62
+ const page = makePage({
63
+ breadcrumbs: [
64
+ { name: "Home", url: "https://example.com/de/" },
65
+ { name: "Page", url: "https://example.com/de/page/" },
66
+ ],
67
+ });
68
+ const ctx = createJsonLdContext(page);
69
+ const node = buildWebPageNode(ctx);
70
+ expect(node.breadcrumb).toEqual({ "@id": ctx.breadcrumbId });
71
+ });
72
+
73
+ it("omits breadcrumb when only one crumb", () => {
74
+ const page = makePage({
75
+ breadcrumbs: [{ name: "Home", url: "https://example.com/de/" }],
76
+ });
77
+ const ctx = createJsonLdContext(page);
78
+ const node = buildWebPageNode(ctx);
79
+ expect(node.breadcrumb).toBeUndefined();
80
+ });
81
+
82
+ it("emits primaryImage when present", () => {
83
+ const page = makePage({
84
+ primaryImage: { url: "https://example.com/img.jpg" },
85
+ });
86
+ const ctx = createJsonLdContext(page);
87
+ const node = buildWebPageNode(ctx);
88
+ expect(node.primaryImageOfPage).toEqual({
89
+ "@type": "ImageObject",
90
+ url: "https://example.com/img.jpg",
91
+ });
92
+ });
93
+ });
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildWebSiteNode } from "../website.ts";
3
+ import { createJsonLdContext } from "../context.ts";
4
+ import { makePage } from "../../tests/helpers.ts";
5
+
6
+ describe("buildWebSiteNode", () => {
7
+ it("builds WebSite node with organization URL and language", () => {
8
+ const page = makePage({ lang: "de" });
9
+ const ctx = createJsonLdContext(page);
10
+ const node = buildWebSiteNode(ctx);
11
+ expect(node["@type"]).toBe("WebSite");
12
+ expect(node["@id"]).toBe(ctx.ids.website);
13
+ expect(node.url).toBe(page.organization.url);
14
+ expect(node.inLanguage).toBe("de");
15
+ expect(node.publisher).toEqual({ "@id": ctx.ids.organization });
16
+ });
17
+
18
+ it("uses organization name for WebSite name", () => {
19
+ const page = makePage({
20
+ organization: makePage().organization,
21
+ });
22
+ page.organization.name = "Test Org";
23
+ const ctx = createJsonLdContext(page);
24
+ const node = buildWebSiteNode(ctx);
25
+ expect(node.name).toBe("Test Org");
26
+ });
27
+ });
@@ -1,3 +1,4 @@
1
+ // test-adjacency: exempt — pure type definitions
1
2
  /*
2
3
  <MODULE_CONTRACT>
3
4
  <purpose>Maintains packages/share/src/semantic/jsonld/types.ts as an authored share authored module so agents can evolve it without rediscovering local boundaries.</purpose>
@@ -1,3 +1,4 @@
1
+ // test-adjacency: exempt — pure type definitions
1
2
  /*
2
3
  <MODULE_CONTRACT>
3
4
  <purpose>Defines types for semantic data structures used throughout the Warpgogol ecosystem.</purpose>
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildAiTxt } from "../ai.ts";
3
+
4
+ describe("buildAiTxt", () => {
5
+ it("generates header from siteUrl", () => {
6
+ const result = buildAiTxt({ policy: "allow" }, "https://example.com");
7
+ expect(result).toContain("# ai.txt for example.com");
8
+ expect(result).toContain("policy: allow");
9
+ });
10
+
11
+ it("includes version and updated", () => {
12
+ const result = buildAiTxt({ policy: "allow", version: "1.0", updated: "2024-01-01" }, "https://example.com");
13
+ expect(result).toContain("version: 1.0");
14
+ expect(result).toContain("updated: 2024-01-01");
15
+ });
16
+
17
+ it("includes optional fields when provided", () => {
18
+ const result = buildAiTxt({
19
+ policy: "limited",
20
+ training: "disallow",
21
+ usage: ["search", "summarization"],
22
+ commercial: "no",
23
+ attribution: "required",
24
+ license: "CC-BY-4.0",
25
+ }, "https://example.com");
26
+ expect(result).toContain("training: disallow");
27
+ expect(result).toContain("usage: search, summarization");
28
+ expect(result).toContain("commercial: no");
29
+ expect(result).toContain("attribution: required");
30
+ expect(result).toContain("license: CC-BY-4.0");
31
+ });
32
+
33
+ it("emits provider sections", () => {
34
+ const result = buildAiTxt({
35
+ policy: "allow",
36
+ providers: [
37
+ { name: "OpenAI", policy: "disallow", training: "disallow" },
38
+ { name: "Anthropic", policy: "allow", url: "https://anthropic.com" },
39
+ ],
40
+ }, "https://example.com");
41
+ expect(result).toContain("[OpenAI]");
42
+ expect(result).toContain("[Anthropic]");
43
+ expect(result).toContain("url: https://anthropic.com");
44
+ });
45
+
46
+ it("includes url and contact in header comments", () => {
47
+ const result = buildAiTxt({
48
+ policy: "allow",
49
+ url: "https://example.com/policy",
50
+ contact: "info@example.com",
51
+ }, "https://example.com");
52
+ expect(result).toContain("# See: https://example.com/policy");
53
+ expect(result).toContain("# Contact: info@example.com");
54
+ });
55
+ });
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { BlockExtractorRegistry, BLOCK_EXTRACTORS } from "../block-extraction.ts";
3
+ import "../block-extractors/index.ts";
4
+
5
+ describe("BlockExtractorRegistry", () => {
6
+ it("registers and retrieves extractors", () => {
7
+ const registry = new BlockExtractorRegistry();
8
+ registry.register({
9
+ blockType: "test-block",
10
+ extract: () => ({ heading: "Test" }),
11
+ });
12
+ expect(registry.has("test-block")).toBe(true);
13
+ expect(registry.get("test-block")?.blockType).toBe("test-block");
14
+ });
15
+
16
+ it("returns undefined for unregistered type", () => {
17
+ const registry = new BlockExtractorRegistry();
18
+ expect(registry.get("nonexistent")).toBeUndefined();
19
+ });
20
+
21
+ it("lists registered types", () => {
22
+ const registry = new BlockExtractorRegistry();
23
+ registry.register({ blockType: "a", extract: () => ({}) });
24
+ registry.register({ blockType: "b", extract: () => ({}) });
25
+ expect(registry.listTypes().sort()).toEqual(["a", "b"]);
26
+ });
27
+ });
28
+
29
+ describe("BLOCK_EXTRACTORS", () => {
30
+ it("is a registry instance with pre-registered extractors", () => {
31
+ expect(BLOCK_EXTRACTORS).toBeInstanceOf(BlockExtractorRegistry);
32
+ expect(BLOCK_EXTRACTORS.listTypes().length).toBeGreaterThan(0);
33
+ });
34
+ });