| // 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. |
| |
| // flutter_ignore_for_file: golden_tag (see analyze.dart) |
| |
| import 'dart:math' as math; |
| import 'dart:typed_data'; |
| |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| /// Class that makes it easy to mock common toStringDeep behavior. |
| class _MockToStringDeep { |
| _MockToStringDeep(String str) : _lines = <String>[] { |
| final List<String> lines = str.split('\n'); |
| for (int i = 0; i < lines.length - 1; ++i) { |
| _lines.add('${lines[i]}\n'); |
| } |
| |
| // If the last line is empty, that really just means that the previous |
| // line was terminated with a line break. |
| if (lines.isNotEmpty && lines.last.isNotEmpty) { |
| _lines.add(lines.last); |
| } |
| } |
| |
| _MockToStringDeep.fromLines(this._lines); |
| |
| /// Lines in the message to display when [toStringDeep] is called. |
| /// For correct toStringDeep behavior, each line should be terminated with a |
| /// line break. |
| final List<String> _lines; |
| |
| String toStringDeep({ String prefixLineOne = '', String prefixOtherLines = '' }) { |
| final StringBuffer sb = StringBuffer(); |
| if (_lines.isNotEmpty) { |
| sb.write('$prefixLineOne${_lines.first}'); |
| } |
| |
| for (int i = 1; i < _lines.length; ++i) { |
| sb.write('$prefixOtherLines${_lines[i]}'); |
| } |
| |
| return sb.toString(); |
| } |
| |
| @override |
| String toString() => toStringDeep(); |
| } |
| |
| void main() { |
| test('hasOneLineDescription', () { |
| expect('Hello', hasOneLineDescription); |
| expect('Hello\nHello', isNot(hasOneLineDescription)); |
| expect(' Hello', isNot(hasOneLineDescription)); |
| expect('Hello ', isNot(hasOneLineDescription)); |
| expect(Object(), isNot(hasOneLineDescription)); |
| }); |
| |
| test('hasAGoodToStringDeep', () { |
| expect(_MockToStringDeep('Hello\n World\n'), hasAGoodToStringDeep); |
| // Not terminated with a line break. |
| expect(_MockToStringDeep('Hello\n World'), isNot(hasAGoodToStringDeep)); |
| // Trailing whitespace on last line. |
| expect(_MockToStringDeep('Hello\n World \n'), |
| isNot(hasAGoodToStringDeep)); |
| expect(_MockToStringDeep('Hello\n World\t\n'), |
| isNot(hasAGoodToStringDeep)); |
| // Leading whitespace on line 1. |
| expect(_MockToStringDeep(' Hello\n World \n'), |
| isNot(hasAGoodToStringDeep)); |
| |
| // Single line. |
| expect(_MockToStringDeep('Hello World'), isNot(hasAGoodToStringDeep)); |
| expect(_MockToStringDeep('Hello World\n'), isNot(hasAGoodToStringDeep)); |
| |
| expect(_MockToStringDeep('Hello: World\nFoo: bar\n'), |
| hasAGoodToStringDeep); |
| expect(_MockToStringDeep('Hello: World\nFoo: 42\n'), |
| hasAGoodToStringDeep); |
| // Contains default Object.toString(). |
| expect(_MockToStringDeep('Hello: World\nFoo: ${Object()}\n'), |
| isNot(hasAGoodToStringDeep)); |
| expect(_MockToStringDeep('A\n├─B\n'), hasAGoodToStringDeep); |
| expect(_MockToStringDeep('A\n├─B\n╘══════\n'), hasAGoodToStringDeep); |
| // Last line is all whitespace or vertical line art. |
| expect(_MockToStringDeep('A\n├─B\n\n'), isNot(hasAGoodToStringDeep)); |
| expect(_MockToStringDeep('A\n├─B\n│\n'), isNot(hasAGoodToStringDeep)); |
| expect(_MockToStringDeep('A\n├─B\n│\n'), isNot(hasAGoodToStringDeep)); |
| expect(_MockToStringDeep('A\n├─B\n│\n'), isNot(hasAGoodToStringDeep)); |
| expect(_MockToStringDeep('A\n├─B\n╎\n'), isNot(hasAGoodToStringDeep)); |
| expect(_MockToStringDeep('A\n├─B\n║\n'), isNot(hasAGoodToStringDeep)); |
| expect(_MockToStringDeep('A\n├─B\n │\n'), isNot(hasAGoodToStringDeep)); |
| expect(_MockToStringDeep('A\n├─B\n ╎\n'), isNot(hasAGoodToStringDeep)); |
| expect(_MockToStringDeep('A\n├─B\n ║\n'), isNot(hasAGoodToStringDeep)); |
| expect(_MockToStringDeep('A\n├─B\n ││\n'), isNot(hasAGoodToStringDeep)); |
| |
| expect(_MockToStringDeep( |
| 'A\n' |
| '├─B\n' |
| '│\n' |
| '└─C\n'), hasAGoodToStringDeep); |
| // Last line is all whitespace or vertical line art. |
| expect(_MockToStringDeep( |
| 'A\n' |
| '├─B\n' |
| '│\n'), isNot(hasAGoodToStringDeep)); |
| |
| expect( |
| _MockToStringDeep.fromLines(<String>[ |
| 'Paragraph#00000\n', |
| ' │ size: (400x200)\n', |
| ' ╘═╦══ text ═══\n', |
| ' ║ TextSpan:\n', |
| ' ║ "I polished up that handle so carefullee\n', |
| ' ║ That now I am the Ruler of the Queen\'s Navee!"\n', |
| ' ╚═══════════\n', |
| ]), |
| hasAGoodToStringDeep, |
| ); |
| |
| // Text span |
| expect( |
| _MockToStringDeep.fromLines(<String>[ |
| 'Paragraph#00000\n', |
| ' │ size: (400x200)\n', |
| ' ╘═╦══ text ═══\n', |
| ' ║ TextSpan:\n', |
| ' ║ "I polished up that handle so carefullee\nThat now I am the Ruler of the Queen\'s Navee!"\n', |
| ' ╚═══════════\n', |
| ]), |
| isNot(hasAGoodToStringDeep), |
| ); |
| }); |
| |
| test('equalsIgnoringHashCodes', () { |
| expect('Foo#34219', equalsIgnoringHashCodes('Foo#00000')); |
| expect('Foo#34219', equalsIgnoringHashCodes('Foo#12345')); |
| expect('Foo#34219', equalsIgnoringHashCodes('Foo#abcdf')); |
| expect('Foo#34219', isNot(equalsIgnoringHashCodes('Foo'))); |
| expect('Foo#34219', isNot(equalsIgnoringHashCodes('Foo#'))); |
| expect('Foo#34219', isNot(equalsIgnoringHashCodes('Foo#0'))); |
| expect('Foo#34219', isNot(equalsIgnoringHashCodes('Foo#00'))); |
| expect('Foo#34219', isNot(equalsIgnoringHashCodes('Foo#00000 '))); |
| expect('Foo#34219', isNot(equalsIgnoringHashCodes('Foo#000000'))); |
| expect('Foo#34219', isNot(equalsIgnoringHashCodes('Foo#123456'))); |
| |
| expect('Foo#34219:', equalsIgnoringHashCodes('Foo#00000:')); |
| expect('Foo#34219:', isNot(equalsIgnoringHashCodes('Foo#00000'))); |
| |
| expect('Foo#a3b4d', equalsIgnoringHashCodes('Foo#00000')); |
| expect('Foo#a3b4d', equalsIgnoringHashCodes('Foo#12345')); |
| expect('Foo#a3b4d', equalsIgnoringHashCodes('Foo#abcdf')); |
| expect('Foo#a3b4d', isNot(equalsIgnoringHashCodes('Foo'))); |
| expect('Foo#a3b4d', isNot(equalsIgnoringHashCodes('Foo#'))); |
| expect('Foo#a3b4d', isNot(equalsIgnoringHashCodes('Foo#0'))); |
| expect('Foo#a3b4d', isNot(equalsIgnoringHashCodes('Foo#00'))); |
| expect('Foo#a3b4d', isNot(equalsIgnoringHashCodes('Foo#00000 '))); |
| expect('Foo#a3b4d', isNot(equalsIgnoringHashCodes('Foo#000000'))); |
| expect('Foo#a3b4d', isNot(equalsIgnoringHashCodes('Foo#123456'))); |
| |
| expect('FOO#A3b4D', equalsIgnoringHashCodes('FOO#00000')); |
| expect('FOO#A3b4J', isNot(equalsIgnoringHashCodes('FOO#00000'))); |
| |
| expect('Foo#12345(Bar#9110f)', |
| equalsIgnoringHashCodes('Foo#00000(Bar#00000)')); |
| expect('Foo#12345(Bar#9110f)', |
| isNot(equalsIgnoringHashCodes('Foo#00000(Bar#)'))); |
| |
| expect('Foo', isNot(equalsIgnoringHashCodes('Foo#00000'))); |
| expect('Foo#', isNot(equalsIgnoringHashCodes('Foo#00000'))); |
| expect('Foo#3421', isNot(equalsIgnoringHashCodes('Foo#00000'))); |
| expect('Foo#342193', isNot(equalsIgnoringHashCodes('Foo#00000'))); |
| expect(<String>['Foo#a3b4d'], equalsIgnoringHashCodes(<String>['Foo#12345'])); |
| expect( |
| <String>['Foo#a3b4d', 'Foo#12345'], |
| equalsIgnoringHashCodes(<String>['Foo#00000', 'Foo#00000']), |
| ); |
| expect( |
| <String>['Foo#a3b4d', 'Bar#12345'], |
| equalsIgnoringHashCodes(<String>['Foo#00000', 'Bar#00000']), |
| ); |
| expect( |
| <String>['Foo#a3b4d', 'Bar#12345'], |
| isNot(equalsIgnoringHashCodes(<String>['Bar#00000', 'Foo#00000'])), |
| ); |
| expect(<String>['Foo#a3b4d'], isNot(equalsIgnoringHashCodes(<String>['Foo']))); |
| expect( |
| <String>['Foo#a3b4d'], |
| isNot(equalsIgnoringHashCodes(<String>['Foo#00000', 'Bar#00000'])), |
| ); |
| }); |
| |
| test('moreOrLessEquals', () { |
| expect(0.0, moreOrLessEquals(1e-11)); |
| expect(1e-11, moreOrLessEquals(0.0)); |
| expect(-1e-11, moreOrLessEquals(0.0)); |
| |
| expect(0.0, isNot(moreOrLessEquals(1e11))); |
| expect(1e11, isNot(moreOrLessEquals(0.0))); |
| expect(-1e11, isNot(moreOrLessEquals(0.0))); |
| |
| expect(0.0, isNot(moreOrLessEquals(1.0))); |
| expect(1.0, isNot(moreOrLessEquals(0.0))); |
| expect(-1.0, isNot(moreOrLessEquals(0.0))); |
| |
| expect(1e-11, moreOrLessEquals(-1e-11)); |
| expect(-1e-11, moreOrLessEquals(1e-11)); |
| |
| expect(11.0, isNot(moreOrLessEquals(-11.0, epsilon: 1.0))); |
| expect(-11.0, isNot(moreOrLessEquals(11.0, epsilon: 1.0))); |
| |
| expect(11.0, moreOrLessEquals(-11.0, epsilon: 100.0)); |
| expect(-11.0, moreOrLessEquals(11.0, epsilon: 100.0)); |
| }); |
| |
| test('matrixMoreOrLessEquals', () { |
| expect( |
| Matrix4.rotationZ(math.pi), |
| matrixMoreOrLessEquals(Matrix4.fromList(<double>[ |
| -1, 0, 0, 0, |
| 0, -1, 0, 0, |
| 0, 0, 1, 0, |
| 0, 0, 0, 1, |
| ])) |
| ); |
| |
| expect( |
| Matrix4.rotationZ(math.pi), |
| matrixMoreOrLessEquals(Matrix4.fromList(<double>[ |
| -2, 0, 0, 0, |
| 0, -2, 0, 0, |
| 0, 0, 1, 0, |
| 0, 0, 0, 1, |
| ]), epsilon: 2) |
| ); |
| |
| expect( |
| Matrix4.rotationZ(math.pi), |
| isNot(matrixMoreOrLessEquals(Matrix4.fromList(<double>[ |
| -2, 0, 0, 0, |
| 0, -2, 0, 0, |
| 0, 0, 1, 0, |
| 0, 0, 0, 1, |
| ]))) |
| ); |
| }); |
| |
| test('rectMoreOrLessEquals', () { |
| expect( |
| const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| rectMoreOrLessEquals(const Rect.fromLTRB(0.0, 0.0, 10.0, 10.00000000001)), |
| ); |
| |
| expect( |
| const Rect.fromLTRB(11.0, 11.0, 20.0, 20.0), |
| isNot(rectMoreOrLessEquals(const Rect.fromLTRB(-11.0, -11.0, 20.0, 20.0), epsilon: 1.0)), |
| ); |
| |
| expect( |
| const Rect.fromLTRB(11.0, 11.0, 20.0, 20.0), |
| rectMoreOrLessEquals(const Rect.fromLTRB(-11.0, -11.0, 20.0, 20.0), epsilon: 100.0), |
| ); |
| }); |
| |
| test('within', () { |
| expect(0.0, within<double>(distance: 0.1, from: 0.05)); |
| expect(0.0, isNot(within<double>(distance: 0.1, from: 0.2))); |
| |
| expect(0, within<int>(distance: 1, from: 1)); |
| expect(0, isNot(within<int>(distance: 1, from: 2))); |
| |
| expect(const Color(0x00000000), within<Color>(distance: 1, from: const Color(0x01000000))); |
| expect(const Color(0x00000000), within<Color>(distance: 1, from: const Color(0x00010000))); |
| expect(const Color(0x00000000), within<Color>(distance: 1, from: const Color(0x00000100))); |
| expect(const Color(0x00000000), within<Color>(distance: 1, from: const Color(0x00000001))); |
| expect(const Color(0x00000000), within<Color>(distance: 1, from: const Color(0x01010101))); |
| expect(const Color(0x00000000), isNot(within<Color>(distance: 1, from: const Color(0x02000000)))); |
| |
| expect(const Offset(1.0, 0.0), within(distance: 1.0, from: Offset.zero)); |
| expect(const Offset(1.0, 0.0), isNot(within(distance: 1.0, from: const Offset(-1.0, 0.0)))); |
| |
| expect(const Rect.fromLTRB(0.0, 1.0, 2.0, 3.0), within<Rect>(distance: 4.0, from: const Rect.fromLTRB(1.0, 3.0, 5.0, 7.0))); |
| expect(const Rect.fromLTRB(0.0, 1.0, 2.0, 3.0), isNot(within<Rect>(distance: 3.9, from: const Rect.fromLTRB(1.0, 3.0, 5.0, 7.0)))); |
| |
| expect(const Size(1.0, 1.0), within<Size>(distance: 1.415, from: const Size(2.0, 2.0))); |
| expect(const Size(1.0, 1.0), isNot(within<Size>(distance: 1.414, from: const Size(2.0, 2.0)))); |
| |
| expect( |
| () => within<bool>(distance: 1, from: false), |
| throwsArgumentError, |
| ); |
| |
| expect( |
| () => within<int>(distance: 1, from: 2, distanceFunction: (int a, int b) => -1).matches(1, <dynamic, dynamic>{}), |
| throwsArgumentError, |
| ); |
| }); |
| |
| test('isSameColorAs', () { |
| expect( |
| const Color(0x87654321), |
| isSameColorAs(const _CustomColor(0x87654321)), |
| ); |
| |
| expect( |
| const _CustomColor(0x87654321), |
| isSameColorAs(const Color(0x87654321)), |
| ); |
| |
| expect( |
| const Color(0x12345678), |
| isNot(isSameColorAs(const _CustomColor(0x87654321))), |
| ); |
| |
| expect( |
| const _CustomColor(0x87654321), |
| isNot(isSameColorAs(const Color(0x12345678))), |
| ); |
| |
| expect( |
| const _CustomColor(0xFF123456), |
| isSameColorAs(const _CustomColor(0xFF123456, isEqual: false)), |
| ); |
| }); |
| |
| group('coversSameAreaAs', () { |
| test('empty Paths', () { |
| expect( |
| Path(), |
| coversSameAreaAs( |
| Path(), |
| areaToCompare: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| ), |
| ); |
| }); |
| |
| test('mismatch', () { |
| final Path rectPath = Path() |
| ..addRect(const Rect.fromLTRB(5.0, 5.0, 6.0, 6.0)); |
| expect( |
| Path(), |
| isNot(coversSameAreaAs( |
| rectPath, |
| areaToCompare: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| )), |
| ); |
| }); |
| |
| test('mismatch out of examined area', () { |
| final Path rectPath = Path() |
| ..addRect(const Rect.fromLTRB(5.0, 5.0, 6.0, 6.0)); |
| rectPath.addRect(const Rect.fromLTRB(5.0, 5.0, 6.0, 6.0)); |
| expect( |
| Path(), |
| coversSameAreaAs( |
| rectPath, |
| areaToCompare: const Rect.fromLTRB(0.0, 0.0, 4.0, 4.0), |
| ), |
| ); |
| }); |
| |
| test('differently constructed rects match', () { |
| final Path rectPath = Path() |
| ..addRect(const Rect.fromLTRB(5.0, 5.0, 6.0, 6.0)); |
| final Path linePath = Path() |
| ..moveTo(5.0, 5.0) |
| ..lineTo(5.0, 6.0) |
| ..lineTo(6.0, 6.0) |
| ..lineTo(6.0, 5.0) |
| ..close(); |
| expect( |
| linePath, |
| coversSameAreaAs( |
| rectPath, |
| areaToCompare: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| ), |
| ); |
| }); |
| |
| test('partially overlapping paths', () { |
| final Path rectPath = Path() |
| ..addRect(const Rect.fromLTRB(5.0, 5.0, 6.0, 6.0)); |
| final Path linePath = Path() |
| ..moveTo(5.0, 5.0) |
| ..lineTo(5.0, 6.0) |
| ..lineTo(6.0, 6.0) |
| ..lineTo(6.0, 5.5) |
| ..close(); |
| expect( |
| linePath, |
| isNot(coversSameAreaAs( |
| rectPath, |
| areaToCompare: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| )), |
| ); |
| }); |
| }); |
| |
| group('matchesGoldenFile', () { |
| late _FakeComparator comparator; |
| |
| Widget boilerplate(Widget child) { |
| return Directionality( |
| textDirection: TextDirection.ltr, |
| child: child, |
| ); |
| } |
| |
| setUp(() { |
| comparator = _FakeComparator(); |
| goldenFileComparator = comparator; |
| }); |
| |
| group('matches', () { |
| testWidgets('if comparator succeeds', (WidgetTester tester) async { |
| await tester.pumpWidget(boilerplate(const Text('hello'))); |
| final Finder finder = find.byType(Text); |
| await expectLater(finder, matchesGoldenFile('foo.png')); |
| expect(comparator.invocation, _ComparatorInvocation.compare); |
| expect(comparator.imageBytes, hasLength(greaterThan(0))); |
| expect(comparator.golden, Uri.parse('foo.png')); |
| }); |
| |
| testWidgets('list of integers', (WidgetTester tester) async { |
| await expectLater(<int>[1, 2], matchesGoldenFile('foo.png')); |
| expect(comparator.invocation, _ComparatorInvocation.compare); |
| expect(comparator.imageBytes, equals(<int>[1, 2])); |
| expect(comparator.golden, Uri.parse('foo.png')); |
| }); |
| |
| testWidgets('future list of integers', (WidgetTester tester) async { |
| await expectLater(Future<List<int>>.value(<int>[1, 2]), matchesGoldenFile('foo.png')); |
| expect(comparator.invocation, _ComparatorInvocation.compare); |
| expect(comparator.imageBytes, equals(<int>[1, 2])); |
| expect(comparator.golden, Uri.parse('foo.png')); |
| }); |
| }); |
| |
| group('does not match', () { |
| testWidgets('if comparator returns false', (WidgetTester tester) async { |
| comparator.behavior = _ComparatorBehavior.returnFalse; |
| await tester.pumpWidget(boilerplate(const Text('hello'))); |
| final Finder finder = find.byType(Text); |
| await expectLater( |
| () => expectLater(finder, matchesGoldenFile('foo.png')), |
| throwsA(isA<TestFailure>().having( |
| (TestFailure error) => error.message, |
| 'message', |
| contains('does not match'), |
| )), |
| ); |
| expect(comparator.invocation, _ComparatorInvocation.compare); |
| }); |
| |
| testWidgets('if comparator throws', (WidgetTester tester) async { |
| comparator.behavior = _ComparatorBehavior.throwTestFailure; |
| await tester.pumpWidget(boilerplate(const Text('hello'))); |
| final Finder finder = find.byType(Text); |
| await expectLater( |
| () => expectLater(finder, matchesGoldenFile('foo.png')), |
| throwsA(isA<TestFailure>().having( |
| (TestFailure error) => error.message, |
| 'message', |
| contains('fake message'), |
| )), |
| ); |
| expect(comparator.invocation, _ComparatorInvocation.compare); |
| }); |
| |
| testWidgets('if finder finds no widgets', (WidgetTester tester) async { |
| await tester.pumpWidget(boilerplate(Container())); |
| final Finder finder = find.byType(Text); |
| await expectLater( |
| () => expectLater(finder, matchesGoldenFile('foo.png')), |
| throwsA(isA<TestFailure>().having( |
| (TestFailure error) => error.message, |
| 'message', |
| contains('no widget was found'), |
| )), |
| ); |
| expect(comparator.invocation, isNull); |
| }); |
| |
| testWidgets('if finder finds multiple widgets', (WidgetTester tester) async { |
| await tester.pumpWidget(boilerplate(const Column( |
| children: <Widget>[Text('hello'), Text('world')], |
| ))); |
| final Finder finder = find.byType(Text); |
| await expectLater( |
| () => expectLater(finder, matchesGoldenFile('foo.png')), |
| throwsA(isA<TestFailure>().having( |
| (TestFailure error) => error.message, |
| 'message', |
| contains('too many widgets'), |
| )), |
| ); |
| expect(comparator.invocation, isNull); |
| }); |
| }); |
| |
| testWidgets('calls update on comparator if autoUpdateGoldenFiles is true', (WidgetTester tester) async { |
| autoUpdateGoldenFiles = true; |
| await tester.pumpWidget(boilerplate(const Text('hello'))); |
| final Finder finder = find.byType(Text); |
| await expectLater(finder, matchesGoldenFile('foo.png')); |
| expect(comparator.invocation, _ComparatorInvocation.update); |
| expect(comparator.imageBytes, hasLength(greaterThan(0))); |
| expect(comparator.golden, Uri.parse('foo.png')); |
| autoUpdateGoldenFiles = false; |
| }); |
| }); |
| |
| group('matchesSemanticsData', () { |
| testWidgets('matches SemanticsData', (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| const Key key = Key('semantics'); |
| await tester.pumpWidget(Semantics( |
| key: key, |
| namesRoute: true, |
| header: true, |
| button: true, |
| link: true, |
| onTap: () { }, |
| onLongPress: () { }, |
| label: 'foo', |
| hint: 'bar', |
| value: 'baz', |
| increasedValue: 'a', |
| decreasedValue: 'b', |
| textDirection: TextDirection.rtl, |
| onTapHint: 'scan', |
| onLongPressHint: 'fill', |
| customSemanticsActions: <CustomSemanticsAction, VoidCallback>{ |
| const CustomSemanticsAction(label: 'foo'): () { }, |
| const CustomSemanticsAction(label: 'bar'): () { }, |
| }, |
| )); |
| |
| expect(tester.getSemantics(find.byKey(key)), |
| matchesSemantics( |
| label: 'foo', |
| hint: 'bar', |
| value: 'baz', |
| increasedValue: 'a', |
| decreasedValue: 'b', |
| textDirection: TextDirection.rtl, |
| hasTapAction: true, |
| hasLongPressAction: true, |
| isButton: true, |
| isLink: true, |
| isHeader: true, |
| namesRoute: true, |
| onTapHint: 'scan', |
| onLongPressHint: 'fill', |
| customActions: <CustomSemanticsAction>[ |
| const CustomSemanticsAction(label: 'foo'), |
| const CustomSemanticsAction(label: 'bar'), |
| ], |
| ), |
| ); |
| |
| // Doesn't match custom actions |
| expect(tester.getSemantics(find.byKey(key)), |
| isNot(matchesSemantics( |
| label: 'foo', |
| hint: 'bar', |
| value: 'baz', |
| textDirection: TextDirection.rtl, |
| hasTapAction: true, |
| hasLongPressAction: true, |
| isButton: true, |
| isLink: true, |
| isHeader: true, |
| namesRoute: true, |
| onTapHint: 'scan', |
| onLongPressHint: 'fill', |
| customActions: <CustomSemanticsAction>[ |
| const CustomSemanticsAction(label: 'foo'), |
| const CustomSemanticsAction(label: 'barz'), |
| ], |
| )), |
| ); |
| |
| // Doesn't match wrong hints |
| expect(tester.getSemantics(find.byKey(key)), |
| isNot(matchesSemantics( |
| label: 'foo', |
| hint: 'bar', |
| value: 'baz', |
| textDirection: TextDirection.rtl, |
| hasTapAction: true, |
| hasLongPressAction: true, |
| isButton: true, |
| isLink: true, |
| isHeader: true, |
| namesRoute: true, |
| onTapHint: 'scans', |
| onLongPressHint: 'fills', |
| customActions: <CustomSemanticsAction>[ |
| const CustomSemanticsAction(label: 'foo'), |
| const CustomSemanticsAction(label: 'bar'), |
| ], |
| )), |
| ); |
| |
| handle.dispose(); |
| }); |
| |
| testWidgets('Can match all semantics flags and actions', (WidgetTester tester) async { |
| int actions = 0; |
| int flags = 0; |
| const CustomSemanticsAction action = CustomSemanticsAction(label: 'test'); |
| for (final SemanticsAction action in SemanticsAction.values) { |
| actions |= action.index; |
| } |
| for (final SemanticsFlag flag in SemanticsFlag.values) { |
| flags |= flag.index; |
| } |
| final SemanticsData data = SemanticsData( |
| flags: flags, |
| actions: actions, |
| attributedLabel: AttributedString('a'), |
| attributedIncreasedValue: AttributedString('b'), |
| attributedValue: AttributedString('c'), |
| attributedDecreasedValue: AttributedString('d'), |
| attributedHint: AttributedString('e'), |
| tooltip: 'f', |
| textDirection: TextDirection.ltr, |
| rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| elevation: 3.0, |
| thickness: 4.0, |
| textSelection: null, |
| scrollIndex: null, |
| scrollChildCount: null, |
| scrollPosition: null, |
| scrollExtentMax: null, |
| scrollExtentMin: null, |
| platformViewId: 105, |
| customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)], |
| currentValueLength: 10, |
| maxValueLength: 15, |
| ); |
| final _FakeSemanticsNode node = _FakeSemanticsNode(data); |
| |
| expect(node, matchesSemantics( |
| rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| size: const Size(10.0, 10.0), |
| elevation: 3.0, |
| thickness: 4.0, |
| platformViewId: 105, |
| currentValueLength: 10, |
| maxValueLength: 15, |
| /* Flags */ |
| hasCheckedState: true, |
| isChecked: true, |
| isCheckStateMixed: true, |
| isSelected: true, |
| isButton: true, |
| isSlider: true, |
| isKeyboardKey: true, |
| isLink: true, |
| isTextField: true, |
| isReadOnly: true, |
| hasEnabledState: true, |
| isFocused: true, |
| isFocusable: true, |
| isEnabled: true, |
| isInMutuallyExclusiveGroup: true, |
| isHeader: true, |
| isObscured: true, |
| isMultiline: true, |
| namesRoute: true, |
| scopesRoute: true, |
| isHidden: true, |
| isImage: true, |
| isLiveRegion: true, |
| hasToggledState: true, |
| isToggled: true, |
| hasImplicitScrolling: true, |
| /* Actions */ |
| hasTapAction: true, |
| hasLongPressAction: true, |
| hasScrollLeftAction: true, |
| hasScrollRightAction: true, |
| hasScrollUpAction: true, |
| hasScrollDownAction: true, |
| hasIncreaseAction: true, |
| hasDecreaseAction: true, |
| hasShowOnScreenAction: true, |
| hasMoveCursorForwardByCharacterAction: true, |
| hasMoveCursorBackwardByCharacterAction: true, |
| hasMoveCursorForwardByWordAction: true, |
| hasMoveCursorBackwardByWordAction: true, |
| hasSetTextAction: true, |
| hasSetSelectionAction: true, |
| hasCopyAction: true, |
| hasCutAction: true, |
| hasPasteAction: true, |
| hasDidGainAccessibilityFocusAction: true, |
| hasDidLoseAccessibilityFocusAction: true, |
| hasDismissAction: true, |
| customActions: <CustomSemanticsAction>[action], |
| )); |
| }); |
| |
| testWidgets('Can match child semantics', (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| const Key key = Key('a'); |
| await tester.pumpWidget(Semantics( |
| key: key, |
| label: 'Foo', |
| container: true, |
| explicitChildNodes: true, |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| label: 'Bar', |
| textDirection: TextDirection.ltr, |
| ), |
| )); |
| final SemanticsNode node = tester.getSemantics(find.byKey(key)); |
| |
| expect(node, matchesSemantics( |
| label: 'Foo', |
| textDirection: TextDirection.ltr, |
| children: <Matcher>[ |
| matchesSemantics( |
| label: 'Bar', |
| textDirection: TextDirection.ltr, |
| ), |
| ], |
| )); |
| handle.dispose(); |
| }); |
| |
| testWidgets('failure does not throw unexpected errors', (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| |
| const Key key = Key('semantics'); |
| await tester.pumpWidget(Semantics( |
| key: key, |
| namesRoute: true, |
| header: true, |
| button: true, |
| link: true, |
| onTap: () { }, |
| onLongPress: () { }, |
| label: 'foo', |
| hint: 'bar', |
| value: 'baz', |
| increasedValue: 'a', |
| decreasedValue: 'b', |
| textDirection: TextDirection.rtl, |
| onTapHint: 'scan', |
| onLongPressHint: 'fill', |
| customSemanticsActions: <CustomSemanticsAction, VoidCallback>{ |
| const CustomSemanticsAction(label: 'foo'): () { }, |
| const CustomSemanticsAction(label: 'bar'): () { }, |
| }, |
| )); |
| |
| // This should fail due to the mis-match between the `namesRoute` value. |
| void failedExpectation() => expect(tester.getSemantics(find.byKey(key)), |
| matchesSemantics( |
| // Adding the explicit `false` for test readability |
| // ignore: avoid_redundant_argument_values |
| namesRoute: false, |
| label: 'foo', |
| hint: 'bar', |
| value: 'baz', |
| increasedValue: 'a', |
| decreasedValue: 'b', |
| textDirection: TextDirection.rtl, |
| hasTapAction: true, |
| hasLongPressAction: true, |
| isButton: true, |
| isLink: true, |
| isHeader: true, |
| onTapHint: 'scan', |
| onLongPressHint: 'fill', |
| customActions: <CustomSemanticsAction>[ |
| const CustomSemanticsAction(label: 'foo'), |
| const CustomSemanticsAction(label: 'bar'), |
| ], |
| ), |
| ); |
| |
| expect(failedExpectation, throwsA(isA<TestFailure>())); |
| handle.dispose(); |
| }); |
| }); |
| |
| group('containsSemantics', () { |
| testWidgets('matches SemanticsData', (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| |
| const Key key = Key('semantics'); |
| await tester.pumpWidget(Semantics( |
| key: key, |
| namesRoute: true, |
| header: true, |
| button: true, |
| link: true, |
| onTap: () { }, |
| onLongPress: () { }, |
| label: 'foo', |
| hint: 'bar', |
| value: 'baz', |
| increasedValue: 'a', |
| decreasedValue: 'b', |
| textDirection: TextDirection.rtl, |
| onTapHint: 'scan', |
| onLongPressHint: 'fill', |
| customSemanticsActions: <CustomSemanticsAction, VoidCallback>{ |
| const CustomSemanticsAction(label: 'foo'): () { }, |
| const CustomSemanticsAction(label: 'bar'): () { }, |
| }, |
| )); |
| |
| expect( |
| tester.getSemantics(find.byKey(key)), |
| containsSemantics( |
| label: 'foo', |
| hint: 'bar', |
| value: 'baz', |
| increasedValue: 'a', |
| decreasedValue: 'b', |
| textDirection: TextDirection.rtl, |
| hasTapAction: true, |
| hasLongPressAction: true, |
| isButton: true, |
| isLink: true, |
| isHeader: true, |
| namesRoute: true, |
| onTapHint: 'scan', |
| onLongPressHint: 'fill', |
| customActions: <CustomSemanticsAction>[ |
| const CustomSemanticsAction(label: 'foo'), |
| const CustomSemanticsAction(label: 'bar'), |
| ], |
| ), |
| ); |
| |
| expect( |
| tester.getSemantics(find.byKey(key)), |
| isNot(containsSemantics( |
| label: 'foo', |
| hint: 'bar', |
| value: 'baz', |
| textDirection: TextDirection.rtl, |
| hasTapAction: true, |
| hasLongPressAction: true, |
| isButton: true, |
| isLink: true, |
| isHeader: true, |
| namesRoute: true, |
| onTapHint: 'scan', |
| onLongPressHint: 'fill', |
| customActions: <CustomSemanticsAction>[ |
| const CustomSemanticsAction(label: 'foo'), |
| const CustomSemanticsAction(label: 'barz'), |
| ], |
| )), |
| reason: 'CustomSemanticsAction "barz" should not have matched "bar".' |
| ); |
| |
| expect( |
| tester.getSemantics(find.byKey(key)), |
| isNot(matchesSemantics( |
| label: 'foo', |
| hint: 'bar', |
| value: 'baz', |
| textDirection: TextDirection.rtl, |
| hasTapAction: true, |
| hasLongPressAction: true, |
| isButton: true, |
| isLink: true, |
| isHeader: true, |
| namesRoute: true, |
| onTapHint: 'scans', |
| onLongPressHint: 'fills', |
| customActions: <CustomSemanticsAction>[ |
| const CustomSemanticsAction(label: 'foo'), |
| const CustomSemanticsAction(label: 'bar'), |
| ], |
| )), |
| reason: 'onTapHint "scans" should not have matched "scan".', |
| ); |
| handle.dispose(); |
| }); |
| |
| testWidgets('can match all semantics flags and actions enabled', (WidgetTester tester) async { |
| int actions = 0; |
| int flags = 0; |
| const CustomSemanticsAction action = CustomSemanticsAction(label: 'test'); |
| for (final SemanticsAction action in SemanticsAction.values) { |
| actions |= action.index; |
| } |
| for (final SemanticsFlag flag in SemanticsFlag.values) { |
| flags |= flag.index; |
| } |
| final SemanticsData data = SemanticsData( |
| flags: flags, |
| actions: actions, |
| attributedLabel: AttributedString('a'), |
| attributedIncreasedValue: AttributedString('b'), |
| attributedValue: AttributedString('c'), |
| attributedDecreasedValue: AttributedString('d'), |
| attributedHint: AttributedString('e'), |
| tooltip: 'f', |
| textDirection: TextDirection.ltr, |
| rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| elevation: 3.0, |
| thickness: 4.0, |
| textSelection: null, |
| scrollIndex: null, |
| scrollChildCount: null, |
| scrollPosition: null, |
| scrollExtentMax: null, |
| scrollExtentMin: null, |
| platformViewId: 105, |
| customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)], |
| currentValueLength: 10, |
| maxValueLength: 15, |
| ); |
| final _FakeSemanticsNode node = _FakeSemanticsNode(data); |
| |
| expect( |
| node, |
| containsSemantics( |
| rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| size: const Size(10.0, 10.0), |
| elevation: 3.0, |
| thickness: 4.0, |
| platformViewId: 105, |
| currentValueLength: 10, |
| maxValueLength: 15, |
| /* Flags */ |
| hasCheckedState: true, |
| isChecked: true, |
| isSelected: true, |
| isButton: true, |
| isSlider: true, |
| isKeyboardKey: true, |
| isLink: true, |
| isTextField: true, |
| isReadOnly: true, |
| hasEnabledState: true, |
| isFocused: true, |
| isFocusable: true, |
| isEnabled: true, |
| isInMutuallyExclusiveGroup: true, |
| isHeader: true, |
| isObscured: true, |
| isMultiline: true, |
| namesRoute: true, |
| scopesRoute: true, |
| isHidden: true, |
| isImage: true, |
| isLiveRegion: true, |
| hasToggledState: true, |
| isToggled: true, |
| hasImplicitScrolling: true, |
| /* Actions */ |
| hasTapAction: true, |
| hasLongPressAction: true, |
| hasScrollLeftAction: true, |
| hasScrollRightAction: true, |
| hasScrollUpAction: true, |
| hasScrollDownAction: true, |
| hasIncreaseAction: true, |
| hasDecreaseAction: true, |
| hasShowOnScreenAction: true, |
| hasMoveCursorForwardByCharacterAction: true, |
| hasMoveCursorBackwardByCharacterAction: true, |
| hasMoveCursorForwardByWordAction: true, |
| hasMoveCursorBackwardByWordAction: true, |
| hasSetTextAction: true, |
| hasSetSelectionAction: true, |
| hasCopyAction: true, |
| hasCutAction: true, |
| hasPasteAction: true, |
| hasDidGainAccessibilityFocusAction: true, |
| hasDidLoseAccessibilityFocusAction: true, |
| hasDismissAction: true, |
| customActions: <CustomSemanticsAction>[action], |
| ), |
| ); |
| }); |
| |
| testWidgets('can match all flags and actions disabled', (WidgetTester tester) async { |
| final SemanticsData data = SemanticsData( |
| flags: 0, |
| actions: 0, |
| attributedLabel: AttributedString('a'), |
| attributedIncreasedValue: AttributedString('b'), |
| attributedValue: AttributedString('c'), |
| attributedDecreasedValue: AttributedString('d'), |
| attributedHint: AttributedString('e'), |
| tooltip: 'f', |
| textDirection: TextDirection.ltr, |
| rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| elevation: 3.0, |
| thickness: 4.0, |
| textSelection: null, |
| scrollIndex: null, |
| scrollChildCount: null, |
| scrollPosition: null, |
| scrollExtentMax: null, |
| scrollExtentMin: null, |
| platformViewId: 105, |
| currentValueLength: 10, |
| maxValueLength: 15, |
| ); |
| final _FakeSemanticsNode node = _FakeSemanticsNode(data); |
| |
| expect( |
| node, |
| containsSemantics( |
| rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| size: const Size(10.0, 10.0), |
| elevation: 3.0, |
| thickness: 4.0, |
| platformViewId: 105, |
| currentValueLength: 10, |
| maxValueLength: 15, |
| /* Flags */ |
| hasCheckedState: false, |
| isChecked: false, |
| isSelected: false, |
| isButton: false, |
| isSlider: false, |
| isKeyboardKey: false, |
| isLink: false, |
| isTextField: false, |
| isReadOnly: false, |
| hasEnabledState: false, |
| isFocused: false, |
| isFocusable: false, |
| isEnabled: false, |
| isInMutuallyExclusiveGroup: false, |
| isHeader: false, |
| isObscured: false, |
| isMultiline: false, |
| namesRoute: false, |
| scopesRoute: false, |
| isHidden: false, |
| isImage: false, |
| isLiveRegion: false, |
| hasToggledState: false, |
| isToggled: false, |
| hasImplicitScrolling: false, |
| /* Actions */ |
| hasTapAction: false, |
| hasLongPressAction: false, |
| hasScrollLeftAction: false, |
| hasScrollRightAction: false, |
| hasScrollUpAction: false, |
| hasScrollDownAction: false, |
| hasIncreaseAction: false, |
| hasDecreaseAction: false, |
| hasShowOnScreenAction: false, |
| hasMoveCursorForwardByCharacterAction: false, |
| hasMoveCursorBackwardByCharacterAction: false, |
| hasMoveCursorForwardByWordAction: false, |
| hasMoveCursorBackwardByWordAction: false, |
| hasSetTextAction: false, |
| hasSetSelectionAction: false, |
| hasCopyAction: false, |
| hasCutAction: false, |
| hasPasteAction: false, |
| hasDidGainAccessibilityFocusAction: false, |
| hasDidLoseAccessibilityFocusAction: false, |
| hasDismissAction: false, |
| ), |
| ); |
| }); |
| |
| testWidgets('only matches given flags and actions', (WidgetTester tester) async { |
| int allActions = 0; |
| int allFlags = 0; |
| for (final SemanticsAction action in SemanticsAction.values) { |
| allActions |= action.index; |
| } |
| for (final SemanticsFlag flag in SemanticsFlag.values) { |
| allFlags |= flag.index; |
| } |
| final SemanticsData emptyData = SemanticsData( |
| flags: 0, |
| actions: 0, |
| attributedLabel: AttributedString('a'), |
| attributedIncreasedValue: AttributedString('b'), |
| attributedValue: AttributedString('c'), |
| attributedDecreasedValue: AttributedString('d'), |
| attributedHint: AttributedString('e'), |
| tooltip: 'f', |
| textDirection: TextDirection.ltr, |
| rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| elevation: 3.0, |
| thickness: 4.0, |
| textSelection: null, |
| scrollIndex: null, |
| scrollChildCount: null, |
| scrollPosition: null, |
| scrollExtentMax: null, |
| scrollExtentMin: null, |
| platformViewId: 105, |
| currentValueLength: 10, |
| maxValueLength: 15, |
| ); |
| final _FakeSemanticsNode emptyNode = _FakeSemanticsNode(emptyData); |
| |
| const CustomSemanticsAction action = CustomSemanticsAction(label: 'test'); |
| final SemanticsData fullData = SemanticsData( |
| flags: allFlags, |
| actions: allActions, |
| attributedLabel: AttributedString('a'), |
| attributedIncreasedValue: AttributedString('b'), |
| attributedValue: AttributedString('c'), |
| attributedDecreasedValue: AttributedString('d'), |
| attributedHint: AttributedString('e'), |
| tooltip: 'f', |
| textDirection: TextDirection.ltr, |
| rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| elevation: 3.0, |
| thickness: 4.0, |
| textSelection: null, |
| scrollIndex: null, |
| scrollChildCount: null, |
| scrollPosition: null, |
| scrollExtentMax: null, |
| scrollExtentMin: null, |
| platformViewId: 105, |
| currentValueLength: 10, |
| maxValueLength: 15, |
| customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)], |
| ); |
| final _FakeSemanticsNode fullNode = _FakeSemanticsNode(fullData); |
| |
| expect( |
| emptyNode, |
| containsSemantics( |
| rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| size: const Size(10.0, 10.0), |
| elevation: 3.0, |
| thickness: 4.0, |
| platformViewId: 105, |
| currentValueLength: 10, |
| maxValueLength: 15, |
| ), |
| ); |
| |
| expect( |
| fullNode, |
| containsSemantics( |
| rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| size: const Size(10.0, 10.0), |
| elevation: 3.0, |
| thickness: 4.0, |
| platformViewId: 105, |
| currentValueLength: 10, |
| maxValueLength: 15, |
| customActions: <CustomSemanticsAction>[action], |
| ), |
| ); |
| }); |
| |
| testWidgets('can match child semantics', (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| const Key key = Key('a'); |
| await tester.pumpWidget(Semantics( |
| key: key, |
| label: 'Foo', |
| container: true, |
| explicitChildNodes: true, |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| label: 'Bar', |
| textDirection: TextDirection.ltr, |
| ), |
| )); |
| final SemanticsNode node = tester.getSemantics(find.byKey(key)); |
| |
| expect( |
| node, |
| containsSemantics( |
| label: 'Foo', |
| textDirection: TextDirection.ltr, |
| children: <Matcher>[ |
| containsSemantics( |
| label: 'Bar', |
| textDirection: TextDirection.ltr, |
| ), |
| ], |
| ), |
| ); |
| |
| handle.dispose(); |
| }); |
| |
| testWidgets('can match only custom actions', (WidgetTester tester) async { |
| const CustomSemanticsAction action = CustomSemanticsAction(label: 'test'); |
| final SemanticsData data = SemanticsData( |
| flags: 0, |
| actions: SemanticsAction.customAction.index, |
| attributedLabel: AttributedString('a'), |
| attributedIncreasedValue: AttributedString('b'), |
| attributedValue: AttributedString('c'), |
| attributedDecreasedValue: AttributedString('d'), |
| attributedHint: AttributedString('e'), |
| tooltip: 'f', |
| textDirection: TextDirection.ltr, |
| rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), |
| elevation: 3.0, |
| thickness: 4.0, |
| textSelection: null, |
| scrollIndex: null, |
| scrollChildCount: null, |
| scrollPosition: null, |
| scrollExtentMax: null, |
| scrollExtentMin: null, |
| platformViewId: 105, |
| currentValueLength: 10, |
| maxValueLength: 15, |
| customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)], |
| ); |
| final _FakeSemanticsNode node = _FakeSemanticsNode(data); |
| |
| expect(node, containsSemantics(customActions: <CustomSemanticsAction>[action])); |
| }); |
| |
| testWidgets('failure does not throw unexpected errors', (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| |
| const Key key = Key('semantics'); |
| await tester.pumpWidget(Semantics( |
| key: key, |
| namesRoute: true, |
| header: true, |
| button: true, |
| link: true, |
| onTap: () { }, |
| onLongPress: () { }, |
| label: 'foo', |
| hint: 'bar', |
| value: 'baz', |
| increasedValue: 'a', |
| decreasedValue: 'b', |
| textDirection: TextDirection.rtl, |
| onTapHint: 'scan', |
| onLongPressHint: 'fill', |
| customSemanticsActions: <CustomSemanticsAction, VoidCallback>{ |
| const CustomSemanticsAction(label: 'foo'): () { }, |
| const CustomSemanticsAction(label: 'bar'): () { }, |
| }, |
| )); |
| |
| // This should fail due to the mis-match between the `namesRoute` value. |
| void failedExpectation() => expect(tester.getSemantics(find.byKey(key)), |
| containsSemantics( |
| label: 'foo', |
| hint: 'bar', |
| value: 'baz', |
| increasedValue: 'a', |
| decreasedValue: 'b', |
| textDirection: TextDirection.rtl, |
| hasTapAction: true, |
| hasLongPressAction: true, |
| isButton: true, |
| isLink: true, |
| isHeader: true, |
| namesRoute: false, |
| onTapHint: 'scan', |
| onLongPressHint: 'fill', |
| customActions: <CustomSemanticsAction>[ |
| const CustomSemanticsAction(label: 'foo'), |
| const CustomSemanticsAction(label: 'bar'), |
| ], |
| ), |
| ); |
| |
| expect(failedExpectation, throwsA(isA<TestFailure>())); |
| handle.dispose(); |
| }); |
| }); |
| |
| group('findsAtLeastNWidgets', () { |
| Widget boilerplate(Widget child) { |
| return Directionality( |
| textDirection: TextDirection.ltr, |
| child: child, |
| ); |
| } |
| |
| testWidgets('succeeds when finds more then the specified count', |
| (WidgetTester tester) async { |
| await tester.pumpWidget(boilerplate(const Column( |
| children: <Widget>[Text('1'), Text('2'), Text('3')], |
| ))); |
| |
| expect(find.byType(Text), findsAtLeastNWidgets(2)); |
| }); |
| |
| testWidgets('succeeds when finds the exact specified count', |
| (WidgetTester tester) async { |
| await tester.pumpWidget(boilerplate(const Column( |
| children: <Widget>[Text('1'), Text('2')], |
| ))); |
| |
| expect(find.byType(Text), findsAtLeastNWidgets(2)); |
| }); |
| |
| testWidgets('fails when finds less then specified count', |
| (WidgetTester tester) async { |
| await tester.pumpWidget(boilerplate(const Column( |
| children: <Widget>[Text('1'), Text('2')], |
| ))); |
| |
| expect(find.byType(Text), isNot(findsAtLeastNWidgets(3))); |
| }); |
| }); |
| } |
| |
| enum _ComparatorBehavior { |
| returnTrue, |
| returnFalse, |
| throwTestFailure, |
| } |
| |
| enum _ComparatorInvocation { |
| compare, |
| update, |
| } |
| |
| class _FakeComparator implements GoldenFileComparator { |
| _ComparatorBehavior behavior = _ComparatorBehavior.returnTrue; |
| _ComparatorInvocation? invocation; |
| Uint8List? imageBytes; |
| Uri? golden; |
| |
| @override |
| Future<bool> compare(Uint8List imageBytes, Uri golden) { |
| invocation = _ComparatorInvocation.compare; |
| this.imageBytes = imageBytes; |
| this.golden = golden; |
| switch (behavior) { |
| case _ComparatorBehavior.returnTrue: |
| return Future<bool>.value(true); |
| case _ComparatorBehavior.returnFalse: |
| return Future<bool>.value(false); |
| case _ComparatorBehavior.throwTestFailure: |
| fail('fake message'); |
| } |
| } |
| |
| @override |
| Future<void> update(Uri golden, Uint8List imageBytes) { |
| invocation = _ComparatorInvocation.update; |
| this.golden = golden; |
| this.imageBytes = imageBytes; |
| return Future<void>.value(); |
| } |
| |
| @override |
| Uri getTestUri(Uri key, int? version) { |
| return key; |
| } |
| } |
| |
| class _FakeSemanticsNode extends SemanticsNode { |
| _FakeSemanticsNode(this.data); |
| |
| SemanticsData data; |
| @override |
| SemanticsData getSemanticsData() => data; |
| } |
| |
| @immutable |
| class _CustomColor extends Color { |
| const _CustomColor(super.value, {this.isEqual}); |
| final bool? isEqual; |
| |
| @override |
| bool operator ==(Object other) => isEqual ?? super == other; |
| |
| @override |
| int get hashCode => Object.hash(super.hashCode, isEqual); |
| } |