| 1 | // Run: tsx src/__tests__/settings-storage-page.test.ts |
| 2 | |
| 3 | import { readFileSync } from "node:fs"; |
| 4 | import { dirname, resolve } from "node:path"; |
| 5 | import { fileURLToPath } from "node:url"; |
| 6 | |
| 7 | const testDir = dirname(fileURLToPath(import.meta.url)); |
| 8 | const settings = readFileSync(resolve(testDir, "../components/SettingsPanel.tsx"), "utf8"); |
| 9 | const settingsNavigation = readFileSync(resolve(testDir, "../components/SettingsNavigation.tsx"), "utf8"); |
| 10 | const storagePage = readFileSync(resolve(testDir, "../components/StorageSettingsPage.tsx"), "utf8"); |
| 11 | const bridge = readFileSync(resolve(testDir, "../lib/bridge.ts"), "utf8"); |
| 12 | const backend = readFileSync(resolve(testDir, "../../../storage_app.go"), "utf8"); |
| 13 | const styles = readFileSync(resolve(testDir, "../styles.css"), "utf8"); |
| 14 | const types = readFileSync(resolve(testDir, "../lib/types.ts"), "utf8"); |
| 15 | const locales = ["zh.ts", "zh-TW.ts", "en.ts"].map((name) => readFileSync(resolve(testDir, `../locales/${name}`), "utf8")); |
| 16 | |
| 17 | let passed = 0; |
| 18 | let failed = 0; |
| 19 | |
| 20 | function ok(condition: boolean, label: string) { |
| 21 | if (condition) { |
| 22 | process.stdout.write(` PASS ${label}\n`); |
| 23 | passed += 1; |
| 24 | } else { |
| 25 | process.stdout.write(` FAIL ${label}\n`); |
| 26 | failed += 1; |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | console.log("\nsettings storage page contract"); |
| 31 | |
| 32 | const tabs = settingsNavigation.match(/SETTINGS_NAV_TABS: SettingsTab\[\] = \[([^\]]+)\]/)?.[1] ?? ""; |
| 33 | ok(/"storage",\s*"updates",?\s*$/.test(tabs), "Storage is immediately before Updates in the settings navigation"); |
| 34 | ok(types.includes('| "storage" | "updates";'), "SettingsTab exposes the storage route before updates"); |
| 35 | ok(settings.includes('tab === "storage"') && settings.includes("<StorageSettingsPage />"), "Storage renders the same page on every platform"); |
| 36 | ok(settings.includes('import("./StorageSettingsPage")'), "Storage data and UI are loaded only when its page bundle renders"); |
| 37 | |
| 38 | const generalStart = settings.indexOf("function GeneralSection("); |
| 39 | const generalEnd = settings.indexOf("function ModelsSection(", generalStart); |
| 40 | const generalSection = settings.slice(generalStart, generalEnd); |
| 41 | ok(!generalSection.includes("StorageSettingsPage"), "General no longer embeds storage settings"); |
| 42 | ok(storagePage.includes('className="mem-input"') && !storagePage.includes('className="text-input"'), "Storage paths reuse the shared settings input recipe"); |
| 43 | ok(storagePage.includes("readOnly"), "Every storage path input is explicitly read-only"); |
| 44 | |
| 45 | const mutations = ["PickStorageFolder", "MigrateStorage", "SetDefaultWorkspace"]; |
| 46 | ok(mutations.every((name) => !storagePage.includes(name) && !bridge.includes(name) && !backend.includes(name)), "Storage mutation methods are absent from UI and desktop bridge surfaces"); |
| 47 | ok(!styles.includes("settings-path-control"), "Storage page contains no migration-only styling"); |
| 48 | ok(!storagePage.includes("storageReadOnlyHint") && locales.every((locale) => !locale.includes("settings.storageReadOnlyHint")), "Storage page omits the redundant read-only notice"); |
| 49 | ok(locales.every((locale) => !/Windows.*(?:迁移|移動|migrat)/i.test(locale.match(/"settings\.pageDesc\.storage":\s*"[^"]*"/)?.[0] ?? "")), "Storage page descriptions do not advertise Windows migration"); |
| 50 | ok(!/\b(?:Walk|sizeBytes|availableBytes|models)\b/.test(backend), "Storage query performs no recursive size scan and exposes no synthetic models path"); |
| 51 | ok(!types.includes("storage: StorageSettingsView"), "General SettingsView no longer owns storage data"); |
| 52 | ok(!settings.match(/const needsSettings[^;]+/)?.[0].includes('tab === "storage"'), "Storage remains available when the general settings request fails"); |
| 53 | |
| 54 | console.log(`\n${passed} passed, ${failed} failed, ${passed + failed} total`); |
| 55 | if (failed > 0) process.exit(1); |
| 56 |