| // Copyright 2014 The Flutter 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 'package:flutter/gestures.dart' show DragStartBehavior; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import 'data_table_test_utils.dart'; |
| |
| class TestDataSource extends DataTableSource { |
| TestDataSource({ |
| this.allowSelection = false, |
| }); |
| |
| final bool allowSelection; |
| |
| int get generation => _generation; |
| int _generation = 0; |
| set generation(int value) { |
| if (_generation == value) { |
| return; |
| } |
| _generation = value; |
| notifyListeners(); |
| } |
| |
| final Set<int> _selectedRows = <int>{}; |
| |
| void _handleSelected(int index, bool? selected) { |
| if (selected ?? false) { |
| _selectedRows.add(index); |
| } else { |
| _selectedRows.remove(index); |
| } |
| notifyListeners(); |
| } |
| |
| @override |
| DataRow getRow(int index) { |
| final Dessert dessert = kDesserts[index % kDesserts.length]; |
| final int page = index ~/ kDesserts.length; |
| return DataRow.byIndex( |
| index: index, |
| selected: _selectedRows.contains(index), |
| cells: <DataCell>[ |
| DataCell(Text('${dessert.name} ($page)')), |
| DataCell(Text('${dessert.calories}')), |
| DataCell(Text('$generation')), |
| ], |
| onSelectChanged: allowSelection ? (bool? selected) => _handleSelected(index, selected) : null, |
| ); |
| } |
| |
| @override |
| int get rowCount => 50 * kDesserts.length; |
| |
| @override |
| bool get isRowCountApproximate => false; |
| |
| @override |
| int get selectedRowCount => _selectedRows.length; |
| } |
| |
| void main() { |
| final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); |
| |
| testWidgets('PaginatedDataTable paging', (WidgetTester tester) async { |
| final TestDataSource source = TestDataSource(); |
| |
| final List<String> log = <String>[]; |
| |
| await tester.pumpWidget(MaterialApp( |
| home: PaginatedDataTable( |
| header: const Text('Test table'), |
| source: source, |
| rowsPerPage: 2, |
| showFirstLastButtons: true, |
| availableRowsPerPage: const <int>[ |
| 2, 4, 8, 16, |
| ], |
| onRowsPerPageChanged: (int? rowsPerPage) { |
| log.add('rows-per-page-changed: $rowsPerPage'); |
| }, |
| onPageChanged: (int rowIndex) { |
| log.add('page-changed: $rowIndex'); |
| }, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| ), |
| )); |
| |
| await tester.tap(find.byTooltip('Next page')); |
| |
| expect(log, <String>['page-changed: 2']); |
| log.clear(); |
| |
| await tester.pump(); |
| |
| expect(find.text('Frozen yogurt (0)'), findsNothing); |
| expect(find.text('Eclair (0)'), findsOneWidget); |
| expect(find.text('Gingerbread (0)'), findsNothing); |
| |
| await tester.tap(find.byIcon(Icons.chevron_left)); |
| |
| expect(log, <String>['page-changed: 0']); |
| log.clear(); |
| |
| await tester.pump(); |
| |
| expect(find.text('Frozen yogurt (0)'), findsOneWidget); |
| expect(find.text('Eclair (0)'), findsNothing); |
| expect(find.text('Gingerbread (0)'), findsNothing); |
| |
| final Finder lastPageButton = find.ancestor( |
| of: find.byTooltip('Last page'), |
| matching: find.byWidgetPredicate((Widget widget) => widget is IconButton), |
| ); |
| |
| expect(tester.widget<IconButton>(lastPageButton).onPressed, isNotNull); |
| |
| await tester.tap(lastPageButton); |
| |
| expect(log, <String>['page-changed: 498']); |
| log.clear(); |
| |
| await tester.pump(); |
| |
| expect(tester.widget<IconButton>(lastPageButton).onPressed, isNull); |
| |
| expect(find.text('Frozen yogurt (0)'), findsNothing); |
| expect(find.text('Donut (49)'), findsOneWidget); |
| expect(find.text('KitKat (49)'), findsOneWidget); |
| |
| final Finder firstPageButton = find.ancestor( |
| of: find.byTooltip('First page'), |
| matching: find.byWidgetPredicate((Widget widget) => widget is IconButton), |
| ); |
| |
| expect(tester.widget<IconButton>(firstPageButton).onPressed, isNotNull); |
| |
| await tester.tap(firstPageButton); |
| |
| expect(log, <String>['page-changed: 0']); |
| log.clear(); |
| |
| await tester.pump(); |
| |
| expect(tester.widget<IconButton>(firstPageButton).onPressed, isNull); |
| |
| expect(find.text('Frozen yogurt (0)'), findsOneWidget); |
| expect(find.text('Eclair (0)'), findsNothing); |
| expect(find.text('Gingerbread (0)'), findsNothing); |
| |
| await tester.tap(find.byIcon(Icons.chevron_left)); |
| |
| expect(log, isEmpty); |
| |
| await tester.tap(find.text('2')); |
| await tester.pumpAndSettle(const Duration(milliseconds: 200)); |
| |
| await tester.tap(find.text('8').last); |
| await tester.pumpAndSettle(const Duration(milliseconds: 200)); |
| |
| expect(log, <String>['rows-per-page-changed: 8']); |
| log.clear(); |
| }); |
| |
| testWidgets('PaginatedDataTable control test', (WidgetTester tester) async { |
| TestDataSource source = TestDataSource() |
| ..generation = 42; |
| |
| final List<String> log = <String>[]; |
| |
| Widget buildTable(TestDataSource source) { |
| return PaginatedDataTable( |
| header: const Text('Test table'), |
| source: source, |
| onPageChanged: (int rowIndex) { |
| log.add('page-changed: $rowIndex'); |
| }, |
| columns: <DataColumn>[ |
| const DataColumn( |
| label: Text('Name'), |
| tooltip: 'Name', |
| ), |
| DataColumn( |
| label: const Text('Calories'), |
| tooltip: 'Calories', |
| numeric: true, |
| onSort: (int columnIndex, bool ascending) { |
| log.add('column-sort: $columnIndex $ascending'); |
| }, |
| ), |
| const DataColumn( |
| label: Text('Generation'), |
| tooltip: 'Generation', |
| ), |
| ], |
| actions: <Widget>[ |
| IconButton( |
| icon: const Icon(Icons.adjust), |
| onPressed: () { |
| log.add('action: adjust'); |
| }, |
| ), |
| ], |
| ); |
| } |
| |
| await tester.pumpWidget(MaterialApp( |
| home: buildTable(source), |
| )); |
| |
| // the column overflows because we're forcing it to 600 pixels high |
| final dynamic exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| // ignore: avoid_dynamic_calls |
| expect(exception.diagnostics.first.level, DiagnosticLevel.summary); |
| // ignore: avoid_dynamic_calls |
| expect(exception.diagnostics.first.toString(), startsWith('A RenderFlex overflowed by ')); |
| |
| expect(find.text('Gingerbread (0)'), findsOneWidget); |
| expect(find.text('Gingerbread (1)'), findsNothing); |
| expect(find.text('42'), findsNWidgets(10)); |
| |
| source.generation = 43; |
| await tester.pump(); |
| |
| expect(find.text('42'), findsNothing); |
| expect(find.text('43'), findsNWidgets(10)); |
| |
| source = TestDataSource() |
| ..generation = 15; |
| |
| await tester.pumpWidget(MaterialApp( |
| home: buildTable(source), |
| )); |
| |
| expect(find.text('42'), findsNothing); |
| expect(find.text('43'), findsNothing); |
| expect(find.text('15'), findsNWidgets(10)); |
| |
| final PaginatedDataTableState state = tester.state(find.byType(PaginatedDataTable)); |
| |
| expect(log, isEmpty); |
| state.pageTo(23); |
| expect(log, <String>['page-changed: 20']); |
| log.clear(); |
| |
| await tester.pump(); |
| |
| expect(find.text('Gingerbread (0)'), findsNothing); |
| expect(find.text('Gingerbread (1)'), findsNothing); |
| expect(find.text('Gingerbread (2)'), findsOneWidget); |
| |
| await tester.tap(find.byIcon(Icons.adjust)); |
| expect(log, <String>['action: adjust']); |
| log.clear(); |
| }); |
| |
| testWidgets('PaginatedDataTable text alignment', (WidgetTester tester) async { |
| await tester.pumpWidget(MaterialApp( |
| home: PaginatedDataTable( |
| header: const Text('HEADER'), |
| source: TestDataSource(), |
| rowsPerPage: 8, |
| availableRowsPerPage: const <int>[ |
| 8, 9, |
| ], |
| onRowsPerPageChanged: (int? rowsPerPage) { }, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('COL1')), |
| DataColumn(label: Text('COL2')), |
| DataColumn(label: Text('COL3')), |
| ], |
| ), |
| )); |
| expect(find.text('Rows per page:'), findsOneWidget); |
| expect(find.text('8'), findsOneWidget); |
| expect(tester.getTopRight(find.text('8')).dx, tester.getTopRight(find.text('Rows per page:')).dx + 40.0); // per spec |
| }); |
| |
| testWidgets('PaginatedDataTable with and without header and actions', (WidgetTester tester) async { |
| await binding.setSurfaceSize(const Size(800, 800)); |
| const String headerText = 'HEADER'; |
| final List<Widget> actions = <Widget>[ |
| IconButton(onPressed: () {}, icon: const Icon(Icons.add)), |
| ]; |
| Widget buildTable({String? header, List<Widget>? actions}) => MaterialApp( |
| home: PaginatedDataTable( |
| header: header != null ? Text(header) : null, |
| actions: actions, |
| source: TestDataSource(allowSelection: true), |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| ), |
| ); |
| |
| await tester.pumpWidget(buildTable(header: headerText)); |
| expect(find.text(headerText), findsOneWidget); |
| expect(find.byIcon(Icons.add), findsNothing); |
| |
| await tester.pumpWidget(buildTable(header: headerText, actions: actions)); |
| expect(find.text(headerText), findsOneWidget); |
| expect(find.byIcon(Icons.add), findsOneWidget); |
| |
| await tester.pumpWidget(buildTable()); |
| expect(find.text(headerText), findsNothing); |
| expect(find.byIcon(Icons.add), findsNothing); |
| |
| expect(() => buildTable(actions: actions), throwsAssertionError); |
| |
| await binding.setSurfaceSize(null); |
| }); |
| |
| testWidgets('PaginatedDataTable with large text', (WidgetTester tester) async { |
| final TestDataSource source = TestDataSource(); |
| await tester.pumpWidget(MaterialApp( |
| home: MediaQuery( |
| data: const MediaQueryData( |
| textScaleFactor: 20.0, |
| ), |
| child: PaginatedDataTable( |
| header: const Text('HEADER'), |
| source: source, |
| rowsPerPage: 501, |
| availableRowsPerPage: const <int>[ 501 ], |
| onRowsPerPageChanged: (int? rowsPerPage) { }, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('COL1')), |
| DataColumn(label: Text('COL2')), |
| DataColumn(label: Text('COL3')), |
| ], |
| ), |
| ), |
| )); |
| // the column overflows because we're forcing it to 600 pixels high |
| final dynamic exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| // ignore: avoid_dynamic_calls |
| expect(exception.diagnostics.first.level, DiagnosticLevel.summary); |
| // ignore: avoid_dynamic_calls |
| expect(exception.diagnostics.first.toString(), contains('A RenderFlex overflowed by')); |
| |
| expect(find.text('Rows per page:'), findsOneWidget); |
| // Test that we will show some options in the drop down even if the lowest option is bigger than the source: |
| assert(501 > source.rowCount); |
| expect(find.text('501'), findsOneWidget); |
| // Test that it fits: |
| expect(tester.getTopRight(find.text('501')).dx, greaterThanOrEqualTo(tester.getTopRight(find.text('Rows per page:')).dx + 40.0)); |
| }, skip: isBrowser); // https://github.com/flutter/flutter/issues/43433 |
| |
| testWidgets('PaginatedDataTable footer scrolls', (WidgetTester tester) async { |
| final TestDataSource source = TestDataSource(); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Align( |
| alignment: Alignment.topLeft, |
| child: SizedBox( |
| width: 100.0, |
| child: PaginatedDataTable( |
| header: const Text('HEADER'), |
| source: source, |
| rowsPerPage: 5, |
| dragStartBehavior: DragStartBehavior.down, |
| availableRowsPerPage: const <int>[ 5 ], |
| onRowsPerPageChanged: (int? rowsPerPage) { }, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('COL1')), |
| DataColumn(label: Text('COL2')), |
| DataColumn(label: Text('COL3')), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| expect(find.text('Rows per page:'), findsOneWidget); |
| expect(tester.getTopLeft(find.text('Rows per page:')).dx, lessThan(0.0)); // off screen |
| await tester.dragFrom( |
| Offset(50.0, tester.getTopLeft(find.text('Rows per page:')).dy), |
| const Offset(1000.0, 0.0), |
| ); |
| await tester.pump(); |
| expect(find.text('Rows per page:'), findsOneWidget); |
| expect(tester.getTopLeft(find.text('Rows per page:')).dx, 18.0); // 14 padding in the footer row, 4 padding from the card |
| }); |
| |
| testWidgets('PaginatedDataTable custom row height', (WidgetTester tester) async { |
| final TestDataSource source = TestDataSource(); |
| |
| Widget buildCustomHeightPaginatedTable({ |
| double? dataRowHeight, |
| double? dataRowMinHeight, |
| double? dataRowMaxHeight, |
| double headingRowHeight = 56.0, |
| }) { |
| return PaginatedDataTable( |
| header: const Text('Test table'), |
| source: source, |
| rowsPerPage: 2, |
| availableRowsPerPage: const <int>[ |
| 2, 4, 8, 16, |
| ], |
| onRowsPerPageChanged: (int? rowsPerPage) {}, |
| onPageChanged: (int rowIndex) {}, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| dataRowHeight: dataRowHeight, |
| dataRowMinHeight: dataRowMinHeight, |
| dataRowMaxHeight: dataRowMaxHeight, |
| headingRowHeight: headingRowHeight, |
| ); |
| } |
| |
| // DEFAULT VALUES |
| await tester.pumpWidget(MaterialApp( |
| home: PaginatedDataTable( |
| header: const Text('Test table'), |
| source: source, |
| rowsPerPage: 2, |
| availableRowsPerPage: const <int>[ |
| 2, 4, 8, 16, |
| ], |
| onRowsPerPageChanged: (int? rowsPerPage) {}, |
| onPageChanged: (int rowIndex) {}, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| ), |
| )); |
| expect(tester.renderObject<RenderBox>( |
| find.widgetWithText(Container, 'Name').first, |
| ).size.height, 56.0); // This is the header row height |
| expect(tester.renderObject<RenderBox>( |
| find.widgetWithText(Container, 'Frozen yogurt (0)').first, |
| ).size.height, 48.0); // This is the data row height |
| |
| // CUSTOM VALUES |
| await tester.pumpWidget(MaterialApp( |
| home: Material(child: buildCustomHeightPaginatedTable(headingRowHeight: 48.0)), |
| )); |
| expect(tester.renderObject<RenderBox>( |
| find.widgetWithText(Container, 'Name').first, |
| ).size.height, 48.0); |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Material(child: buildCustomHeightPaginatedTable(headingRowHeight: 64.0)), |
| )); |
| expect(tester.renderObject<RenderBox>( |
| find.widgetWithText(Container, 'Name').first, |
| ).size.height, 64.0); |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Material(child: buildCustomHeightPaginatedTable(dataRowHeight: 30.0)), |
| )); |
| expect(tester.renderObject<RenderBox>( |
| find.widgetWithText(Container, 'Frozen yogurt (0)').first, |
| ).size.height, 30.0); |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Material(child: buildCustomHeightPaginatedTable(dataRowHeight: 56.0)), |
| )); |
| expect(tester.renderObject<RenderBox>( |
| find.widgetWithText(Container, 'Frozen yogurt (0)').first, |
| ).size.height, 56.0); |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Material(child: buildCustomHeightPaginatedTable(dataRowMinHeight: 51.0, dataRowMaxHeight: 51.0)), |
| )); |
| expect(tester.renderObject<RenderBox>( |
| find.widgetWithText(Container, 'Frozen yogurt (0)').first, |
| ).size.height, 51.0); |
| }); |
| |
| testWidgets('PaginatedDataTable custom horizontal padding - checkbox', (WidgetTester tester) async { |
| const double defaultHorizontalMargin = 24.0; |
| const double defaultColumnSpacing = 56.0; |
| const double customHorizontalMargin = 10.0; |
| const double customColumnSpacing = 15.0; |
| |
| const double width = 400; |
| const double height = 400; |
| |
| final Size originalSize = binding.renderView.size; |
| |
| // Ensure the containing Card is small enough that we don't expand too |
| // much, resulting in our custom margin being ignored. |
| await binding.setSurfaceSize(const Size(width, height)); |
| |
| final TestDataSource source = TestDataSource(allowSelection: true); |
| Finder cellContent; |
| Finder checkbox; |
| Finder padding; |
| |
| await tester.pumpWidget(MaterialApp( |
| home: PaginatedDataTable( |
| header: const Text('Test table'), |
| source: source, |
| rowsPerPage: 2, |
| availableRowsPerPage: const <int>[ |
| 2, 4, |
| ], |
| onRowsPerPageChanged: (int? rowsPerPage) {}, |
| onPageChanged: (int rowIndex) {}, |
| onSelectAll: (bool? value) {}, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| ), |
| )); |
| |
| // default checkbox padding |
| checkbox = find.byType(Checkbox).first; |
| padding = find.ancestor(of: checkbox, matching: find.byType(Padding)).first; |
| expect( |
| tester.getRect(checkbox).left - tester.getRect(padding).left, |
| defaultHorizontalMargin, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(checkbox).right, |
| defaultHorizontalMargin / 2, |
| ); |
| |
| // default first column padding |
| padding = find.widgetWithText(Padding, 'Frozen yogurt (0)').first; |
| cellContent = find.widgetWithText(Align, 'Frozen yogurt (0)'); // DataTable wraps its DataCells in an Align widget |
| expect( |
| tester.getRect(cellContent).left - tester.getRect(padding).left, |
| defaultHorizontalMargin / 2, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(cellContent).right, |
| defaultColumnSpacing / 2, |
| ); |
| |
| // default middle column padding |
| padding = find.widgetWithText(Padding, '159').first; |
| cellContent = find.widgetWithText(Align, '159'); |
| expect( |
| tester.getRect(cellContent).left - tester.getRect(padding).left, |
| defaultColumnSpacing / 2, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(cellContent).right, |
| defaultColumnSpacing / 2, |
| ); |
| |
| // default last column padding |
| padding = find.widgetWithText(Padding, '0').first; |
| cellContent = find.widgetWithText(Align, '0').first; |
| expect( |
| tester.getRect(cellContent).left - tester.getRect(padding).left, |
| defaultColumnSpacing / 2, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(cellContent).right, |
| defaultHorizontalMargin, |
| ); |
| |
| // CUSTOM VALUES |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: PaginatedDataTable( |
| header: const Text('Test table'), |
| source: source, |
| rowsPerPage: 2, |
| availableRowsPerPage: const <int>[ |
| 2, 4, |
| ], |
| onRowsPerPageChanged: (int? rowsPerPage) {}, |
| onPageChanged: (int rowIndex) {}, |
| onSelectAll: (bool? value) {}, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| horizontalMargin: customHorizontalMargin, |
| columnSpacing: customColumnSpacing, |
| ), |
| ), |
| )); |
| |
| // custom checkbox padding |
| checkbox = find.byType(Checkbox).first; |
| padding = find.ancestor(of: checkbox, matching: find.byType(Padding)).first; |
| expect( |
| tester.getRect(checkbox).left - tester.getRect(padding).left, |
| customHorizontalMargin, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(checkbox).right, |
| customHorizontalMargin / 2, |
| ); |
| |
| // custom first column padding |
| padding = find.widgetWithText(Padding, 'Frozen yogurt (0)').first; |
| cellContent = find.widgetWithText(Align, 'Frozen yogurt (0)'); // DataTable wraps its DataCells in an Align widget |
| expect( |
| tester.getRect(cellContent).left - tester.getRect(padding).left, |
| customHorizontalMargin / 2, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(cellContent).right, |
| customColumnSpacing / 2, |
| ); |
| |
| // custom middle column padding |
| padding = find.widgetWithText(Padding, '159').first; |
| cellContent = find.widgetWithText(Align, '159'); |
| expect( |
| tester.getRect(cellContent).left - tester.getRect(padding).left, |
| customColumnSpacing / 2, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(cellContent).right, |
| customColumnSpacing / 2, |
| ); |
| |
| // custom last column padding |
| padding = find.widgetWithText(Padding, '0').first; |
| cellContent = find.widgetWithText(Align, '0').first; |
| expect( |
| tester.getRect(cellContent).left - tester.getRect(padding).left, |
| customColumnSpacing / 2, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(cellContent).right, |
| customHorizontalMargin, |
| ); |
| |
| // Reset the surface size. |
| await binding.setSurfaceSize(originalSize); |
| }); |
| |
| testWidgets('PaginatedDataTable custom horizontal padding - no checkbox', (WidgetTester tester) async { |
| const double defaultHorizontalMargin = 24.0; |
| const double defaultColumnSpacing = 56.0; |
| const double customHorizontalMargin = 10.0; |
| const double customColumnSpacing = 15.0; |
| final TestDataSource source = TestDataSource(); |
| Finder cellContent; |
| Finder padding; |
| |
| await tester.pumpWidget(MaterialApp( |
| home: PaginatedDataTable( |
| header: const Text('Test table'), |
| source: source, |
| rowsPerPage: 2, |
| availableRowsPerPage: const <int>[ |
| 2, 4, 8, 16, |
| ], |
| onRowsPerPageChanged: (int? rowsPerPage) {}, |
| onPageChanged: (int rowIndex) {}, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| ), |
| )); |
| |
| // default first column padding |
| padding = find.widgetWithText(Padding, 'Frozen yogurt (0)').first; |
| cellContent = find.widgetWithText(Align, 'Frozen yogurt (0)'); // DataTable wraps its DataCells in an Align widget |
| expect( |
| tester.getRect(cellContent).left - tester.getRect(padding).left, |
| defaultHorizontalMargin, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(cellContent).right, |
| defaultColumnSpacing / 2, |
| ); |
| |
| // default middle column padding |
| padding = find.widgetWithText(Padding, '159').first; |
| cellContent = find.widgetWithText(Align, '159'); |
| expect( |
| tester.getRect(cellContent).left - tester.getRect(padding).left, |
| defaultColumnSpacing / 2, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(cellContent).right, |
| defaultColumnSpacing / 2, |
| ); |
| |
| // default last column padding |
| padding = find.widgetWithText(Padding, '0').first; |
| cellContent = find.widgetWithText(Align, '0').first; |
| expect( |
| tester.getRect(cellContent).left - tester.getRect(padding).left, |
| defaultColumnSpacing / 2, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(cellContent).right, |
| defaultHorizontalMargin, |
| ); |
| |
| // CUSTOM VALUES |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: PaginatedDataTable( |
| header: const Text('Test table'), |
| source: source, |
| rowsPerPage: 2, |
| availableRowsPerPage: const <int>[ |
| 2, 4, 8, 16, |
| ], |
| onRowsPerPageChanged: (int? rowsPerPage) {}, |
| onPageChanged: (int rowIndex) {}, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| horizontalMargin: customHorizontalMargin, |
| columnSpacing: customColumnSpacing, |
| ), |
| ), |
| )); |
| |
| // custom first column padding |
| padding = find.widgetWithText(Padding, 'Frozen yogurt (0)').first; |
| cellContent = find.widgetWithText(Align, 'Frozen yogurt (0)'); |
| expect( |
| tester.getRect(cellContent).left - tester.getRect(padding).left, |
| customHorizontalMargin, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(cellContent).right, |
| customColumnSpacing / 2, |
| ); |
| |
| // custom middle column padding |
| padding = find.widgetWithText(Padding, '159').first; |
| cellContent = find.widgetWithText(Align, '159'); |
| expect( |
| tester.getRect(cellContent).left - tester.getRect(padding).left, |
| customColumnSpacing / 2, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(cellContent).right, |
| customColumnSpacing / 2, |
| ); |
| |
| // custom last column padding |
| padding = find.widgetWithText(Padding, '0').first; |
| cellContent = find.widgetWithText(Align, '0').first; |
| expect( |
| tester.getRect(cellContent).left - tester.getRect(padding).left, |
| customColumnSpacing / 2, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(cellContent).right, |
| customHorizontalMargin, |
| ); |
| }); |
| |
| testWidgets('PaginatedDataTable table fills Card width', (WidgetTester tester) async { |
| final TestDataSource source = TestDataSource(); |
| |
| // 800 is wide enough to ensure that all of the columns fit in the |
| // Card. The test makes sure that the DataTable is exactly as wide |
| // as the Card, minus the Card's margin. |
| const double originalWidth = 800; |
| const double expandedWidth = 1600; |
| const double height = 400; |
| |
| // By default, the margin of a Card is 4 in all directions, so |
| // the size of the DataTable (inside the Card) is horizontally |
| // reduced by 4 * 2; the left and right margins. |
| const double cardMargin = 8; |
| |
| final Size originalSize = binding.renderView.size; |
| |
| Widget buildWidget() => MaterialApp( |
| home: PaginatedDataTable( |
| header: const Text('Test table'), |
| source: source, |
| rowsPerPage: 2, |
| availableRowsPerPage: const <int>[ |
| 2, 4, 8, 16, |
| ], |
| onRowsPerPageChanged: (int? rowsPerPage) {}, |
| onPageChanged: (int rowIndex) {}, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| ), |
| ); |
| |
| await binding.setSurfaceSize(const Size(originalWidth, height)); |
| await tester.pumpWidget(buildWidget()); |
| |
| double cardWidth = tester.renderObject<RenderBox>(find.byType(Card).first).size.width; |
| |
| // Widths should be equal before we resize... |
| expect( |
| tester.renderObject<RenderBox>(find.byType(DataTable).first).size.width, |
| moreOrLessEquals(cardWidth - cardMargin), |
| ); |
| |
| await binding.setSurfaceSize(const Size(expandedWidth, height)); |
| await tester.pumpWidget(buildWidget()); |
| |
| cardWidth = tester.renderObject<RenderBox>(find.byType(Card).first).size.width; |
| |
| // ... and should still be equal after the resize. |
| expect( |
| tester.renderObject<RenderBox>(find.byType(DataTable).first).size.width, |
| moreOrLessEquals(cardWidth - cardMargin), |
| ); |
| |
| // Double check to ensure we actually resized the surface properly. |
| expect(cardWidth, moreOrLessEquals(expandedWidth)); |
| |
| // Reset the surface size. |
| await binding.setSurfaceSize(originalSize); |
| }); |
| |
| testWidgets('PaginatedDataTable with optional column checkbox', (WidgetTester tester) async { |
| await binding.setSurfaceSize(const Size(800, 800)); |
| addTearDown(() => binding.setSurfaceSize(null)); |
| |
| Widget buildTable(bool checkbox) => MaterialApp( |
| home: PaginatedDataTable( |
| header: const Text('Test table'), |
| source: TestDataSource(allowSelection: true), |
| showCheckboxColumn: checkbox, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| ), |
| ); |
| |
| await tester.pumpWidget(buildTable(true)); |
| expect(find.byType(Checkbox), findsNWidgets(11)); |
| |
| await tester.pumpWidget(buildTable(false)); |
| expect(find.byType(Checkbox), findsNothing); |
| }); |
| |
| testWidgets('Table should not use decoration from DataTableTheme', (WidgetTester tester) async { |
| final Size originalSize = binding.renderView.size; |
| await binding.setSurfaceSize(const Size(800, 800)); |
| |
| Widget buildTable() { |
| return MaterialApp( |
| theme: ThemeData.light().copyWith( |
| dataTableTheme: const DataTableThemeData( |
| decoration: BoxDecoration(color: Colors.white), |
| ), |
| ), |
| home: PaginatedDataTable( |
| header: const Text('Test table'), |
| source: TestDataSource(allowSelection: true), |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildTable()); |
| final Finder tableContainerFinder = find.ancestor(of: find.byType(Table), matching: find.byType(Container)).first; |
| expect(tester.widget<Container>(tableContainerFinder).decoration, const BoxDecoration()); |
| |
| // Reset the surface size. |
| await binding.setSurfaceSize(originalSize); |
| }); |
| |
| testWidgets('PaginatedDataTable custom checkboxHorizontalMargin properly applied', (WidgetTester tester) async { |
| const double customCheckboxHorizontalMargin = 15.0; |
| const double customHorizontalMargin = 10.0; |
| |
| const double width = 400; |
| const double height = 400; |
| |
| final Size originalSize = binding.renderView.size; |
| |
| // Ensure the containing Card is small enough that we don't expand too |
| // much, resulting in our custom margin being ignored. |
| await binding.setSurfaceSize(const Size(width, height)); |
| |
| final TestDataSource source = TestDataSource(allowSelection: true); |
| Finder cellContent; |
| Finder checkbox; |
| Finder padding; |
| |
| // CUSTOM VALUES |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: PaginatedDataTable( |
| header: const Text('Test table'), |
| source: source, |
| rowsPerPage: 2, |
| availableRowsPerPage: const <int>[ |
| 2, 4, |
| ], |
| onRowsPerPageChanged: (int? rowsPerPage) {}, |
| onPageChanged: (int rowIndex) {}, |
| onSelectAll: (bool? value) {}, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| horizontalMargin: customHorizontalMargin, |
| checkboxHorizontalMargin: customCheckboxHorizontalMargin, |
| ), |
| ), |
| )); |
| |
| // Custom checkbox padding. |
| checkbox = find.byType(Checkbox).first; |
| padding = find.ancestor(of: checkbox, matching: find.byType(Padding)).first; |
| expect( |
| tester.getRect(checkbox).left - tester.getRect(padding).left, |
| customCheckboxHorizontalMargin, |
| ); |
| expect( |
| tester.getRect(padding).right - tester.getRect(checkbox).right, |
| customCheckboxHorizontalMargin, |
| ); |
| |
| // Custom first column padding. |
| padding = find.widgetWithText(Padding, 'Frozen yogurt (0)').first; |
| cellContent = find.widgetWithText(Align, 'Frozen yogurt (0)'); // DataTable wraps its DataCells in an Align widget. |
| expect( |
| tester.getRect(cellContent).left - tester.getRect(padding).left, |
| customHorizontalMargin, |
| ); |
| |
| // Reset the surface size. |
| await binding.setSurfaceSize(originalSize); |
| }); |
| |
| testWidgets('Items selected text uses secondary color', (WidgetTester tester) async { |
| const Color selectedTextColor = Color(0xff00ddff); |
| final ColorScheme colors = const ColorScheme.light().copyWith(secondary: selectedTextColor); |
| final ThemeData theme = ThemeData.from(colorScheme: colors); |
| |
| Widget buildTable() { |
| return MaterialApp( |
| theme: theme, |
| home: PaginatedDataTable( |
| header: const Text('Test table'), |
| source: TestDataSource(allowSelection: true), |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| ), |
| ); |
| } |
| |
| await binding.setSurfaceSize(const Size(800, 800)); |
| await tester.pumpWidget(buildTable()); |
| expect(find.text('Test table'), findsOneWidget); |
| |
| // Select a row with yogurt |
| await tester.tap(find.text('Frozen yogurt (0)')); |
| await tester.pumpAndSettle(); |
| |
| // The header should be replace with a selected text item |
| expect(find.text('Test table'), findsNothing); |
| expect(find.text('1 item selected'), findsOneWidget); |
| |
| // The color of the selected text item should be the colorScheme.secondary |
| final TextStyle selectedTextStyle = tester.renderObject<RenderParagraph>(find.text('1 item selected')).text.style!; |
| expect(selectedTextStyle.color, equals(selectedTextColor)); |
| |
| await binding.setSurfaceSize(null); |
| }); |
| |
| testWidgets('PaginatedDataTable arrowHeadColor set properly', (WidgetTester tester) async { |
| await binding.setSurfaceSize(const Size(800, 800)); |
| addTearDown(() => binding.setSurfaceSize(null)); |
| const Color arrowHeadColor = Color(0xFFE53935); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: PaginatedDataTable( |
| arrowHeadColor: arrowHeadColor, |
| showFirstLastButtons: true, |
| header: const Text('Test table'), |
| source: TestDataSource(), |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| ), |
| ) |
| ); |
| |
| final Iterable<Icon> icons = tester.widgetList(find.byType(Icon)); |
| |
| expect(icons.elementAt(0).color, arrowHeadColor); |
| expect(icons.elementAt(1).color, arrowHeadColor); |
| expect(icons.elementAt(2).color, arrowHeadColor); |
| expect(icons.elementAt(3).color, arrowHeadColor); |
| }); |
| |
| testWidgets('OverflowBar header left alignment', (WidgetTester tester) async { |
| // Test an old special case that tried to align the first child of a ButtonBar |
| // and the left edge of a Text header widget. Still possible with OverflowBar |
| // albeit without any special case in the implementation's build method. |
| Widget buildFrame(Widget header) { |
| return MaterialApp( |
| home: PaginatedDataTable( |
| header: header, |
| rowsPerPage: 2, |
| source: TestDataSource(), |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildFrame(const Text('HEADER'))); |
| final double headerX = tester.getTopLeft(find.text('HEADER')).dx; |
| final Widget overflowBar = OverflowBar( |
| children: <Widget>[ElevatedButton(onPressed: () {}, child: const Text('BUTTON'))], |
| ); |
| await tester.pumpWidget(buildFrame(overflowBar)); |
| expect(headerX, tester.getTopLeft(find.byType(ElevatedButton)).dx); |
| }); |
| |
| testWidgets('PaginatedDataTable can be scrolled using ScrollController', (WidgetTester tester) async { |
| final TestDataSource source = TestDataSource(); |
| final ScrollController scrollController = ScrollController(); |
| |
| Widget buildTable(TestDataSource source) { |
| return Align( |
| alignment: Alignment.topLeft, |
| child: SizedBox( |
| width: 100, |
| child: PaginatedDataTable( |
| controller: scrollController, |
| header: const Text('Test table'), |
| source: source, |
| rowsPerPage: 2, |
| columns: const <DataColumn>[ |
| DataColumn( |
| label: Text('Name'), |
| tooltip: 'Name', |
| ), |
| DataColumn( |
| label: Text('Calories'), |
| tooltip: 'Calories', |
| numeric: true, |
| ), |
| DataColumn( |
| label: Text('Generation'), |
| tooltip: 'Generation', |
| ), |
| ], |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(MaterialApp( |
| home: buildTable(source), |
| )); |
| |
| // DataTable uses provided ScrollController |
| final Scrollable bodyScrollView = tester.widget(find.byType(Scrollable).first); |
| expect(bodyScrollView.controller, scrollController); |
| |
| expect(scrollController.offset, 0.0); |
| scrollController.jumpTo(50.0); |
| await tester.pumpAndSettle(); |
| |
| expect(scrollController.offset, 50.0); |
| }); |
| |
| testWidgets('PaginatedDataTable uses PrimaryScrollController when primary ', (WidgetTester tester) async { |
| final ScrollController primaryScrollController = ScrollController(); |
| final TestDataSource source = TestDataSource(); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: PrimaryScrollController( |
| controller: primaryScrollController, |
| child: PaginatedDataTable( |
| primary: true, |
| header: const Text('Test table'), |
| source: source, |
| rowsPerPage: 2, |
| columns: const <DataColumn>[ |
| DataColumn(label: Text('Name')), |
| DataColumn(label: Text('Calories'), numeric: true), |
| DataColumn(label: Text('Generation')), |
| ], |
| ), |
| ), |
| ) |
| ); |
| |
| // DataTable uses primaryScrollController |
| final Scrollable bodyScrollView = tester.widget(find.byType(Scrollable).first); |
| expect(bodyScrollView.controller, primaryScrollController); |
| |
| // Footer does not use primaryScrollController |
| final Scrollable footerScrollView = tester.widget(find.byType(Scrollable).last); |
| expect(footerScrollView.controller, null); |
| }); |
| } |