import 'dart:io'; import 'package:clock/clock.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:halkyon_learn_flutter/src/feature/dictionary/data/device_dictionaries.dart'; import 'package:halkyon_learn_flutter/src/feature/dictionary/data/device_dictionary_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/dictionary/data/japanese_analyser.dart'; import 'package:halkyon_learn_flutter/src/feature/dictionary/model/device_dictionary.dart'; import 'package:halkyon_learn_flutter/src/feature/dictionary/model/sentence_words.dart'; /// An instance offering [offered] for devices, each file's bytes by its /// address in [bytes]; offering none by default. class FakeDeviceDictionaryGateway({ var List offered = const [], final Map> bytes = const {}, }) implements DeviceDictionaryGateway { /// The addresses downloaded, in order. final downloaded = []; /// How many times the files were asked for. int asked = 0; /// Thrown by [files] while set: the instance can't be reached. Exception? fail; @override Future> files(String language) async { asked++; if (fail case final error?) { throw error; } return language == 'ja' ? offered : const []; } @override Stream> download(Uri url) async* { downloaded.add(url); final all = bytes[url] ?? (throw HttpException('Not found', uri: url)); // In chunks, as a download comes. for (var at = 0; at < all.length; at += 4096) { yield all.sublist(at, at + 4096 > all.length ? all.length : at + 4096); } } } /// Cuts each sentence into the parts [cut] gives, and looks a stretch up /// as [stretch] gives it; records what it was asked. class FakeJapaneseAnalyser({ final List Function(String sentence)? cut, final SentencePart Function(String text, int start)? stretch, }) implements JapaneseAnalyser { /// The sentences asked for, each call's. final asked = >[]; /// The folders and databases asked to read. final read = []; @override Future>> wordsOf({ required String ipadic, required String database, required List sentences, }) async { asked.add(sentences); read ..add(ipadic) ..add(database); return [ for (final sentence in sentences) cut?.call(sentence) ?? [SentencePart(text: sentence, start: 0)], ]; } @override Future wordOfStretch({ required String database, required String text, required int start, }) async { read.add(database); return stretch?.call(text, start) ?? SentencePart(text: text, start: start); } } /// Dictionaries on a device that holds none, nor is offered any: lines are /// cut through the instance. DeviceDictionaries noDeviceDictionaries() => DeviceDictionaries( FakeDeviceDictionaryGateway(), FakeJapaneseAnalyser(), folder: Directory('${Directory.systemTemp.path}/halkyon-no-dictionaries'), ); /// The files an instance offers devices for Japanese, IPADIC and JMdict, as /// large as learn.halkyon.eu's would be: 34.0 MB together. List offeredJapanese() => [ DeviceFile( name: 'mecab-ipadic', url: Uri.parse('https://learn.example.org/device-dictionaries/ipadic'), sha256: '11' * 32, size: 13953908, ), DeviceFile( name: 'jmdict-en', url: Uri.parse('https://learn.example.org/device-dictionaries/jmdict'), sha256: '22' * 32, size: 21749430, ), ]; /// What a device holds once it set them up: 150.0 MB, checked just now. DeviceInstallation installedJapanese() => DeviceInstallation( ipadic: 'ipadic-1111111111111111', database: 'jmdict-en-2222222222222222.sqlite', source: 'jmdict', glossLanguage: 'en', files: {'mecab-ipadic': '11' * 32, 'jmdict-en': '22' * 32}, bytes: 150 * 1024 * 1024, installedAt: clock.now().toUtc(), checkedAt: clock.now().toUtc(), ); /// Dictionaries on a device, kept in a folder of their own that goes after /// the test, offered by [gateway] and cut with [analyser]. DeviceDictionaries deviceDictionariesIn( FakeDeviceDictionaryGateway gateway, { FakeJapaneseAnalyser? analyser, DeviceDictionaryStatus initial = const DeviceDictionaryStatus(), }) { final folder = Directory.systemTemp.createTempSync('dictionaries'); addTearDown(() => folder.deleteSync(recursive: true)); return DeviceDictionaries( gateway, analyser ?? FakeJapaneseAnalyser(), folder: folder, initial: initial, ); }