Add table related semantics role (#162339)
issue: https://github.com/flutter/flutter/issues/45205 This PR added roles: table,cell,row,columnheader in engine side. Also added table, cell , columnheader in framework widgets `Table` and `DataTable`. This PR didn't add `row` in framework side because right now TableCell is ParentDataWidget expecting its parent to be a Table, tableRow is not a widget or a renderObject, but just some data. If we want to add a role to `row`, we need to do some refactor to `TableCell`, `TableRow` and `Table`. If we need to add row, I will do that in another PR. ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
0fc52bc91f
commit
7cf38fd93c
@ -42731,6 +42731,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/route.dart + ../../
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/table.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tabs.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tappable.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart + ../../../flutter/LICENSE
|
||||
@ -45705,6 +45706,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/route.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/table.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tabs.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tappable.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart
|
||||
|
@ -348,14 +348,14 @@ enum SemanticsRole {
|
||||
|
||||
/// A tab button.
|
||||
///
|
||||
/// see also:
|
||||
/// See also:
|
||||
///
|
||||
/// * [tabBar], which is the role for containers of tab buttons.
|
||||
tab,
|
||||
|
||||
/// Contains tab buttons.
|
||||
///
|
||||
/// see also:
|
||||
/// See also:
|
||||
///
|
||||
/// * [tab], which is the role for tab buttons.
|
||||
tabBar,
|
||||
@ -368,6 +368,34 @@ enum SemanticsRole {
|
||||
|
||||
/// An alert dialog.
|
||||
alertDialog,
|
||||
|
||||
/// A table structure containing data arranged in rows and columns.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [cell], [row], [columnHeader] for table related roles.
|
||||
table,
|
||||
|
||||
/// A cell in a [table] that does not contain column or row header information.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [table],[row], [columnHeader] for table related roles.
|
||||
cell,
|
||||
|
||||
/// A row of [cell]s or or [columnHeader]s in a [table].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [table] ,[cell],[columnHeader] for table related roles.
|
||||
row,
|
||||
|
||||
/// A cell in a [table] contains header information for a column.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [table] ,[cell], [row] for table related roles.
|
||||
columnHeader,
|
||||
}
|
||||
|
||||
/// A Boolean value that can be associated with a semantics node.
|
||||
|
@ -68,6 +68,10 @@ enum class SemanticsRole : int32_t {
|
||||
kTab = 1,
|
||||
kTabBar = 2,
|
||||
kTabPanel = 3,
|
||||
kTable = 4,
|
||||
kCell = 5,
|
||||
kRow = 6,
|
||||
kColumnHeader = 7,
|
||||
};
|
||||
|
||||
/// C/C++ representation of `SemanticsFlags` defined in
|
||||
|
@ -256,7 +256,19 @@ class SemanticsFlag {
|
||||
}
|
||||
|
||||
// Mirrors engine/src/flutter/lib/ui/semantics.dart
|
||||
enum SemanticsRole { none, tab, tabBar, tabPanel, dialog, alertDialog }
|
||||
|
||||
enum SemanticsRole {
|
||||
none,
|
||||
tab,
|
||||
tabBar,
|
||||
tabPanel,
|
||||
dialog,
|
||||
alertDialog,
|
||||
table,
|
||||
cell,
|
||||
row,
|
||||
columnHeader,
|
||||
}
|
||||
|
||||
// When adding a new StringAttributeType, the classes in these file must be
|
||||
// updated as well.
|
||||
|
@ -160,6 +160,7 @@ export 'engine/semantics/route.dart';
|
||||
export 'engine/semantics/scrollable.dart';
|
||||
export 'engine/semantics/semantics.dart';
|
||||
export 'engine/semantics/semantics_helper.dart';
|
||||
export 'engine/semantics/table.dart';
|
||||
export 'engine/semantics/tabs.dart';
|
||||
export 'engine/semantics/tappable.dart';
|
||||
export 'engine/semantics/text_field.dart';
|
||||
|
@ -16,6 +16,7 @@ export 'semantics/platform_view.dart';
|
||||
export 'semantics/scrollable.dart';
|
||||
export 'semantics/semantics.dart';
|
||||
export 'semantics/semantics_helper.dart';
|
||||
export 'semantics/table.dart';
|
||||
export 'semantics/tabs.dart';
|
||||
export 'semantics/tappable.dart';
|
||||
export 'semantics/text_field.dart';
|
||||
|
@ -32,6 +32,7 @@ import 'platform_view.dart';
|
||||
import 'route.dart';
|
||||
import 'scrollable.dart';
|
||||
import 'semantics_helper.dart';
|
||||
import 'table.dart';
|
||||
import 'tabs.dart';
|
||||
import 'tappable.dart';
|
||||
import 'text_field.dart';
|
||||
@ -422,6 +423,18 @@ enum EngineSemanticsRole {
|
||||
/// An alert dialog.
|
||||
alertDialog,
|
||||
|
||||
/// A table structure containing data arranged in rows and columns.
|
||||
table,
|
||||
|
||||
/// A cell in a [table] that does not contain column or row header information.
|
||||
cell,
|
||||
|
||||
/// A row of [cell]s or or [columnHeader]s in a [table].
|
||||
row,
|
||||
|
||||
/// A cell in a [table] contains header information for a column.
|
||||
columnHeader,
|
||||
|
||||
/// A role used when a more specific role cannot be assigend to
|
||||
/// a [SemanticsObject].
|
||||
///
|
||||
@ -1755,6 +1768,14 @@ class SemanticsObject {
|
||||
return EngineSemanticsRole.dialog;
|
||||
case ui.SemanticsRole.alertDialog:
|
||||
return EngineSemanticsRole.alertDialog;
|
||||
case ui.SemanticsRole.table:
|
||||
return EngineSemanticsRole.table;
|
||||
case ui.SemanticsRole.cell:
|
||||
return EngineSemanticsRole.cell;
|
||||
case ui.SemanticsRole.row:
|
||||
return EngineSemanticsRole.row;
|
||||
case ui.SemanticsRole.columnHeader:
|
||||
return EngineSemanticsRole.columnHeader;
|
||||
case ui.SemanticsRole.none:
|
||||
// fallback to checking semantics properties.
|
||||
}
|
||||
@ -1806,6 +1827,10 @@ class SemanticsObject {
|
||||
EngineSemanticsRole.tabPanel => SemanticTabPanel(this),
|
||||
EngineSemanticsRole.dialog => SemanticDialog(this),
|
||||
EngineSemanticsRole.alertDialog => SemanticAlertDialog(this),
|
||||
EngineSemanticsRole.table => SemanticTable(this),
|
||||
EngineSemanticsRole.cell => SemanticCell(this),
|
||||
EngineSemanticsRole.row => SemanticRow(this),
|
||||
EngineSemanticsRole.columnHeader => SemanticColumnHeader(this),
|
||||
EngineSemanticsRole.generic => GenericRole(this),
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,82 @@
|
||||
// Copyright 2013 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 'label_and_value.dart';
|
||||
import 'semantics.dart';
|
||||
|
||||
/// Indicates a table element.
|
||||
///
|
||||
/// Uses aria table role to convey this semantic information to the element.
|
||||
///
|
||||
/// Screen-readers takes advantage of "aria-label" to describe the visual.
|
||||
class SemanticTable extends SemanticRole {
|
||||
SemanticTable(SemanticsObject semanticsObject)
|
||||
: super.withBasics(
|
||||
EngineSemanticsRole.table,
|
||||
semanticsObject,
|
||||
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
||||
) {
|
||||
setAriaRole('table');
|
||||
}
|
||||
|
||||
@override
|
||||
bool focusAsRouteDefault() => focusable?.focusAsRouteDefault() ?? false;
|
||||
}
|
||||
|
||||
/// Indicates a table cell element.
|
||||
///
|
||||
/// Uses aria cell role to convey this semantic information to the element.
|
||||
///
|
||||
/// Screen-readers takes advantage of "aria-label" to describe the visual.
|
||||
class SemanticCell extends SemanticRole {
|
||||
SemanticCell(SemanticsObject semanticsObject)
|
||||
: super.withBasics(
|
||||
EngineSemanticsRole.cell,
|
||||
semanticsObject,
|
||||
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
||||
) {
|
||||
setAriaRole('cell');
|
||||
}
|
||||
|
||||
@override
|
||||
bool focusAsRouteDefault() => focusable?.focusAsRouteDefault() ?? false;
|
||||
}
|
||||
|
||||
/// Indicates a table row element.
|
||||
///
|
||||
/// Uses aria row role to convey this semantic information to the element.
|
||||
///
|
||||
/// Screen-readers takes advantage of "aria-label" to describe the visual.
|
||||
class SemanticRow extends SemanticRole {
|
||||
SemanticRow(SemanticsObject semanticsObject)
|
||||
: super.withBasics(
|
||||
EngineSemanticsRole.row,
|
||||
semanticsObject,
|
||||
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
||||
) {
|
||||
setAriaRole('row');
|
||||
}
|
||||
|
||||
@override
|
||||
bool focusAsRouteDefault() => focusable?.focusAsRouteDefault() ?? false;
|
||||
}
|
||||
|
||||
/// Indicates a table column header element.
|
||||
///
|
||||
/// Uses aria columnheader role to convey this semantic information to the element.
|
||||
///
|
||||
/// Screen-readers takes advantage of "aria-label" to describe the visual.
|
||||
class SemanticColumnHeader extends SemanticRole {
|
||||
SemanticColumnHeader(SemanticsObject semanticsObject)
|
||||
: super.withBasics(
|
||||
EngineSemanticsRole.columnHeader,
|
||||
semanticsObject,
|
||||
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
||||
) {
|
||||
setAriaRole('columnheader');
|
||||
}
|
||||
|
||||
@override
|
||||
bool focusAsRouteDefault() => focusable?.focusAsRouteDefault() ?? false;
|
||||
}
|
@ -123,6 +123,9 @@ void runSemanticsTests() {
|
||||
group('tabs', () {
|
||||
_testTabs();
|
||||
});
|
||||
group('table', () {
|
||||
_testTables();
|
||||
});
|
||||
}
|
||||
|
||||
void _testSemanticRole() {
|
||||
@ -3723,6 +3726,94 @@ void _testTabs() {
|
||||
});
|
||||
}
|
||||
|
||||
void _testTables() {
|
||||
test('nodes with table role', () {
|
||||
semantics()
|
||||
..debugOverrideTimestampFunction(() => _testTime)
|
||||
..semanticsEnabled = true;
|
||||
|
||||
SemanticsObject pumpSemantics() {
|
||||
final SemanticsTester tester = SemanticsTester(owner());
|
||||
tester.updateNode(
|
||||
id: 0,
|
||||
role: ui.SemanticsRole.table,
|
||||
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||
);
|
||||
tester.apply();
|
||||
return tester.getSemanticsObject(0);
|
||||
}
|
||||
|
||||
final SemanticsObject object = pumpSemantics();
|
||||
expect(object.semanticRole?.kind, EngineSemanticsRole.table);
|
||||
expect(object.element.getAttribute('role'), 'table');
|
||||
});
|
||||
|
||||
test('nodes with cell role', () {
|
||||
semantics()
|
||||
..debugOverrideTimestampFunction(() => _testTime)
|
||||
..semanticsEnabled = true;
|
||||
|
||||
SemanticsObject pumpSemantics() {
|
||||
final SemanticsTester tester = SemanticsTester(owner());
|
||||
tester.updateNode(
|
||||
id: 0,
|
||||
role: ui.SemanticsRole.cell,
|
||||
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||
);
|
||||
tester.apply();
|
||||
return tester.getSemanticsObject(0);
|
||||
}
|
||||
|
||||
final SemanticsObject object = pumpSemantics();
|
||||
expect(object.semanticRole?.kind, EngineSemanticsRole.cell);
|
||||
expect(object.element.getAttribute('role'), 'cell');
|
||||
});
|
||||
|
||||
test('nodes with row role', () {
|
||||
semantics()
|
||||
..debugOverrideTimestampFunction(() => _testTime)
|
||||
..semanticsEnabled = true;
|
||||
|
||||
SemanticsObject pumpSemantics() {
|
||||
final SemanticsTester tester = SemanticsTester(owner());
|
||||
tester.updateNode(
|
||||
id: 0,
|
||||
role: ui.SemanticsRole.row,
|
||||
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||
);
|
||||
tester.apply();
|
||||
return tester.getSemanticsObject(0);
|
||||
}
|
||||
|
||||
final SemanticsObject object = pumpSemantics();
|
||||
expect(object.semanticRole?.kind, EngineSemanticsRole.row);
|
||||
expect(object.element.getAttribute('role'), 'row');
|
||||
});
|
||||
|
||||
test('nodes with column header role', () {
|
||||
semantics()
|
||||
..debugOverrideTimestampFunction(() => _testTime)
|
||||
..semanticsEnabled = true;
|
||||
|
||||
SemanticsObject pumpSemantics() {
|
||||
final SemanticsTester tester = SemanticsTester(owner());
|
||||
tester.updateNode(
|
||||
id: 0,
|
||||
role: ui.SemanticsRole.columnHeader,
|
||||
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||
);
|
||||
tester.apply();
|
||||
return tester.getSemanticsObject(0);
|
||||
}
|
||||
|
||||
final SemanticsObject object = pumpSemantics();
|
||||
expect(object.semanticRole?.kind, EngineSemanticsRole.columnHeader);
|
||||
expect(object.element.getAttribute('role'), 'columnheader');
|
||||
});
|
||||
|
||||
semantics().semanticsEnabled = false;
|
||||
}
|
||||
|
||||
/// A facade in front of [ui.SemanticsUpdateBuilder.updateNode] that
|
||||
/// supplies default values for semantics attributes.
|
||||
void updateNode(
|
||||
|
@ -7,6 +7,7 @@
|
||||
library;
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' show SemanticsRole;
|
||||
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -879,22 +880,25 @@ class DataTable extends StatelessWidget {
|
||||
}) {
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
final DataTableThemeData dataTableTheme = DataTableTheme.of(context);
|
||||
label = Row(
|
||||
textDirection: numeric ? TextDirection.rtl : null,
|
||||
mainAxisAlignment: headingRowAlignment,
|
||||
children: <Widget>[
|
||||
if (headingRowAlignment == MainAxisAlignment.center && onSort != null)
|
||||
const SizedBox(width: _SortArrowState._arrowIconSize + _sortArrowPadding),
|
||||
label,
|
||||
if (onSort != null) ...<Widget>[
|
||||
_SortArrow(
|
||||
visible: sorted,
|
||||
up: sorted ? ascending : null,
|
||||
duration: _sortArrowAnimationDuration,
|
||||
),
|
||||
const SizedBox(width: _sortArrowPadding),
|
||||
label = Semantics(
|
||||
role: SemanticsRole.columnHeader,
|
||||
child: Row(
|
||||
textDirection: numeric ? TextDirection.rtl : null,
|
||||
mainAxisAlignment: headingRowAlignment,
|
||||
children: <Widget>[
|
||||
if (headingRowAlignment == MainAxisAlignment.center && onSort != null)
|
||||
const SizedBox(width: _SortArrowState._arrowIconSize + _sortArrowPadding),
|
||||
label,
|
||||
if (onSort != null) ...<Widget>[
|
||||
_SortArrow(
|
||||
visible: sorted,
|
||||
up: sorted ? ascending : null,
|
||||
duration: _sortArrowAnimationDuration,
|
||||
),
|
||||
const SizedBox(width: _sortArrowPadding),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final TextStyle effectiveHeadingTextStyle =
|
||||
@ -1013,7 +1017,7 @@ class DataTable extends StatelessWidget {
|
||||
child: label,
|
||||
);
|
||||
}
|
||||
return label;
|
||||
return TableCell(child: label);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -4,8 +4,10 @@
|
||||
|
||||
import 'dart:collection';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' show SemanticsRole;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/semantics.dart';
|
||||
|
||||
import 'box.dart';
|
||||
import 'object.dart';
|
||||
@ -601,6 +603,13 @@ class RenderTable extends RenderBox {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
config.role = SemanticsRole.table;
|
||||
config.explicitChildNodes = true;
|
||||
}
|
||||
|
||||
/// Replaces the children of this table with the given cells.
|
||||
///
|
||||
/// The cells are divided into the specified number of columns before
|
||||
|
@ -110,8 +110,14 @@ sealed class _DebugSemanticsRoleChecks {
|
||||
SemanticsRole.tab => _semanticsTab,
|
||||
SemanticsRole.tabBar => _semanticsTabBar,
|
||||
SemanticsRole.tabPanel => _noCheckRequired,
|
||||
SemanticsRole.table => _noCheckRequired,
|
||||
SemanticsRole.cell => _semanticsCell,
|
||||
SemanticsRole.row => _unimplementedError,
|
||||
SemanticsRole.columnHeader => _semanticsColumnHeader,
|
||||
}(node);
|
||||
|
||||
static FlutterError? _unimplementedError(SemanticsNode node) =>
|
||||
FlutterError('This semantics role is not implemented');
|
||||
static FlutterError? _noCheckRequired(SemanticsNode node) => null;
|
||||
|
||||
static FlutterError? _semanticsTab(SemanticsNode node) {
|
||||
@ -140,6 +146,20 @@ sealed class _DebugSemanticsRoleChecks {
|
||||
});
|
||||
return error;
|
||||
}
|
||||
|
||||
static FlutterError? _semanticsCell(SemanticsNode node) {
|
||||
if (node.parent?.role != SemanticsRole.table) {
|
||||
return FlutterError('A cell must be a child of a table');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static FlutterError? _semanticsColumnHeader(SemanticsNode node) {
|
||||
if (node.parent?.role != SemanticsRole.table) {
|
||||
return FlutterError('A columnHeader must be a child of a table');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// A tag for a [SemanticsNode].
|
||||
|
@ -7,6 +7,7 @@
|
||||
library;
|
||||
|
||||
import 'dart:collection';
|
||||
import 'dart:ui' show SemanticsRole;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
@ -438,7 +439,8 @@ class _TableElement extends RenderObjectElement {
|
||||
/// as the [child].
|
||||
class TableCell extends ParentDataWidget<TableCellParentData> {
|
||||
/// Creates a widget that controls how a child of a [Table] is aligned.
|
||||
const TableCell({super.key, this.verticalAlignment, required super.child});
|
||||
TableCell({super.key, this.verticalAlignment, required Widget child})
|
||||
: super(child: Semantics(role: SemanticsRole.cell, child: child));
|
||||
|
||||
/// How this cell is aligned vertically.
|
||||
final TableCellVerticalAlignment? verticalAlignment;
|
||||
|
@ -6,6 +6,7 @@
|
||||
library;
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' show SemanticsRole;
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -13,6 +14,7 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:vector_math/vector_math_64.dart' show Matrix3;
|
||||
|
||||
import '../widgets/semantics_tester.dart';
|
||||
import 'data_table_test_utils.dart';
|
||||
|
||||
void main() {
|
||||
@ -2211,6 +2213,78 @@ void main() {
|
||||
expect(table.columnWidths![1], const IntrinsicColumnWidth());
|
||||
expect(table.columnWidths![2], const IntrinsicColumnWidth(flex: 1));
|
||||
});
|
||||
|
||||
testWidgets('DataTable has correct roles in semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: DataTable(
|
||||
columns: const <DataColumn>[
|
||||
DataColumn(label: Text('Column 1')),
|
||||
DataColumn(label: Text('Column 2')),
|
||||
],
|
||||
rows: const <DataRow>[
|
||||
DataRow(
|
||||
cells: <DataCell>[DataCell(Text('Data Cell 1')), DataCell(Text('Data Cell 2'))],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestSemantics expectedSemantics = TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
role: SemanticsRole.table,
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
label: 'Column 1',
|
||||
textDirection: TextDirection.ltr,
|
||||
role: SemanticsRole.columnHeader,
|
||||
),
|
||||
TestSemantics(
|
||||
label: 'Column 2',
|
||||
textDirection: TextDirection.ltr,
|
||||
role: SemanticsRole.columnHeader,
|
||||
),
|
||||
TestSemantics(
|
||||
label: 'Data Cell 1',
|
||||
textDirection: TextDirection.ltr,
|
||||
role: SemanticsRole.cell,
|
||||
),
|
||||
TestSemantics(
|
||||
label: 'Data Cell 2',
|
||||
textDirection: TextDirection.ltr,
|
||||
role: SemanticsRole.cell,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
expect(
|
||||
semantics,
|
||||
hasSemantics(expectedSemantics, ignoreTransform: true, ignoreId: true, ignoreRect: true),
|
||||
);
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
RenderParagraph _getTextRenderObject(WidgetTester tester, String text) {
|
||||
|
@ -2,9 +2,11 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' show SemanticsRole;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
class TestStatefulWidget extends StatefulWidget {
|
||||
const TestStatefulWidget({super.key});
|
||||
@ -951,5 +953,64 @@ void main() {
|
||||
expect(boxD.size.height, greaterThan(boxA.size.height));
|
||||
});
|
||||
|
||||
// TODO(ianh): Test handling of TableCell object
|
||||
testWidgets('Table has correct roles in semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Table(
|
||||
children: <TableRow>[
|
||||
TableRow(
|
||||
children: <Widget>[
|
||||
TableCell(child: const Text('Data Cell 1')),
|
||||
TableCell(child: const Text('Data Cell 2')),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestSemantics expectedSemantics = TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
role: SemanticsRole.table,
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
label: 'Data Cell 1',
|
||||
textDirection: TextDirection.ltr,
|
||||
role: SemanticsRole.cell,
|
||||
),
|
||||
TestSemantics(
|
||||
label: 'Data Cell 2',
|
||||
textDirection: TextDirection.ltr,
|
||||
role: SemanticsRole.cell,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
expect(
|
||||
semantics,
|
||||
hasSemantics(expectedSemantics, ignoreTransform: true, ignoreId: true, ignoreRect: true),
|
||||
);
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user