halkyon/apps/learn/halkyon_learn_curator/test/krdict_test.dart
Tizian 「ティツィアーン」 649499be23
chore: Halkyon Learn moves into apps/learn
Its server, client, app and curator, and the tools that write its
assets (maps, Anki fixtures, icons), as they are; paths are fixed in
the commits that follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-22 18:11:02 +02:00

144 lines
4.8 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:halkyon_learn_curator/src/sources/krdict.dart';
import 'package:halkyon_learn_curator/src/sources/wiktionary.dart';
import 'package:halkyon_learn_curator/src/wordfreq.dart';
import 'package:test/test.dart';
void main() {
/// The fixture packed as KRDict packs its export: its first file.
List<int> export() => ZipEncoder().encodeBytes(
Archive()..add(
ArchiveFile.bytes(
'1_5000_20260919.json',
File('test/fixtures/krdict-sample.json').readAsBytesSync(),
),
),
);
Future<List<Map<String, Object?>>> converted({
WordFrequencies? frequencies,
WiktionaryKorean? korean,
}) async {
final (:version, :entries) = krdictExport(export());
return [
for (final line in await krdictDictionary(
entries,
sourceVersion: version,
frequencies: frequencies,
korean: korean,
).toList())
jsonDecode(line) as Map<String, Object?>,
];
}
test('an export of another shape is refused rather than written as a '
'dictionary of no words', () async {
for (final files in [
<ArchiveFile>[],
[ArchiveFile.bytes('1_5000_20260919.xml', utf8.encode('<xml/>'))],
[
ArchiveFile.bytes(
'1_5000_20260919.json',
utf8.encode(jsonEncode({'LexicalResource': <String, Object?>{}})),
),
],
]) {
final archive = Archive();
files.forEach(archive.add);
final zip = ZipEncoder().encodeBytes(archive);
final (version: _, :entries) = krdictExport(zip);
await expectLater(entries.toList(), throwsFormatException);
}
});
test("KRDict's words with their level, pronunciation where it differs from "
'the spelling, Hanja, forms and English senses', () async {
final [header, ...entries] = await converted();
expect(header['source'], 'krdict');
expect(header['language'], 'ko');
expect(header['sourceVersion'], '2026-09-19');
expect(
[
for (final credit in header['credits']! as List)
(credit as Map)['name'],
],
['한국어기초사전 (Basic Korean Dictionary)'],
);
final school = entries.singleWhere((entry) => entry['headword'] == '학교');
expect(school['id'], '73276');
expect(school['reading'], '학꾜');
expect(school['labels'], ['level:beginner']);
expect(school['keys'], containsAll(['학교', '學校', 'school']));
const definition =
'An institution that educates students according to a specific '
'purpose, curriculum, system, etc.';
expect(school['entry'], {
'pos': 'noun',
'hanja': ['學校'],
'senses': [
{
'glosses': ['school'],
'info': [definition],
},
],
});
final read = entries.singleWhere((entry) => entry['headword'] == '읽다');
expect(read['reading'], '익따');
expect(read['textKeys'], containsAll(['읽다', '읽어', '읽고']));
expect(
(read['entry']! as Map)['inflections'],
contains(equals({'text': '읽어'})),
);
final together = entries.where((entry) => entry['headword'] == '같이');
expect(
[for (final entry in together) (entry['entry']! as Map)['pos']],
['particle', 'adv'],
);
});
test('an idiom or a proverb is an entry of its own, never found in running '
'text, with an id told apart from its word', () async {
final [_, ...entries] = await converted();
final proverb = entries.singleWhere(
(entry) => entry['headword'] == '가는 날이 장날',
);
expect(proverb['id'], '27500:가는 날이 장날');
expect((proverb['entry']! as Map)['pos'], 'proverb');
expect(proverb.containsKey('textKeys'), isFalse);
expect(
entries.singleWhere((entry) => entry['headword'] == '가다')['id'],
'27500',
);
});
test("Wiktionary's forms find a word in text; a verb rarely written as "
'the dictionary writes it ranks by its most used form', () async {
final korean = WiktionaryKorean()..forms['가다'] = {'가요', '갔어요', '갑니다'};
final [_, ...entries] = await converted(
korean: korean,
frequencies: WordFrequencies(const {'갔어요': 50}),
);
final go = entries.singleWhere((entry) => entry['headword'] == '가다');
expect(go['textKeys'], containsAll(['가다', '가요', '갔어요']));
expect(go['labels'], contains('common'));
expect(go['rank'], 100000 + 100);
});
test("the export's JSON download is the third its page offers", () {
expect(
krdictJsonDownloadOf('''
<button onclick="batchDownload('215')">엑셀</button>
<button onclick="batchDownload('216')">XML</button>
<button onclick="batchDownload('217')">Json</button>'''),
'217',
);
expect(krdictJsonDownloadOf("batchDownload('0')"), isNull);
});
}