blob: b4d747f8d532bc8eced3539012bca19af1fd282e [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fuchsia_scenic_flutter/fuchsia_view.dart';
/// Since mock method call handlers can resolve quickly (before streams get to
/// report a new event to their listeners), it's helpful to have a
/// semaphore-like class to ensure that stream listeners get a chance to execute
/// before the next mock method call is handled.
class ReadersWriterLock {
int _numReaders = 0;
int _completedReaders = 0;
void registerReader() {
++_numReaders;
}
void readerFinished() {
++_completedReaders;
}
Future<void> lockExclusive() async {
// Drain the event loop until all readers have finished reading.
while (_completedReaders < _numReaders) {
await Future(() {});
}
_completedReaders = 0;
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('FocusState: current and next', () async {
// Because this test doesn't purely rely on Futures and relies on callbacks
// (the mock method call handler), create a completer that will finish when
// all of the focus states have been dispatched and received.
final completer = Completer();
final lock = ReadersWriterLock();
// A list of expected focus events.
final focuses = [false, true, true, false, true, false, true, true];
// The index of the current focus state of the test.
int focusIndex = 0;
FuchsiaViewsService.instance.platformViewChannel
.setMockMethodCallHandler((call) async {
expect(call.arguments, null);
if (call.method == 'View.focus.getNext') {
// Wait for streams to be ready before advancing the focus index.
await lock.lockExclusive();
if (++focusIndex < focuses.length) {
return focuses[focusIndex];
}
completer.complete();
} else if (call.method == 'View.focus.getCurrent') {
return focuses[focusIndex];
} else {
fail('Invalid method!');
}
});
void testStream(int targetFocusIndex) async {
// Defer our execution in the event loop until
// focusIndex == targetFocusIndex.
while (focusIndex < targetFocusIndex) {
await Future(() {});
}
lock.registerReader();
// Set the index at the correct initial position. We use this to keep
// track of which focus event we're at.
int index = focusIndex;
late StreamSubscription<bool> stream;
stream = FocusState.instance.stream().listen((focused) async {
// Ensure stream callbacks and the focus state position are consistent.
expect(index, focusIndex);
// Check if the focused value is accurate.
expect(focused, focuses[index]);
expect(await FocusState.instance.isFocused(), focused);
// Advance the stream index. If there are no more focus events, cancel
// and complete this stream.
if (++index >= focuses.length) {
await stream.cancel();
}
// Unblock the next focus state.
lock.readerFinished();
});
}
testStream(0);
testStream(3);
testStream(6);
return completer.future;
});
test('FocusState: requestFocus', () async {
late final MethodCall invokedMethod;
FuchsiaViewsService.instance.platformViewChannel
.setMockMethodCallHandler((call) {
invokedMethod = call;
return Future.value(0);
});
await FocusState.instance.requestFocus(42);
expect(invokedMethod.method, 'View.focus.request');
expect(invokedMethod.arguments, <String, dynamic>{'viewRef': 42});
});
}