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
125
apps/learn/halkyon_learn_curator/test/kanjidic_test.dart
Normal file
125
apps/learn/halkyon_learn_curator/test/kanjidic_test.dart
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:halkyon_learn_curator/src/sources/kanjidic.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
late List<Map<String, Object?>> lines;
|
||||
|
||||
setUpAll(() async {
|
||||
final jlpt = await readKanjiJlpt(
|
||||
File('test/fixtures/kanji-data-sample.json'),
|
||||
);
|
||||
lines = [
|
||||
for (final line in kanjidicDictionary(
|
||||
await File('test/fixtures/kanjidic2-sample.xml').readAsString(),
|
||||
jlpt: jlpt,
|
||||
))
|
||||
jsonDecode(line) as Map<String, Object?>,
|
||||
];
|
||||
});
|
||||
|
||||
Map<String, Object?> entry(String character) =>
|
||||
lines.skip(1).firstWhere((line) => line['id'] == character);
|
||||
Map<String, Object?> fields(String character) =>
|
||||
entry(character)['entry']! as Map<String, Object?>;
|
||||
|
||||
test('the header says the file holds characters, and whose they are', () {
|
||||
final header = lines.first;
|
||||
expect(header['source'], 'kanjidic');
|
||||
expect(header['kind'], 'characters');
|
||||
expect(header['sourceVersion'], '2026-282');
|
||||
expect((header['language'], header['glossLanguage']), ('ja', 'en'));
|
||||
expect(header['license'], 'CC-BY-SA-4.0');
|
||||
expect(
|
||||
[
|
||||
for (final credit in header['credits']! as List)
|
||||
(credit as Map)['name'],
|
||||
],
|
||||
['KANJIDIC2', 'JLPT kanji lists'],
|
||||
);
|
||||
});
|
||||
|
||||
test('a kanji: its readings, English meanings only, grade, strokes, '
|
||||
'frequency and JLPT level', () {
|
||||
final eat = entry('食');
|
||||
expect(eat['headword'], '食');
|
||||
expect(eat['reading'], isNull);
|
||||
expect(eat['labels'], ['jlpt:n5', 'grade:2', 'radical:184']);
|
||||
expect(fields('食'), {
|
||||
'meanings': ['eat', 'food'],
|
||||
'on': ['ショク', 'ジキ'],
|
||||
'kun': ['く.う', 'く.らう', 'た.べる', 'は.む'],
|
||||
'grade': 2,
|
||||
'strokes': 9,
|
||||
'frequency': 328,
|
||||
'jlpt': 5,
|
||||
});
|
||||
expect(fields('一')['meanings'], isNot(contains('un')));
|
||||
});
|
||||
|
||||
test('a search finds a kanji by its on readings in either kana and in '
|
||||
'romaji, by whole kun readings and by its meanings; the kanji part '
|
||||
'of a kun reading only loosely', () {
|
||||
final eat = entry('食');
|
||||
expect(
|
||||
eat['keys'],
|
||||
containsAll([
|
||||
'食',
|
||||
'ショク',
|
||||
'しょく',
|
||||
'shoku',
|
||||
'たべる',
|
||||
'taberu',
|
||||
'くらう',
|
||||
'eat',
|
||||
'food',
|
||||
]),
|
||||
);
|
||||
expect(eat['keys'], isNot(contains('た')));
|
||||
expect(eat['looseKeys'], containsAll(['た', 'ta', 'く', 'ku']));
|
||||
// ひと- and ひと.つ: the suffix's dash goes, its kanji part is whole.
|
||||
expect(entry('一')['keys'], containsAll(['ひと', 'ひとつ', 'hitotsu']));
|
||||
expect(entry('一')['looseKeys'], isNot(contains('ひと')));
|
||||
});
|
||||
|
||||
test('levels, grades and radicals label the kanji; level and grade rank '
|
||||
'them: JLPT level, then school grade, then frequency', () {
|
||||
expect(entry('亜')['labels'], ['jlpt:n1', 'grade:secondary', 'radical:7']);
|
||||
// KANJIDIC2's classical radical, not Nelson's.
|
||||
expect(entry('唖')['labels'], ['radical:30']);
|
||||
final ranks = [
|
||||
for (final kanji in ['一', '食', '亜', '唖']) entry(kanji)['rank']! as int,
|
||||
];
|
||||
expect(ranks, orderedEquals([...ranks]..sort()));
|
||||
});
|
||||
|
||||
test('a character with no Japanese reading and no English meaning is '
|
||||
'left out', () {
|
||||
expect(lines.skip(1).where((line) => line['id'] == '僲'), isEmpty);
|
||||
});
|
||||
|
||||
test('hiragana and katakana follow, the kana table each, before every '
|
||||
'kanji, found by their romaji', () {
|
||||
final kana = [
|
||||
for (final line in lines.skip(1))
|
||||
if ((line['labels']! as List).any(['hiragana', 'katakana'].contains))
|
||||
line,
|
||||
];
|
||||
expect(kana, hasLength(142));
|
||||
expect(kana.first['id'], 'あ');
|
||||
expect(kana[71]['id'], 'ア');
|
||||
expect(kana.last['id'], 'ポ');
|
||||
expect(entry('ア')['labels'], ['katakana']);
|
||||
expect(entry('ア')['keys'], containsAll(['ア', 'あ', 'a']));
|
||||
expect(fields('ぢ'), {'romaji': 'ji', 'script': 'hiragana'});
|
||||
// を is "wo" in the table, found by "o" too.
|
||||
expect(fields('を')['romaji'], 'wo');
|
||||
expect(entry('を')['keys'], containsAll(['wo', 'o']));
|
||||
final lastKana = kana
|
||||
.map((line) => line['rank']! as int)
|
||||
.reduce((a, b) => a > b ? a : b);
|
||||
expect(entry('食')['rank']! as int, greaterThan(lastKana));
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue