import 'dart:io'; import 'dart:typed_data'; import 'package:halkyon_learn_flutter/src/core/asset/data/asset_file_cache.dart'; import 'package:halkyon_learn_flutter/src/core/asset/data/asset_gateway.dart'; import 'package:halkyon_learn_flutter/src/core/asset/data/asset_repository.dart'; import 'package:halkyon_learn_flutter/src/core/asset/data/asset_server_gateway.dart'; import 'package:halkyon_learn_flutter/src/core/asset/data/sound_player.dart'; import 'package:halkyon_learn_flutter/src/core/dependencies/dependencies.dart'; import 'package:halkyon_learn_flutter/src/core/dependencies/person_dependencies.dart'; import 'package:halkyon_learn_flutter/src/core/device_settings/data/device_settings_store.dart'; import 'package:halkyon_learn_flutter/src/core/device_settings/model/device_settings.dart'; import 'package:halkyon_learn_flutter/src/core/files/file_picker.dart'; import 'package:halkyon_learn_flutter/src/core/files/file_saver.dart'; import 'package:halkyon_learn_flutter/src/core/sync/pending_changes_store.dart'; import 'package:halkyon_learn_flutter/src/core/sync/sync_service.dart'; import 'package:halkyon_learn_flutter/src/core/version/app_version.dart'; import 'package:halkyon_learn_flutter/src/core/video/media_players.dart'; import 'package:halkyon_learn_flutter/src/core/video/youtube_videos.dart'; import 'package:halkyon_learn_flutter/src/feature/about/data/about_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/about/data/about_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/anki/data/anki_package.dart'; import 'package:halkyon_learn_flutter/src/feature/anki/data/anki_package_reader.dart'; import 'package:halkyon_learn_flutter/src/feature/archive/data/archive_reader.dart'; import 'package:halkyon_learn_flutter/src/feature/archive/model/import_archive.dart'; import 'package:halkyon_learn_flutter/src/feature/assistant/data/assistant_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/assistant/data/assistant_hand_off.dart'; import 'package:halkyon_learn_flutter/src/feature/assistant/data/assistant_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/assistant/data/web_search.dart'; import 'package:halkyon_learn_flutter/src/feature/auth/data/auth_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/auth/data/auth_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/auth/data/sign_in_addresses.dart'; import 'package:halkyon_learn_flutter/src/feature/auth/data/signed_out_mark.dart'; import 'package:halkyon_learn_flutter/src/feature/card/data/card_deletions.dart'; import 'package:halkyon_learn_flutter/src/feature/card/data/card_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/dictionary/data/device_dictionaries.dart'; import 'package:halkyon_learn_flutter/src/feature/dictionary/data/dictionary_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/dictionary/data/dictionary_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/discussion/data/discussion_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/discussion/data/discussion_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/explore/data/copy_updater.dart'; import 'package:halkyon_learn_flutter/src/feature/explore/data/explore_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/explore/data/explore_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/explore/data/release_copier.dart'; import 'package:halkyon_learn_flutter/src/feature/export/data/export_archive.dart'; import 'package:halkyon_learn_flutter/src/feature/export/data/studyset_markdown.dart'; import 'package:halkyon_learn_flutter/src/feature/feedback/data/feedback_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/feedback/data/feedback_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/geo_map/data/geo_map_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/jellyfin/data/jellyfin_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/jellyfin/data/jellyfin_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/jellyfin/data/jellyfin_store.dart'; import 'package:halkyon_learn_flutter/src/feature/jellyfin/data/jellyfin_suggestion.dart'; import 'package:halkyon_learn_flutter/src/feature/kanji/data/stroke_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_deletions.dart'; import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/learning_path/data/path_answer_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/learning_path/data/path_progress_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/note/data/note_deletions.dart'; import 'package:halkyon_learn_flutter/src/feature/note/data/note_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/note/data/note_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/pdf_source/data/pdf_deletions.dart'; import 'package:halkyon_learn_flutter/src/feature/pdf_source/data/pdf_engine.dart'; import 'package:halkyon_learn_flutter/src/feature/pdf_source/data/pdf_source_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/pdf_source/data/pdf_source_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/pomodoro/data/pomodoro_platform.dart'; import 'package:halkyon_learn_flutter/src/feature/pomodoro/data/pomodoro_run_store.dart'; import 'package:halkyon_learn_flutter/src/feature/profile/data/profile_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/profile/data/profile_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/publish/data/publish_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/publish/data/publish_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/publish/data/published_store.dart'; import 'package:halkyon_learn_flutter/src/feature/settings/data/settings_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/settings/data/settings_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/study/data/review_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/study/data/review_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_deletions.dart'; import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_progress_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_tabs.dart'; import 'package:halkyon_learn_flutter/src/feature/tag/data/tag_gateway.dart'; import 'package:halkyon_learn_flutter/src/feature/tag/data/tag_repository.dart'; import 'fake_about_gateway.dart'; import 'fake_asset_gateway.dart'; import 'fake_asset_server_gateway.dart'; import 'fake_assistant_gateway.dart'; import 'fake_auth_gateway.dart'; import 'fake_card_gateway.dart'; import 'fake_device_dictionaries.dart'; import 'fake_dictionary_gateway.dart'; import 'fake_discussion_gateway.dart'; import 'fake_explore_gateway.dart'; import 'fake_feedback_gateways.dart'; import 'fake_file_picker.dart'; import 'fake_file_saver.dart'; import 'fake_jellyfin.dart'; import 'fake_learning_path_gateway.dart'; import 'fake_note_gateway.dart'; import 'fake_path_answer_gateway.dart'; import 'fake_pdf_engine.dart'; import 'fake_pdf_source_gateway.dart'; import 'fake_pomodoro_platform.dart'; import 'fake_profile_gateway.dart'; import 'fake_publish_gateway.dart'; import 'fake_review_gateway.dart'; import 'fake_settings_gateway.dart'; import 'fake_sound_player.dart'; import 'fake_studyset_gateway.dart'; import 'fake_tag_gateway.dart'; import 'fake_youtube_videos.dart'; import 'memory_asset_file_cache.dart'; import 'memory_device_settings_store.dart'; import 'memory_pomodoro_run_store.dart'; import 'memory_published_store.dart'; import 'memory_sign_in_addresses.dart'; import 'memory_signed_out_mark.dart'; import 'memory_studyset_tab_store.dart'; /// The app's dependencies with fakes instead of the server and the device's /// database; each test passes in only the fakes it looks at. /// /// Every person gets [studysetGateway], [cardGateway], [noteGateway], /// [pathGateway], [answerGateway], [tagGateway], [settingsGateway] and the /// asset fakes (or fresh fakes of their own), /// unless [openPerson] replaces how a person's dependencies are opened. /// /// The device starts with [deviceSettings], kept in memory unless a test /// passes the [deviceSettingsStore] to look at. /// /// A person's sync runs [syncOnce] (by default, going through without doing /// anything): once when their data opens if a test passes it, and whenever /// the person asks; never on timers, which a widget test would find pending. Dependencies testDependencies({ AuthGateway? authGateway, SignedOutMark? signedOut, SignInAddresses? signInAddresses, StudysetGateway? studysetGateway, CardGateway? cardGateway, NoteGateway? noteGateway, LearningPathGateway? pathGateway, PathAnswerGateway? answerGateway, PdfSourceGateway? pdfGateway, ReviewGateway? reviewGateway, TagGateway? tagGateway, GeoMapRepository? maps, StrokeRepository? strokes, SoundPlayer? sounds, FilePicker? files, FileSaver? fileSaver, YoutubeVideos? videos, PdfEngine? pdfEngine, SettingsGateway? settingsGateway, AssetGateway? assetGateway, AssetServerGateway? assetServer, AssetFileCache? assetFiles, AboutGateway? aboutGateway, DictionaryGateway? dictionaryGateway, DeviceDictionaries? deviceDictionaries, DeviceSettingsStore? deviceSettingsStore, DeviceSettings deviceSettings = const DeviceSettings(), AppVersion appVersion = const AppVersion(version: '1.0.0', build: '1'), PomodoroPlatform? pomodoro, PomodoroRunStore? pomodoroRuns, ProfileGateway? profileGateway, PublishGateway? publishGateway, PublishedStore? publishedStore, StudysetTabStore? studysetTabStore, ExploreGateway? exploreGateway, FeedbackWriteGateway? feedbackWriteGateway, FeedbackServerGateway? feedbackServerGateway, DiscussionGateway? discussionGateway, SyncOnce? syncOnce, PendingChangesStore? pendingChanges, // A device that synced before, as most are; false for one whose first // sync is still to go through. bool syncedBefore = true, OpenPerson? openPerson, AssistantHandOff? assistantHandOff, WebSearch? webSearch, AssistantGateway? assistantGateway, JellyfinGateway? jellyfinGateway, JellyfinStore? jellyfinStore, JellyfinSuggestion? jellyfinSuggestion, MediaPlayers? mediaPlayers, }) { final device = deviceDictionaries ?? noDeviceDictionaries(); return Dependencies( serverName: 'learn.example.org', auth: AuthRepository( authGateway ?? FakeAuthGateway(), signInAddresses ?? MemorySignInAddresses(), ), signedOut: signedOut ?? MemorySignedOutMark(), about: AboutRepository(aboutGateway ?? FakeAboutGateway()), dictionaries: DictionaryRepository( dictionaryGateway ?? FakeDictionaryGateway(), device, ), deviceDictionaries: device, maps: maps ?? GeoMapRepository(), strokes: strokes ?? StrokeRepository(), sounds: sounds ?? FakeSoundPlayer(), files: files ?? FakeFilePicker(), fileSaver: fileSaver ?? FakeFileSaver(), videos: videos ?? FakeYoutubeVideos(), pdfEngine: pdfEngine ?? FakePdfEngine(), ankiPackages: InlineAnkiPackageReader(), archives: InlineArchiveReader(), exportArchiver: InlineExportArchiver(), deviceSettingsStore: deviceSettingsStore ?? MemoryDeviceSettingsStore(deviceSettings), deviceSettings: deviceSettings, appVersion: appVersion, pomodoro: pomodoro ?? FakePomodoroPlatform(), assistantHandOff: assistantHandOff ?? AssistantHandOff(open: (url) async => true), webSearch: webSearch ?? WebSearch( open: (url) async => true, deviceSearch: (query) async => false, ), assistant: AssistantRepository(assistantGateway ?? FakeAssistantGateway()), jellyfin: JellyfinRepository( (device) => jellyfinGateway ?? FakeJellyfinGateway(), jellyfinStore ?? MemoryJellyfinStore(), deviceName: 'Test', appVersion: appVersion.version, ), jellyfinSuggestion: jellyfinSuggestion ?? JellyfinSuggestion(() async => null), mediaPlayers: mediaPlayers ?? FakeMediaPlayers(), openPerson: openPerson ?? (person) async { final sync = SyncService( syncOnce ?? ({required onMerge}) async {}, pendingChanges: pendingChanges, syncedBefore: syncedBefore, ); if (syncOnce != null) { sync.syncNow(); } final studysets = StudysetRepository( studysetGateway ?? FakeStudysetGateway(), ); final cards = CardRepository(cardGateway ?? FakeCardGateway()); final notes = NoteRepository(noteGateway ?? FakeNoteGateway()); final paths = LearningPathRepository( pathGateway ?? FakeLearningPathGateway(), ); final pathProgress = PathProgressRepository( answerGateway ?? FakePathAnswerGateway(), ); final tags = TagRepository( tagGateway ?? FakeTagGateway(), cardChanges: cards.changes, noteChanges: notes.changes, pathChanges: paths.changes, ); final settings = SettingsRepository( settingsGateway ?? FakeSettingsGateway(), ); final reviews = ReviewRepository( reviewGateway ?? FakeReviewGateway(), ); final assets = AssetRepository( assetGateway ?? FakeAssetGateway(), assetServer ?? FakeAssetServerGateway(), assetFiles ?? MemoryAssetFileCache(), ); final pdfSources = PdfSourceRepository( pdfGateway ?? FakePdfSourceGateway(), assets, ); final deletions = StudysetDeletions( studysets, cards, notes, paths, pdfSources, ); final studysetProgress = StudysetProgressRepository( studysets: studysets, cards: cards, notes: notes, paths: paths, pdfs: pdfSources, reviews: reviews, settings: settings, ); final noteDeletions = NoteDeletions(notes); final cardDeletions = CardDeletions(cards); final pathDeletions = LearningPathDeletions(paths); final stepDeletions = StepDeletions(paths); final pdfDeletions = PdfDeletions(pdfSources); final studysetTabs = StudysetTabs( studysetTabStore ?? MemoryStudysetTabStore(), ); await studysetTabs.open(); return PersonDependencies( sync: sync, studysets: studysets, cards: cards, notes: notes, paths: paths, pathProgress: pathProgress, pdfSources: pdfSources, reviews: reviews, studysetProgress: studysetProgress, tags: tags, settings: settings, assets: assets, deletions: deletions, noteDeletions: noteDeletions, cardDeletions: cardDeletions, pathDeletions: pathDeletions, stepDeletions: stepDeletions, pdfDeletions: pdfDeletions, pomodoroRuns: pomodoroRuns ?? MemoryPomodoroRunStore(), studysetTabs: studysetTabs, // The picture as it is: drawing it anew is real work a widget // test's fake clock can't wait for (avatar_image_test.dart has it). profile: ProfileRepository( profileGateway ?? FakeProfileGateway(), drawAvatar: (picture) async => picture, ), publishing: PublishRepository( publishGateway ?? FakePublishGateway(), publishedStore ?? MemoryPublishedStore(), ), explore: ExploreRepository( exploreGateway ?? FakeExploreGateway(), ReleaseCopier( studysets, cards, notes, paths, pdfSources, tags, assets, ), CopyUpdater( studysets, cards, notes, paths, pdfSources, tags, assets, ), studysets, ), feedback: FeedbackRepository( feedbackWriteGateway ?? FakeFeedbackWriteGateway(), feedbackServerGateway ?? FakeFeedbackServerGateway(), cards, notes, paths, assets, ), discussions: DiscussionRepository( discussionGateway ?? FakeDiscussionGateway(), ), onDispose: () async { await deletions.dispose(); await noteDeletions.dispose(); await cardDeletions.dispose(); await pathDeletions.dispose(); await stepDeletions.dispose(); await pdfDeletions.dispose(); await studysetProgress.dispose(); await studysets.dispose(); await tags.dispose(); await settings.dispose(); await reviews.dispose(); await cards.dispose(); await notes.dispose(); await paths.dispose(); await pathProgress.dispose(); await pdfSources.dispose(); await assets.dispose(); await sync.dispose(); }, ); }, ); } /// Reads Anki packages where it is asked to: a widget test's fake clock /// can't wait for another isolate. class InlineAnkiPackageReader() implements AnkiPackageReader { @override Future read(Uint8List bytes) async => readAnkiPackage(bytes, temp: Directory.systemTemp); } /// Reads archives where it is asked to, as [InlineAnkiPackageReader] reads. class InlineArchiveReader() implements ArchiveReader { @override Future read(Uint8List bytes) async => readArchive(bytes); } /// Packs exports where it is asked to, as [InlineAnkiPackageReader] reads. class InlineExportArchiver() implements ExportArchiver { @override Future zip(List files) async => zipExport(files); }