storebound/apps/desktop/tests/webview.test.ts

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([]);
});
});