- Add grouped token, cache, and latency details - Tighten column spacing and preserve custom widths - Move pagination to the footer and add CSV export - Support dark mode and responsive layouts
114 lines
4.7 KiB
TypeScript
114 lines
4.7 KiB
TypeScript
import { describe, expect, it } from 'bun:test';
|
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import ts from 'typescript';
|
|
|
|
const sourceRoot = fileURLToPath(new URL('../src/', import.meta.url));
|
|
const technicalText = new Set([
|
|
'EasyCLIProxyAPI',
|
|
'WebSocket',
|
|
'Fast',
|
|
'HTTP',
|
|
'auto',
|
|
'excluded_models',
|
|
'headers',
|
|
'ms',
|
|
'note',
|
|
'Models.dev',
|
|
'LiteLLM',
|
|
]);
|
|
const technicalPlaceholders = new Set([
|
|
'1h',
|
|
'sk-...',
|
|
'gpt-5.6-terra',
|
|
'https://...',
|
|
'socks5://127.0.0.1:1080',
|
|
]);
|
|
|
|
function componentFiles(directory: string): string[] {
|
|
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
const path = join(directory, entry.name);
|
|
if (entry.isDirectory()) return entry.name === 'i18n' ? [] : componentFiles(path);
|
|
return entry.name.endsWith('.tsx') ? [path] : [];
|
|
});
|
|
}
|
|
|
|
describe('UI localization boundaries', () => {
|
|
it('preserves the raw reasoning effort in usage text and tooltips', () => {
|
|
const file = join(sourceRoot, 'pages', 'UsageEventsView.tsx');
|
|
const source = ts.createSourceFile(file, readFileSync(file, 'utf8'), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
let effortCase: ts.CaseClause | undefined;
|
|
const visit = (node: ts.Node) => {
|
|
if (ts.isCaseClause(node) && ts.isStringLiteral(node.expression) && node.expression.text === 'effort') {
|
|
effortCase = node;
|
|
} else {
|
|
ts.forEachChild(node, visit);
|
|
}
|
|
};
|
|
const component = source.statements.find((node) => ts.isFunctionDeclaration(node) && node.name?.text === 'UsageEventCell');
|
|
expect(component).toBeDefined();
|
|
if (component) visit(component);
|
|
const displayedExpressions: string[] = [];
|
|
const titleExpressions: string[] = [];
|
|
const inspectEffort = (node: ts.Node) => {
|
|
if (ts.isJsxExpression(node) && !ts.isJsxAttribute(node.parent) && node.expression) {
|
|
displayedExpressions.push(node.expression.getText(source));
|
|
}
|
|
if (ts.isJsxAttribute(node) && node.name.getText(source) === 'title' && node.initializer) {
|
|
titleExpressions.push(node.initializer.getText(source));
|
|
}
|
|
ts.forEachChild(node, inspectEffort);
|
|
};
|
|
expect(effortCase).toBeDefined();
|
|
if (effortCase) inspectEffort(effortCase);
|
|
expect(displayedExpressions).toContain("record.reasoning_effort || 'auto'");
|
|
expect(titleExpressions).toContain("{record.reasoning_effort || 'auto'}");
|
|
});
|
|
|
|
it('routes visible prose and accessibility labels through translations', () => {
|
|
const hardcoded: string[] = [];
|
|
for (const file of componentFiles(sourceRoot)) {
|
|
const source = ts.createSourceFile(file, readFileSync(file, 'utf8'), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
const report = (node: ts.Node, text: string, allowed = technicalText) => {
|
|
const normalized = text.trim();
|
|
if (/\p{L}/u.test(normalized) && !allowed.has(normalized)) {
|
|
const line = source.getLineAndCharacterOfPosition(node.getStart()).line + 1;
|
|
hardcoded.push(`${file}:${line}: ${normalized}`);
|
|
}
|
|
};
|
|
const inspectExpression = (expression?: ts.Expression) => {
|
|
if (!expression) return;
|
|
if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) {
|
|
report(expression, expression.text);
|
|
} else if (ts.isConditionalExpression(expression)) {
|
|
inspectExpression(expression.whenTrue);
|
|
inspectExpression(expression.whenFalse);
|
|
} else if (ts.isBinaryExpression(expression)) {
|
|
inspectExpression(expression.right);
|
|
}
|
|
};
|
|
const visit = (node: ts.Node) => {
|
|
if (ts.isJsxText(node)) report(node, node.text);
|
|
if (ts.isJsxExpression(node) && !ts.isJsxAttribute(node.parent)) inspectExpression(node.expression);
|
|
if (ts.isJsxAttribute(node) && node.initializer) {
|
|
const name = node.name.getText(source);
|
|
if (['title', 'aria-label', 'alt', 'placeholder'].includes(name) && ts.isStringLiteral(node.initializer)) {
|
|
report(node, node.initializer.text, name === 'placeholder' ? technicalPlaceholders : technicalText);
|
|
}
|
|
}
|
|
ts.forEachChild(node, visit);
|
|
};
|
|
visit(source);
|
|
}
|
|
expect(hardcoded).toEqual([]);
|
|
});
|
|
|
|
it('does not add a redundant hardcoded caption above localized page titles', () => {
|
|
for (const file of ['ApiAccessPage', 'ManagementPages', 'AuthFileManagementPage', 'AgentsPage', 'QuotaPage']) {
|
|
const source = readFileSync(join(sourceRoot, 'pages', `${file}.tsx`), 'utf8');
|
|
expect(source).not.toMatch(/<span>(?:Providers|OAuth|Auth Files|Agent Clients|Quota)<\/span>/);
|
|
}
|
|
});
|
|
});
|