import assert from 'node:assert/strict';
import { readFile, access } from 'node:fs/promises';
import { test } from 'node:test';
import { loadTypescript } from './support/load-typescript.mjs';
const moduleURL = new URL('../src/state/PetName.ts', import.meta.url);
assert.equal(await access(moduleURL).then(() => true, () => false), true, 'application-wide pet name state must exist');
const { normalizePetName, petNameForms, createPetNameStore, PET_NAME_STORAGE_KEY } = await loadTypescript(moduleURL);
test('friendly Korean name forms avoid duplicated 이 and match natural particles', () => {
for (const [raw, display, object, subject] of [
['', '멍멍이', '멍멍이를', '멍멍이가'], ['호롱', '호롱이', '호롱이를', '호롱이가'],
['초코', '초코', '초코를', '초코가'], ['보리', '보리', '보리를', '보리가'],
['호롱이', '호롱이', '호롱이를', '호롱이가'],
]) assert.deepEqual(petNameForms(raw), { display, object, subject, possessive: `${display}의` });
});
test('name normalization trims, composes Korean, bounds Unicode safely, preserves literal text', () => {
assert.equal(normalizePetName(' 호롱 '), '호롱');
assert.equal(normalizePetName(' \n\t '), '');
assert.equal(normalizePetName('호롱'), '호롱');
assert.equal(normalizePetName('🐕'.repeat(25)), '🐕'.repeat(20));
assert.equal(normalizePetName('
'), '
');
});
test('one store publishes normalized state, persists reloads and unsubscribe stops updates', () => {
const values = new Map(), storage = { getItem: key => values.get(key) ?? null, setItem: (key, value) => values.set(key, value) };
const store = createPetNameStore(() => storage), updates = [], signal = new AbortController();
store.subscribe(() => updates.push(store.name), signal.signal);
store.setName(' 호롱 '); store.setName('호롱');
assert.deepEqual(updates, ['호롱']); assert.equal(values.get(PET_NAME_STORAGE_KEY), '호롱');
assert.equal(createPetNameStore(() => storage).name, '호롱');
store.setName(''); assert.equal(createPetNameStore(() => storage).name, '');
signal.abort(); store.setName('초코'); assert.deepEqual(updates, ['호롱', '']);
});
test('storage getter, read and write failures never prevent session edits', () => {
for (const getStorage of [() => undefined, () => { throw Error('blocked'); }, () => ({ getItem() { throw Error('blocked'); }, setItem() { throw Error('full'); } }), () => ({ getItem: () => '호롱', setItem() { throw Error('full'); } })]) {
const store = createPetNameStore(getStorage); store.setName('보리');
assert.equal(store.name, '보리'); assert.equal(store.storageUnavailable, true);
}
});
test('removed settings surfaces are outside settings and inert, landing sample/retry stay available', async () => {
const html = await readFile(new URL('../index.html', import.meta.url), 'utf8');
const settings = html.slice(html.indexOf('