flutter/examples/api/test/material/selection_area/selection_area.2_test.dart
Renzo Olivares f3f72ede04
Add SelectionListener/SelectedContentRange (#154202)
https://github.com/user-attachments/assets/59225cf7-5506-414e-87da-aa4d3227e7f6

Adds:
* `SelectionListener`, allows a user to listen to selection changes
under the subtree it wraps given their is an ancestor `SelectionArea` or
`SelectableRegion`. These selection changes can be listened to through
the `SelectionListenerNotifier` that is provided to a
`SelectionListener`.
* `SelectionListenerNotifier`, used with `SelectionListener`, allows a
user listen to selection changes for the subtree of the
`SelectionListener` it was provided to. Provides access to individual
selection values through the `SelectionDetails` object `selection`.
* `SelectableRegionSelectionStatusScope`, allows the user to listen to
when a parent `SelectableRegion` is changing or finalizing the
selection.
* `SelectedContentRange`, provides information about the selection range
under a `SelectionHandler` or `Selectable` through the `getSelection()`
method. This includes a start and end offset relative to the
`Selectable`s content.
* `SelectionHandler.contentLength`, to describe the length of the
content contained by a selectable.

Original PR & Discussion: https://github.com/flutter/flutter/pull/148998

Fixes: #110594

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

---------

Co-authored-by: Renzo Olivares <roliv@google.com>
2024-11-26 00:14:30 +00:00

126 lines
7.7 KiB
Dart

// 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.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_api_samples/material/selection_area/selection_area.2.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('SelectionArea Color Text Red Example Smoke Test', (WidgetTester tester) async {
await tester.pumpWidget(
const example.SelectionAreaColorTextRedExampleApp(),
);
expect(find.widgetWithIcon(FloatingActionButton, Icons.undo), findsOneWidget);
expect(find.byType(Column), findsNWidgets(2));
expect(find.textContaining('This is some bulleted list:\n'), findsOneWidget);
for (int i = 1; i <= 7; i += 1) {
expect(find.widgetWithText(Text, '• Bullet $i'), findsOneWidget);
}
expect(find.textContaining('This is some text in a text widget.'), findsOneWidget);
expect(find.textContaining(' This is some more text in the same text widget.'), findsOneWidget);
expect(find.textContaining('This is some text in another text widget.'), findsOneWidget);
});
testWidgets('SelectionArea Color Text Red Example - colors selected range red', (WidgetTester tester) async {
await tester.pumpWidget(
const example.SelectionAreaColorTextRedExampleApp(),
);
await tester.pumpAndSettle();
final Finder paragraph1Finder = find.descendant(of: find.textContaining('This is some bulleted list').first, matching: find.byType(RichText).first);
final Finder paragraph3Finder = find.descendant(of: find.textContaining('This is some text in another text widget.'), matching: find.byType(RichText));
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(paragraph1Finder);
final List<RenderParagraph> bullets = tester.renderObjectList<RenderParagraph>(find.descendant(of: find.textContaining('• Bullet'), matching: find.byType(RichText))).toList();
expect(bullets.length, 7);
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.textContaining('This is some text in a text widget.'), matching: find.byType(RichText)));
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(paragraph3Finder);
// Drag to select from paragraph 1 position 4 to paragraph 3 position 25.
final TestGesture gesture = await tester.startGesture(tester.getRect(paragraph1Finder).topLeft + const Offset(50.0, 10.0), kind: PointerDeviceKind.mouse);
addTearDown(gesture.removePointer);
await tester.pump();
await gesture.moveTo(tester.getRect(paragraph3Finder).centerLeft + const Offset(360.0, 0.0));
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
// Verify selection.
// Bulleted list title.
expect(paragraph1.selections.length, 1);
expect(paragraph1.selections[0], const TextSelection(baseOffset: 4, extentOffset: 27));
// Bulleted list.
for (final RenderParagraph paragraphBullet in bullets) {
expect(paragraphBullet.selections.length, 1);
expect(paragraphBullet.selections[0], const TextSelection(baseOffset: 0, extentOffset: 10));
}
// Second text widget.
expect(paragraph2.selections.length, 1);
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 83));
// Third text widget.
expect(paragraph3.selections.length, 1);
expect(paragraph3.selections[0], const TextSelection(baseOffset: 0, extentOffset: 25));
// Color selection red.
expect(find.textContaining('Color Text Red'), findsOneWidget);
await tester.tap(find.textContaining('Color Text Red'));
await tester.pumpAndSettle();
// Verify selection is red.
final TextSpan paragraph1ResultingSpan = paragraph1.text as TextSpan;
final TextSpan paragraph2ResultingSpan = paragraph2.text as TextSpan;
final TextSpan paragraph3ResultingSpan = paragraph3.text as TextSpan;
// Title of bulleted list is partially red.
expect(paragraph1ResultingSpan.children, isNotNull);
expect(paragraph1ResultingSpan.children!.length, 1);
expect((paragraph1ResultingSpan.children![0] as TextSpan).children, isNotNull);
expect((paragraph1ResultingSpan.children![0] as TextSpan).children!.length, 3);
expect((paragraph1ResultingSpan.children![0] as TextSpan).children![0].style, isNull);
expect((paragraph1ResultingSpan.children![0] as TextSpan).children![1], isA<TextSpan>());
expect(((paragraph1ResultingSpan.children![0] as TextSpan).children![1] as TextSpan).text, isNotNull);
expect(((paragraph1ResultingSpan.children![0] as TextSpan).children![1] as TextSpan).text, ' is some bulleted list:\n');
expect((paragraph1ResultingSpan.children![0] as TextSpan).children![1].style, isNotNull);
expect((paragraph1ResultingSpan.children![0] as TextSpan).children![1].style!.color, isNotNull);
expect((paragraph1ResultingSpan.children![0] as TextSpan).children![1].style!.color, Colors.red);
expect((paragraph1ResultingSpan.children![0] as TextSpan).children![2], isA<WidgetSpan>());
// Bullets are red.
for (final RenderParagraph paragraphBullet in bullets) {
final TextSpan resultingBulletSpan = paragraphBullet.text as TextSpan;
expect(resultingBulletSpan.children, isNotNull);
expect(resultingBulletSpan.children!.length, 1);
expect(resultingBulletSpan.children![0], isA<TextSpan>());
expect((resultingBulletSpan.children![0] as TextSpan).children, isNotNull);
expect((resultingBulletSpan.children![0] as TextSpan).children!.length, 1);
expect((resultingBulletSpan.children![0] as TextSpan).children![0], isA<TextSpan>());
expect(((resultingBulletSpan.children![0] as TextSpan).children![0] as TextSpan).style, isNotNull);
expect(((resultingBulletSpan.children![0] as TextSpan).children![0] as TextSpan).style!.color, isNotNull);
expect(((resultingBulletSpan.children![0] as TextSpan).children![0] as TextSpan).style!.color, Colors.red);
}
// Second text widget is red.
expect(paragraph2ResultingSpan.children, isNotNull);
expect(paragraph2ResultingSpan.children!.length, 1);
expect(paragraph2ResultingSpan.children![0], isA<TextSpan>());
expect((paragraph2ResultingSpan.children![0] as TextSpan).children, isNotNull);
for (final InlineSpan span in (paragraph2ResultingSpan.children![0] as TextSpan).children!) {
if (span is TextSpan) {
expect(span.style, isNotNull);
expect(span.style!.color, isNotNull);
expect(span.style!.color, Colors.red);
}
}
// Part of third text widget is red.
expect(paragraph3ResultingSpan.children, isNotNull);
expect(paragraph3ResultingSpan.children!.length, 1);
expect(paragraph3ResultingSpan.children![0], isA<TextSpan>());
expect((paragraph3ResultingSpan.children![0] as TextSpan).children, isNotNull);
expect((paragraph3ResultingSpan.children![0] as TextSpan).children!.length, 2);
expect((paragraph3ResultingSpan.children![0] as TextSpan).children![0], isA<TextSpan>());
expect(((paragraph3ResultingSpan.children![0] as TextSpan).children![0] as TextSpan).text, isNotNull);
expect(((paragraph3ResultingSpan.children![0] as TextSpan).children![0] as TextSpan).text, 'This is some text in ano');
expect((paragraph3ResultingSpan.children![0] as TextSpan).children![0].style, isNotNull);
expect((paragraph3ResultingSpan.children![0] as TextSpan).children![0].style!.color, isNotNull);
expect((paragraph3ResultingSpan.children![0] as TextSpan).children![0].style!.color, Colors.red);
expect((paragraph3ResultingSpan.children![0] as TextSpan).children![1].style, isNull);
});
}