180 lines
5.5 KiB
TypeScript
180 lines
5.5 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
|
|
import ts from 'typescript';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
type ExpectedMessagePushRoute = {
|
|
componentPath: string;
|
|
name: string;
|
|
path: string;
|
|
title: string;
|
|
};
|
|
|
|
const messagePushRoutes: ExpectedMessagePushRoute[] = [
|
|
{
|
|
componentPath: '#/views/qqbot/message-subscription/list',
|
|
name: 'QqBotMessageSubscription',
|
|
path: '/qqbot/message-subscription',
|
|
title: '消息订阅',
|
|
},
|
|
{
|
|
componentPath: '#/views/qqbot/message-template/list',
|
|
name: 'QqBotMessageTemplate',
|
|
path: '/qqbot/message-template',
|
|
title: '消息模板',
|
|
},
|
|
];
|
|
|
|
function getPropertyName(property: ts.ObjectLiteralElementLike) {
|
|
if (!ts.isPropertyAssignment(property) || property.name === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
return ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)
|
|
? property.name.text
|
|
: undefined;
|
|
}
|
|
|
|
function getProperty(object: ts.ObjectLiteralExpression, name: string) {
|
|
return object.properties.find(
|
|
(property): property is ts.PropertyAssignment =>
|
|
getPropertyName(property) === name && ts.isPropertyAssignment(property),
|
|
);
|
|
}
|
|
|
|
function getStringProperty(object: ts.ObjectLiteralExpression, name: string) {
|
|
const property = getProperty(object, name);
|
|
|
|
return property && ts.isStringLiteral(property.initializer)
|
|
? property.initializer.text
|
|
: undefined;
|
|
}
|
|
|
|
function getLazyComponentPath(object: ts.ObjectLiteralExpression) {
|
|
const component = getProperty(object, 'component');
|
|
|
|
expect(component, 'route must declare a lazy component').toBeDefined();
|
|
expect(component?.initializer).toSatisfy(ts.isArrowFunction);
|
|
|
|
const body = (component?.initializer as ts.ArrowFunction).body;
|
|
expect(body).toSatisfy(ts.isCallExpression);
|
|
|
|
const importCall = body as ts.CallExpression;
|
|
expect(importCall.expression.kind).toBe(ts.SyntaxKind.ImportKeyword);
|
|
expect(importCall.arguments).toHaveLength(1);
|
|
expect(importCall.arguments[0]).toSatisfy(ts.isStringLiteral);
|
|
|
|
return (importCall.arguments[0] as ts.StringLiteral).text;
|
|
}
|
|
|
|
function getQqBotChildren(sourceFile: ts.SourceFile) {
|
|
const routesDeclaration = sourceFile.statements.find(
|
|
(statement): statement is ts.VariableStatement => {
|
|
if (!ts.isVariableStatement(statement)) {
|
|
return false;
|
|
}
|
|
|
|
return statement.declarationList.declarations.some(
|
|
(declaration) =>
|
|
ts.isIdentifier(declaration.name) &&
|
|
declaration.name.text === 'routes',
|
|
);
|
|
},
|
|
);
|
|
const routesInitializer =
|
|
routesDeclaration?.declarationList.declarations.find(
|
|
(declaration) =>
|
|
ts.isIdentifier(declaration.name) && declaration.name.text === 'routes',
|
|
)?.initializer;
|
|
|
|
expect(routesInitializer).toSatisfy(ts.isArrayLiteralExpression);
|
|
|
|
const qqbotRoute = (
|
|
routesInitializer as ts.ArrayLiteralExpression
|
|
).elements.find(
|
|
(element): element is ts.ObjectLiteralExpression =>
|
|
ts.isObjectLiteralExpression(element) &&
|
|
getStringProperty(element, 'name') === 'QqBot',
|
|
);
|
|
|
|
expect(qqbotRoute, 'routes must contain a direct QqBot parent').toBeDefined();
|
|
|
|
const children = getProperty(
|
|
qqbotRoute as ts.ObjectLiteralExpression,
|
|
'children',
|
|
);
|
|
expect(children?.initializer).toSatisfy(ts.isArrayLiteralExpression);
|
|
|
|
return (children?.initializer as ts.ArrayLiteralExpression).elements.filter(
|
|
(element) => ts.isObjectLiteralExpression(element),
|
|
);
|
|
}
|
|
|
|
function getAllMessagePushRoutes(sourceFile: ts.SourceFile) {
|
|
const routeNames = new Set(messagePushRoutes.map((route) => route.name));
|
|
const matches: ts.ObjectLiteralExpression[] = [];
|
|
|
|
function visit(node: ts.Node): void {
|
|
if (ts.isObjectLiteralExpression(node)) {
|
|
const routeName = getStringProperty(node, 'name');
|
|
if (typeof routeName === 'string' && routeNames.has(routeName)) {
|
|
matches.push(node);
|
|
}
|
|
}
|
|
|
|
ts.forEachChild(node, visit);
|
|
}
|
|
|
|
visit(sourceFile);
|
|
return matches;
|
|
}
|
|
|
|
describe('qqbot routes', () => {
|
|
it('adds exactly two flat message-push route children without loading future pages', () => {
|
|
const sourceFile = ts.createSourceFile(
|
|
'qqbot.ts',
|
|
readFileSync(
|
|
resolve('apps/web-antdv-next/src/router/routes/modules/qqbot.ts'),
|
|
'utf8',
|
|
),
|
|
ts.ScriptTarget.Latest,
|
|
true,
|
|
);
|
|
const directChildren = getQqBotChildren(sourceFile);
|
|
const allMessagePushRouteNodes = getAllMessagePushRoutes(sourceFile);
|
|
|
|
expect(allMessagePushRouteNodes).toHaveLength(messagePushRoutes.length);
|
|
|
|
for (const expectedRoute of messagePushRoutes) {
|
|
const matchingDirectChildren = directChildren.filter(
|
|
(route) => getStringProperty(route, 'name') === expectedRoute.name,
|
|
);
|
|
|
|
expect(matchingDirectChildren).toHaveLength(1);
|
|
|
|
const route = matchingDirectChildren[0];
|
|
if (!route) {
|
|
return;
|
|
}
|
|
|
|
expect(getStringProperty(route, 'path')).toBe(expectedRoute.path);
|
|
expect(getLazyComponentPath(route)).toBe(expectedRoute.componentPath);
|
|
expect(getProperty(route, 'children')).toBeUndefined();
|
|
|
|
const meta = getProperty(route, 'meta');
|
|
expect(meta?.initializer).toSatisfy(ts.isObjectLiteralExpression);
|
|
if (!meta || !ts.isObjectLiteralExpression(meta.initializer)) {
|
|
return;
|
|
}
|
|
|
|
expect(getStringProperty(meta.initializer, 'title')).toBe(
|
|
expectedRoute.title,
|
|
);
|
|
expect(getStringProperty(meta.initializer, 'icon')).toMatch(
|
|
/^lucide:\S+$/,
|
|
);
|
|
}
|
|
});
|
|
});
|