blob: a9e5122b90a20371c0fe89d3db22dd67d9fb88af [file] [log] [blame]
// 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.
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
bool get isIosSafari => browserEngine == BrowserEngine.webkit &&
operatingSystem == OperatingSystem.iOs;
String fontFamilyToAttribute(String fontFamily) {
fontFamily = canonicalizeFontFamily(fontFamily)!;
if (browserEngine == BrowserEngine.firefox) {
fontFamily = fontFamily.replaceAll('"', '"');
} else if (browserEngine == BrowserEngine.blink ||
browserEngine == BrowserEngine.samsung ||
browserEngine == BrowserEngine.webkit) {
fontFamily = fontFamily.replaceAll('"', '');
}
return 'font-family: $fontFamily;';
}
final String defaultFontFamily = fontFamilyToAttribute('Ahem');
const String defaultColor = 'color: rgb(255, 0, 0);';
const String defaultFontSize = 'font-size: 14px;';
const String paragraphStyle =
'position: absolute; white-space: pre;';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
Future<void> testMain() async {
await webOnlyInitializeTestDomRenderer();
setUpAll(() {
WebExperiments.ensureInitialized();
});
test('Builds a text-only canvas paragraph', () {
final EngineParagraphStyle style = EngineParagraphStyle(fontSize: 13.0);
final CanvasParagraphBuilder builder = CanvasParagraphBuilder(style);
builder.addText('Hello');
final CanvasParagraph paragraph = builder.build();
expect(paragraph.paragraphStyle, style);
expect(paragraph.toPlainText(), 'Hello');
expect(paragraph.spans, hasLength(1));
paragraph.layout(const ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
'<p style="font-size: 13px; $defaultFontFamily $paragraphStyle">'
'<span style="$defaultColor font-size: 13px; $defaultFontFamily">'
'Hello'
'</span>'
'</p>',
);
// Should break "Hello" into "Hel" and "lo".
paragraph.layout(const ParagraphConstraints(width: 39.0));
expect(
paragraph.toDomElement().outerHtml,
'<p style="font-size: 13px; $defaultFontFamily $paragraphStyle">'
'<span style="$defaultColor font-size: 13px; $defaultFontFamily">'
'Hel<br>lo'
'</span>'
'</p>',
);
final ParagraphSpan span = paragraph.spans.single;
expect(span, isA<FlatTextSpan>());
final FlatTextSpan textSpan = span as FlatTextSpan;
expect(textSpan.textOf(paragraph), 'Hello');
expect(textSpan.style, styleWithDefaults(fontSize: 13.0));
});
test('Correct defaults', () {
final EngineParagraphStyle style = EngineParagraphStyle();
final CanvasParagraphBuilder builder = CanvasParagraphBuilder(style);
builder.addText('Hello');
final CanvasParagraph paragraph = builder.build();
expect(paragraph.paragraphStyle, style);
expect(paragraph.toPlainText(), 'Hello');
expect(paragraph.spans, hasLength(1));
paragraph.layout(const ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
'<p style="$defaultFontSize $defaultFontFamily $paragraphStyle">'
'<span style="$defaultColor $defaultFontSize $defaultFontFamily">'
'Hello'
'</span>'
'</p>',
);
final FlatTextSpan textSpan = paragraph.spans.single as FlatTextSpan;
expect(textSpan.style, styleWithDefaults());
});
test('Sets correct styles for max-lines', () {
final EngineParagraphStyle style = EngineParagraphStyle(maxLines: 2);
final CanvasParagraphBuilder builder = CanvasParagraphBuilder(style);
builder.addText('Hello');
final CanvasParagraph paragraph = builder.build();
expect(paragraph.paragraphStyle, style);
expect(paragraph.toPlainText(), 'Hello');
double expectedHeight = 14.0;
if (isIosSafari) {
// On iOS Safari, the height measurement is one extra pixel.
expectedHeight++;
}
paragraph.layout(const ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
'<p style="$defaultFontSize $defaultFontFamily $paragraphStyle overflow-y: hidden; height: ${expectedHeight}px;">'
'<span style="$defaultColor $defaultFontSize $defaultFontFamily">'
'Hello'
'</span>'
'</p>',
);
});
test('Sets correct styles for ellipsis', () {
final EngineParagraphStyle style = EngineParagraphStyle(ellipsis: '...');
final CanvasParagraphBuilder builder = CanvasParagraphBuilder(style);
builder.addText('HelloWorld');
final CanvasParagraph paragraph = builder.build();
expect(paragraph.paragraphStyle, style);
expect(paragraph.toPlainText(), 'HelloWorld');
double expectedHeight = 14.0;
if (isIosSafari) {
// On iOS Safari, the height measurement is one extra pixel.
expectedHeight++;
}
paragraph.layout(const ParagraphConstraints(width: 100.0));
expect(
paragraph.toDomElement().outerHtml,
'<p style="$defaultFontSize $defaultFontFamily $paragraphStyle width: 100px; overflow-y: hidden; height: ${expectedHeight}px;">'
'<span style="$defaultColor $defaultFontSize $defaultFontFamily">'
'Hell...'
'</span>'
'</p>',
);
});
test('Builds a single-span paragraph with complex styles', () {
final EngineParagraphStyle style =
EngineParagraphStyle(fontSize: 13.0, height: 1.5);
final CanvasParagraphBuilder builder = CanvasParagraphBuilder(style);
builder.pushStyle(TextStyle(fontSize: 9.0));
builder.pushStyle(TextStyle(fontWeight: FontWeight.bold));
builder.pushStyle(TextStyle(fontSize: 40.0));
builder.pop();
builder
.pushStyle(TextStyle(fontStyle: FontStyle.italic, letterSpacing: 2.0));
builder.addText('Hello');
final CanvasParagraph paragraph = builder.build();
expect(paragraph.toPlainText(), 'Hello');
expect(paragraph.spans, hasLength(1));
paragraph.layout(const ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
'<p style="line-height: 1.5; font-size: 9px; $defaultFontFamily $paragraphStyle">'
'<span style="$defaultColor line-height: 1.5; font-size: 9px; font-weight: bold; font-style: italic; $defaultFontFamily letter-spacing: 2px;">'
'Hello'
'</span>'
'</p>',
);
final FlatTextSpan span = paragraph.spans.single as FlatTextSpan;
expect(span.textOf(paragraph), 'Hello');
expect(
span.style,
styleWithDefaults(
height: 1.5,
fontSize: 9.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
letterSpacing: 2.0,
),
);
});
test('Builds a multi-span paragraph', () {
final EngineParagraphStyle style = EngineParagraphStyle(fontSize: 13.0);
final CanvasParagraphBuilder builder = CanvasParagraphBuilder(style);
builder.pushStyle(TextStyle(fontWeight: FontWeight.bold));
builder.addText('Hello');
builder.pop();
builder.pushStyle(TextStyle(fontStyle: FontStyle.italic));
builder.addText(' world');
final CanvasParagraph paragraph = builder.build();
expect(paragraph.toPlainText(), 'Hello world');
expect(paragraph.spans, hasLength(2));
paragraph.layout(const ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
'<p style="font-size: 13px; $defaultFontFamily $paragraphStyle">'
'<span style="$defaultColor font-size: 13px; font-weight: bold; $defaultFontFamily">'
'Hello'
'</span>'
'<span style="$defaultColor font-size: 13px; font-style: italic; $defaultFontFamily">'
' world'
'</span>'
'</p>',
);
// Should break "Hello world" into "Hello" and " world".
paragraph.layout(const ParagraphConstraints(width: 75.0));
expect(
paragraph.toDomElement().outerHtml,
'<p style="font-size: 13px; $defaultFontFamily $paragraphStyle width: 75px;">'
'<span style="$defaultColor font-size: 13px; font-weight: bold; $defaultFontFamily">'
'Hello'
'</span>'
'<span style="$defaultColor font-size: 13px; font-style: italic; $defaultFontFamily">'
' <br>world'
'</span>'
'</p>',
);
final FlatTextSpan hello = paragraph.spans.first as FlatTextSpan;
expect(hello.textOf(paragraph), 'Hello');
expect(
hello.style,
styleWithDefaults(
fontSize: 13.0,
fontWeight: FontWeight.bold,
),
);
final FlatTextSpan world = paragraph.spans.last as FlatTextSpan;
expect(world.textOf(paragraph), ' world');
expect(
world.style,
styleWithDefaults(
fontSize: 13.0,
fontStyle: FontStyle.italic,
),
);
});
test('Builds a multi-span paragraph with complex styles', () {
final EngineParagraphStyle style = EngineParagraphStyle(fontSize: 13.0);
final CanvasParagraphBuilder builder = CanvasParagraphBuilder(style);
builder.pushStyle(TextStyle(fontWeight: FontWeight.bold));
builder.pushStyle(TextStyle(height: 2.0));
builder.addText('Hello');
builder.pop(); // pop TextStyle(height: 2.0).
builder.pushStyle(TextStyle(fontStyle: FontStyle.italic));
builder.addText(' world');
builder.pushStyle(TextStyle(fontWeight: FontWeight.normal));
builder.addText('!');
final CanvasParagraph paragraph = builder.build();
expect(paragraph.toPlainText(), 'Hello world!');
expect(paragraph.spans, hasLength(3));
paragraph.layout(const ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
'<p style="font-size: 13px; $defaultFontFamily $paragraphStyle">'
'<span style="$defaultColor line-height: 2; font-size: 13px; font-weight: bold; $defaultFontFamily">'
'Hello'
'</span>'
'<span style="$defaultColor font-size: 13px; font-weight: bold; font-style: italic; $defaultFontFamily">'
' world'
'</span>'
'<span style="$defaultColor font-size: 13px; font-weight: normal; font-style: italic; $defaultFontFamily">'
'!'
'</span>'
'</p>',
);
final FlatTextSpan hello = paragraph.spans[0] as FlatTextSpan;
expect(hello.textOf(paragraph), 'Hello');
expect(
hello.style,
styleWithDefaults(
fontSize: 13.0,
fontWeight: FontWeight.bold,
height: 2.0,
),
);
final FlatTextSpan world = paragraph.spans[1] as FlatTextSpan;
expect(world.textOf(paragraph), ' world');
expect(
world.style,
styleWithDefaults(
fontSize: 13.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
),
);
final FlatTextSpan bang = paragraph.spans[2] as FlatTextSpan;
expect(bang.textOf(paragraph), '!');
expect(
bang.style,
styleWithDefaults(
fontSize: 13.0,
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
),
);
});
test('Paragraph with new lines generates correct DOM', () {
final EngineParagraphStyle style = EngineParagraphStyle(fontSize: 13.0);
final CanvasParagraphBuilder builder = CanvasParagraphBuilder(style);
builder.addText('First\nSecond ');
builder.pushStyle(TextStyle(fontStyle: FontStyle.italic));
builder.addText('ThirdLongLine');
final CanvasParagraph paragraph = builder.build();
expect(paragraph.toPlainText(), 'First\nSecond ThirdLongLine');
expect(paragraph.spans, hasLength(2));
// There's a new line between "First" and "Second", but "Second" and
// "ThirdLongLine" remain together since constraints are infinite.
paragraph.layout(const ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
'<p style="font-size: 13px; $defaultFontFamily $paragraphStyle">'
'<span style="$defaultColor font-size: 13px; $defaultFontFamily">'
'First<br>Second '
'</span>'
'<span style="$defaultColor font-size: 13px; font-style: italic; $defaultFontFamily">'
'ThirdLongLine'
'</span>'
'</p>',
);
// Should break the paragraph into "First", "Second" and "ThirdLongLine".
paragraph.layout(const ParagraphConstraints(width: 180.0));
expect(
paragraph.toDomElement().outerHtml,
'<p style="font-size: 13px; $defaultFontFamily $paragraphStyle width: 180px;">'
'<span style="$defaultColor font-size: 13px; $defaultFontFamily">'
'First<br>Second <br>'
'</span>'
'<span style="$defaultColor font-size: 13px; font-style: italic; $defaultFontFamily">'
'ThirdLongLine'
'</span>'
'</p>',
);
});
test('various font sizes', () {
// Paragraphs and spans force the Ahem font in test mode. We need to trick
// them into thinking they are not in test mode, so they use the provided
// font family.
debugEmulateFlutterTesterEnvironment = false;
final EngineParagraphStyle style = EngineParagraphStyle(fontSize: 12.0, fontFamily: 'first');
final CanvasParagraphBuilder builder = CanvasParagraphBuilder(style);
builder.addText('First ');
builder.pushStyle(TextStyle(fontSize: 18.0, fontFamily: 'second'));
builder.addText('Second ');
builder.pushStyle(TextStyle(fontSize: 10.0, fontFamily: 'third'));
builder.addText('Third');
final CanvasParagraph paragraph = builder.build();
expect(paragraph.toPlainText(), 'First Second Third');
expect(paragraph.spans, hasLength(3));
// The paragraph should take the font size and family from the span with the
// greatest font size.
paragraph.layout(const ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
'<p style="font-size: 18px; ${fontFamilyToAttribute('second')} $paragraphStyle">'
'<span style="$defaultColor font-size: 12px; ${fontFamilyToAttribute('first')}">'
'First '
'</span>'
'<span style="$defaultColor font-size: 18px; ${fontFamilyToAttribute('second')}">'
'Second '
'</span>'
'<span style="$defaultColor font-size: 10px; ${fontFamilyToAttribute('third')}">'
'Third'
'</span>'
'</p>',
);
debugEmulateFlutterTesterEnvironment = true;
});
}
TextStyle styleWithDefaults({
Color color = const Color(0xFFFF0000),
String fontFamily = DomRenderer.defaultFontFamily,
double fontSize = DomRenderer.defaultFontSize,
FontWeight? fontWeight,
FontStyle? fontStyle,
double? height,
double? letterSpacing,
}) {
return TextStyle(
color: color,
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: fontWeight,
fontStyle: fontStyle,
height: height,
letterSpacing: letterSpacing,
);
}