Text-free sync settings via ssui 0.7.0
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run

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 <noreply@anthropic.com>
This commit is contained in:
Krzysztof Rudnicki 2026-11-11 01:32:02 +01:00
parent edf6969949
commit 203e61d6ac
9 changed files with 81 additions and 19 deletions

View File

@ -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);
}

View File

@ -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:

View File

@ -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

View File

@ -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
});

View File

@ -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<void>.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<void>.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);
});
});

View File

@ -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<FakeNoteRepository> 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,

View File

@ -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<TextFreeMaterialLocalizations>(),
);
expect(delegate.shouldReload(delegate), isFalse);
});
}

View File

@ -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,

View File

@ -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 = <Object?>[];
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 {