import 'dart:convert'; import 'dart:io'; import 'package:halkyon_learn_curator/src/sources/jlpt.dart'; import 'package:halkyon_learn_curator/src/sources/jmdict_xml.dart'; import 'package:test/test.dart'; import 'package:xml/xml.dart'; /// [text] as a stream of pieces [size] characters long. Stream pieces(String text, int size) => Stream.fromIterable([ for (var at = 0; at < text.length; at += size) text.substring(at, (at + size).clamp(0, text.length)), ]); void main() { late String xml; late List> lines; setUpAll(() async { xml = await File('test/fixtures/jmdict-e-examp-sample.xml').readAsString(); final jlpt = await readJlptLevels(Directory('test/fixtures/jlpt')); lines = [ for (final line in await jmdictXmlDictionary( Stream.value(xml), jlpt: jlpt, examples: true, ).toList()) jsonDecode(line) as Map, ]; }); Map entry(String id) => lines.skip(1).firstWhere((line) => line['id'] == id); List> senses(String id) => [ for (final sense in (entry(id)['entry']! as Map)['senses'] as List) sense as Map, ]; test("the header names EDRDG's JMdict and the day it was made, and " 'credits the sentences and the levels', () { final header = lines.first; expect(header['source'], 'jmdict'); expect(header['kind'], 'words'); expect(header['sourceVersion'], '2026-10-09'); expect( [ for (final credit in header['credits']! as List) (credit as Map)['name'], ], ['JMdict', 'JLPT vocabulary lists', 'Tatoeba'], ); expect(lines, hasLength(6)); }); test('a word as from the JSON: its headword and reading, labels, keys, ' "and its senses with JMdict's codes as codes", () { final taberu = entry('1358280'); expect((taberu['headword'], taberu['reading']), ('食べる', 'たべる')); expect(taberu['labels'], ['common', 'jlpt:n5']); expect(taberu['keys'], containsAll(['食べる', 'たべる', 'taberu', 'eat'])); expect(senses('1358280').first['pos'], ['v1', 'vt']); }); test("a sense's sentences from Tatoeba, the word as it is in each", () { expect((senses('1358280').first['examples']! as List).first, { 'text': '食べる', 'ja': 'もっと果物を食べるべきです。', 'en': 'You should eat more fruit.', 'tatoeba': '193344', }); }); test('a word usually written in kana has a kana headword; a sense kept to ' 'one reading says so', () { final sono = entry('1006830'); expect(sono['headword'], 'その'); expect(senses('1006830').first['misc'], ['uk']); expect(senses('1006830').last['readings'], ['その']); }); test('the reading is the one restricted to the headword', () { expect(entry('1000110')['reading'], 'シーディープレーヤー'); }); test( 'entities cut between two pieces of the stream are read whole', () async { final again = [ for (final line in await jmdictXmlDictionary( pieces(xml, 7), examples: true, ).toList()) jsonDecode(line) as Map, ]; expect( [for (final line in again.skip(1)) line['entry']], [for (final line in lines.skip(1)) line['entry']], ); }, ); test('a sense without parts of speech has those of the sense before', () { final word = jmdictWordOf( XmlDocument.parse(''' 1あ none two advthree''').rootElement, ); expect( [ for (final sense in word['sense']! as List) (sense as Map)['partOfSpeech'], ], [ ['n'], ['n'], ['adv'], ], ); }); }