blob: c7b60a0f5cdd13a14a6a642a5a6efaf143c11556 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {assert} from 'chai';
import sinon from 'sinon';
import * as SDK from '../../core/sdk/sdk.js';
import type * as Protocol from '../../generated/protocol.js';
import * as ComputedStyle from '../../models/computed_style/computed_style.js';
import {renderElementIntoDOM} from '../../testing/DOMHelpers.js';
import {describeWithEnvironment} from '../../testing/EnvironmentHelpers.js';
import {createStubbedDomNodeWithModels} from '../../testing/StyleHelpers.js';
import * as Elements from './elements.js';
describeWithEnvironment('MetricsSidebarPane', () => {
function createWidget(
computedStyle: Map<string, string>,
boxModel: Protocol.DOM.BoxModel|null,
inlineStyle: SDK.CSSStyleDeclaration.CSSStyleDeclaration|null = null,
): Elements.MetricsSidebarPane.MetricsSidebarPane {
const {node, cssModel} = createStubbedDomNodeWithModels({nodeId: 1});
(node.nodeType as sinon.SinonStub).returns(Node.ELEMENT_NODE);
(node.boxModel as sinon.SinonStub).resolves(boxModel);
(cssModel.isEnabled as sinon.SinonStub).returns(true);
(cssModel.getComputedStyle as sinon.SinonStub).resolves(computedStyle);
(cssModel.getInlineStyles as sinon.SinonStub).resolves(inlineStyle ? {inlineStyle, attributesStyle: null} : null);
const computedStyleModel = new ComputedStyle.ComputedStyleModel.ComputedStyleModel(node);
const widget = new Elements.MetricsSidebarPane.MetricsSidebarPane(computedStyleModel);
renderElementIntoDOM(widget);
return widget;
}
function getMetricsValues(widget: Elements.MetricsSidebarPane.MetricsSidebarPane) {
const getText = (box: string, side: string) =>
widget.contentElement.querySelector(`.${box} > .${side}`)?.textContent?.trim() ?? '';
const spans = widget.contentElement.querySelectorAll('.content span');
return {
margin: `${getText('margin', 'top')} ${getText('margin', 'right')} ${getText('margin', 'bottom')} ${
getText('margin', 'left')}`,
border: `${getText('border', 'top')} ${getText('border', 'right')} ${getText('border', 'bottom')} ${
getText('border', 'left')}`,
padding: `${getText('padding', 'top')} ${getText('padding', 'right')} ${getText('padding', 'bottom')} ${
getText('padding', 'left')}`,
contentWidth: spans[0]?.textContent?.trim() ?? '',
contentHeight: spans[2]?.textContent?.trim() ?? '',
};
}
function createBoxSizingComputedStyle(boxSizing: 'border-box'|'content-box') {
return new Map([
['display', 'block'],
['position', 'static'],
['box-sizing', boxSizing],
['width', '55px'],
['height', '55px'],
['margin-top', '1px'],
['margin-right', '1px'],
['margin-bottom', '1px'],
['margin-left', '1px'],
['padding-top', '7px'],
['padding-right', '7px'],
['padding-bottom', '7px'],
['padding-left', '7px'],
['border-top-width', '3px'],
['border-right-width', '3px'],
['border-bottom-width', '3px'],
['border-left-width', '3px'],
]);
}
function createStubInlineStyle() {
const inlineStyle = sinon.createStubInstance(SDK.CSSStyleDeclaration.CSSStyleDeclaration);
inlineStyle.allProperties.returns([]);
inlineStyle.appendProperty.callsFake((_name: string, _value: string, callback?: (success: boolean) => void) => {
callback?.(true);
});
return inlineStyle;
}
function editContentWidth(widget: Elements.MetricsSidebarPane.MetricsSidebarPane, newWidth: string) {
const contentWidthSpan = widget.contentElement.querySelectorAll('.content span')[0] as HTMLElement;
contentWidthSpan.dispatchEvent(new MouseEvent('dblclick', {bubbles: true}));
contentWidthSpan.textContent = newWidth;
contentWidthSpan.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter', bubbles: true}));
}
it('renders content width and height from boxModel.content when available', async () => {
const computedStyle = new Map([
['display', 'block'],
['position', 'static'],
['width', '300px'],
['height', '100px'],
['box-sizing', 'content-box'],
]);
const boxModel: Protocol.DOM.BoxModel = {
content: [10, 10, 275, 10, 275, 90, 10, 90],
padding: [0, 0, 300, 0, 300, 100, 0, 100],
border: [0, 0, 300, 0, 300, 100, 0, 100],
margin: [0, 0, 300, 0, 300, 100, 0, 100],
width: 300,
height: 100,
};
const widget = createWidget(computedStyle, boxModel);
widget.wasShown();
await widget.performUpdate();
const spans = widget.contentElement.querySelectorAll('.content span');
assert.exists(spans);
assert.strictEqual(spans[0].textContent, '265');
assert.strictEqual(spans[2].textContent, '80');
widget.detach();
});
it('falls back to computed style width and height when boxModel is not available', async () => {
const computedStyle = new Map([
['display', 'block'],
['position', 'static'],
['width', '300px'],
['height', '100px'],
['box-sizing', 'content-box'],
]);
const widget = createWidget(computedStyle, null);
widget.wasShown();
await widget.performUpdate();
const spans = widget.contentElement.querySelectorAll('.content span');
assert.exists(spans);
assert.strictEqual(spans[0].textContent, '300');
assert.strictEqual(spans[2].textContent, '100');
widget.detach();
});
it('handles border-box dimensions and calculates inline width on edit', async () => {
const computedStyle = createBoxSizingComputedStyle('border-box');
const inlineStyle = createStubInlineStyle();
const widget = createWidget(computedStyle, null, inlineStyle);
widget.wasShown();
await widget.performUpdate();
const initialMetrics = getMetricsValues(widget);
assert.strictEqual(initialMetrics.margin, '1 1 1 1');
assert.strictEqual(initialMetrics.border, '3 3 3 3');
assert.strictEqual(initialMetrics.padding, '7 7 7 7');
assert.strictEqual(initialMetrics.contentWidth, '35');
assert.strictEqual(initialMetrics.contentHeight, '35');
editContentWidth(widget, '60');
// For border-box, editing content width to 60px adds border (3+3) and padding (7+7) to set inline width to 80px.
assert.isTrue(inlineStyle.appendProperty.calledOnceWith('width', '80px'));
computedStyle.set('width', '80px');
await widget.performUpdate();
const modifiedMetrics = getMetricsValues(widget);
assert.strictEqual(modifiedMetrics.margin, '1 1 1 1');
assert.strictEqual(modifiedMetrics.border, '3 3 3 3');
assert.strictEqual(modifiedMetrics.padding, '7 7 7 7');
assert.strictEqual(modifiedMetrics.contentWidth, '60');
assert.strictEqual(modifiedMetrics.contentHeight, '35');
widget.detach();
});
it('handles content-box dimensions and calculates inline width on edit', async () => {
const computedStyle = createBoxSizingComputedStyle('content-box');
const inlineStyle = createStubInlineStyle();
const widget = createWidget(computedStyle, null, inlineStyle);
widget.wasShown();
await widget.performUpdate();
const initialMetrics = getMetricsValues(widget);
assert.strictEqual(initialMetrics.margin, '1 1 1 1');
assert.strictEqual(initialMetrics.border, '3 3 3 3');
assert.strictEqual(initialMetrics.padding, '7 7 7 7');
assert.strictEqual(initialMetrics.contentWidth, '55');
assert.strictEqual(initialMetrics.contentHeight, '55');
editContentWidth(widget, '60');
// For content-box, editing content width to 60px directly sets inline width to 60px.
assert.isTrue(inlineStyle.appendProperty.calledOnceWith('width', '60px'));
computedStyle.set('width', '60px');
await widget.performUpdate();
const modifiedMetrics = getMetricsValues(widget);
assert.strictEqual(modifiedMetrics.margin, '1 1 1 1');
assert.strictEqual(modifiedMetrics.border, '3 3 3 3');
assert.strictEqual(modifiedMetrics.padding, '7 7 7 7');
assert.strictEqual(modifiedMetrics.contentWidth, '60');
assert.strictEqual(modifiedMetrics.contentHeight, '55');
widget.detach();
});
});