mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
* change from build_runner to dart2js * add internalBootstrapBrowserTest to some of the tests * add internalBootstrapBrowserTest to all remaining tests * make tests build in paralel. Total time dropped from 586 to 177 seconds for 8 core MacBook * change isolates with pool * fixing analysis errors * skipping canvaskit tests for ios-safari * copy image files to the build directory * adding internalBootstrapBrowserTest to newly added tests * add internalBootstrapBrowserTest to faling path iterator test * necessary changes to make chrome windows work * in windows test in chrome instead of edge. our edge code was for legacy edge * do not run golden unit tests on Windows LUCI bots for now * addressing reviewer comments. Adding a method for deciding when to run integration tests. * remove lines that I forgot to remove * fixing analysis error. add issue for todo * add bootstap to a test file * adding bootstrap to another test * add internalBootstrapBrowserTest to a golden test * return test result in bat file. use archieve package to unzip * fixing logs for chrome_installer * use archieve and archieve entity instead of dynamic * adding comments for windows platform archieve part * addressing reviewer comments * change readme file
511 lines
19 KiB
Dart
511 lines
19 KiB
Dart
// 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.
|
|
|
|
// @dart = 2.6
|
|
import 'dart:html';
|
|
|
|
import 'package:test/bootstrap/browser.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import 'package:ui/ui.dart';
|
|
import 'package:ui/src/engine.dart';
|
|
|
|
import 'matchers.dart';
|
|
|
|
void main() {
|
|
internalBootstrapBrowserTest(() => testMain);
|
|
}
|
|
|
|
void testMain() async {
|
|
const double baselineRatio = 1.1662499904632568;
|
|
|
|
await webOnlyInitializeTestDomRenderer();
|
|
|
|
String fallback;
|
|
setUp(() {
|
|
if (operatingSystem == OperatingSystem.macOs ||
|
|
operatingSystem == OperatingSystem.iOs) {
|
|
fallback = '-apple-system, BlinkMacSystemFont';
|
|
} else {
|
|
fallback = 'Arial';
|
|
}
|
|
});
|
|
|
|
test('predictably lays out a single-line paragraph', () {
|
|
for (double fontSize in <double>[10.0, 20.0, 30.0, 40.0]) {
|
|
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: 'Ahem',
|
|
fontStyle: FontStyle.normal,
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: fontSize,
|
|
));
|
|
builder.addText('Test');
|
|
final Paragraph paragraph = builder.build();
|
|
paragraph.layout(const ParagraphConstraints(width: 400.0));
|
|
|
|
expect(paragraph.height, fontSize);
|
|
expect(paragraph.width, 400.0);
|
|
expect(paragraph.minIntrinsicWidth, fontSize * 4.0);
|
|
expect(paragraph.maxIntrinsicWidth, fontSize * 4.0);
|
|
expect(paragraph.alphabeticBaseline, fontSize * .8);
|
|
expect(
|
|
paragraph.ideographicBaseline,
|
|
within(
|
|
distance: 0.001,
|
|
from: paragraph.alphabeticBaseline * baselineRatio),
|
|
);
|
|
}
|
|
});
|
|
|
|
test('predictably lays out a multi-line paragraph', () {
|
|
for (double fontSize in <double>[10.0, 20.0, 30.0, 40.0]) {
|
|
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: 'Ahem',
|
|
fontStyle: FontStyle.normal,
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: fontSize,
|
|
));
|
|
builder.addText('Test Ahem');
|
|
final Paragraph paragraph = builder.build();
|
|
paragraph.layout(ParagraphConstraints(width: fontSize * 5.0));
|
|
|
|
expect(paragraph.height, fontSize * 2.0); // because it wraps
|
|
expect(paragraph.width, fontSize * 5.0);
|
|
expect(paragraph.minIntrinsicWidth, fontSize * 4.0);
|
|
|
|
// TODO(yjbanov): due to https://github.com/flutter/flutter/issues/21965
|
|
// Flutter reports a different number. Ours is correct
|
|
// though.
|
|
expect(paragraph.maxIntrinsicWidth, fontSize * 9.0);
|
|
expect(paragraph.alphabeticBaseline, fontSize * .8);
|
|
expect(
|
|
paragraph.ideographicBaseline,
|
|
within(
|
|
distance: 0.001,
|
|
from: paragraph.alphabeticBaseline * baselineRatio),
|
|
);
|
|
}
|
|
});
|
|
|
|
test('lay out unattached paragraph', () {
|
|
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: 'sans-serif',
|
|
fontStyle: FontStyle.normal,
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: 14.0,
|
|
));
|
|
builder.addText('How do you do this fine morning?');
|
|
final EngineParagraph paragraph = builder.build();
|
|
|
|
expect(paragraph.paragraphElement.parent, isNull);
|
|
expect(paragraph.height, 0.0);
|
|
expect(paragraph.width, -1.0);
|
|
expect(paragraph.minIntrinsicWidth, 0.0);
|
|
expect(paragraph.maxIntrinsicWidth, 0.0);
|
|
expect(paragraph.alphabeticBaseline, -1.0);
|
|
expect(paragraph.ideographicBaseline, -1.0);
|
|
|
|
paragraph.layout(const ParagraphConstraints(width: 60.0));
|
|
|
|
expect(paragraph.paragraphElement.parent, isNull);
|
|
expect(paragraph.height, greaterThan(0.0));
|
|
expect(paragraph.width, greaterThan(0.0));
|
|
expect(paragraph.minIntrinsicWidth, greaterThan(0.0));
|
|
expect(paragraph.maxIntrinsicWidth, greaterThan(0.0));
|
|
expect(paragraph.minIntrinsicWidth, lessThan(paragraph.maxIntrinsicWidth));
|
|
expect(paragraph.alphabeticBaseline, greaterThan(0.0));
|
|
expect(paragraph.ideographicBaseline, greaterThan(0.0));
|
|
});
|
|
|
|
Paragraph measure(
|
|
{String text = 'Hello', double fontSize = 14.0, double width = 50.0}) {
|
|
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: 'sans-serif',
|
|
fontStyle: FontStyle.normal,
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: fontSize,
|
|
));
|
|
builder.addText(text);
|
|
final Paragraph paragraph = builder.build();
|
|
paragraph.layout(ParagraphConstraints(width: width));
|
|
return paragraph;
|
|
}
|
|
|
|
test('baseline increases with font size', () {
|
|
Paragraph previousParagraph = measure(fontSize: 10.0);
|
|
for (int i = 0; i < 6; i++) {
|
|
final double fontSize = 20.0 + 10.0 * i;
|
|
final Paragraph paragraph = measure(fontSize: fontSize);
|
|
expect(paragraph.alphabeticBaseline,
|
|
greaterThan(previousParagraph.alphabeticBaseline));
|
|
expect(paragraph.ideographicBaseline,
|
|
greaterThan(previousParagraph.ideographicBaseline));
|
|
previousParagraph = paragraph;
|
|
}
|
|
});
|
|
|
|
test('baseline does not depend on text', () {
|
|
final Paragraph golden = measure(fontSize: 30.0);
|
|
for (int i = 1; i < 30; i++) {
|
|
final Paragraph paragraph = measure(text: 'hello ' * i, fontSize: 30.0);
|
|
expect(paragraph.alphabeticBaseline, golden.alphabeticBaseline);
|
|
expect(paragraph.ideographicBaseline, golden.ideographicBaseline);
|
|
}
|
|
});
|
|
|
|
test('$ParagraphBuilder detects plain text', () {
|
|
ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: 'sans-serif',
|
|
fontStyle: FontStyle.normal,
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: 15.0,
|
|
));
|
|
builder.addText('hi');
|
|
EngineParagraph paragraph = builder.build();
|
|
expect(paragraph.plainText, isNotNull);
|
|
expect(paragraph.geometricStyle.fontWeight, FontWeight.normal);
|
|
|
|
builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: 'sans-serif',
|
|
fontStyle: FontStyle.normal,
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: 15.0,
|
|
));
|
|
builder.pushStyle(TextStyle(fontWeight: FontWeight.bold));
|
|
builder.addText('hi');
|
|
paragraph = builder.build();
|
|
expect(paragraph.plainText, isNotNull);
|
|
expect(paragraph.geometricStyle.fontWeight, FontWeight.bold);
|
|
});
|
|
|
|
test('$ParagraphBuilder detects rich text', () {
|
|
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: 'sans-serif',
|
|
fontStyle: FontStyle.normal,
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: 15.0,
|
|
));
|
|
builder.addText('h');
|
|
builder.pushStyle(TextStyle(fontWeight: FontWeight.bold));
|
|
builder.addText('i');
|
|
final EngineParagraph paragraph = builder.build();
|
|
expect(paragraph.plainText, isNull);
|
|
expect(paragraph.geometricStyle.fontWeight, FontWeight.normal);
|
|
});
|
|
|
|
test('$ParagraphBuilder treats empty text as plain', () {
|
|
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: 'sans-serif',
|
|
fontStyle: FontStyle.normal,
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: 15.0,
|
|
));
|
|
builder.pushStyle(TextStyle(fontWeight: FontWeight.bold));
|
|
final EngineParagraph paragraph = builder.build();
|
|
expect(paragraph.plainText, '');
|
|
expect(paragraph.geometricStyle.fontWeight, FontWeight.bold);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/34931.
|
|
test('hit test on styled text returns correct span offset', () {
|
|
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: 'sans-serif',
|
|
fontStyle: FontStyle.normal,
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: 15.0,
|
|
));
|
|
builder.pushStyle(TextStyle(fontWeight: FontWeight.bold));
|
|
const String firstSpanText = 'XYZ';
|
|
builder.addText(firstSpanText);
|
|
builder.pushStyle(TextStyle(fontWeight: FontWeight.normal));
|
|
const String secondSpanText = '1234';
|
|
builder.addText(secondSpanText);
|
|
builder.pushStyle(TextStyle(fontStyle: FontStyle.italic));
|
|
builder.addText('followed by a link');
|
|
final EngineParagraph paragraph = builder.build();
|
|
paragraph.layout(const ParagraphConstraints(width: 800.0));
|
|
expect(paragraph.plainText, isNull);
|
|
const int secondSpanStartPosition = firstSpanText.length;
|
|
const int thirdSpanStartPosition =
|
|
firstSpanText.length + secondSpanText.length;
|
|
expect(paragraph.getPositionForOffset(const Offset(50, 0)).offset,
|
|
secondSpanStartPosition);
|
|
expect(paragraph.getPositionForOffset(const Offset(150, 0)).offset,
|
|
thirdSpanStartPosition);
|
|
});
|
|
|
|
test('hit test on the nested text span and returns correct span offset', () {
|
|
const fontFamily = 'sans-serif';
|
|
const fontSize = 20.0;
|
|
final style = TextStyle(fontFamily: fontFamily, fontSize: fontSize);
|
|
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: fontFamily,
|
|
fontSize: fontSize,
|
|
));
|
|
|
|
const text00 = 'test test test test test te00 ';
|
|
const text010 = 'test010 ';
|
|
const text02 = 'test test test test te02 ';
|
|
const text030 = 'test030 ';
|
|
const text04 = 'test test test test test test test test test test te04 ';
|
|
const text050 = 'test050 ';
|
|
|
|
/* Logical arrangement: Tree
|
|
|
|
Root TextSpan: 0
|
|
*/
|
|
builder.pushStyle(style);
|
|
{
|
|
// 1st child TextSpan of Root: 0.0
|
|
builder.pushStyle(style);
|
|
builder.addText(text00);
|
|
builder.pop();
|
|
|
|
// 2nd child TextSpan of Root: 0.1
|
|
builder.pushStyle(style);
|
|
{
|
|
// 1st child TextSpan of 0.1: 0.1.0
|
|
builder.pushStyle(style);
|
|
builder.addText(text010);
|
|
builder.pop();
|
|
}
|
|
builder.pop();
|
|
|
|
// 3rd child TextSpan of Root: 0.2
|
|
builder.pushStyle(style);
|
|
builder.addText(text02);
|
|
builder.pop();
|
|
|
|
// 4th child TextSpan of Root: 0.3
|
|
builder.pushStyle(style);
|
|
{
|
|
// 1st child TextSpan of 0.3: 0.3.0
|
|
builder.pushStyle(style);
|
|
builder.addText(text030);
|
|
builder.pop();
|
|
}
|
|
builder.pop();
|
|
|
|
// 5th child TextSpan of Root: 0.4
|
|
builder.pushStyle(style);
|
|
builder.addText(text04);
|
|
builder.pop();
|
|
|
|
// 6th child TextSpan of Root: 0.5
|
|
builder.pushStyle(style);
|
|
{
|
|
// 1st child TextSpan of 0.5: 0.5.0
|
|
builder.pushStyle(style);
|
|
builder.addText(text050);
|
|
builder.pop();
|
|
}
|
|
builder.pop();
|
|
}
|
|
builder.pop();
|
|
|
|
/* Display arrangement: Visible texts
|
|
|
|
Because `const fontSize = 20.0`, the width of each character is 20 and the
|
|
height is 20. `Display arrangement` squashes `Logical arrangement` to the
|
|
(x, y) plane. That means `Display arrangement` only shows the visible texts.
|
|
The order of texts is text00 --> text010 --> text02 --> text030 --> text04
|
|
--> text050.
|
|
|
|
The output is like that.
|
|
|
|
|------------ 600 ------------| Begin of test010
|
|
|--------------- 760 ----------------| End of test010
|
|
|---------- 500 ---------| Begin of test030
|
|
|------------- 660 -------------| End of test030
|
|
|-- 180 --| Begin of test050
|
|
|------ 360 -----| End of test050
|
|
'test test test test test te00 test010 '
|
|
'test test test test te02 test030 test '
|
|
'test test test test test test test test '
|
|
'test te04 test050 '
|
|
*/
|
|
|
|
final Paragraph paragraph = builder.build();
|
|
paragraph.layout(ParagraphConstraints(width: 800));
|
|
|
|
// Reference the offsets with the output of `Display arrangement`.
|
|
const offset010 = text00.length;
|
|
const offset030 = offset010 + text010.length + text02.length;
|
|
const offset04 = offset030 + text030.length;
|
|
const offset050 = offset04 + text04.length;
|
|
// Tap text010.
|
|
expect(paragraph.getPositionForOffset(Offset(700, 10)).offset, offset010);
|
|
// Tap text030
|
|
expect(paragraph.getPositionForOffset(Offset(600, 30)).offset, offset030);
|
|
// Tap text050
|
|
expect(paragraph.getPositionForOffset(Offset(220, 70)).offset, offset050);
|
|
// Tap the left neighbor of text050
|
|
expect(paragraph.getPositionForOffset(Offset(199, 70)).offset, offset04);
|
|
// Tap the right neighbor of text050. No matter who the right neighbor of
|
|
// text0505 is, it must not be text050 itself.
|
|
expect(paragraph.getPositionForOffset(Offset(360, 70)).offset,
|
|
isNot(offset050));
|
|
// Tap the neighbor above text050
|
|
expect(paragraph.getPositionForOffset(Offset(220, 59)).offset, offset04);
|
|
// Tap the neighbor below text050. No matter who the neighbor above text050,
|
|
// it must not be text050 itself.
|
|
expect(paragraph.getPositionForOffset(Offset(220, 80)).offset,
|
|
isNot(offset050));
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/38972
|
|
test(
|
|
'should not set fontFamily to effectiveFontFamily for spans in rich text',
|
|
() {
|
|
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: 'Roboto',
|
|
fontStyle: FontStyle.normal,
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: 15.0,
|
|
));
|
|
builder
|
|
.pushStyle(TextStyle(fontFamily: 'Menlo', fontWeight: FontWeight.bold));
|
|
const String firstSpanText = 'abc';
|
|
builder.addText(firstSpanText);
|
|
builder.pushStyle(TextStyle(fontSize: 30.0, fontWeight: FontWeight.normal));
|
|
const String secondSpanText = 'def';
|
|
builder.addText(secondSpanText);
|
|
final EngineParagraph paragraph = builder.build();
|
|
paragraph.layout(const ParagraphConstraints(width: 800.0));
|
|
expect(paragraph.plainText, isNull);
|
|
final List<SpanElement> spans =
|
|
paragraph.paragraphElement.querySelectorAll('span');
|
|
expect(spans[0].style.fontFamily, 'Ahem, $fallback, sans-serif');
|
|
// The nested span here should not set it's family to default sans-serif.
|
|
expect(spans[1].style.fontFamily, 'Ahem, $fallback, sans-serif');
|
|
},
|
|
// TODO(nurhan): https://github.com/flutter/flutter/issues/50771
|
|
// TODO(nurhan): https://github.com/flutter/flutter/issues/46638
|
|
skip: (browserEngine == BrowserEngine.firefox ||
|
|
browserEngine == BrowserEngine.edge));
|
|
|
|
test('adds Arial and sans-serif as fallback fonts', () {
|
|
// Set this to false so it doesn't default to 'Ahem' font.
|
|
debugEmulateFlutterTesterEnvironment = false;
|
|
|
|
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: 'SomeFont',
|
|
fontSize: 12.0,
|
|
));
|
|
|
|
builder.addText('Hello');
|
|
|
|
final EngineParagraph paragraph = builder.build();
|
|
expect(paragraph.paragraphElement.style.fontFamily,
|
|
'SomeFont, $fallback, sans-serif');
|
|
|
|
debugEmulateFlutterTesterEnvironment = true;
|
|
},
|
|
// TODO(nurhan): https://github.com/flutter/flutter/issues/50771
|
|
// TODO(nurhan): https://github.com/flutter/flutter/issues/46638
|
|
skip: (browserEngine == BrowserEngine.firefox ||
|
|
browserEngine == BrowserEngine.edge));
|
|
|
|
test('does not add fallback fonts to generic families', () {
|
|
// Set this to false so it doesn't default to 'Ahem' font.
|
|
debugEmulateFlutterTesterEnvironment = false;
|
|
|
|
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: 'serif',
|
|
fontSize: 12.0,
|
|
));
|
|
|
|
builder.addText('Hello');
|
|
|
|
final EngineParagraph paragraph = builder.build();
|
|
expect(paragraph.paragraphElement.style.fontFamily, 'serif');
|
|
|
|
debugEmulateFlutterTesterEnvironment = true;
|
|
});
|
|
|
|
test('can set font families that need to be quoted', () {
|
|
// Set this to false so it doesn't default to 'Ahem' font.
|
|
debugEmulateFlutterTesterEnvironment = false;
|
|
|
|
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
|
fontFamily: 'MyFont 2000',
|
|
fontSize: 12.0,
|
|
));
|
|
|
|
builder.addText('Hello');
|
|
|
|
final EngineParagraph paragraph = builder.build();
|
|
expect(paragraph.paragraphElement.style.fontFamily,
|
|
'"MyFont 2000", $fallback, sans-serif');
|
|
|
|
debugEmulateFlutterTesterEnvironment = true;
|
|
},
|
|
// TODO(nurhan): https://github.com/flutter/flutter/issues/50771
|
|
skip: browserEngine == BrowserEngine.edge);
|
|
|
|
group('TextRange', () {
|
|
test('empty ranges are correct', () {
|
|
const TextRange range = TextRange(start: -1, end: -1);
|
|
expect(range, equals(const TextRange.collapsed(-1)));
|
|
expect(range, equals(TextRange.empty));
|
|
});
|
|
test('isValid works', () {
|
|
expect(TextRange.empty.isValid, isFalse);
|
|
expect(const TextRange(start: 0, end: 0).isValid, isTrue);
|
|
expect(const TextRange(start: 0, end: 10).isValid, isTrue);
|
|
expect(const TextRange(start: 10, end: 10).isValid, isTrue);
|
|
expect(const TextRange(start: -1, end: 10).isValid, isFalse);
|
|
expect(const TextRange(start: 10, end: 0).isValid, isTrue);
|
|
expect(const TextRange(start: 10, end: -1).isValid, isFalse);
|
|
});
|
|
test('isCollapsed works', () {
|
|
expect(TextRange.empty.isCollapsed, isTrue);
|
|
expect(const TextRange(start: 0, end: 0).isCollapsed, isTrue);
|
|
expect(const TextRange(start: 0, end: 10).isCollapsed, isFalse);
|
|
expect(const TextRange(start: 10, end: 10).isCollapsed, isTrue);
|
|
expect(const TextRange(start: -1, end: 10).isCollapsed, isFalse);
|
|
expect(const TextRange(start: 10, end: 0).isCollapsed, isFalse);
|
|
expect(const TextRange(start: 10, end: -1).isCollapsed, isFalse);
|
|
});
|
|
test('isNormalized works', () {
|
|
expect(TextRange.empty.isNormalized, isTrue);
|
|
expect(const TextRange(start: 0, end: 0).isNormalized, isTrue);
|
|
expect(const TextRange(start: 0, end: 10).isNormalized, isTrue);
|
|
expect(const TextRange(start: 10, end: 10).isNormalized, isTrue);
|
|
expect(const TextRange(start: -1, end: 10).isNormalized, isTrue);
|
|
expect(const TextRange(start: 10, end: 0).isNormalized, isFalse);
|
|
expect(const TextRange(start: 10, end: -1).isNormalized, isFalse);
|
|
});
|
|
test('textBefore works', () {
|
|
expect(const TextRange(start: 0, end: 0).textBefore('hello'), isEmpty);
|
|
expect(
|
|
const TextRange(start: 1, end: 1).textBefore('hello'), equals('h'));
|
|
expect(
|
|
const TextRange(start: 1, end: 2).textBefore('hello'), equals('h'));
|
|
expect(const TextRange(start: 5, end: 5).textBefore('hello'),
|
|
equals('hello'));
|
|
expect(const TextRange(start: 0, end: 5).textBefore('hello'), isEmpty);
|
|
});
|
|
test('textAfter works', () {
|
|
expect(const TextRange(start: 0, end: 0).textAfter('hello'),
|
|
equals('hello'));
|
|
expect(
|
|
const TextRange(start: 1, end: 1).textAfter('hello'), equals('ello'));
|
|
expect(
|
|
const TextRange(start: 1, end: 2).textAfter('hello'), equals('llo'));
|
|
expect(const TextRange(start: 5, end: 5).textAfter('hello'), isEmpty);
|
|
expect(const TextRange(start: 0, end: 5).textAfter('hello'), isEmpty);
|
|
});
|
|
test('textInside works', () {
|
|
expect(const TextRange(start: 0, end: 0).textInside('hello'), isEmpty);
|
|
expect(const TextRange(start: 1, end: 1).textInside('hello'), isEmpty);
|
|
expect(
|
|
const TextRange(start: 1, end: 2).textInside('hello'), equals('e'));
|
|
expect(const TextRange(start: 5, end: 5).textInside('hello'), isEmpty);
|
|
expect(const TextRange(start: 0, end: 5).textInside('hello'),
|
|
equals('hello'));
|
|
});
|
|
});
|
|
}
|