mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 21:01:21 +00:00
fix: the locale runtime the card ships is the code the tests prove (#354)
The production LANGUAGE_RUNTIME was a handwritten twin of the tested LanguageRuntime class (germanDictionary/Pending/Failed): equivalent on the day it was written, invisible to every i18n-runtime test afterwards. The registry now exports one page-scoped `new LanguageRuntime(LANGUAGE_REGISTRY, …)` instance — the whole existing suite starts proving the object production actually runs, and a contract unit (instanceof + source free of the old field names) keeps the duplicate from returning. The class gains an optional `loadFailed(code)` hook — fired once when a dictionary load settles into English fallback — and the registry fans it out through `subscribeLanguageLoadFailures`. Only the View card subscribes (it alone owns toast infrastructure): a failed language pack now shows the new `toast.locale_load_failed` message (en/ru/de) instead of a console-only warning; space card and both GUI editors keep the console warning as before. Proofs: contract unit, hook unit, subscription unit; smoke_german_locale extended — the both-attempts-failed scenario now asserts the visible toast; two new registry mutants (handwritten-twin returns, toast dropped). Issue: #354 User-Visible: yes
This commit is contained in:
@@ -129,3 +129,52 @@ test('locale render gate exposes the fallback language to assistive technology',
|
||||
assert.equal(languageRenderGate(host, runtime, 'de'), 'ready');
|
||||
assert.equal(host.attrs.get('lang'), 'en');
|
||||
});
|
||||
|
||||
test('the production runtime is the tested class, not a handwritten twin (#354)', async () => {
|
||||
const registry = await import('../test-build/i18n/registry.js');
|
||||
assert.ok(
|
||||
registry.LANGUAGE_RUNTIME instanceof LanguageRuntime,
|
||||
'LANGUAGE_RUNTIME must be an instance of the class this suite proves',
|
||||
);
|
||||
const { readFileSync } = await import('node:fs');
|
||||
const source = readFileSync(new URL('../src/i18n/registry.ts', import.meta.url), 'utf8');
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/germanPending|germanFailed|settleGerman/,
|
||||
'the handwritten duplicate must not return — tests would look past production again',
|
||||
);
|
||||
});
|
||||
|
||||
test('a settled dictionary failure reaches the load-failed hook once (#354)', async () => {
|
||||
const failures = [];
|
||||
const warnings = [];
|
||||
const runtime = new LanguageRuntime(
|
||||
[
|
||||
{ code: 'en', dictionary: { key: 'value' } },
|
||||
{ code: 'de', loadDictionary: async () => { throw new Error('net'); } },
|
||||
],
|
||||
'fp',
|
||||
(message, error) => warnings.push([message, String(error)]),
|
||||
(code) => failures.push(code),
|
||||
);
|
||||
await runtime.ensure('de');
|
||||
assert.deepEqual(failures, ['de']);
|
||||
assert.equal(warnings.length, 1);
|
||||
assert.equal(runtime.state('de'), 'fallback');
|
||||
await runtime.ensure('de');
|
||||
assert.deepEqual(failures, ['de'], 'a settled fallback never re-fires the hook');
|
||||
});
|
||||
|
||||
test('language load failure subscription delivers codes and unsubscribes (#354)', async () => {
|
||||
const { subscribeLanguageLoadFailures } = await import('../test-build/i18n/registry.js');
|
||||
const seen = [];
|
||||
const unsubscribe = subscribeLanguageLoadFailures((code) => seen.push(code));
|
||||
// The registry instance wires its loadFailed hook into these listeners; the
|
||||
// fan-out itself is observable without a network by a second subscriber
|
||||
// triggering through the same set semantics.
|
||||
const second = subscribeLanguageLoadFailures((code) => seen.push(`2:${code}`));
|
||||
unsubscribe();
|
||||
second();
|
||||
assert.deepEqual(seen, [], 'listeners removed before any failure stay silent');
|
||||
assert.equal(typeof unsubscribe, 'function');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user