aves_mio/.flutter/packages/flutter/test/services/text_boundary_test.dart
Fabio Micheluz 2c988f959b
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
first commit
2026-02-19 13:25:23 +01:00

318 lines
12 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
class _ConsistentTextRangeImplementationMatcher extends Matcher {
_ConsistentTextRangeImplementationMatcher(int length)
: range = TextRange(start: -1, end: length + 1),
assert(length >= 0);
final TextRange range;
@override
Description describe(Description description) {
return description.add(
'The implementation of TextBoundary.getTextBoundaryAt is consistent with its other methods.',
);
}
@override
Description describeMismatch(
dynamic item,
Description mismatchDescription,
Map<dynamic, dynamic> matchState,
bool verbose,
) {
final boundary = matchState['textBoundary'] as TextBoundary;
final position = matchState['position'] as int;
final int leading = boundary.getLeadingTextBoundaryAt(position) ?? -1;
final int trailing = boundary.getTrailingTextBoundaryAt(position) ?? -1;
return mismatchDescription.add(
'at position $position, expected ${TextRange(start: leading, end: trailing)} but got ${boundary.getTextBoundaryAt(position)}',
);
}
@override
bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
for (int i = range.start; i <= range.end; i++) {
final int? leading = (item as TextBoundary).getLeadingTextBoundaryAt(i);
final int? trailing = item.getTrailingTextBoundaryAt(i);
final TextRange boundary = item.getTextBoundaryAt(i);
final bool consistent = boundary.start == (leading ?? -1) && boundary.end == (trailing ?? -1);
if (!consistent) {
matchState['textBoundary'] = item;
matchState['position'] = i;
return false;
}
}
return true;
}
}
Matcher _hasConsistentTextRangeImplementationWithinRange(int length) =>
_ConsistentTextRangeImplementationMatcher(length);
void main() {
test('Character boundary works', () {
const boundary = CharacterBoundary('abc');
expect(boundary, _hasConsistentTextRangeImplementationWithinRange(3));
expect(boundary.getLeadingTextBoundaryAt(-1), null);
expect(boundary.getTrailingTextBoundaryAt(-1), 0);
expect(boundary.getLeadingTextBoundaryAt(0), 0);
expect(boundary.getTrailingTextBoundaryAt(0), 1);
expect(boundary.getLeadingTextBoundaryAt(1), 1);
expect(boundary.getTrailingTextBoundaryAt(1), 2);
expect(boundary.getLeadingTextBoundaryAt(2), 2);
expect(boundary.getTrailingTextBoundaryAt(2), 3);
expect(boundary.getLeadingTextBoundaryAt(3), 3);
expect(boundary.getTrailingTextBoundaryAt(3), null);
expect(boundary.getLeadingTextBoundaryAt(4), 3);
expect(boundary.getTrailingTextBoundaryAt(4), null);
});
test('Character boundary works with grapheme', () {
const text = 'a❄c';
const boundary = CharacterBoundary(text);
expect(boundary, _hasConsistentTextRangeImplementationWithinRange(text.length));
expect(boundary.getLeadingTextBoundaryAt(-1), null);
expect(boundary.getTrailingTextBoundaryAt(-1), 0);
expect(boundary.getLeadingTextBoundaryAt(0), 0);
expect(boundary.getTrailingTextBoundaryAt(0), 1);
// The `❄` takes two character length.
expect(boundary.getLeadingTextBoundaryAt(1), 1);
expect(boundary.getTrailingTextBoundaryAt(1), 3);
expect(boundary.getLeadingTextBoundaryAt(2), 1);
expect(boundary.getTrailingTextBoundaryAt(2), 3);
expect(boundary.getLeadingTextBoundaryAt(3), 3);
expect(boundary.getTrailingTextBoundaryAt(3), 4);
expect(boundary.getLeadingTextBoundaryAt(text.length), text.length);
expect(boundary.getTrailingTextBoundaryAt(text.length), null);
});
test('wordBoundary.moveByWordBoundary', () {
const text =
'ABC ABC\n' // [0, 10)
'AÁ Á\n' // [10, 20)
' \n' // [20, 30)
'ABC!!!ABC\n' // [30, 40)
' !ABC !!\n' // [40, 50)
'A 𑗋𑗋 A\n'; // [50, 60)
final textPainter = TextPainter()
..textDirection = TextDirection.ltr
..text = const TextSpan(text: text)
..layout();
final TextBoundary boundary = textPainter.wordBoundaries.moveByWordBoundary;
// 4 points to the 2nd whitespace in the first line.
// Don't break between horizontal spaces and letters/numbers.
expect(boundary.getLeadingTextBoundaryAt(4), 0);
expect(boundary.getTrailingTextBoundaryAt(4), 9);
// Works when words are starting/ending with a combining diacritical mark.
expect(boundary.getLeadingTextBoundaryAt(14), 10);
expect(boundary.getTrailingTextBoundaryAt(14), 19);
// Do break before and after newlines.
expect(boundary.getLeadingTextBoundaryAt(24), 20);
expect(boundary.getTrailingTextBoundaryAt(24), 29);
// Do not break on punctuations.
expect(boundary.getLeadingTextBoundaryAt(34), 30);
expect(boundary.getTrailingTextBoundaryAt(34), 39);
// Ok to break if next to punctuations or separating spaces.
expect(boundary.getLeadingTextBoundaryAt(44), 43);
expect(boundary.getTrailingTextBoundaryAt(44), 46);
// 44 points to a low surrogate of a punctuation.
expect(boundary.getLeadingTextBoundaryAt(54), 50);
expect(boundary.getTrailingTextBoundaryAt(54), 59);
});
test('line boundary works', () {
final boundary = LineBoundary(TestTextLayoutMetrics());
expect(boundary.getLeadingTextBoundaryAt(3), TestTextLayoutMetrics.lineAt3.start);
expect(boundary.getTrailingTextBoundaryAt(3), TestTextLayoutMetrics.lineAt3.end);
expect(boundary.getTextBoundaryAt(3), TestTextLayoutMetrics.lineAt3);
});
group('paragraph boundary', () {
test('works for simple cases', () {
const textA = 'abcd efg hi\njklmno\npqrstuv';
const boundaryA = ParagraphBoundary(textA);
// Position enclosed inside of paragraph, 'abcd efg h|i\n'.
const position = 10;
// The range includes the line terminator.
expect(boundaryA.getLeadingTextBoundaryAt(position), 0);
expect(boundaryA.getTrailingTextBoundaryAt(position), 12);
// This text includes a carriage return followed by a line feed.
const textB = 'abcd efg hi\r\njklmno\npqrstuv';
const boundaryB = ParagraphBoundary(textB);
expect(boundaryB.getLeadingTextBoundaryAt(position), 0);
expect(boundaryB.getTrailingTextBoundaryAt(position), 13);
const textF =
'Now is the time for\n' // 20
'all good people\n' // 20 + 16 => 36
'to come to the aid\n' // 36 + 19 => 55
'of their country.'; // 55 + 17 => 72
const boundaryF = ParagraphBoundary(textF);
const positionF = 11;
expect(boundaryF.getLeadingTextBoundaryAt(positionF), 0);
expect(boundaryF.getTrailingTextBoundaryAt(positionF), 20);
});
test('works for consecutive line terminators involving CRLF', () {
const textI =
'Now is the time for\n' // 20
'all good people\n\r\n' // 20 + 16 => 38
'to come to the aid\n' // 38 + 19 => 57
'of their country.'; // 57 + 17 => 74
const boundaryI = ParagraphBoundary(textI);
const positionI = 56; // \n at the end of the third line.
const positionJ = 38; // t at beginning of third line.
const positionK = 37; // \n at end of second line.
expect(boundaryI.getLeadingTextBoundaryAt(positionI), 38);
expect(boundaryI.getTrailingTextBoundaryAt(positionI), 57);
expect(boundaryI.getLeadingTextBoundaryAt(positionJ), 38);
expect(boundaryI.getTrailingTextBoundaryAt(positionJ), 57);
expect(boundaryI.getLeadingTextBoundaryAt(positionK), 36);
expect(boundaryI.getTrailingTextBoundaryAt(positionK), 38);
});
test('works for consecutive line terminators', () {
const textI =
'Now is the time for\n' // 20
'all good people\n\n' // 20 + 16 => 37
'to come to the aid\n' // 37 + 19 => 56
'of their country.'; // 56 + 17 => 73
const boundaryI = ParagraphBoundary(textI);
const positionI = 55; // \n at the end of the third line.
const positionJ = 37; // t at beginning of third line.
const positionK = 36; // \n at end of second line.
expect(boundaryI.getLeadingTextBoundaryAt(positionI), 37);
expect(boundaryI.getTrailingTextBoundaryAt(positionI), 56);
expect(boundaryI.getLeadingTextBoundaryAt(positionJ), 37);
expect(boundaryI.getTrailingTextBoundaryAt(positionJ), 56);
expect(boundaryI.getLeadingTextBoundaryAt(positionK), 36);
expect(boundaryI.getTrailingTextBoundaryAt(positionK), 37);
});
test('leading boundary works for consecutive CRLF', () {
// This text includes multiple consecutive carriage returns followed by line feeds (CRLF).
const textH = 'abcd efg hi\r\n\r\n\r\n\r\n\r\n\r\n\r\n\n\n\n\n\njklmno\npqrstuv';
const boundaryH = ParagraphBoundary(textH);
const positionH = 18;
expect(boundaryH.getLeadingTextBoundaryAt(positionH), 17);
expect(boundaryH.getTrailingTextBoundaryAt(positionH), 19);
});
test('trailing boundary works for consecutive CRLF', () {
// This text includes multiple consecutive carriage returns followed by line feeds (CRLF).
const textG = 'abcd efg hi\r\n\n\n\n\n\n\r\n\r\n\r\n\r\n\n\n\n\n\njklmno\npqrstuv';
const boundaryG = ParagraphBoundary(textG);
const positionG = 18;
expect(boundaryG.getLeadingTextBoundaryAt(positionG), 18);
expect(boundaryG.getTrailingTextBoundaryAt(positionG), 20);
});
test('works when position is between two CRLF', () {
const textE = 'abcd efg hi\r\nhello\r\n\n';
const boundaryE = ParagraphBoundary(textE);
// Position enclosed inside of paragraph, 'abcd efg hi\r\nhello\r\n\n'.
const positionE = 16;
expect(boundaryE.getLeadingTextBoundaryAt(positionE), 13);
expect(boundaryE.getTrailingTextBoundaryAt(positionE), 20);
});
test('works for multiple consecutive line terminators', () {
// This text includes multiple consecutive line terminators.
const textC = 'abcd efg hi\r\n\n\n\n\n\n\n\n\n\n\n\njklmno\npqrstuv';
const boundaryC = ParagraphBoundary(textC);
// Position enclosed inside of paragraph, 'abcd efg hi\r\n\n\n\n\n\n|\n\n\n\n\n\njklmno\npqrstuv'.
const positionC = 18;
expect(boundaryC.getLeadingTextBoundaryAt(positionC), 18);
expect(boundaryC.getTrailingTextBoundaryAt(positionC), 19);
const textD = 'abcd efg hi\r\n\n\n\n';
const boundaryD = ParagraphBoundary(textD);
// Position enclosed inside of paragraph, 'abcd efg hi\r\n\n|\n\n'.
const positionD = 14;
expect(boundaryD.getLeadingTextBoundaryAt(positionD), 14);
expect(boundaryD.getTrailingTextBoundaryAt(positionD), 15);
});
});
test('document boundary works', () {
const text = 'abcd efg hi\njklmno\npqrstuv';
const boundary = DocumentBoundary(text);
expect(boundary, _hasConsistentTextRangeImplementationWithinRange(text.length));
expect(boundary.getLeadingTextBoundaryAt(-1), null);
expect(boundary.getTrailingTextBoundaryAt(-1), text.length);
expect(boundary.getLeadingTextBoundaryAt(0), 0);
expect(boundary.getTrailingTextBoundaryAt(0), text.length);
expect(boundary.getLeadingTextBoundaryAt(10), 0);
expect(boundary.getTrailingTextBoundaryAt(10), text.length);
expect(boundary.getLeadingTextBoundaryAt(text.length), 0);
expect(boundary.getTrailingTextBoundaryAt(text.length), null);
expect(boundary.getLeadingTextBoundaryAt(text.length + 1), 0);
expect(boundary.getTrailingTextBoundaryAt(text.length + 1), null);
});
}
class TestTextLayoutMetrics extends TextLayoutMetrics {
static const TextSelection lineAt3 = TextSelection(baseOffset: 0, extentOffset: 10);
static const TextRange wordBoundaryAt3 = TextRange(start: 4, end: 7);
@override
TextSelection getLineAtOffset(TextPosition position) {
if (position.offset == 3) {
return lineAt3;
}
throw UnimplementedError();
}
@override
TextPosition getTextPositionAbove(TextPosition position) {
throw UnimplementedError();
}
@override
TextPosition getTextPositionBelow(TextPosition position) {
throw UnimplementedError();
}
@override
TextRange getWordBoundary(TextPosition position) {
if (position.offset == 3) {
return wordBoundaryAt3;
}
throw UnimplementedError();
}
}