| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as SDK from '../../core/sdk/sdk.js'; |
| import {createTarget, stubNoopSettings} from '../../testing/EnvironmentHelpers.js'; |
| import { |
| describeWithMockConnection, |
| } from '../../testing/MockConnection.js'; |
| import * as UI from '../../ui/legacy/legacy.js'; |
| |
| import * as Elements from './elements.js'; |
| |
| describeWithMockConnection('ElementStatePaneWidget', () => { |
| let target: SDK.Target.Target; |
| let view: Elements.ElementStatePaneWidget.ElementStatePaneWidget; |
| |
| const pseudoClasses = [ |
| 'enabled', 'disabled', 'valid', 'invalid', 'user-valid', |
| 'user-invalid', 'required', 'optional', 'read-only', 'read-write', |
| 'in-range', 'out-of-range', 'visited', 'link', 'checked', |
| 'indeterminate', 'placeholder-shown', 'autofill', 'open', 'target-current', |
| ]; |
| |
| beforeEach(() => { |
| stubNoopSettings(); |
| target = createTarget(); |
| }); |
| |
| const assertExpectedPseudoClasses = async ( |
| nodeName: string, expectedPseudoClasses: string[], formAssociated = false, attribute?: [string, string]) => { |
| view = new Elements.ElementStatePaneWidget.ElementStatePaneWidget(); |
| |
| const model = target.model(SDK.DOMModel.DOMModel); |
| assert.exists(model); |
| |
| const node = new SDK.DOMModel.DOMNode(model); |
| |
| sinon.stub(node, 'nodeType').returns(Node.ELEMENT_NODE); |
| sinon.stub(node, 'nodeName').returns(nodeName); |
| sinon.stub(node, 'enclosingElementOrSelf').returns(node); |
| sinon.stub(node, 'callFunction').resolves({value: formAssociated}); |
| if (attribute) { |
| sinon.stub(node, 'getAttribute').withArgs(attribute[0]).returns(attribute[1]); |
| } |
| if (nodeName.startsWith('::')) { |
| sinon.stub(node, 'pseudoType').returns(nodeName.slice(2)); |
| } |
| UI.Context.Context.instance().setFlavor(SDK.DOMModel.DOMNode, node); |
| |
| await view.updateComplete; |
| const header = view.contentElement.querySelector('.force-specific-element-header'); |
| assert.instanceOf(header, HTMLElement); |
| |
| for (const pseudoClass of pseudoClasses) { |
| const div = view.contentElement.querySelector(`#${pseudoClass}`); |
| const shouldShow = expectedPseudoClasses.includes(pseudoClass); |
| assert.strictEqual( |
| Boolean(div), shouldShow, `Checkbox for ${pseudoClass} should be ${shouldShow ? 'shown' : 'hidden'}`); |
| } |
| }; |
| |
| it('Calls the right backend functions', async () => { |
| view = new Elements.ElementStatePaneWidget.ElementStatePaneWidget(); |
| |
| const model = target.model(SDK.DOMModel.DOMModel); |
| assert.exists(model); |
| |
| const node = new SDK.DOMModel.DOMNode(model); |
| |
| sinon.stub(node, 'nodeType').returns(Node.ELEMENT_NODE); |
| sinon.stub(node, 'nodeName').returns('input'); |
| sinon.stub(node, 'enclosingElementOrSelf').returns(node); |
| sinon.stub(node, 'callFunction').resolves({value: false}); |
| const checkboxes = sinon.spy(node.domModel().cssModel(), 'forcePseudoState'); |
| |
| UI.Context.Context.instance().setFlavor(SDK.DOMModel.DOMNode, node); |
| await view.updateComplete; |
| |
| for (const pseudoClass of pseudoClasses) { |
| const div = view.contentElement.querySelector(`#${pseudoClass}`); |
| if (!div) { |
| continue; |
| } |
| const checkbox = div.children[0] as UI.UIUtils.CheckboxLabel; |
| checkbox.click(); |
| |
| const args = checkboxes.lastCall.args; |
| assert.strictEqual(args[0], node, 'Called forcePseudoState with wrong node'); |
| assert.strictEqual(args[1], pseudoClass, 'Called forcePseudoState with wrong pseudo-state'); |
| assert.isTrue(args[2], 'Called forcePseudoState with wrong enable state'); |
| } |
| }); |
| |
| it('Hidden state for not ELEMENT_NODE type', async () => { |
| view = new Elements.ElementStatePaneWidget.ElementStatePaneWidget(); |
| |
| const model = target.model(SDK.DOMModel.DOMModel); |
| assert.exists(model); |
| const node = new SDK.DOMModel.DOMNode(model); |
| sinon.stub(node, 'nodeType').returns(Node.TEXT_NODE); |
| |
| UI.Context.Context.instance().setFlavor(SDK.DOMModel.DOMNode, node); |
| await view.updateComplete; |
| |
| const details = view.contentElement.querySelector('.specific-details'); |
| assert.exists(details, 'The details element doesn\'t exist'); |
| assert.instanceOf(details, HTMLDetailsElement, 'The details element is not an instance of HTMLDetailsElement'); |
| assert.isTrue(details.hidden, 'The details element is not hidden'); |
| }); |
| |
| it('Hidden state for not supported element type', async () => { |
| view = new Elements.ElementStatePaneWidget.ElementStatePaneWidget(); |
| |
| const model = target.model(SDK.DOMModel.DOMModel); |
| assert.exists(model); |
| const node = new SDK.DOMModel.DOMNode(model); |
| sinon.stub(node, 'nodeName').returns('not supported'); |
| sinon.stub(node, 'enclosingElementOrSelf').returns(node); |
| sinon.stub(node, 'callFunction').resolves({value: false}); |
| |
| UI.Context.Context.instance().setFlavor(SDK.DOMModel.DOMNode, node); |
| await view.updateComplete; |
| |
| const details = view.contentElement.querySelector('.specific-details'); |
| assert.exists(details, 'The details element doesn\'t exist'); |
| assert.instanceOf(details, HTMLDetailsElement, 'The details element is not an instance of HTMLDetailsElement'); |
| assert.isTrue(details.hidden, 'The details element is not hidden'); |
| }); |
| |
| it('Shows the specific pseudo-classes for input', async () => { |
| await assertExpectedPseudoClasses( |
| 'input', |
| [ |
| 'disabled', |
| 'valid', |
| 'invalid', |
| 'user-valid', |
| 'user-invalid', |
| 'required', |
| 'read-only', |
| 'placeholder-shown', |
| 'autofill', |
| 'open', |
| ], |
| ); |
| }); |
| |
| it('Shows the specific pseudo-classes for button', async () => { |
| await assertExpectedPseudoClasses( |
| 'button', |
| ['disabled', 'valid', 'invalid', 'read-write'], |
| ); |
| }); |
| |
| it('Shows the specific pseudo-classes for fieldset', async () => { |
| await assertExpectedPseudoClasses( |
| 'fieldset', |
| ['disabled', 'valid', 'invalid', 'read-write'], |
| ); |
| }); |
| |
| it('Shows the specific pseudo-classes for textarea', async () => { |
| await assertExpectedPseudoClasses( |
| 'textarea', |
| [ |
| 'disabled', |
| 'valid', |
| 'invalid', |
| 'user-valid', |
| 'user-invalid', |
| 'required', |
| 'read-only', |
| 'placeholder-shown', |
| ], |
| ); |
| await assertExpectedPseudoClasses( |
| 'textarea', |
| [ |
| 'disabled', |
| 'valid', |
| 'invalid', |
| 'user-valid', |
| 'user-invalid', |
| 'required', |
| 'read-write', |
| 'placeholder-shown', |
| ], |
| false, ['readonly', '']); |
| await assertExpectedPseudoClasses( |
| 'textarea', |
| [ |
| 'enabled', |
| 'valid', |
| 'invalid', |
| 'user-valid', |
| 'user-invalid', |
| 'required', |
| 'read-write', |
| 'placeholder-shown', |
| ], |
| false, ['disabled', '']); |
| await assertExpectedPseudoClasses( |
| 'textarea', |
| [ |
| 'disabled', |
| 'valid', |
| 'invalid', |
| 'user-valid', |
| 'user-invalid', |
| 'optional', |
| 'read-only', |
| 'placeholder-shown', |
| ], |
| false, ['required', '']); |
| }); |
| |
| it('Shows the specific pseudo-classes for select', async () => { |
| await assertExpectedPseudoClasses( |
| 'select', |
| ['disabled', 'valid', 'invalid', 'user-valid', 'user-invalid', 'required', 'read-write', 'open'], |
| ); |
| }); |
| |
| it('Shows the specific pseudo-classes for option', async () => { |
| await assertExpectedPseudoClasses( |
| 'option', |
| ['disabled', 'checked', 'read-write'], |
| ); |
| }); |
| |
| it('Shows the specific pseudo-classes for optgroup', async () => { |
| await assertExpectedPseudoClasses( |
| 'optgroup', |
| ['disabled', 'read-write'], |
| ); |
| }); |
| |
| it('Shows the specific pseudo-classes for FormAssociated', async () => { |
| await assertExpectedPseudoClasses( |
| 'CustomFormAssociatedElement', |
| ['disabled', 'valid', 'invalid', 'read-write'], |
| true, |
| ); |
| }); |
| |
| it('Shows the specific pseudo-classes for object, output and img', async () => { |
| await assertExpectedPseudoClasses( |
| 'object', |
| ['valid', 'invalid', 'read-write'], |
| ); |
| |
| await assertExpectedPseudoClasses( |
| 'output', |
| ['valid', 'invalid', 'read-write'], |
| ); |
| |
| await assertExpectedPseudoClasses( |
| 'img', |
| ['valid', 'invalid', 'read-write'], |
| ); |
| }); |
| it('Shows the specific pseudo-classes for progress', async () => { |
| await assertExpectedPseudoClasses( |
| 'progress', |
| ['read-write', 'indeterminate'], |
| ); |
| }); |
| |
| it('Shows the specific pseudo-classes for a and area with href', async () => { |
| await assertExpectedPseudoClasses( |
| 'a', |
| ['visited', 'link', 'read-write', 'target-current'], |
| false, |
| ['href', 'www.google.com'], |
| ); |
| |
| await assertExpectedPseudoClasses( |
| 'area', |
| ['visited', 'link', 'read-write'], |
| false, |
| ['href', 'www.google.com'], |
| ); |
| }); |
| |
| it('Shows the specific pseudo-classes for a and area without href', async () => { |
| await assertExpectedPseudoClasses( |
| 'a', |
| ['read-write'], |
| ); |
| |
| await assertExpectedPseudoClasses( |
| 'area', |
| ['read-write'], |
| ); |
| }); |
| |
| it('Shows the specific pseudo-classes for contenteditable div', async () => { |
| await assertExpectedPseudoClasses( |
| 'div', |
| ['read-only'], |
| false, |
| ['contenteditable', ''], |
| ); |
| }); |
| |
| it('Shows the specific pseudo-classes for radio or checkbox inputs', async () => { |
| await assertExpectedPseudoClasses( |
| 'input', |
| [ |
| 'disabled', |
| 'valid', |
| 'invalid', |
| 'user-valid', |
| 'user-invalid', |
| 'required', |
| 'read-only', |
| 'placeholder-shown', |
| 'autofill', |
| 'checked', |
| 'indeterminate', |
| 'open', |
| ], |
| false, |
| ['type', 'checkbox'], |
| ); |
| await assertExpectedPseudoClasses( |
| 'input', |
| [ |
| 'disabled', |
| 'valid', |
| 'invalid', |
| 'user-valid', |
| 'user-invalid', |
| 'required', |
| 'read-only', |
| 'placeholder-shown', |
| 'autofill', |
| 'checked', |
| 'indeterminate', |
| 'open', |
| ], |
| false, |
| ['type', 'radio'], |
| ); |
| }); |
| |
| it('Shows the specific pseudo-classes for datalist, label, legend and meter', async () => { |
| await assertExpectedPseudoClasses( |
| 'datalist', |
| ['read-write'], |
| ); |
| await assertExpectedPseudoClasses( |
| 'label', |
| ['read-write'], |
| ); |
| await assertExpectedPseudoClasses( |
| 'legend', |
| ['read-write'], |
| ); |
| await assertExpectedPseudoClasses( |
| 'meter', |
| ['read-write'], |
| ); |
| }); |
| |
| it('Shows the specific pseudo-classes for ::scroll-marker', async () => { |
| await assertExpectedPseudoClasses( |
| '::scroll-marker', |
| ['read-write', 'target-current'], |
| ); |
| }); |
| }); |