Skip to content

Commit af48603

Browse files
committed
chore(docs): add custom data reference tests
Signed-off-by: John Yanarella <jyanarella@nvidia.com>
1 parent 75f42f5 commit af48603

1 file changed

Lines changed: 217 additions & 0 deletions

File tree

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import{readFile,readdir}from'node:fs/promises';
5+
import{dirname,join,relative,resolve}from'node:path';
6+
import{describe,expect,it}from'vitest';
7+
8+
constDOCUMENTATION_BASE_URL='https://nvidia.github.io/elements/';
9+
constCUSTOM_DATA_SCHEMA_URL=
10+
'https://raw.githubusercontent.com/microsoft/vscode-html-languageservice/main/docs/customData.schema.json';
11+
constDOCUMENTATION_ROOT=import.meta.dirname;
12+
constPROJECTS_ROOT=resolve(DOCUMENTATION_ROOT,'../../..');
13+
14+
interfaceComponentDocumentation{
15+
tag: string;
16+
url: string;
17+
}
18+
19+
interfaceCustomDataReference{
20+
name: string;
21+
url: string;
22+
}
23+
24+
interfaceCustomDataTag{
25+
name: string;
26+
references?: CustomDataReference[];
27+
}
28+
29+
interfaceHtmlCustomData{
30+
$schema: string;
31+
tags?: CustomDataTag[];
32+
version: number;
33+
}
34+
35+
interfaceCustomElementDeclaration{
36+
metadata?: Record<string,unknown>;
37+
tagName?: string;
38+
}
39+
40+
interfaceCustomElementsManifest{
41+
modules: {
42+
declarations?: CustomElementDeclaration[];
43+
}[];
44+
}
45+
46+
interfaceProjectPackage{
47+
customElements?: string;
48+
name?: string;
49+
}
50+
51+
describe('HTML Custom Data references',()=>{
52+
it('uses the V1 reference shape with valid HTTPS URLs',async()=>{
53+
for(constcustomDataofawaitgetCustomData()){
54+
expect(customData.$schema).toBe(CUSTOM_DATA_SCHEMA_URL);
55+
expect(customData.version).toBe(1.1);
56+
57+
for(consttagofcustomData.tags??[]){
58+
for(constreferenceoftag.references??[]){
59+
expect(reference).toMatchObject({
60+
name: expect.any(String),
61+
url: expect.any(String)
62+
});
63+
expect(newURL(reference.url).protocol).toBe('https:');
64+
}
65+
}
66+
}
67+
});
68+
69+
it('maps CEM ARIA metadata to the WAI-ARIA Reference label',async()=>{
70+
constdeclarationsByTag=awaitgetManifestDeclarations();
71+
constcustomDataTagsByName=newMap(
72+
(awaitgetCustomData()).flatMap(customData=>(customData.tags??[]).map(tag=>[tag.name,tag]))
73+
);
74+
75+
for(const[tagName,declaration]ofdeclarationsByTag){
76+
consturl=declaration.metadata?.aria;
77+
if(!isHttpsUrl(url)){
78+
continue;
79+
}
80+
81+
expect(customDataTagsByName.get(tagName)?.references).toContainEqual({name: 'WAI-ARIA Reference', url });
82+
}
83+
});
84+
85+
it('generates canonical references for every documented public tag',async()=>{
86+
constdocumentation=awaitgetComponentDocumentation();
87+
constdeclarationsByTag=awaitgetManifestDeclarations();
88+
constcustomDataTagsByName=newMap(
89+
(awaitgetCustomData()).flatMap(customData=>(customData.tags??[]).map(tag=>[tag.name,tag]))
90+
);
91+
92+
expect(documentation.length).toBeGreaterThan(0);
93+
94+
for(constcomponentofdocumentation){
95+
expect(declarationsByTag.get(component.tag)?.metadata?.documentation).toBe(component.url);
96+
97+
consttag=customDataTagsByName.get(component.tag);
98+
expect(tag?.references).toContainEqual({name: 'Documentation',url: component.url});
99+
100+
consturl=newURL(component.url);
101+
expect(url.origin).toBe('https://nvidia.github.io');
102+
expect(url.pathname).toMatch(/^\/elements\/docs\//);
103+
}
104+
});
105+
});
106+
107+
asyncfunctiongetComponentDocumentation(): Promise<ComponentDocumentation[]>{
108+
constfiles=[
109+
...(awaitgetTopLevelMarkdownFiles(join(DOCUMENTATION_ROOT,'elements'))),
110+
join(DOCUMENTATION_ROOT,'elements/data-grid/index.md'),
111+
...(awaitgetTopLevelMarkdownFiles(join(DOCUMENTATION_ROOT,'code'))),
112+
...(awaitgetTopLevelMarkdownFiles(join(DOCUMENTATION_ROOT,'monaco'))),
113+
...(awaitgetTopLevelMarkdownFiles(join(DOCUMENTATION_ROOT,'media'))),
114+
join(DOCUMENTATION_ROOT,'markdown/index.md')
115+
];
116+
117+
constdocumentation=awaitPromise.all(
118+
files.map(asyncfile=>{
119+
constcontent=awaitreadFile(file,'utf8');
120+
consttags=[
121+
content.match(/tag:\s*'([^']+)'/)?.[1],
122+
...[...(content.match(/associatedElements:\s*\[([\s\S]*?)\]/)?.[1]?.matchAll(/'([^']+)'/g)??[])].map(
123+
match=>match[1]
124+
)
125+
].filter((tag): tag is string=>!!tag);
126+
127+
returntags.map(tag=>({ tag,url: getDocumentationUrl(file)}));
128+
})
129+
);
130+
131+
return[...newMap(documentation.flat().map(component=>[component.tag,component])).values()];
132+
}
133+
134+
asyncfunctiongetCustomData(): Promise<HtmlCustomData[]>{
135+
constcustomData=awaitPromise.all(
136+
(awaitgetElementsPackages()).map(async({ customElements, directory })=>{
137+
try{
138+
constdataPath=join(directory,dirname(customElements),'data.html.json');
139+
returnJSON.parse(awaitreadFile(dataPath,'utf8'))asHtmlCustomData;
140+
}catch{
141+
returnnull;
142+
}
143+
})
144+
);
145+
146+
returncustomData.filter((data): data is HtmlCustomData=>data!==null);
147+
}
148+
149+
asyncfunctiongetManifestDeclarations(): Promise<Map<string,CustomElementDeclaration>>{
150+
constmanifests=awaitPromise.all(
151+
(awaitgetElementsPackages()).map(async({ customElements, directory })=>{
152+
returnJSON.parse(awaitreadFile(join(directory,customElements),'utf8'))asCustomElementsManifest;
153+
})
154+
);
155+
156+
returnnewMap(
157+
manifests.flatMap(manifest=>
158+
manifest.modules.flatMap(module=>
159+
(module.declarations??[])
160+
.filter((declaration): declaration is CustomElementDeclaration&{tagName: string}=>!!declaration.tagName)
161+
.map(declaration=>[declaration.tagName,declaration])
162+
)
163+
)
164+
);
165+
}
166+
167+
asyncfunctiongetElementsPackages(): Promise<{customElements: string;directory: string}[]>{
168+
constdirectories=awaitreaddir(PROJECTS_ROOT,{withFileTypes: true});
169+
constpackages=awaitPromise.all(
170+
directories
171+
.filter(directory=>directory.isDirectory())
172+
.map(asyncdirectory=>{
173+
constprojectDirectory=join(PROJECTS_ROOT,directory.name);
174+
try{
175+
constpackageJson=JSON.parse(
176+
awaitreadFile(join(projectDirectory,'package.json'),'utf8')
177+
)asProjectPackage;
178+
returnpackageJson.name?.startsWith('@nvidia-elements/')&&packageJson.customElements
179+
? {customElements: packageJson.customElements,directory: projectDirectory}
180+
: null;
181+
}catch{
182+
returnnull;
183+
}
184+
})
185+
);
186+
187+
returnpackages.filter((project): project is {customElements: string;directory: string}=>project!==null);
188+
}
189+
190+
asyncfunctiongetTopLevelMarkdownFiles(directory: string): Promise<string[]>{
191+
try{
192+
constentries=awaitreaddir(directory,{withFileTypes: true});
193+
returnentries
194+
.filter(entry=>entry.isFile()&&entry.name.endsWith('.md'))
195+
.map(entry=>join(directory,entry.name));
196+
}catch{
197+
return[];
198+
}
199+
}
200+
201+
functiongetDocumentationUrl(file: string): string{
202+
constfilePath=relative(DOCUMENTATION_ROOT,file).replace(/\.md$/,'');
203+
constroute=filePath.endsWith('/index') ? filePath.slice(0,-'/index'.length) : filePath;
204+
returnnewURL(`docs/${route}/`,DOCUMENTATION_BASE_URL).href;
205+
}
206+
207+
functionisHttpsUrl(value: unknown): value is string{
208+
if(typeofvalue!=='string'){
209+
returnfalse;
210+
}
211+
212+
try{
213+
returnnewURL(value).protocol==='https:';
214+
}catch{
215+
returnfalse;
216+
}
217+
}

0 commit comments

Comments
 (0)