fix(watch): finalise correctif sync workoutHistory/exercise et distance live montre (#157)
This commit is contained in:
@ -87,6 +87,36 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('shows reps target on step manual score content', (tester) async {
|
||||
final client = _FakeNativeWatchBridgeClient();
|
||||
final viewModel = WatchSessionViewModel(nativeClient: client);
|
||||
|
||||
tester.view.devicePixelRatio = 1;
|
||||
tester.view.physicalSize = const Size(192, 192);
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: watchTheme(),
|
||||
home: WatchSessionScreen(viewModel: viewModel),
|
||||
),
|
||||
);
|
||||
|
||||
client.emitProjection(_manualScoreProjectionWithRepsTarget());
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.text('Répétitions : 10 · Cible : 8'), findsOneWidget);
|
||||
expect(find.text('SCORE'), findsOneWidget);
|
||||
expect(find.byTooltip('Ajouter'), findsOneWidget);
|
||||
expect(find.byTooltip('Valider l’étape'), findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
viewModel.dispose();
|
||||
});
|
||||
|
||||
testWidgets('hides set timer even when it is projected as dominant', (
|
||||
tester,
|
||||
) async {
|
||||
@ -683,22 +713,63 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('vibrates once when a countdown timer reaches zero', (
|
||||
testWidgets(
|
||||
'uses a strong pulse sequence when a countdown timer reaches zero',
|
||||
(tester) async {
|
||||
final hapticCalls = <MethodCall>[];
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, (call) async {
|
||||
if (call.method == 'HapticFeedback.vibrate') {
|
||||
hapticCalls.add(call);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
addTearDown(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, null);
|
||||
});
|
||||
|
||||
final client = _FakeNativeWatchBridgeClient();
|
||||
final viewModel = WatchSessionViewModel(nativeClient: client);
|
||||
|
||||
tester.view.devicePixelRatio = 1;
|
||||
tester.view.physicalSize = const Size(192, 192);
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: watchTheme(),
|
||||
home: WatchSessionScreen(viewModel: viewModel),
|
||||
),
|
||||
);
|
||||
|
||||
client.emitProjection(_countdownProjection(accumulatedMs: 29000));
|
||||
await tester.pump();
|
||||
expect(hapticCalls, isEmpty);
|
||||
|
||||
client.emitProjection(_countdownProjection(accumulatedMs: 30000));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 400));
|
||||
|
||||
expect(hapticCalls, hasLength(3));
|
||||
expect(
|
||||
hapticCalls.map((call) => call.arguments),
|
||||
everyElement('HapticFeedbackType.heavyImpact'),
|
||||
);
|
||||
|
||||
client.emitProjection(_countdownProjection(accumulatedMs: 30000));
|
||||
await tester.pump();
|
||||
expect(hapticCalls, hasLength(3));
|
||||
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
viewModel.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('expires an orphaned active projection after its TTL', (
|
||||
tester,
|
||||
) async {
|
||||
final hapticCalls = <MethodCall>[];
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, (call) async {
|
||||
if (call.method == 'HapticFeedback.vibrate') {
|
||||
hapticCalls.add(call);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
addTearDown(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, null);
|
||||
});
|
||||
|
||||
final client = _FakeNativeWatchBridgeClient();
|
||||
final viewModel = WatchSessionViewModel(nativeClient: client);
|
||||
|
||||
@ -714,20 +785,18 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
client.emitProjection(_countdownProjection(accumulatedMs: 29000));
|
||||
client.emitProjection(
|
||||
_expiringProjection(expiresIn: const Duration(seconds: 1)),
|
||||
);
|
||||
await tester.pump();
|
||||
expect(hapticCalls, isEmpty);
|
||||
expect(find.text('Squat jump'), findsOneWidget);
|
||||
|
||||
client.emitProjection(_countdownProjection(accumulatedMs: 30000));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
|
||||
expect(hapticCalls, hasLength(1));
|
||||
expect(hapticCalls.single.arguments, 'HapticFeedbackType.heavyImpact');
|
||||
|
||||
client.emitProjection(_countdownProjection(accumulatedMs: 30000));
|
||||
await tester.pump();
|
||||
expect(hapticCalls, hasLength(1));
|
||||
expect(viewModel.value.projection.phase, WatchSessionPhase.noActiveSession);
|
||||
expect(viewModel.value.connectionLost, isTrue);
|
||||
expect(client.invalidatedProjectionCount, 1);
|
||||
expect(find.text('Téléphone indisponible'), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
viewModel.dispose();
|
||||
@ -745,6 +814,7 @@ final class _FakeNativeWatchBridgeClient implements NativeWatchBridgeClient {
|
||||
|
||||
var resyncRequests = 0;
|
||||
var capabilityRefreshRequests = 0;
|
||||
var invalidatedProjectionCount = 0;
|
||||
final sentCommands = <WatchCommandEnvelope>[];
|
||||
|
||||
@override
|
||||
@ -782,6 +852,11 @@ final class _FakeNativeWatchBridgeClient implements NativeWatchBridgeClient {
|
||||
capabilityRefreshRequests += 1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> invalidateActiveProjection() async {
|
||||
invalidatedProjectionCount += 1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> requestResync() async {
|
||||
resyncRequests += 1;
|
||||
@ -794,10 +869,12 @@ final class _FakeNativeWatchBridgeClient implements NativeWatchBridgeClient {
|
||||
}
|
||||
|
||||
WatchSessionProjection _runningProjection() {
|
||||
final projectedAt = DateTime.utc(2026, 7, 27, 10).millisecondsSinceEpoch;
|
||||
return WatchSessionProjection(
|
||||
deviceSessionId: 'session-1',
|
||||
revision: 1,
|
||||
projectedAtEpochMs: DateTime.utc(2026, 7, 27, 10).millisecondsSinceEpoch,
|
||||
projectedAtEpochMs: projectedAt,
|
||||
expiresAtEpochMs: projectedAt + const Duration(seconds: 12).inMilliseconds,
|
||||
phase: WatchSessionPhase.running,
|
||||
phoneReachable: true,
|
||||
seriesIndex: 2,
|
||||
@ -820,6 +897,23 @@ WatchSessionProjection _runningProjection() {
|
||||
);
|
||||
}
|
||||
|
||||
WatchSessionProjection _expiringProjection({required Duration expiresIn}) {
|
||||
final nowMs = DateTime.now().toUtc().millisecondsSinceEpoch;
|
||||
return WatchSessionProjection(
|
||||
deviceSessionId: 'session-1',
|
||||
revision: 99,
|
||||
projectedAtEpochMs: nowMs,
|
||||
expiresAtEpochMs: nowMs + expiresIn.inMilliseconds,
|
||||
phase: WatchSessionPhase.running,
|
||||
phoneReachable: true,
|
||||
seriesIndex: 1,
|
||||
seriesTotal: 3,
|
||||
exerciseName: 'Squat jump',
|
||||
statusLabel: 'Chrono étape',
|
||||
primaryAction: WatchPrimaryAction.pauseSession,
|
||||
);
|
||||
}
|
||||
|
||||
WatchSessionProjection _noSessionStartProjection({bool phoneReachable = true}) {
|
||||
return WatchSessionProjection(
|
||||
deviceSessionId: '',
|
||||
@ -980,6 +1074,29 @@ WatchSessionProjection _manualScoreProjectionWithTimer() {
|
||||
);
|
||||
}
|
||||
|
||||
WatchSessionProjection _manualScoreProjectionWithRepsTarget() {
|
||||
return WatchSessionProjection(
|
||||
deviceSessionId: 'session-1',
|
||||
revision: 4,
|
||||
projectedAtEpochMs: DateTime.utc(2026, 7, 27, 10).millisecondsSinceEpoch,
|
||||
phase: WatchSessionPhase.running,
|
||||
phoneReachable: true,
|
||||
seriesIndex: 1,
|
||||
seriesTotal: 3,
|
||||
exerciseName: 'Pompes tempo',
|
||||
stepName: 'Score libre',
|
||||
statusLabel: 'Score manuel',
|
||||
primaryAction: WatchPrimaryAction.pauseSession,
|
||||
hasManualScore: true,
|
||||
currentManualScoreValue: 3,
|
||||
canDecrementScore: true,
|
||||
manualScoreTargetValue: 8,
|
||||
manualScoreTargetLabel: 'Cible',
|
||||
manualScoreRepsTargetValue: 10,
|
||||
manualScoreScope: WatchManualScoreScope.step,
|
||||
);
|
||||
}
|
||||
|
||||
WatchSessionProjection _restProjection() {
|
||||
return WatchSessionProjection(
|
||||
deviceSessionId: 'session-1',
|
||||
|
||||
Reference in New Issue
Block a user