chrome-devtools-frontend
Version:
Chrome DevTools UI
364 lines (319 loc) • 11 kB
text/typescript
// Copyright 2024 The Chromium 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 * 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',
];
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]);
}
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'],
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'],
);
});
});