blob: c48e2a61b11cd459463b951d3a4c7929fea81aca [file] [log] [blame]
// Copyright 2019 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 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fuchsia_logger/logger.dart';
import 'package:mockito/mockito.dart';
// ignore_for_file: implementation_imports
import 'package:simple_browser/src/blocs/tabs_bloc.dart';
import 'package:simple_browser/src/blocs/webpage_bloc.dart';
import 'package:simple_browser/src/models/tabs_action.dart';
import 'package:simple_browser/src/services/simple_browser_navigation_event_listener.dart';
import 'package:simple_browser/src/services/simple_browser_web_service.dart';
import 'package:simple_browser/src/widgets/tabs_widget.dart';
const _emptyTitle = 'NEW TAB';
void main() {
setupLogger(name: 'tabs_widget_test');
TabsBloc tabsBloc;
SimpleBrowserWebService mockWebService;
SimpleBrowserNavigationEventListener mockListener;
ValueNotifier titleNotifier = ValueNotifier<String>('');
setUp(() {
mockWebService = MockSimpleBrowserWebService();
mockListener = MockSimpleBrowserNavigationEventListener();
when(mockWebService.navigationEventListener)
.thenAnswer((_) => mockListener);
when(mockListener.pageTitleNotifier).thenAnswer((_) => titleNotifier);
tabsBloc = TabsBloc(
tabFactory: () => WebPageBloc(
webService: mockWebService,
),
disposeTab: (tab) => tab.dispose(),
);
});
testWidgets('Should create tab widgets when more than two tabs added.',
(WidgetTester tester) async {
// Initial state: Tabsbloc does not have any tabs.
expect(tabsBloc.tabs.length, 0);
await _setUpTabsWidget(tester, tabsBloc);
// Sees if there is no tab widgets on the screen.
expect(_findNewTabWidgets(), findsNothing,
reason: 'Expected no tab widgets when no tabs added.');
// Adds one tab and sees if there is a tab in the tabsBloc, but still no tab widgets
// on the screen.
await _addNTabsToTabsBloc(tester, tabsBloc, 1);
expect(_findNewTabWidgets(), findsNothing,
reason: 'Expected no tab widgets when only 1 tab added.');
// Adds one more tab and sees if there are two tabs in the tabsBloc.
await _addNTabsToTabsBloc(tester, tabsBloc, 1);
// Sees if there are two Expanded widgets for the tab widgets.
// A tabsBloc should be wrapped by an Expanded widget when the total widths of
// the currently displayed tabs is smaller than the browser width.
expect(find.byType(Expanded), findsNWidgets(2),
reason: 'Expected 2 Expanded widgets when 2 tabs added.');
// See if the tab widgets have the default title.
expect(_findNewTabWidgets(), findsNWidgets(2),
reason: '''Expected 2 tab widgets with the title, $_emptyTitle,
when 2 tabs added.''');
});
testWidgets(
'Should create tab widgets with a custom title when one is given.',
(WidgetTester tester) async {
// Gives a custom title, 'TAB_WIDGET_TEST'.
String expectedTitle = 'TAB_WIDGET_TEST';
when(mockListener.pageTitle).thenReturn(expectedTitle);
await _setUpTabsWidget(tester, tabsBloc);
// Adds two tabs since tab widgets are only created when there are more than one tab.
await _addNTabsToTabsBloc(tester, tabsBloc, 2);
// Sees if there are two tab widgets with the given title.
expect(find.text(expectedTitle), findsNWidgets(2),
reason: '''Expected 2 tab widgets with the title $expectedTitle
when 2 new tabs with it have been added.''');
});
testWidgets('''Should give the minimum width to the tab widgets
when the total sum of the tab widths is larger than the browser width.''',
(WidgetTester tester) async {
await _setUpTabsWidget(tester, tabsBloc);
// Adds seven tabs.
await _addNTabsToTabsBloc(tester, tabsBloc, 7);
// Sees if there are seven SizedBox widgets with the minimum width for the tab widgets.
// A tabsBloc should be wrapped by a SizedBox widget and have the minimum width
// when the total widths of the currently displayed tabs is larger than the browser width.
expect(_findMinTabWidgets(), findsNWidgets(7));
});
testWidgets('Should change the focus to it when an unfocused tab is tapped.',
(WidgetTester tester) async {
await _setUpTabsWidget(tester, tabsBloc);
await _addNTabsToTabsBloc(tester, tabsBloc, 3);
expect(tabsBloc.currentTabIdx, 2,
reason: 'Expected the 3rd tab widget was focused by default.');
final tabs = _findNewTabWidgets();
await tester.tap(tabs.at(0));
await tester.pumpAndSettle();
expect(tabsBloc.currentTabIdx, 0,
reason: 'Expected the 1st tab widget is focused when tapped on it.');
});
testWidgets(
'Should show close buttons on the focused tab and a hovered tab if any.',
(WidgetTester tester) async {
await _setUpTabsWidget(tester, tabsBloc);
await _addNTabsToTabsBloc(tester, tabsBloc, 3);
final tabs = _findNewTabWidgets();
expect(tabs, findsNWidgets(3),
reason: 'Expected to find 3 tab widgets when 3 tabs added.');
// Sees if there is only one tab that has a close button on it.
expect(_findClose(), findsOneWidget,
reason: 'Expected to find 1 close button when hovering no tabs.');
// Configures a gesture.
final TestGesture gesture =
await tester.createGesture(kind: PointerDeviceKind.mouse);
addTearDown(gesture.removePointer);
// Mouse Enters onto the first tab.
final Offset firstTabCenter = tester.getCenter(tabs.at(0));
await gesture.moveTo(firstTabCenter);
await tester.pumpAndSettle();
// Sees if there are two tabs that have a close button on it.
expect(_findClose(), findsNWidgets(2),
reason: 'Expected to find 2 close buttons when hovering a tab.');
// Mouse is out from the first tab and moves to the currently focused tab.
final Offset focusedTabCenter =
tester.getCenter(tabs.at(tabsBloc.currentTabIdx));
await gesture.moveTo(focusedTabCenter);
await tester.pumpAndSettle();
// Sees if there is only one tab that has a close button on it.
expect(_findClose(), findsOneWidget,
reason:
'Expected to find 1 close button when hovering the focused tab.');
});
testWidgets('Should close the tab when its close button is tapped.',
(WidgetTester tester) async {
await _setUpTabsWidget(tester, tabsBloc);
await _addNTabsToTabsBloc(tester, tabsBloc, 4);
expect(_findNewTabWidgets(), findsNWidgets(4),
reason: 'Expected to find 4 tab widgets when 4 tabs added.');
expect(tabsBloc.currentTabIdx, 3,
reason: 'Expected the 4th tab widget was focused by default.');
Finder closeButtons = _findClose();
expect(closeButtons, findsOneWidget,
reason: 'Expected to find 1 close button when hovering no tabs.');
await tester.tap(closeButtons);
await tester.pumpAndSettle();
expect(tabsBloc.tabs.length, 3,
reason: 'Expected 3 tabs in tabsBloc after tapped the close.');
expect(_findNewTabWidgets(), findsNWidgets(3),
reason: 'Expected to find 3 tab widgets after tapped the close.');
expect(tabsBloc.currentTabIdx, 2,
reason:
'Expected the 3rd tab widget was focused after tapped the close.');
});
testWidgets(
'The tab widget list should scroll if needed depending on the offset of the selected tab.',
(WidgetTester tester) async {
final rightScrollMargin = 800.0 - kScrollToMargin - kMinTabWidth;
final rightMinMargin = 800.0 - kMinTabWidth;
final leftScrollMargin = kScrollToMargin;
const leftMinMargin = 0.0;
await _setUpTabsWidget(tester, tabsBloc);
// Creates 8 tabs.
await _addNTabsToTabsBloc(tester, tabsBloc, 8);
// The screen(container) width: 800.0
// The total width of the tab widgets: 120.0 * 8 = 960.
// The expected display of the tab widgets (* is the currently focused tab):
// |- 0 -| |- 1 -| |- 2 -| |- 3 -| |- 4 -| |- 5 -| |- 6 -| |- 7* -|
// |- SCREEN -|
// See if the last tab is currenly focused.
expect(tabsBloc.currentTabIdx, 7,
reason: 'Expected the 8th tab widget is focused by default.');
final tabsOnScreen = _findMinTabWidgets();
// Sees if there are 7 tab widgets on the screen.
expect(tabsOnScreen, findsNWidgets(7),
reason: 'Expected 7 out of 8 tab widgets on the 800-wide viewport.');
final lastTab = tabsOnScreen.last;
final fixedY = tester.getTopLeft(lastTab).dy;
final expectedLastPosition = Offset(rightMinMargin, fixedY);
expect(tester.getTopLeft(lastTab), expectedLastPosition,
reason: '''Expected the initial X position of the last tab widget
to be $expectedLastPosition.''');
// The fifth tab in the tabsBloc is the forth tab widget on the current screen.
final fifthTab = tabsOnScreen.at(3);
final expectedFifthPosition = tester.getTopLeft(fifthTab);
// Taps on the fifth tab to change the focus.
await tester.tap(fifthTab);
await tester.pumpAndSettle();
// The expected display of the tab widgets (* is the currently focused tab):
// |- 0 -| |- 1 -| |- 2 -| |- 3 -| |- 4* -| |- 5 -| |- 6 -| |- 7 -|
// |- SCREEN -|
expect(tabsBloc.currentTabIdx, 4,
reason: 'Expected the 5th tab to be focused when tapped on it.');
expect(tester.getTopLeft(fifthTab), expectedFifthPosition,
reason: '''Expected the tab widget list stay still when tapped on
the 5th tab, which is the 4th tab widget on the screen.''');
// The second tab in the tabsBloc is the first tab widget on the current screen.
final secondTab = tabsOnScreen.at(0);
final expectedSecondPosition = Offset(leftScrollMargin, fixedY);
await tester.tap(secondTab);
await tester.pumpAndSettle();
// The expected display of the tab widgets (* is the currently focused tab):
// |- 0 -| |- 1* -| |- 2 -| |- 3 -| |- 4 -| |- 5 -| |- 6 -| |- 7 -|
// |- SCREEN -|
expect(tabsOnScreen, findsNWidgets(8),
reason: '''Expected 8 tab widgets on the screen when tapped on the
2nd tab, which was the 1st tab widget on the screen.''');
expect(tabsBloc.currentTabIdx, 1,
reason: '''Expected the 2nd tab to be focused when tapped on it.''');
expect(tester.getTopLeft(tabsOnScreen.at(1)), expectedSecondPosition,
reason: '''Expected the tab widget list to shift from left to right
and the 2nd tab widget to be fully revealed on the screen when
tapped on it.''');
final expectedFirstPosition = Offset(leftMinMargin, fixedY);
// Directly adds the FocusTabAction to the tabsBloc since tester.tap() does not
// work on the widget whose center is offscreen.
tabsBloc.request.add(FocusTabAction(tab: tabsBloc.tabs[0]));
await tester.pumpAndSettle();
// The expected display of the tab widgets (* is the currently focused tab):
// |- 0* -| |- 1 -| |- 2 -| |- 3 -| |- 4 -| |- 5 -| |- 6 -| |- 7 -|
// |- SCREEN -|
expect(tabsOnScreen, findsNWidgets(7),
reason:
'Expected 7 tab widgets on the screen when 1st tab is focused.');
expect(tabsBloc.currentTabIdx, 0,
reason: 'Expected the 1st tab to be focused when moved focus on it.');
expect(tester.getTopLeft(tabsOnScreen.at(0)), expectedFirstPosition,
reason: '''Expected the tab widget list to shift from left to right
and the 1st tab widget to be fully revealed on the screen when
tapped on it.''');
final seventhTab = tabsOnScreen.at(6);
final expectedSeventhPosition = Offset(rightScrollMargin, fixedY);
await tester.tap(seventhTab);
await tester.pumpAndSettle();
// The expected display of the tab widgets (* is the currently focused tab):
// |- 0 -| |- 1 -| |- 2 -| |- 3 -| |- 4 -| |- 5 -| |- 6* -| |- 7 -|
// |- SCREEN -|
expect(tabsOnScreen, findsNWidgets(8),
reason: '''Expected 8 tab widgets on the screen when tapped on the
7th tab widget.''');
expect(tabsBloc.currentTabIdx, 6,
reason: '''Expected the 7th tab to be focused when tapped on it.''');
expect(tester.getTopLeft(seventhTab), expectedSeventhPosition,
reason: '''Expected the tab widget list to shift from right to left
and the 7th tab widget to be fully revealed on the screen when
tapped on it.''');
});
}
Future<void> _setUpTabsWidget(WidgetTester tester, TabsBloc tabsBloc) async {
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Container(
width: 800,
child: TabsWidget(
bloc: tabsBloc,
),
),
),
));
await tester.pumpAndSettle();
}
Future<void> _addNTabsToTabsBloc(
WidgetTester tester, TabsBloc tabsBloc, int n) async {
int currentNumTabs = tabsBloc.tabs.length;
for (int i = 0; i < n; i++) {
tabsBloc.request.add(NewTabAction());
await tester.pumpAndSettle();
}
expect(tabsBloc.tabs.length, currentNumTabs + n);
}
Finder _findMinTabWidgets() => find.byWidgetPredicate(
(Widget widget) => widget is SizedBox && (widget.width) == kMinTabWidth);
Finder _findNewTabWidgets() => find.text(_emptyTitle);
Finder _findClose() => find.text(kCloseMark);
class MockSimpleBrowserNavigationEventListener extends Mock
implements SimpleBrowserNavigationEventListener {}
class MockSimpleBrowserWebService extends Mock
implements SimpleBrowserWebService {}
class MockWebPageBloc extends Mock implements WebPageBloc {}