74 lines
3.5 KiB
TypeScript
74 lines
3.5 KiB
TypeScript
// What the webview code may do: only the app commands (no plugin JS API, no network of its own, no eval), no secret
|
|
// ever passes through it, the catalogs are complete, and the release bundle carries no dev fixture mode.
|
|
import { describe, expect, test } from 'bun:test';
|
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { checkCatalogs } from '../../../tools/i18n-check';
|
|
import { en } from '../src/i18n/en';
|
|
import { tr } from '../src/i18n/tr';
|
|
|
|
const ROOT = join(import.meta.dir, '..');
|
|
const src = (f: string) => readFileSync(join(ROOT, f), 'utf8');
|
|
function files(dir: string): string[] {
|
|
return readdirSync(join(ROOT, dir)).flatMap(n => {
|
|
const rel = join(dir, n);
|
|
return statSync(join(ROOT, rel)).isDirectory() ? files(rel) : /\.(ts|tsx)$/.test(n) ? [rel] : [];
|
|
});
|
|
}
|
|
|
|
describe('webview rules', () => {
|
|
test('only app commands: no plugin JS APIs, no direct network, no eval', () => {
|
|
const all = files('src');
|
|
expect(all.length).toBeGreaterThan(20);
|
|
for (const f of all) {
|
|
const s = src(f);
|
|
expect(s, f).not.toMatch(/@tauri-apps\/plugin-/);
|
|
expect(s, f).not.toMatch(/\bfetch\(|XMLHttpRequest|WebSocket|eval\(|new Function|dangerouslySetInnerHTML|innerHTML\s*=/);
|
|
// only api.ts talks to the host
|
|
if (!f.endsWith('api.ts')) expect(s, f).not.toMatch(/@tauri-apps\/api/);
|
|
}
|
|
// runtime deps: Tauri's API, React, and the chart (visx) and icon (Phosphor) libraries, all bundled by Vite
|
|
const deps = Object.keys(JSON.parse(src('package.json')).dependencies).sort();
|
|
expect(deps.filter(d => !d.startsWith('@visx/'))).toEqual(['@phosphor-icons/react', '@tauri-apps/api', 'react', 'react-dom']);
|
|
expect(deps.filter(d => d.startsWith('@visx/')).length).toBeGreaterThan(0);
|
|
});
|
|
test('the confirm token and credentials never appear in the webview code', () => {
|
|
for (const f of files('src')) expect(src(f), f).not.toMatch(/confirmToken|credentials|PLAY_SERVICE_ACCOUNT|ASC_P8/);
|
|
});
|
|
test('fixtures load only behind import.meta.env.DEV', () => {
|
|
for (const f of files('src')) {
|
|
const s = src(f);
|
|
if (f.includes(`${'dev'}/`)) continue;
|
|
for (const m of s.matchAll(/import\(['"]\.\/dev\/fixtures['"]\)/g)) {
|
|
const before = s.slice(Math.max(0, m.index! - 120), m.index!);
|
|
expect(before, f).toContain('import.meta.env.DEV');
|
|
}
|
|
expect(s, f).not.toMatch(/^import .*dev\/fixtures/m);
|
|
}
|
|
});
|
|
test('built bundle (when present) has no fixture mode and no dev switches', () => {
|
|
const dist = join(ROOT, 'dist', 'assets');
|
|
// tools/ci.ts runs this file again as "desktop bundle check" after the build; its test shards skip it here
|
|
if (!existsSync(dist) || process.env.STOREBOUND_SKIP_DIST_CHECK === '1') return;
|
|
for (const n of readdirSync(dist)) {
|
|
const s = readFileSync(join(dist, n), 'utf8');
|
|
expect(s, n).not.toContain('storebound-dev-fixtures');
|
|
expect(s, n).not.toContain('mockInvoke');
|
|
expect(s, n).not.toContain('VITE_START');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('i18n', () => {
|
|
test('en and tr: same keys, no empty strings, matching placeholders, house style', () => {
|
|
expect(checkCatalogs(en, { tr })).toEqual([]);
|
|
});
|
|
test('every t() key used in the code exists', () => {
|
|
const keys = new Set(Object.keys(en));
|
|
const missing: string[] = [];
|
|
for (const f of files('src')) {
|
|
for (const m of src(f).matchAll(/\bt\('([a-zA-Z0-9_.-]+)'/g)) if (!keys.has(m[1]!)) missing.push(`${f}: ${m[1]}`);
|
|
}
|
|
expect(missing).toEqual([]);
|
|
});
|
|
});
|