| // 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); |
| |
| _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)); |
| } |