import 'dart:convert'; import 'dart:io'; import 'package:halkyon_learn_curator/src/sources/kanjidic.dart'; import 'package:test/test.dart'; void main() { late List> 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, ]; }); Map entry(String character) => lines.skip(1).firstWhere((line) => line['id'] == character); Map fields(String character) => entry(character)['entry']! as Map; 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)); }); }