import assert from 'node:assert/strict'; import { test } from 'node:test'; import { buildDelivery, coveragePercent, packSummary } from '../src/stringPacksModel.ts'; import { kindForFile, itemState, type StoreItem } from '../src/storeModel.ts'; import type { PageInventory } from '../../runtime/src/stringPacks.ts'; const INVENTORY: PageInventory = { site: 'demo', catalog: 'web', text: 'a0000001 a0000002 a0000003', message: { b0000001: 'count*' }, closed: 'c0000001' }; const pack = (strings: Record, extra: Record = {}) => JSON.stringify({ format: 1, site: 'demo', catalog: 'web', language: 'eo', name: 'Esperanto', authors: ['A'], strings, ...extra, }); test('pack summary: coverage against the inventory, ignored and refused keys, a sample of the pack\'s own text', () => { const summary = packSummary(pack({ a0000001: 'Hejmo', a0000002: 'Esplori', b0000001: '{count, plural, one {# afiŝo} other {# afiŝoj}}', zzzzzzzz: 'x', a0000003: 'x', c0000001: 'y' }), INVENTORY)!; assert.deepEqual(summary, { language: 'eo', translated: 3, translatable: 4, closed: 1, unknown: 1, refused: 2, sample: ['Hejmo', 'Esplori'], otherCatalog: false }); assert.equal(coveragePercent(summary), '75.0%'); assert.equal(packSummary('not json', INVENTORY), null); assert.equal(packSummary('{"strings": 1}', INVENTORY), null); assert.equal(packSummary(pack({ a0000001: 'x' }, { catalog: 'guest' }), INVENTORY)!.otherCatalog, true); }); test('delivery: the packs that are on, in order, with only the inventory slice their keys need', () => { const first = pack({ a0000001: 'Hejmo' }); const second = pack({ a0000002: 'Esplori', b0000001: '{count}' }, { name: 'Second' }); const delivery = JSON.parse(buildDelivery([first, second], INVENTORY)!); assert.deepEqual(delivery.inventory, { site: 'demo', catalog: 'web', text: 'a0000001 a0000002', message: { b0000001: 'count*' }, closed: '', partial: true }); assert.deepEqual(delivery.packs.map((p: { name: string }) => p.name), ['Esperanto', 'Second']); // Nothing usable: no delivery (the tabs drop theirs). assert.equal(buildDelivery([], INVENTORY), null); assert.equal(buildDelivery([pack({ zzzzzzzz: 'x' }), 'junk'], INVENTORY), null); }); test('store model: string packs by file name and by install record', () => { assert.equal(kindForFile('eo.strings.json'), 'strings'); assert.equal(kindForFile('theme.json'), 'theme'); const item = { kind: 'strings', id: 'esperanto', version: '1.1.0', sha256: 'b'.repeat(64) } as StoreItem; const context = { plugins: {}, builtinThemes: [], userThemes: [], records: {} as Record }; assert.deepEqual(itemState(item, context), { state: 'install' }); const record = { kind: 'strings' as const, source: 'store' as const, version: '1.0.0', sha256: 'a'.repeat(64), installedAt: '' }; assert.deepEqual(itemState(item, { ...context, records: { 'strings:esperanto': record } as never }), { state: 'update', version: '1.0.0' }); assert.deepEqual(itemState(item, { ...context, records: { 'strings:esperanto': { ...record, sha256: 'b'.repeat(64) } } as never }), { state: 'installed', version: '1.0.0' }); }); test('delivery: a rewording pack counts as usable and brings the whole inventory; rich keys with slots are translatable', () => { const rich: PageInventory = { ...INVENTORY, rich: { d0000001: 2 } }; const wording = pack({}, { name: 'Wording', language: 'en-x-twitter', when: { languages: ['en'] }, rewrite: { words: { Post: 'Tweet' } } }); const delivery = JSON.parse(buildDelivery([wording, pack({ a0000001: 'Hejmo' })], rich)!); assert.deepEqual(delivery.inventory, rich, 'the whole inventory, not a slice'); assert.deepEqual(delivery.packs.map((p: { name: string }) => p.name), ['Wording', 'Esperanto']); assert.equal(buildDelivery([wording], rich) !== null, true); assert.equal(buildDelivery([pack({})], rich), null, 'a pack with neither strings nor a rule changes nothing'); const summary = packSummary(pack({ d0000001: 'Legu {e1} antaŭ {e0}.', a0000001: 'Hejmo' }), rich)!; assert.equal(summary.translatable, 5, 'the rich key with slots is counted as translatable'); assert.equal(summary.translated, 2); assert.equal(summary.refused, 0); });