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>
This commit is contained in:
parent
11abd9afce
commit
649499be23
1 changed files with 0 additions and 0 deletions
144
apps/learn/halkyon_learn_curator/test/krdict_test.dart
Normal file
144
apps/learn/halkyon_learn_curator/test/krdict_test.dart
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
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);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue