@@ -1790,6 +1790,136 @@ void main() {
17901790 ),
17911791 );
17921792 });
1793+
1794+ testWidgets ('supports heading levels' , (WidgetTester tester) async {
1795+ // Default: not a heading.
1796+ expect (
1797+ Semantics (child: const Text ('dummy text' )).properties.headingLevel,
1798+ isNull,
1799+ );
1800+
1801+ // Headings level 1-6.
1802+ for (int level = 1 ; level <= 6 ; level++ ) {
1803+ final Semantics semantics = Semantics (
1804+ headingLevel: level,
1805+ child: const Text ('dummy text' ),
1806+ );
1807+ expect (semantics.properties.headingLevel, level);
1808+ }
1809+
1810+ // Invalid heading levels.
1811+ for (final int badLevel in const < int > [- 1 , 0 , 7 , 8 , 9 ]) {
1812+ expect (
1813+ () => Semantics (
1814+ headingLevel: badLevel,
1815+ child: const Text ('dummy text' ),
1816+ ),
1817+ throwsAssertionError,
1818+ );
1819+ }
1820+ });
1821+
1822+ testWidgets ('parent heading level takes precendence when it absorbs a child' , (WidgetTester tester) async {
1823+ final SemanticsTester semantics = SemanticsTester (tester);
1824+
1825+ Future <SemanticsConfiguration > pumpHeading (int ? level) async {
1826+ final ValueKey <String > key = ValueKey <String >('heading-$level ' );
1827+ await tester.pumpWidget (
1828+ Semantics (
1829+ key: key,
1830+ headingLevel: level,
1831+ child: Text (
1832+ 'Heading level $level ' ,
1833+ textDirection: TextDirection .ltr,
1834+ ),
1835+ )
1836+ );
1837+ final RenderSemanticsAnnotations object = tester.renderObject <RenderSemanticsAnnotations >(find.byKey (key));
1838+ final SemanticsConfiguration config = SemanticsConfiguration ();
1839+ object.describeSemanticsConfiguration (config);
1840+ return config;
1841+ }
1842+
1843+ // Tuples contain (parent level, child level, expected combined level).
1844+ final List <(int , int , int )> scenarios = < (int , int , int )> [
1845+ // Case: neither are headings
1846+ (0 , 0 , 0 ), // expect not a heading
1847+
1848+ // Case: parent not a heading, child always wins.
1849+ (0 , 1 , 1 ),
1850+ (0 , 2 , 2 ),
1851+
1852+ // Case: child not a heading, parent always wins.
1853+ (1 , 0 , 1 ),
1854+ (2 , 0 , 2 ),
1855+
1856+ // Case: child heading level higher, parent still wins.
1857+ (3 , 2 , 3 ),
1858+ (4 , 1 , 4 ),
1859+
1860+ // Case: parent heading level higher, parent still wins.
1861+ (2 , 3 , 2 ),
1862+ (1 , 5 , 1 ),
1863+ ];
1864+
1865+ for (final (int , int , int ) scenario in scenarios) {
1866+ final int parentLevel = scenario.$1;
1867+ final int childLevel = scenario.$2;
1868+ final int resultLevel = scenario.$3;
1869+
1870+ final SemanticsConfiguration parent = await pumpHeading (parentLevel == 0 ? null : parentLevel);
1871+ final SemanticsConfiguration child = SemanticsConfiguration ()
1872+ ..headingLevel = childLevel;
1873+ parent.absorb (child);
1874+ expect (
1875+ reason: 'parent heading level is $parentLevel , '
1876+ 'child heading level is $childLevel , '
1877+ 'expecting $resultLevel .' ,
1878+ parent.headingLevel, resultLevel);
1879+ }
1880+
1881+ semantics.dispose ();
1882+ });
1883+
1884+ testWidgets ('applies heading semantics to semantics tree' , (WidgetTester tester) async {
1885+ final SemanticsTester semantics = SemanticsTester (tester);
1886+
1887+ await tester.pumpWidget (
1888+ MaterialApp (
1889+ home: Scaffold (
1890+ appBar: AppBar (title: const Text ('Headings' )),
1891+ body: ListView (
1892+ children: < Widget > [
1893+ for (int level = 1 ; level <= 6 ; level++ )
1894+ Semantics (
1895+ key: ValueKey <String >('heading-$level ' ),
1896+ headingLevel: level,
1897+ child: Text ('Heading level $level ' ),
1898+ ),
1899+ const Text ('This is not a heading' ),
1900+ ],
1901+ ),
1902+ ),
1903+ ),
1904+ );
1905+
1906+ for (int level = 1 ; level <= 6 ; level++ ) {
1907+ final ValueKey <String > key = ValueKey <String >('heading-$level ' );
1908+ final SemanticsNode node = tester.getSemantics (find.byKey (key));
1909+ expect (
1910+ '$node ' ,
1911+ contains ('headingLevel: $level ' ),
1912+ );
1913+ }
1914+
1915+ final SemanticsNode notHeading = tester.getSemantics (find.text ('This is not a heading' ));
1916+ expect (
1917+ notHeading,
1918+ isNot (contains ('headingLevel' )),
1919+ );
1920+
1921+ semantics.dispose ();
1922+ });
17931923}
17941924
17951925class CustomSortKey extends OrdinalSortKey {
0 commit comments