From 203e61d6ac64b31e37cf6eefa1070334fdfded11 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Wed, 11 Nov 2026 01:32:02 +0100 Subject: [PATCH] Text-free sync settings via ssui 0.7.0 Bump sync_settings_ui 0.7.0 and github_device_auth 0.4.0 (glyph + spoken label). Guard covers the Sync settings screen; degraded sync gets its own warning glyph; tooltip delegate and copy button tested. Co-Authored-By: Claude Opus 5.5 --- lib/ui/capture_screen_sync.dart | 2 +- pubspec.lock | 12 ++++---- pubspec.yaml | 6 ++-- test/github_mirror_device_flow_test.dart | 5 ++-- test/settings_screen_backup_test.dart | 15 +++++----- test/settings_screen_harness.dart | 3 ++ test/text_free_localizations_test.dart | 30 +++++++++++++++++++ test/text_free_screens_test.dart | 8 +++++ .../ui_images/cloud_images_settings_test.dart | 19 ++++++++++++ 9 files changed, 81 insertions(+), 19 deletions(-) create mode 100644 test/text_free_localizations_test.dart diff --git a/lib/ui/capture_screen_sync.dart b/lib/ui/capture_screen_sync.dart index 2d25507..09f9d19 100644 --- a/lib/ui/capture_screen_sync.dart +++ b/lib/ui/capture_screen_sync.dart @@ -115,7 +115,7 @@ extension _CaptureSyncBehaviour on _CaptureScreenState { final (icon, error) = !outcome.ok ? (Icons.sync_problem, true) : outcome.degraded - ? (Icons.cloud_off, true) + ? (Icons.warning_amber, true) : (Icons.cloud_done, false); showCue(context, icon, spoken, error: error); } diff --git a/pubspec.lock b/pubspec.lock index 725a092..acc5ad6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -399,11 +399,11 @@ packages: dependency: "direct main" description: path: github_device_auth - ref: "github_device_auth-v0.3.2" - resolved-ref: "4ccaba1bb3e495ef72d70c6a7e7d7ec98d755792" + ref: "github_device_auth-v0.4.0" + resolved-ref: "3084080d744c348a217727e26a6be838e7d5a4ff" url: "https://github.com/kuhyx/utils" source: git - version: "0.3.2" + version: "0.4.0" glob: dependency: transitive description: @@ -1006,11 +1006,11 @@ packages: dependency: "direct main" description: path: sync_settings_ui - ref: "sync_settings_ui-v0.6.3" - resolved-ref: eef7af4c86837ee82891493587ff3456297e5dea + ref: "sync_settings_ui-v0.7.0" + resolved-ref: "3084080d744c348a217727e26a6be838e7d5a4ff" url: "https://github.com/kuhyx/utils" source: git - version: "0.6.3" + version: "0.7.0" synchronized: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b2ead95..4b58b40 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.76+1 +version: 1.0.77+1 environment: sdk: ^3.12.2 @@ -59,7 +59,7 @@ dependencies: github_device_auth: git: url: https://github.com/kuhyx/utils - ref: github_device_auth-v0.3.2 + ref: github_device_auth-v0.4.0 path: github_device_auth # One-tap Google ID token via the framework AccountManager chooser (FOSS, # no Play Services sign-in SDK). Used by the sync backend in lib/sync/. @@ -97,7 +97,7 @@ dependencies: sync_settings_ui: git: url: https://github.com/kuhyx/utils - ref: sync_settings_ui-v0.6.3 + ref: sync_settings_ui-v0.7.0 path: sync_settings_ui url_launcher: 6.3.3 uuid: 4.6.0 diff --git a/test/github_mirror_device_flow_test.dart b/test/github_mirror_device_flow_test.dart index b997a87..1d32f70 100644 --- a/test/github_mirror_device_flow_test.dart +++ b/test/github_mirror_device_flow_test.dart @@ -203,7 +203,8 @@ void main() { await tester.pump(const Duration(milliseconds: 1)); // _delay(0) fires await tester.pump(); // token error throws → _error set - expect(find.textContaining('nope'), findsOneWidget); // error rendered + await tester.pump(const Duration(milliseconds: 300)); // dialog faded in + expect(spoken('nope'), findsOneWidget); // error rendered (spoken glyph) // Tap the "open on GitHub" action: copies the code and launches the URL. // _openPage awaits Clipboard.setData then the launcher's supportsMode + @@ -213,7 +214,7 @@ void main() { await tester.pumpAndSettle(); expect(launcher.launched, 'https://github.com/login/device'); - await tester.tap(find.text('Cancel')); + await tester.tap(find.bySemanticsLabel('Cancel')); await tester.pumpAndSettle(); // finish the dialog pop animation expect(find.text('WXYZ-1234'), findsNothing); // dialog dismissed }); diff --git a/test/settings_screen_backup_test.dart b/test/settings_screen_backup_test.dart index 919775c..0c56513 100644 --- a/test/settings_screen_backup_test.dart +++ b/test/settings_screen_backup_test.dart @@ -24,6 +24,7 @@ import 'fake_note_repository.dart'; import 'fake_secure_storage.dart'; import 'settings_screen_harness.dart'; +import 'text_free_guard.dart'; /// Stub file picker that returns a fixed in-memory file (no disk I/O, so the /// `_import` flow stays timer-free and deterministic under the widget tester). @@ -101,7 +102,7 @@ void main() { // _export does real file I/O on desktop, so drive it under runAsync. await tester.runAsync(() async { - await tester.tap(find.text('Export notes')); + await tester.tap(find.bySemanticsLabel('Export notes')); await Future.delayed(const Duration(milliseconds: 50)); }); await tester.pump(); @@ -120,12 +121,12 @@ void main() { await tester.pumpAndSettle(); await tester.runAsync(() async { - await tester.tap(find.text('Export notes')); + await tester.tap(find.bySemanticsLabel('Export notes')); await Future.delayed(const Duration(milliseconds: 50)); }); await tester.pump(); - expect(find.textContaining('Export failed'), findsOneWidget); + expect(spoken('Export failed'), findsOneWidget); }); testWidgets('Import notes reads the picked file and merges', ( @@ -152,7 +153,7 @@ void main() { await tester.tap(find.byIcon(Icons.sync)); await tester.pumpAndSettle(); - await tester.tap(find.text('Import notes')); + await tester.tap(find.bySemanticsLabel('Import notes')); await tester.pump(); // openFile resolves (in-memory) await tester.pump(); // read + parse + merge + setState @@ -168,7 +169,7 @@ void main() { await tester.tap(find.byIcon(Icons.sync)); await tester.pumpAndSettle(); - await tester.tap(find.text('Import notes')); + await tester.tap(find.bySemanticsLabel('Import notes')); await tester.pump(); await tester.pump(); @@ -186,11 +187,11 @@ void main() { await tester.tap(find.byIcon(Icons.sync)); await tester.pumpAndSettle(); - await tester.tap(find.text('Import notes')); + await tester.tap(find.bySemanticsLabel('Import notes')); await tester.pump(); await tester.pump(); - expect(find.textContaining('Import failed'), findsOneWidget); + expect(spoken('Import failed'), findsOneWidget); expect(await repo.listNotes(), isEmpty); }); }); diff --git a/test/settings_screen_harness.dart b/test/settings_screen_harness.dart index dbf3670..fd3b45b 100644 --- a/test/settings_screen_harness.dart +++ b/test/settings_screen_harness.dart @@ -26,6 +26,7 @@ import 'package:todo/sync/notes_markdown.dart'; import 'package:todo/sync/sync_settings.dart'; import 'package:todo/ui/github_mirror_screen.dart'; import 'package:todo/ui/settings_screen.dart'; +import 'package:todo/ui/text_free_localizations.dart'; import 'fake_note_repository.dart'; import 'fake_secure_storage.dart'; @@ -93,6 +94,8 @@ Future pumpSettings( addTearDown(repo.close); await tester.pumpWidget( MaterialApp( + // As in main.dart: Material's own button tooltips are blanked. + localizationsDelegates: kTextFreeLocalizations, home: SettingsScreen( initial: initial, repository: repo, diff --git a/test/text_free_localizations_test.dart b/test/text_free_localizations_test.dart new file mode 100644 index 0000000..307903b --- /dev/null +++ b/test/text_free_localizations_test.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/ui/text_free_localizations.dart'; + +void main() { + test('every button tooltip is blank', () { + const l = TextFreeMaterialLocalizations(); + expect([ + l.backButtonTooltip, + l.closeButtonTooltip, + l.openAppDrawerTooltip, + l.showMenuTooltip, + l.moreButtonTooltip, + l.deleteButtonTooltip, + ], everyElement(isEmpty)); + // Everything else is still the default English. + expect(l.okButtonLabel, const DefaultMaterialLocalizations().okButtonLabel); + }); + + test('the delegate loads them for English only, once', () async { + final delegate = kTextFreeLocalizations.single; + expect(delegate.isSupported(const Locale('en')), isTrue); + expect(delegate.isSupported(const Locale('pl')), isFalse); + expect( + await delegate.load(const Locale('en')), + isA(), + ); + expect(delegate.shouldReload(delegate), isFalse); + }); +} diff --git a/test/text_free_screens_test.dart b/test/text_free_screens_test.dart index 9d6c98c..d362063 100644 --- a/test/text_free_screens_test.dart +++ b/test/text_free_screens_test.dart @@ -83,6 +83,14 @@ void main() { expectNoChromeWords(t); }); + testWidgets('sync settings paint no words', (t) async { + await pumpSettings(t); + await t.tap(find.bySemanticsLabel('Sync settings: Firebase')); + await _frames(t); + await _frames(t); + expectNoChromeWords(t); + }); + testWidgets('GitHub sync and its test result paint no words', (t) async { await pumpMirror( t, diff --git a/test/ui_images/cloud_images_settings_test.dart b/test/ui_images/cloud_images_settings_test.dart index 539a2e9..ffeb8d0 100644 --- a/test/ui_images/cloud_images_settings_test.dart +++ b/test/ui_images/cloud_images_settings_test.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; @@ -121,6 +122,24 @@ void main() { findsOneWidget, ); expect(find.bySemanticsLabel('Copy: $kAddLoginCommand'), findsOneWidget); + + // The command is copied, not painted: the button puts it on the + // clipboard (stubbed channel; the test host has no clipboard plugin). + final copied = []; + final messenger = + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; + messenger.setMockMethodCallHandler(SystemChannels.platform, (call) async { + if (call.method == 'Clipboard.setData') copied.add(call.arguments); + return null; + }); + addTearDown( + () => messenger.setMockMethodCallHandler(SystemChannels.platform, null), + ); + await tester.tap(find.bySemanticsLabel('Copy: $kAddLoginCommand')); + await tester.pump(); + expect(copied, [ + {'text': kAddLoginCommand}, + ]); }); testWidgets('closing the screen mid-test is harmless', (tester) async {