Web fix: deterministic feature strings, suggest_category vectors, raw bodies in the fake server
- Root cause of the raw keys (web_accounts_load_failed, the settle missing state) on main: Vitest 5's file-system module cache (on by default) skips only modules whose source contains the literal `import.meta.glob(`. catalog.ts wrote `import.meta.glob<…>(`, so its transform was cached by its own content and kept the glob result from before a feature added its i18n/*.json; the main checkout's node_modules/.vitest-cache served that stale catalog until a config change invalidated it. Fresh worktrees passed. - catalog.ts: glob without a type argument (typed by annotation), with the reason. - catalog.test.ts: the loaded feature catalogs must equal the i18n files on disk. - A string two features render moves to the shared catalog: web_accounts_load_failed (accounts/PageStates, also rendered by Home) is now web_ledger_load_failed in src/i18n/web-strings.csv; sv.json/en.json regenerated. - vectors.test.ts: suggest_category is PORTED and runs against features/expenses/suggest. - fakeServer: bodies by Content-Type (JSON parsed, text/* as text, else raw bytes); ProfilePage's picture test asserts the uploaded bytes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { readdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { featureCatalogs, fill, LOADED_FEATURE_CATALOGS, mergeCatalogs, translate, translatePlural } from './catalog';
|
||||
import en from './en.json';
|
||||
@@ -46,6 +48,26 @@ describe('feature catalogs', () => {
|
||||
expect(merged).toEqual({ common_save: 'Spara', web_settle_hint: 'Tips' });
|
||||
});
|
||||
|
||||
it('loads every feature catalog on disk (no stale transform of catalog.ts)', () => {
|
||||
const featuresDir = join(process.cwd(), 'src', 'features'); // Vitest runs from web/
|
||||
const onDisk = readdirSync(featuresDir, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.flatMap((entry) => {
|
||||
let files: string[] = [];
|
||||
try {
|
||||
files = readdirSync(join(featuresDir, entry.name, 'i18n'));
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
||||
}
|
||||
return files.filter((file) => /^(sv|en)\.json$/.test(file)).map((file) => `${entry.name}/${file}`);
|
||||
})
|
||||
.sort();
|
||||
const loaded = Object.entries(LOADED_FEATURE_CATALOGS)
|
||||
.flatMap(([feature, catalogs]) => Object.keys(catalogs).map((language) => `${feature}/${language}.json`))
|
||||
.sort();
|
||||
expect(loaded).toEqual(onDisk);
|
||||
});
|
||||
|
||||
it('every loaded feature has the same web_ keys in sv and en, none shared', () => {
|
||||
for (const [feature, catalogs] of Object.entries(LOADED_FEATURE_CATALOGS)) {
|
||||
const svKeys = Object.keys(catalogs.sv ?? {}).sort();
|
||||
|
||||
@@ -46,7 +46,13 @@ export function mergeCatalogs(base: Catalog, features: readonly (Catalog | undef
|
||||
return merged;
|
||||
}
|
||||
|
||||
const FEATURES = featureCatalogs(import.meta.glob<{ default: Catalog }>('../features/*/i18n/*.json', { eager: true }));
|
||||
// No type argument on `import.meta.glob` (typed by the annotation instead): Vitest's file-system module cache
|
||||
// (on by default since Vitest 5) skips only modules whose source contains the literal text `import.meta.glob(`.
|
||||
// Written as `import.meta.glob<…>(`, this module was cached by its own content, so a feature catalog added later was
|
||||
// missing from the cached glob and its keys rendered raw in tests. catalog.test.ts compares the loaded catalogs with
|
||||
// the files on disk.
|
||||
const FEATURE_MODULES: FeatureModules = import.meta.glob('../features/*/i18n/*.json', { eager: true });
|
||||
const FEATURES = featureCatalogs(FEATURE_MODULES);
|
||||
|
||||
const CATALOGS: Record<Language, Catalog> = {
|
||||
sv: mergeCatalogs(
|
||||
|
||||
@@ -595,6 +595,7 @@
|
||||
"web_language_auto": "Browser language",
|
||||
"web_language_en": "English",
|
||||
"web_language_sv": "Svenska",
|
||||
"web_ledger_load_failed": "Could not get the household's expenses",
|
||||
"web_login_body": "Enter an invite code from Settings › Pair a device on your phone.",
|
||||
"web_login_name_needed": "This code adds a new person to the household. Enter your name.",
|
||||
"web_login_server": "Server",
|
||||
|
||||
@@ -595,6 +595,7 @@
|
||||
"web_language_auto": "Webbläsarens språk",
|
||||
"web_language_en": "English",
|
||||
"web_language_sv": "Svenska",
|
||||
"web_ledger_load_failed": "Kunde inte hämta hushållets utgifter",
|
||||
"web_login_body": "Skriv in en inbjudningskod från Inställningar › Para ihop en enhet på telefonen.",
|
||||
"web_login_name_needed": "Koden lägger till en ny person i hushållet. Skriv ditt namn.",
|
||||
"web_login_server": "Server",
|
||||
|
||||
@@ -32,3 +32,4 @@ web_not_found_title,Page not found,Sidan finns inte,module=web; unknown route un
|
||||
web_not_found_action,Go to Home,Gå till Översikt,module=web
|
||||
web_account_detail_title,Account,Konto,module=web; placeholder title of /app/accounts/:id until the page shows the account name
|
||||
web_expense_detail_title,Expense,Utgift,module=web; placeholder title of /app/expenses/:id until the page shows the description
|
||||
web_ledger_load_failed,Could not get the household's expenses,Kunde inte hämta hushållets utgifter,module=web; title of the failed first read of the ledger (Home and the account pages)
|
||||
|
||||
|
Reference in New Issue
Block a user