diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5fae401f4..ee1808b55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1229,6 +1229,9 @@ importers: '@nvidia-elements/themes': specifier: workspace:* version: link:../themes + '@vitest/coverage-istanbul': + specifier: 'catalog:' + version: 4.1.5(vitest@4.1.5) compare-versions: specifier: 6.1.1 version: 6.1.1 @@ -12592,8 +12595,8 @@ packages: third-party-web@0.27.0: resolution: {integrity: sha512-h0JYX+dO2Zr3abCQpS6/uFjujaOjA1DyDzGQ41+oFn9VW/ARiq9g5ln7qEP9+BTzDpOMyIfsfj4OvfgXAsMUSA==} - third-party-web@0.29.0: - resolution: {integrity: sha512-nBDSJw5B7Sl1YfsATG2XkW5qgUPODbJhXw++BKygi9w6O/NKS98/uY/nR/DxDq2axEjL6halHW1v+jhm/j1DBQ==} + third-party-web@0.29.2: + resolution: {integrity: sha512-fegtha91tq2DHphyoiBXVHjVi2YG9zFaRnboT9C28tO1en9Y3wJsfspuy40F+u5wl3hHVbw7cnd1b67kEGHb8g==} three-forcegraph@1.43.4: resolution: {integrity: sha512-FtmiZP/T16ZQaHza3JDaDn0YTXFtg9e7pGnTeU8nzu0NNkx7MpWbF/GvmpbQsWHx3rukHtkRv1fTorLPB3FDEA==} @@ -16834,7 +16837,7 @@ snapshots: '@paulirish/trace_engine@0.0.61': dependencies: legacy-javascript: 0.0.1 - third-party-web: 0.29.0 + third-party-web: 0.29.2 '@pkgjs/parseargs@0.11.0': optional: true @@ -21709,7 +21712,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 25.6.0 + '@types/node': 25.6.2 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -25363,7 +25366,7 @@ snapshots: third-party-web@0.27.0: {} - third-party-web@0.29.0: {} + third-party-web@0.29.2: {} three-forcegraph@1.43.4(three@0.184.0): dependencies: diff --git a/projects/site/package.json b/projects/site/package.json index e623f7f98..2a2b325ce 100644 --- a/projects/site/package.json +++ b/projects/site/package.json @@ -10,6 +10,7 @@ "build": "wireit", "lint": "wireit", "lint:fix": "wireit", + "test": "wireit", "test:lighthouse": "wireit", "preview": "wireit" }, @@ -17,7 +18,8 @@ "ci": { "dependencies": [ "lint", - "build" + "build", + "test" ] }, "dev": { @@ -187,6 +189,18 @@ "../lint:build" ] }, + "test": { + "command": "vitest run --config=vitest.config.ts", + "files": [ + "src/**/*.test.ts", + "src/_11ty/layouts/metadata.js", + "vitest.config.ts" + ], + "output": [], + "dependencies": [ + "../internals/vite:ci" + ] + }, "test:lighthouse": { "command": "playwright-lock 'vitest run --config=vitest.lighthouse.ts'", "files": [ @@ -234,6 +248,7 @@ "@nvidia-elements/monaco": "workspace:*", "@nvidia-elements/styles": "workspace:*", "@nvidia-elements/themes": "workspace:*", + "@vitest/coverage-istanbul": "catalog:", "compare-versions": "6.1.1", "eslint": "catalog:", "stylelint": "catalog:", diff --git a/projects/site/src/_11ty/layouts/metadata.js b/projects/site/src/_11ty/layouts/metadata.js index 6489e83c4..75f550326 100644 --- a/projects/site/src/_11ty/layouts/metadata.js +++ b/projects/site/src/_11ty/layouts/metadata.js @@ -149,11 +149,13 @@ export function renderJsonLd(data, meta) { segments.forEach((seg, i) => { cumulative += `/${seg}`; const item = i === segments.length - 1 && !meta.url.endsWith('/') ? meta.url : `${cumulative}/`; + if (!hasGeneratedPage(data, generatedUrls, item)) return; + itemListElement.push({ '@type': 'ListItem', - position: i + 2, + position: itemListElement.length + 1, name: titleCaseSegment(seg), - ...(hasGeneratedPage(data, generatedUrls, item) ? { item: `${SITE_ORIGIN}${PATH_PREFIX}${item}` } : {}) + item: `${SITE_ORIGIN}${PATH_PREFIX}${item}` }); }); diff --git a/projects/site/src/_11ty/layouts/metadata.test.ts b/projects/site/src/_11ty/layouts/metadata.test.ts new file mode 100644 index 000000000..f986aa608 --- /dev/null +++ b/projects/site/src/_11ty/layouts/metadata.test.ts @@ -0,0 +1,105 @@ +import { afterAll, describe, expect, it, vi } from 'vitest'; + +vi.stubEnv('ELEMENTS_SITE_URL', 'https://nvidia.github.io'); +vi.stubEnv('PAGES_BASE_URL', '/elements/'); + +afterAll(() => { + vi.unstubAllEnvs(); +}); + +vi.mock('../../index.11tydata.js', () => ({ siteData: { elements: [] } })); + +const { renderJsonLd } = await import('./metadata.js'); + +interface JsonLdListItem { + '@type': 'ListItem'; + position: number; + name: string; + item: string; +} + +interface BreadcrumbList { + '@type': 'BreadcrumbList'; + itemListElement: JsonLdListItem[]; +} + +function isBreadcrumbList(value: unknown): value is BreadcrumbList { + if (typeof value !== 'object' || value === null) return false; + + const candidate = value as Record; + + return candidate['@type'] === 'BreadcrumbList' && Array.isArray(candidate.itemListElement); +} + +function getBreadcrumbJsonLd(html: string): BreadcrumbList { + const scripts = [...html.matchAll(/