/** * 16-assets: 资源完整性——静态扫描 + 运行时路径校验 * 原测试: Node fs 扫描 → 保留同样的扫描逻辑 */ import { describe, it, expect } from 'vitest' import { existsSync, readFileSync, readdirSync } from 'fs' import { join, resolve } from 'path' import { fileURLToPath } from 'url' import { WALLPAPERS } from '../../src/composables/useSettings' const __filename = fileURLToPath(import.meta.url) const __dirname = join(__filename, '..') const ROOT = resolve(__dirname, '../..') const PUBLIC = join(ROOT, 'public') describe('16-assets — 资源完整性', () => { // ===== 静态扫描:收集所有 assets/ 引用 ===== function collectAssetRefs(): string[] { const refs = new Set() const srcDir = join(ROOT, 'src') const files = walkFiles(srcDir).concat([join(ROOT, 'index.html')]) for (const file of files) { try { const content = readFileSync(file, 'utf-8') for (const m of content.matchAll(/(?:src|href|icon)=["']\/?(assets\/[^"']+?)["']/g)) { refs.add(m[1]) } } catch { /* ignore */ } } return [...refs].sort() } function walkFiles(dir: string): string[] { const result: string[] = [] try { for (const e of readdirSync(dir, { withFileTypes: true })) { const full = join(dir, e.name) if (e.isDirectory() && e.name !== 'node_modules') result.push(...walkFiles(full)) else if (/\.(ts|vue|css|html|js)$/.test(e.name)) result.push(full) } } catch { /* ignore */ } return result } // ===== WALLPAPERS ===== it('所有 WALLPAPERS src 指向的文件存在', () => { const missing: string[] = [] for (const w of WALLPAPERS) { const p = join(PUBLIC, w.src) if (!existsSync(p)) missing.push(w.src) } expect(missing).toEqual([]) }) it('bigsur 暗色变体文件存在', () => { const bigsur = WALLPAPERS.find(w => w.id === 'bigsur')! const p = join(PUBLIC, bigsur.dark!) expect(existsSync(p)).toBe(true) }) // ===== 关键图标 ===== const requiredIcons = [ 'finder.png', 'folder.svg', 'txt.svg', 'trash.svg', 'trash-full.svg', 'settings.png', 'safari.png', 'calendar.png', 'calculator.png', 'terminal.png', 'music.png', 'photos.png', 'notes.png', 'appstore.png', 'launchpad.png', 'messages.png', 'facetime.png', 'maps.png', 'mail.png', 'preview.svg', 'quicktime.svg', 'activitymonitor.svg', 'textedit.svg', ] it('关键应用图标存在', () => { const missing: string[] = [] for (const icon of requiredIcons) { const p = join(PUBLIC, 'assets/icons', icon) if (!existsSync(p)) missing.push(icon) } expect(missing).toEqual([]) }) // ===== 音频和视频目录 ===== it('assets/audio 目录存在且含 mp3 文件', () => { const audioDir = join(PUBLIC, 'assets/audio') expect(existsSync(audioDir)).toBe(true) const files = readdirSync(audioDir).filter((f: string) => f.endsWith('.mp3')) expect(files.length).toBeGreaterThanOrEqual(6) }) it('assets/video 目录存在且含 mp4/webm 文件', () => { const videoDir = join(PUBLIC, 'assets/video') expect(existsSync(videoDir)).toBe(true) const files = readdirSync(videoDir) expect(files.length).toBeGreaterThanOrEqual(6) }) // ===== 静态引用完整性 ===== it('代码中引用的 assets 路径指向存在的文件', () => { const refs = collectAssetRefs() const missing: string[] = [] for (const ref of refs) { // 跳过模板变量(如 ${src}) if (ref.includes('${') || ref.includes('{')) continue const p = join(PUBLIC, ref) if (!existsSync(p)) missing.push(ref) } expect(missing).toEqual([]) }) // ===== Store 图标 ===== const storeIcons = ['bear.png', 'typora.png', 'vscode.png', 'keynote.png', 'podcasts.png', 'news.png', 'github.png', 'tv.png'] it('Store 应用图标存在', () => { const missing: string[] = [] for (const icon of storeIcons) { if (!existsSync(join(PUBLIC, 'assets/icons', icon))) missing.push(icon) } expect(missing).toEqual([]) }) // ===== 其他资源 ===== it('avatar.svg 存在', () => { expect(existsSync(join(PUBLIC, 'assets/icons/avatar.svg'))).toBe(true) }) it('pdf.svg 存在', () => { expect(existsSync(join(PUBLIC, 'assets/icons/pdf.svg'))).toBe(true) }) })