From c3b0476dcc5235e812fd02cec57b9c4bbc324eac Mon Sep 17 00:00:00 2001 From: sunlei Date: Mon, 15 Jun 2026 10:09:15 +0800 Subject: [PATCH] =?UTF-8?q?test:=20=E8=A1=A5=E5=BC=BA=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=E6=94=B6=E6=95=9B=E5=AF=BC=E5=85=A5=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../architecture-convergence.spec.ts | 81 ++++++++++++++----- 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/test/refactor-v3/architecture-convergence.spec.ts b/test/refactor-v3/architecture-convergence.spec.ts index bba8e7b..2c9e750 100644 --- a/test/refactor-v3/architecture-convergence.spec.ts +++ b/test/refactor-v3/architecture-convergence.spec.ts @@ -1,15 +1,27 @@ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'; -import { join, relative } from 'node:path'; +import { + dirname, + join, + normalize, + relative, + resolve, + sep, +} from 'node:path'; const repoRoot = join(__dirname, '..', '..'); const srcRoot = join(repoRoot, 'src'); -const legacyRoots = ['admin', 'blog', 'minio', 'wordpress', 'qqbot']; +const legacyRootNames = ['admin', 'blog', 'minio', 'wordpress', 'qqbot']; +const legacyRootPaths = legacyRootNames.map((root) => join(srcRoot, root)); const moduleRoots = ['admin', 'asset', 'blog', 'wordpress', 'qqbot']; const qqbotRoots = ['core', 'napcat', 'plugin-platform', 'plugins']; +function isDirectory(path: string): boolean { + return existsSync(path) && statSync(path).isDirectory(); +} + function listFiles(dir: string): string[] { - if (!existsSync(dir)) return []; + if (!isDirectory(dir)) return []; return readdirSync(dir).flatMap((entry) => { const absolute = join(dir, entry); const stat = statSync(absolute); @@ -18,19 +30,51 @@ function listFiles(dir: string): string[] { }); } -function readTextFiles(dir: string): Array<{ file: string; text: string }> { +function readTextFiles( + dir: string, +): Array<{ absolute: string; file: string; text: string }> { return listFiles(dir) .filter((file) => /\.(ts|tsx|vue|js|mjs|cjs)$/.test(file)) .map((file) => ({ + absolute: file, file: relative(repoRoot, file).replace(/\\/g, '/'), text: readFileSync(file, 'utf8'), })); } +function extractImportSpecifiers(text: string): string[] { + const importPattern = + /(?:import|export)\s+(?:type\s+)?(?:[^'"]*?\s+from\s+)?['"]([^'"]+)['"]|import\(\s*['"]([^'"]+)['"]\s*\)/g; + return [...text.matchAll(importPattern)] + .map((match) => match[1] || match[2]) + .filter((specifier): specifier is string => Boolean(specifier)); +} + +function resolvesInsideLegacyRoot(fromFile: string, specifier: string): boolean { + let resolved: string | null = null; + if (specifier === '@') resolved = srcRoot; + if (specifier.startsWith('@/')) { + resolved = join(srcRoot, specifier.slice(2)); + } + if (specifier.startsWith('.')) { + resolved = resolve(dirname(fromFile), specifier); + } + if (!resolved) return false; + + const normalized = normalize(resolved); + return legacyRootPaths.some((legacyRoot) => { + const normalizedLegacy = normalize(legacyRoot); + return ( + normalized === normalizedLegacy || + normalized.startsWith(`${normalizedLegacy}${sep}`) + ); + }); +} + describe('architecture convergence', () => { it('does not keep API legacy source roots', () => { - const existing = legacyRoots.filter((root) => - existsSync(join(srcRoot, root)), + const existing = legacyRootNames.filter((root) => + isDirectory(join(srcRoot, root)), ); expect(existing).toEqual([]); @@ -38,7 +82,7 @@ describe('architecture convergence', () => { it('keeps all business modules under src/modules', () => { const missing = moduleRoots.filter( - (root) => !existsSync(join(srcRoot, 'modules', root)), + (root) => !isDirectory(join(srcRoot, 'modules', root)), ); expect(missing).toEqual([]); @@ -46,29 +90,30 @@ describe('architecture convergence', () => { it('keeps QQBot subdomains under src/modules/qqbot', () => { const missing = qqbotRoots.filter( - (root) => !existsSync(join(srcRoot, 'modules', 'qqbot', root)), + (root) => !isDirectory(join(srcRoot, 'modules', 'qqbot', root)), ); expect(missing).toEqual([]); }); it('does not import old roots from src/modules', () => { - const forbidden = /@\/(?:admin|blog|minio|wordpress|qqbot)\//; const offenders = readTextFiles(join(srcRoot, 'modules')) - .filter(({ text }) => forbidden.test(text)) - .map(({ file }) => file); + .flatMap(({ absolute, file, text }) => + extractImportSpecifiers(text) + .filter((specifier) => resolvesInsideLegacyRoot(absolute, specifier)) + .map((specifier) => `${file} -> ${specifier}`), + ); expect(offenders).toEqual([]); }); it('does not import old roots from app module', () => { - const appModule = readFileSync(join(srcRoot, 'app.module.ts'), 'utf8'); + const appModulePath = join(srcRoot, 'app.module.ts'); + const appModule = readFileSync(appModulePath, 'utf8'); + const offenders = extractImportSpecifiers(appModule).filter((specifier) => + resolvesInsideLegacyRoot(appModulePath, specifier), + ); - expect(appModule).not.toMatch( - /from ['"]\.\/(?:admin|blog|minio|wordpress|qqbot)/, - ); - expect(appModule).not.toMatch( - /from ['"]@\/(?:admin|blog|minio|wordpress|qqbot)\//, - ); + expect(offenders).toEqual([]); }); });