From 97fcc75539f6c64c7bb96b58ea1f800c03215892 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Wed, 18 Oct 2023 17:35:40 -0500 Subject: [PATCH] Add pinned extent assertions --- .../two_dimensional_scrollables/CHANGELOG.md | 4 + .../lib/src/table_view/table.dart | 54 ++++++ .../test/table_view/table_test.dart | 162 ++++++++++++++++++ 3 files changed, 220 insertions(+) diff --git a/packages/two_dimensional_scrollables/CHANGELOG.md b/packages/two_dimensional_scrollables/CHANGELOG.md index 570519e6898..da5dc332594 100644 --- a/packages/two_dimensional_scrollables/CHANGELOG.md +++ b/packages/two_dimensional_scrollables/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Adds assertions against pinned row and column configurations that exceed the viewport or block additional cells. + ## 0.0.3 * Fixes paint issue when axes are reversed and TableView has pinned rows and columns. diff --git a/packages/two_dimensional_scrollables/lib/src/table_view/table.dart b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart index b8829f04ccf..eeb8bbae7dc 100644 --- a/packages/two_dimensional_scrollables/lib/src/table_view/table.dart +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart @@ -588,6 +588,58 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { } } + bool _debugCheckPinnedSpanExtents() { + // Ensure pinned spans do not exceed the viewport dimensions. Pinned spans + // do not scroll. + assert( + _pinnedRowsExtent <= viewportDimension.height, + 'The extent of pinned rows has overflowed the viewport by ' + '${_pinnedRowsExtent - viewportDimension.height} pixels.', + ); + assert( + _pinnedColumnsExtent <= viewportDimension.width, + 'The extent of pinned columns has overflowed the viewport by ' + '${_pinnedColumnsExtent - viewportDimension.width} pixels.', + ); + // If pinned rows or columns take up the full extent of the viewport, and + // there are additional rows and columns that follow, + assert(() { + if (_pinnedRowsExtent == viewportDimension.height && + _firstNonPinnedRow != null) { + throw FlutterError.fromParts([ + ErrorSummary( + 'The extent of pinned rows in the TableView has consumed the ' + 'full extent of the viewport.', + ), + ErrorDescription( + 'The unpinned rows that follow will not be visible in the ' + 'viewport as the pinned extent has taken up all of the ' + 'available space.\n', + ), + ]); + } + return true; + }()); + assert(() { + if (_pinnedColumnsExtent == viewportDimension.width && + _firstNonPinnedColumn != null) { + throw FlutterError.fromParts([ + ErrorSummary( + 'The extent of pinned columns in the TableView has consumed the ' + 'full extent of the viewport.', + ), + ErrorDescription( + 'The unpinned columns that follow will not be visible in the ' + 'viewport as the pinned extent has taken up all of the ' + 'available space.\n', + ), + ]); + } + return true; + }()); + return true; + } + @override void layoutChildSequence() { if (needsDelegateRebuild || didResize) { @@ -605,6 +657,8 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { return; } + assert(_debugCheckPinnedSpanExtents()); + final double? offsetIntoColumn = _firstNonPinnedColumn != null ? horizontalOffset.pixels - _columnMetrics[_firstNonPinnedColumn]!.leadingOffset - diff --git a/packages/two_dimensional_scrollables/test/table_view/table_test.dart b/packages/two_dimensional_scrollables/test/table_view/table_test.dart index 75cdfb0b9e3..745d784ff62 100644 --- a/packages/two_dimensional_scrollables/test/table_view/table_test.dart +++ b/packages/two_dimensional_scrollables/test/table_view/table_test.dart @@ -4,6 +4,7 @@ import 'dart:io' show Platform; +import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -1159,6 +1160,167 @@ void main() { SystemMouseCursors.basic, ); }); + + testWidgets('asserts pinned span extents cannot exceed viewport dimensions', + (WidgetTester tester) async { + final List exceptions = []; + final FlutterExceptionHandler? oldHandler = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + // Row + TableView tableView = TableView.builder( + rowCount: 1, + columnCount: 1, + pinnedRowCount: 1, + pinnedColumnCount: 1, + rowBuilder: (int index) => const TableSpan( + // Exceeds the viewport height. + extent: FixedTableSpanExtent(1000), + ), + columnBuilder: (int index) => span, + cellBuilder: (BuildContext context, TableVicinity vicinty) => cell, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + FlutterError.onError = oldHandler; + expect(exceptions.length, 2); + expect( + exceptions[0].toString(), + contains('The extent of pinned rows has overflowed the viewport'), + ); + + await tester.pumpWidget(Container()); + exceptions.clear(); + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + // Column + tableView = TableView.builder( + rowCount: 1, + columnCount: 1, + pinnedRowCount: 1, + pinnedColumnCount: 1, + columnBuilder: (int index) => const TableSpan( + // Exceeds the viewport height. + extent: FixedTableSpanExtent(1000), + ), + rowBuilder: (int index) => span, + cellBuilder: (BuildContext context, TableVicinity vicinty) => cell, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + FlutterError.onError = oldHandler; + expect(exceptions.length, 2); + expect( + exceptions[0].toString(), + contains('The extent of pinned columns has overflowed the viewport'), + ); + }); + + testWidgets('asserts for pinned spans that block additional unpinned spans', + (WidgetTester tester) async { + final List exceptions = []; + final FlutterExceptionHandler? oldHandler = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + + // Row ---------- + TableView tableView = TableView.builder( + rowCount: 1, + columnCount: 1, + pinnedRowCount: 1, + pinnedColumnCount: 1, + rowBuilder: (int index) => const TableSpan( + // Exceeds the viewport height. + extent: FixedTableSpanExtent(600), // == viewportDimension.height + ), + columnBuilder: (int index) => span, + cellBuilder: (BuildContext context, TableVicinity vicinty) => cell, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + FlutterError.onError = oldHandler; + // No additional rows, so this is ok. + expect(exceptions.length, 0); + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + tableView = TableView.builder( + rowCount: 2, // One row would be obscured + columnCount: 1, + pinnedRowCount: 1, + pinnedColumnCount: 1, + rowBuilder: (int index) => const TableSpan( + // Exceeds the viewport height. + extent: FixedTableSpanExtent(600), // == viewportDimension.height + ), + columnBuilder: (int index) => span, + cellBuilder: (BuildContext context, TableVicinity vicinty) => cell, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + FlutterError.onError = oldHandler; + expect(exceptions.length, 2); + expect( + exceptions[0].toString(), + contains( + 'The extent of pinned rows in the TableView has consumed the full ' + 'extent of the viewport.', + ), + ); + + await tester.pumpWidget(Container()); + exceptions.clear(); + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + // Column ---------- + tableView = TableView.builder( + rowCount: 1, + columnCount: 1, + pinnedRowCount: 1, + pinnedColumnCount: 1, + columnBuilder: (int index) => const TableSpan( + // Exceeds the viewport height. + extent: FixedTableSpanExtent(800), // == viewportDimension.width + ), + rowBuilder: (int index) => span, + cellBuilder: (BuildContext context, TableVicinity vicinty) => cell, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + FlutterError.onError = oldHandler; + // No additional rows, so this is ok. + expect(exceptions.length, 0); + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + tableView = TableView.builder( + rowCount: 1, + columnCount: 2, // One column would be obscured + pinnedRowCount: 1, + pinnedColumnCount: 1, + columnBuilder: (int index) => const TableSpan( + // Exceeds the viewport height. + extent: FixedTableSpanExtent(800), // == viewportDimension.height + ), + rowBuilder: (int index) => span, + cellBuilder: (BuildContext context, TableVicinity vicinty) => cell, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + FlutterError.onError = oldHandler; + expect(exceptions.length, 2); + expect( + exceptions[0].toString(), + contains( + 'The extent of pinned columns in the TableView has consumed the full ' + 'extent of the viewport.', + ), + ); + }); }); }