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