mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-09 02:49:18 +08:00
* 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>
184 lines
6.2 KiB
TypeScript
Executable File
184 lines
6.2 KiB
TypeScript
Executable File
/* 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();
|
||
});
|
||
}
|
||
}
|
||
});
|