Files
ant-design/scripts/check-site.ts
遇见同学 3abfb8c2dc chore: ci fix to remove parse5 (#56421)
* chore: ci fix

* chore: update

* chore: update

* chore: fix lint

* chore: remove outdated parse5 dependencies from package.json

---------

Co-authored-by: 二货机器人 <smith3816@gmail.com>
Co-authored-by: thinkasany <480968828@qq.com>
2025-12-30 23:05:56 +08:00

184 lines
6.2 KiB
TypeScript
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* eslint-disable unicorn/prefer-dom-node-text-content */
import type http from 'http';
import type https from 'https';
import { join } from 'path';
import { DOMParser } from 'domparser-rs';
import { globSync } from 'glob';
import { createServer } from 'http-server';
import fetch from 'isomorphic-fetch';
import uniq from 'lodash/uniq';
import portfinder from 'portfinder';
const components = uniq(
globSync('components/!(overview)/*.md', { cwd: join(process.cwd()), dot: false }).map((path) =>
path.replace(/(\/index)?((\.zh-cn)|(\.en-us))?\.md$/i, ''),
),
).filter((component) => !component.includes('_util'));
describe('site test', () => {
let server: http.Server | https.Server;
const portPromise = portfinder.getPortPromise({
port: 3000,
});
const render = async (path: string) => {
const port = await portPromise;
const resp = await fetch(`http://127.0.0.1:${port}${path}`).then(async (res) => {
const html: string = await res.text();
const root = new DOMParser().parseFromString(html, 'text/html');
function getTextContent(node: any): string {
if (!node) return '';
if (typeof node.textContent === 'string') return node.textContent.trim();
if (typeof node.innerText === 'string') return node.innerText.trim();
// Fallback: recursively get text from children
if (node.children && node.children.length > 0) {
return Array.from(node.children)
.map((child: any) => getTextContent(child))
.join('')
.trim();
}
return '';
}
function wrap(nodes: any[]) {
const list = Array.isArray(nodes) ? nodes : [];
return {
length: list.length,
text: () => {
if (list.length === 0) return '';
return list.map((n) => getTextContent(n)).join('');
},
first: () => wrap(list.slice(0, 1)),
};
}
const $ = (selector: string) => {
if (!root.querySelector) {
console.warn('DOMParser does not support querySelector');
return wrap([]);
}
// Handle complex selectors that domparser-rs might not support
if (selector === '.markdown table') {
// Find all .markdown elements and then find tables within them
const markdownElements = root.querySelectorAll('.markdown');
const tables = [];
for (const markdown of Array.from(markdownElements)) {
const tablesInMarkdown = markdown.querySelectorAll('table');
tables.push(...Array.from(tablesInMarkdown));
}
return wrap(tables);
} else {
// Use querySelectorAll for simple selectors
const elements = root.querySelectorAll(selector);
const elementsArray = Array.from(elements);
return wrap(elementsArray);
}
};
return { status: res.status, $, root };
});
return resp;
};
const handleComponentName = (name: string) => {
const [, componentName] = name.split('/');
return componentName.toLowerCase().replace('-cn', '').replace('-', '');
};
const expectComponent = async (component: string) => {
const { status, $, root } = await render(`/${component}/`);
expect(status).toBe(200);
// Get all h1 elements and find the one in main content (not in header)
const h1Elements = root.querySelectorAll('h1');
let mainH1Text = '';
if (h1Elements.length >= 2) {
// The second h1 should be the main content title
const mainH1 = h1Elements[1];
mainH1Text = mainH1.textContent || (mainH1 as any).innerText || '';
} else if (h1Elements.length === 1) {
// If only one h1, check its content
const h1 = h1Elements[0];
mainH1Text = h1.textContent || (h1 as any).innerText || '';
}
// Clean up the text and extract the main component name
mainH1Text = mainH1Text.trim();
expect(mainH1Text.toLowerCase()).toMatch(handleComponentName(component));
/**
* 断言组件的 api table 数量是否符合预期。
* 在 #45066, #45017 中,因为 markdown 写法问题,导致 api table 无法渲染。
* 结合每个组件页的 table 数量变动,可以判断出是否存在问题。
* table 数量相对比较稳定,如果 PR 有新增,则应该更新这里快照)
*/
const tables = $('.markdown table');
expect(tables.length).toMatchSnapshot();
};
beforeAll(async () => {
const port = await portPromise;
server = createServer({ root: join(process.cwd(), '_site') });
server.listen(port);
console.log(`site static server run: http://localhost:${port}`);
});
afterAll(() => {
server?.close();
});
it('Basic Pages en', async () => {
const { status, $ } = await render('/');
expect($('title').first().text()).toEqual(
`Ant Design - The world's second most popular React UI framework`,
);
expect(status).toBe(200);
});
it('Basic Pages zh', async () => {
const { status, $ } = await render('/index-cn');
expect($('title').first().text()).toEqual(`Ant Design - 一套企业级 UI 设计语言和 React 组件库`);
expect(status).toBe(200);
});
it('Overview en', async () => {
const { status, $ } = await render('/components/overview');
expect(status).toBe(200);
expect($('h1').text()).toMatch(`Overview`);
});
it('Overview zh', async () => {
const { status, $ } = await render('/components/overview-cn');
expect(status).toBe(200);
expect($('h1').text()).toMatch(`组件总览`);
});
it('Resource en', async () => {
const { status, $ } = await render('/docs/resources');
expect(status).toBe(200);
expect($('h1').text()).toMatch(`Resources`);
});
it('Resource zh', async () => {
const { status, $ } = await render('/docs/resources-cn');
expect(status).toBe(200);
expect($('h1').text()).toMatch(`资源`);
});
for (const component of components) {
if (component.split('/').length < 3) {
it(`Component ${component} zh Page`, async () => {
await expectComponent(`${component}-cn`);
expect(component).toBeTruthy();
});
it(`Component ${component} en Page`, async () => {
await expectComponent(component);
expect(component).toBeTruthy();
});
}
}
});