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>
125 lines
4.2 KiB
Dart
125 lines
4.2 KiB
Dart
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));
|
|
});
|
|
}
|