divan/api/src/dictionary.test.ts
Anas Rashid 3201a1c478 Word dictionary in full: all senses with usage examples, every IPA with dialect labels, every recording, full etymologies, synonyms, derived and related words
Panel folds the extras (more meanings, examples, long etymologies, long word lists). 175 MB in PostgreSQL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 22:27:37 +02:00

100 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { key, kaikkiEntry, glossKeys, urduEntry, definitions, dumpPages } from './dictionary.ts';
test('lookup key: spelling variants across Urdu, Persian and Arabic meet', () => {
const same = (a: string, b: string) => assert.equal(key(a), key(b), `${a} vs ${b}`);
same('ناداں', 'نادان');
same('قِسْمَت', 'قسمت');
same('نگاہ', 'نگاه');
same('معنی', 'معنى');
same('كتاب', 'کتاب');
assert.notEqual(key('کھ'), key('کہ'), 'do-chashmi heh stays distinct');
assert.notEqual(key('دیوانے'), key('دیوانی'), 'bari ye stays distinct');
});
test('kaikki entry kept in full: senses with labels and examples, every IPA and recording, form-of', () => {
const e = kaikkiEntry({
word: 'قسمت', pos: 'noun', etymology_text: 'From Arabic',
senses: [{ glosses: ['fate, destiny'], examples: [{ text: 'قسمت کا لکھا', roman: 'qismat kā likhā', english: 'what fate wrote' }] },
{ glosses: ['division'], tags: ['archaic'] }, { links: [] }],
sounds: [{ ipa: '/qɪs.mət̪/', tags: ['Standard'] }, { ipa: '[qɪs.mət̪]' }, { ipa: '/qɪs.mət̪/' },
{ audio: 'x.wav', mp3_url: 'https://upload.wikimedia.org/x.mp3', tags: ['Iran'] }, { ogg_url: 'https://upload.wikimedia.org/y.ogg' },
{ mp3_url: 'https://evil.example/x.mp3' }],
forms: [{ form: 'قِسْمَت', tags: ['canonical'] }, { form: 'qismat', tags: ['romanization'] }],
synonyms: [{ word: 'تقدیر' }], derived: [{ word: 'قسمت والا' }, { word: 'قسمت والا' }],
});
assert.deepEqual(e, {
pos: 'noun', formOf: false, tr: 'qismat', form: 'قِسْمَت',
senses: [{ gloss: 'fate, destiny', tags: [], examples: [{ text: 'قسمت کا لکھا', roman: 'qismat kā likhā', english: 'what fate wrote' }] },
{ gloss: 'division', tags: ['archaic'], examples: [] }],
ipa: [{ ipa: '/qɪs.mət̪/', tags: ['Standard'] }, { ipa: '[qɪs.mət̪]', tags: [] }],
audio: [{ url: 'https://upload.wikimedia.org/x.mp3', tags: ['Iran'] }, { url: 'https://upload.wikimedia.org/y.ogg', tags: [] }],
ety: 'From Arabic', synonyms: ['تقدیر'], derived: ['قسمت والا'], related: [],
});
assert.equal(kaikkiEntry({ word: 'x', pos: 'noun', senses: [{ glosses: ['plural of y'], tags: ['form-of'] }] }).formOf, true);
});
test('pivot keys from English glosses', () => {
assert.deepEqual(glossKeys(['fate, destiny', 'to love (someone)', 'a very long description of something that is not a key']), ['fate', 'destiny', 'love']);
assert.deepEqual(glossKeys(['zephyr; soft breeze; especially, a morning breeze']), ['zephyr', 'soft breeze', 'morning breeze']);
});
test('ur.wiktionary entry: meanings and origin', () => {
const wt = 'وِصال {وِصال} ([[عربی]])\n\nاسم [[نکرہ]]\n\n==معانی==\n\n1. بھیٹ، ملاقات، [[عاشق و معشوق]] کی محبت۔\n\n==مرکبات==\n\nوِصال کا دِن';
assert.deepEqual(urduEntry(wt), { defs: ['بھیٹ، ملاقات، عاشق و معشوق کی محبت۔'], origin: 'عربی', links: [] });
});
test('fa./ar.wiktionary definitions: skip etymology, Arabic pointers', () => {
const fa = '==فارسی==\n===ریشه‌شناسی===\n* [[عربی]]\n# قسمة\n===اسم===\n#بهره، نصیب.\n#:example\n#سرنوشت، تقدیر.\n====برگردان‌ها====\n# x y z';
assert.deepEqual(definitions(fa, 'fa').defs, ['بهره، نصیب.', 'سرنوشت، تقدیر.']);
const ar = '== {{اللغة|عربية}} ==\nهل تقصد:\n* [[عِشْق]]\n* [[عَشَقَ]]\n----\n== {{اللغة|أردية}} ==\n# [[x]]';
assert.deepEqual(definitions(ar, 'ar'), { defs: [], origin: null, links: ['عِشْق', 'عَشَقَ'] });
assert.deepEqual(definitions('# {{مصدر|عَشِقَ}}.\n# فرط الحب.', 'ar').defs, ['فرط الحب.']);
});
test('dump pages: articles only, entities decoded, redirects skipped', () => {
const xml = '<page><title>عشق</title><ns>0</ns><revision><text bytes="9">a &amp; b &lt;x&gt;</text></revision></page>' +
'<page><title>Talk:x</title><ns>1</ns><text>t</text></page><page><title>r</title><ns>0</ns><redirect title="y" /><text>#R</text></page>';
assert.deepEqual([...dumpPages(xml)], [{ title: 'عشق', text: 'a & b <x>' }]);
});
test('etymology kept in full and well formed', () => {
const ety = kaikkiEntry({ word: 'x', pos: 'noun', senses: [], etymology_text: 'a'.repeat(400) + '𑀭\ud804' }).ety!;
assert.ok(ety.isWellFormed() && ety.length > 400);
});
test('ur.wiktionary: English entries give Urdu words; Urdu entries fall back to # lines or prose', async () => {
const { englishToUrdu, isEnglish } = await import('./dictionary.ts');
assert.ok(isEnglish('north') && !isEnglish('شمال'));
assert.deepEqual(englishToUrdu('==انگریزی==\n===صفت===\n{{en-adjective}}\n# [[شمالی]]۔\n# [[شمال]] کی [[جانب]]۔\n#: [[x]]'), ['شمالی', 'شمال', 'جانب']);
assert.deepEqual(urduEntry('===اسم===\n# [[بہار]] کا [[موسم]]۔').defs, ['بہار کا موسم۔']);
assert.deepEqual(urduEntry("'''پھوڑی''' اس چادر کو کہتے ہیں جس پر بیٹھتے ہیں۔").defs, ['پھوڑی اس چادر کو کہتے ہیں جس پر بیٹھتے ہیں۔']);
});
test('etymology drops the "Etymology tree" summary', () => {
const ety = kaikkiEntry({ word: 'x', pos: 'noun', senses: [], etymology_text: 'Etymology tree Arabic قَسَمَ (qasama)bor. Urdu قِسْمَت Borrowed from Classical Persian قِسْمَت (qismat).' }).ety;
assert.equal(ety, 'Borrowed from Classical Persian قِسْمَت (qismat).');
});
test('similar words: punctuation-free keys, inflection stems, consonant skeleton regex', async () => {
const { stems, skeleton } = await import('./dictionary.ts');
assert.equal(key('بے ثبوت'), key('بےثبوت'));
assert.equal(key('دل،'), key('دل'));
assert.equal(key('دل-جان۔'), key('دلجان'));
assert.ok(stems(key('آنکھوں')).includes(key('آنکھ')));
assert.ok(stems(key('دیوانے')).includes(key('دیوانہ')));
assert.ok(stems(key('جاتے')).includes(key('جانا')));
const re = new RegExp(skeleton(key('دیوانگی'))!);
assert.ok(re.test(key('دیوانگی')) && re.test(key('دیونگی')) && !re.test(key('دیوار')));
assert.equal(skeleton(key('آ')), null);
});
test('suggestion titles are shown clean', async () => {
const { clean } = await import('./dictionary.ts');
assert.equal(clean('ج.ا'), 'جا');
assert.equal(clean('ـجات'), 'جات');
assert.equal(clean('دیوانه‌تر'), 'دیوانهتر');
assert.equal(clean('بے ثبوت،'), 'بےثبوت');
});