Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/two_dimensional_scrollables/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(<DiagnosticsNode>[
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(<DiagnosticsNode>[
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) {
Expand All @@ -605,6 +657,8 @@ class RenderTableViewport extends RenderTwoDimensionalViewport {
return;
}

assert(_debugCheckPinnedSpanExtents());

final double? offsetIntoColumn = _firstNonPinnedColumn != null
? horizontalOffset.pixels -
_columnMetrics[_firstNonPinnedColumn]!.leadingOffset -
Expand Down
162 changes: 162 additions & 0 deletions packages/two_dimensional_scrollables/test/table_view/table_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -1159,6 +1160,167 @@ void main() {
SystemMouseCursors.basic,
);
});

testWidgets('asserts pinned span extents cannot exceed viewport dimensions',
(WidgetTester tester) async {
final List<Object> exceptions = <Object>[];
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<Object> exceptions = <Object>[];
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.',
),
);
});
});
}

Expand Down