kt-template-admin/test/router/routes/modules/qqbot.spec.ts

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+$/,
);
}
});
});