blob: 205bdafa9f4a1695b09d712c4fc8d41b2a1a4cf5 [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';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() {
final DomElement rootNode = domDocument.createElement('div');
domDocument.body!.append(rootNode);
group('ShadowDomHostNode', () {
final HostNode hostNode = ShadowDomHostNode(rootNode, '14px monospace');
test('Initializes and attaches a shadow root', () {
expect(domInstanceOfString(hostNode.node, 'ShadowRoot'), isTrue);
expect((hostNode.node as DomShadowRoot).host, rootNode);
expect(hostNode.node, rootNode.shadowRoot);
// The shadow root should be initialized with correct parameters.
expect(rootNode.shadowRoot!.mode, 'open');
if (browserEngine != BrowserEngine.firefox &&
browserEngine != BrowserEngine.webkit) {
// Older versions of Safari and Firefox don't support this flag yet.
// See: https://caniuse.com/mdn-api_shadowroot_delegatesfocus
expect(rootNode.shadowRoot!.delegatesFocus, isFalse);
}
});
test('Attaches a stylesheet to the shadow root', () {
final DomElement? style =
hostNode.querySelector('#flt-internals-stylesheet');
expect(style, isNotNull);
expect(style!.tagName, equalsIgnoringCase('style'));
});
test('(Self-test) hasCssRule can extract rules', () {
final DomElement? style =
hostNode.querySelector('#flt-internals-stylesheet');
final bool hasRule = hasCssRule(style,
selector: '.flt-text-editing::placeholder',
declaration: 'opacity: 0');
final bool hasFakeRule = hasCssRule(style,
selector: 'input::selection', declaration: 'color: #fabada;');
expect(hasRule, isTrue);
expect(hasFakeRule, isFalse);
});
test('Attaches outrageous text styles to flt-scene-host', () {
final DomElement? style =
hostNode.querySelector('#flt-internals-stylesheet');
final bool hasColorRed = hasCssRule(style,
selector: 'flt-scene-host', declaration: 'color: red');
bool hasFont = false;
if (isSafari) {
// Safari expands the shorthand rules, so we check for all we've set (separately).
hasFont = hasCssRule(style,
selector: 'flt-scene-host',
declaration: 'font-family: monospace') &&
hasCssRule(style,
selector: 'flt-scene-host', declaration: 'font-size: 14px');
} else {
hasFont = hasCssRule(style,
selector: 'flt-scene-host', declaration: 'font: 14px monospace');
}
expect(hasColorRed, isTrue,
reason: 'Should make foreground color red within scene host.');
expect(hasFont, isTrue, reason: 'Should pass default css font.');
});
test('Attaches styling to remove password reveal icons on Edge', () {
final DomElement? style =
hostNode.querySelector('#flt-internals-stylesheet');
// Check that style.sheet! contains input::-ms-reveal rule
final bool hidesRevealIcons = hasCssRule(style,
selector: 'input::-ms-reveal', declaration: 'display: none');
final bool codeRanInFakeyBrowser = hasCssRule(style,
selector: 'input.fallback-for-fakey-browser-in-ci',
declaration: 'display: none');
if (codeRanInFakeyBrowser) {
print('Please, fix https://github.com/flutter/flutter/issues/116302');
}
expect(hidesRevealIcons || codeRanInFakeyBrowser, isTrue,
reason: 'In Edge, stylesheet must contain "input::-ms-reveal" rule.');
}, skip: !isEdge);
test('Does not attach the Edge-specific style tag on non-Edge browsers',
() {
final DomElement? style =
hostNode.querySelector('#flt-internals-stylesheet');
// Check that style.sheet! contains input::-ms-reveal rule
final bool hidesRevealIcons = hasCssRule(style,
selector: 'input::-ms-reveal', declaration: 'display: none');
expect(hidesRevealIcons, isFalse);
}, skip: isEdge);
test(
'Attaches styles to hide the autofill overlay for browsers that support it',
() {
final DomElement? style =
hostNode.querySelector('#flt-internals-stylesheet');
final String vendorPrefix = (isSafari || isFirefox) ? '' : '-webkit-';
final bool autofillOverlay = hasCssRule(style,
selector: '.transparentTextEditing:${vendorPrefix}autofill',
declaration: 'opacity: 0 !important');
final bool autofillOverlayHovered = hasCssRule(style,
selector: '.transparentTextEditing:${vendorPrefix}autofill:hover',
declaration: 'opacity: 0 !important');
final bool autofillOverlayFocused = hasCssRule(style,
selector: '.transparentTextEditing:${vendorPrefix}autofill:focus',
declaration: 'opacity: 0 !important');
final bool autofillOverlayActive = hasCssRule(style,
selector: '.transparentTextEditing:${vendorPrefix}autofill:active',
declaration: 'opacity: 0 !important');
expect(autofillOverlay, isTrue);
expect(autofillOverlayHovered, isTrue);
expect(autofillOverlayFocused, isTrue);
expect(autofillOverlayActive, isTrue);
}, skip: !browserHasAutofillOverlay());
_runDomTests(hostNode);
});
group('ElementHostNode', () {
final HostNode hostNode = ElementHostNode(rootNode, '');
test('Initializes and attaches a child element', () {
expect(domInstanceOfString(hostNode.node, 'Element'), isTrue);
expect((hostNode.node as DomElement).shadowRoot, isNull);
expect(hostNode.node.parentNode, rootNode);
});
_runDomTests(hostNode);
});
}
// The common test suite that all types of HostNode implementations need to pass.
void _runDomTests(HostNode hostNode) {
group('DOM operations', () {
final DomElement target = domDocument.createElement('div')..id = 'yep';
setUp(() {
hostNode.appendAll(<DomNode>[
domDocument.createElement('div'),
target,
domDocument.createElement('flt-span'),
domDocument.createElement('div'),
]);
});
tearDown(() {
hostNode.node.clearChildren();
});
test('querySelector', () {
final DomElement? found = hostNode.querySelector('#yep');
expect(found, target);
});
test('.contains and .append', () {
final DomElement another = domDocument.createElement('div')
..id = 'another';
expect(hostNode.contains(target), isTrue);
expect(hostNode.contains(another), isFalse);
expect(hostNode.contains(null), isFalse);
hostNode.append(another);
expect(hostNode.contains(another), isTrue);
});
test('querySelectorAll', () {
final List<DomNode> found = hostNode.querySelectorAll('div').toList();
expect(found.length, 3);
expect(found[1], target);
});
});
}
/// Finds out whether a given CSS Rule ([selector] { [declaration]; }) exists in a [styleSheet].
bool hasCssRule(
DomElement? styleSheet, {
required String selector,
required String declaration,
}) {
assert(styleSheet != null);
assert((styleSheet! as DomHTMLStyleElement).sheet != null);
// regexr.com/740ff
final RegExp ruleLike =
RegExp('[^{]*(?:$selector)[^{]*{[^}]*(?:$declaration)[^}]*}');
final DomCSSStyleSheet sheet =
(styleSheet! as DomHTMLStyleElement).sheet! as DomCSSStyleSheet;
// Check that the cssText of any rule matches the ruleLike RegExp.
return sheet.cssRules
.map((DomCSSRule rule) => rule.cssText)
.any((String rule) => ruleLike.hasMatch(rule));
}