|
2 | 2 | // Use of this source code is governed by a BSD-style license that can be |
3 | 3 | // found in the LICENSE file. |
4 | 4 |
|
| 5 | +import * as SDK from '../../core/sdk/sdk.js'; |
| 6 | +import type * as Protocol from '../../generated/protocol.js'; |
| 7 | +import {createTarget, stubNoopSettings} from '../../testing/EnvironmentHelpers.js'; |
| 8 | +import {describeWithMockConnection, setMockConnectionResponseHandler} from '../../testing/MockConnection.js'; |
| 9 | +import {getMatchedStylesWithBlankRule} from '../../testing/StyleHelpers.js'; |
| 10 | +import * as UI from '../../ui/legacy/legacy.js'; |
| 11 | + |
5 | 12 | import * as Elements from './elements.js'; |
6 | 13 |
|
7 | | -describe('CSSValueTraceView', () => { |
8 | | - it('works', async () => { |
9 | | - const view = new Elements.CSSValueTraceView.CSSValueTraceView(); |
10 | | - view.showTrace( |
11 | | - [[document.createTextNode('sub 1')], [document.createTextNode('sub 2')]], |
12 | | - [[document.createTextNode('eval 1')], [document.createTextNode('eval 2')]], [document.createTextNode('final')]); |
13 | | - const {performUpdate} = view; |
14 | | - const performUpdatePromise = Promise.withResolvers<void>(); |
15 | | - sinon.stub(view, 'performUpdate').callsFake(function(this: unknown) { |
16 | | - performUpdate.call(this); |
17 | | - performUpdatePromise.resolve(); |
| 14 | +async function setUpStyles() { |
| 15 | + stubNoopSettings(); |
| 16 | + setMockConnectionResponseHandler('CSS.enable', () => ({})); |
| 17 | + const computedStyleModel = new Elements.ComputedStyleModel.ComputedStyleModel(); |
| 18 | + const cssModel = new SDK.CSSModel.CSSModel(createTarget()); |
| 19 | + await cssModel.resumeModel(); |
| 20 | + const domModel = cssModel.domModel(); |
| 21 | + const node = new SDK.DOMModel.DOMNode(domModel); |
| 22 | + node.id = 0 as Protocol.DOM.NodeId; |
| 23 | + UI.Context.Context.instance().setFlavor(SDK.DOMModel.DOMNode, node); |
| 24 | + const matchedStyles = await getMatchedStylesWithBlankRule(cssModel); |
| 25 | + const stylesPane = new Elements.StylesSidebarPane.StylesSidebarPane(computedStyleModel); |
| 26 | + |
| 27 | + return {matchedStyles, stylesPane}; |
| 28 | +} |
| 29 | + |
| 30 | +async function getTreeElement( |
| 31 | + matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles, stylesPane: Elements.StylesSidebarPane.StylesSidebarPane, |
| 32 | + name: string, value: string, variables?: Record<string, {value: string, computedValue?: string}>) { |
| 33 | + const property = new SDK.CSSProperty.CSSProperty( |
| 34 | + matchedStyles.nodeStyles()[0], matchedStyles.nodeStyles()[0].pastLastSourcePropertyIndex(), name, value, true, |
| 35 | + false, true, false, '', undefined, []); |
| 36 | + const treeElement = new Elements.StylePropertyTreeElement.StylePropertyTreeElement({ |
| 37 | + stylesPane, |
| 38 | + section: sinon.createStubInstance(Elements.StylePropertiesSection.StylePropertiesSection), |
| 39 | + matchedStyles, |
| 40 | + property, |
| 41 | + isShorthand: false, |
| 42 | + inherited: false, |
| 43 | + overloaded: false, |
| 44 | + newProperty: true, |
| 45 | + }); |
| 46 | + |
| 47 | + if (variables) { |
| 48 | + const varMap = |
| 49 | + new Map(Object.getOwnPropertyNames(variables) |
| 50 | + .map( |
| 51 | + name => new SDK.CSSProperty.CSSProperty( |
| 52 | + matchedStyles.nodeStyles()[0], matchedStyles.nodeStyles()[0].pastLastSourcePropertyIndex(), |
| 53 | + name, variables[name].value, true, false, true, false, '', undefined, [])) |
| 54 | + .map(property => [property.name, { |
| 55 | + value: variables[property.name].computedValue ?? variables[property.name].value, |
| 56 | + declaration: new SDK.CSSMatchedStyles.CSSValueSource(property), |
| 57 | + }])); |
| 58 | + sinon.stub(matchedStyles, 'computeCSSVariable').callsFake((_, name) => varMap.get(name) ?? null); |
| 59 | + } |
| 60 | + |
| 61 | + return {matchedStyles, property, treeElement}; |
| 62 | +} |
| 63 | + |
| 64 | +async function showTrace( |
| 65 | + property: SDK.CSSProperty.CSSProperty, matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles, |
| 66 | + treeElement: Elements.StylePropertyTreeElement.StylePropertyTreeElement): |
| 67 | + Promise<Elements.CSSValueTraceView.ViewInput> { |
| 68 | + let renderPromise = Promise.withResolvers<Elements.CSSValueTraceView.ViewInput>(); |
| 69 | + const view = new Elements.CSSValueTraceView.CSSValueTraceView( |
| 70 | + sinon.stub<Parameters<Elements.CSSValueTraceView.View>>().callsFake(input => { |
| 71 | + renderPromise.resolve(input); |
| 72 | + })); |
| 73 | + await renderPromise.promise; |
| 74 | + renderPromise = Promise.withResolvers<Elements.CSSValueTraceView.ViewInput>(); |
| 75 | + view.showTrace(property, matchedStyles, new Map(), treeElement.getPropertyRenderers()); |
| 76 | + return await renderPromise.promise; |
| 77 | +} |
| 78 | + |
| 79 | +describeWithMockConnection('CSSValueTraceView', () => { |
| 80 | + beforeEach(() => { |
| 81 | + setMockConnectionResponseHandler('CSS.resolveValues', ({values}) => { |
| 82 | + const results = values.map((v: string) => { |
| 83 | + if (v.endsWith('em')) { |
| 84 | + return `${Number(v.substring(0, v.length - 2)) * 16}px`; |
| 85 | + } |
| 86 | + if (v.endsWith('vw')) { |
| 87 | + return `${Number(v.substring(0, v.length - 2)) * 980 / 100}px`; |
| 88 | + } |
| 89 | + switch (v) { |
| 90 | + case 'calc(clamp(16px, calc(1vw + 1em), 24px) + 3.2px)': |
| 91 | + return '27.7px'; |
| 92 | + case 'clamp(16px, calc(1vw + 1em), 24px)': |
| 93 | + return '24px'; |
| 94 | + case 'calc(1vw + 1em)': |
| 95 | + return '24.53px'; |
| 96 | + } |
| 97 | + return v; |
| 98 | + }); |
| 99 | + return {results}; |
18 | 100 | }); |
19 | | - await performUpdatePromise.promise; |
20 | | - assert.deepEqual( |
21 | | - view.contentElement.textContent?.split('\n').map(l => l.trim()).filter(l => l), |
22 | | - ['\u21B3sub 1\u21B3sub 2', '=eval 1', '=eval 2', '=final']); |
| 101 | + }); |
| 102 | + |
| 103 | + it('shows simple values', async () => { |
| 104 | + const {matchedStyles, stylesPane} = await setUpStyles(); |
| 105 | + for (const value of ['40', '40px', 'red']) { |
| 106 | + const {property, treeElement} = await getTreeElement(matchedStyles, stylesPane, 'property', value); |
| 107 | + |
| 108 | + const input = await showTrace(property, matchedStyles, treeElement); |
| 109 | + |
| 110 | + const substitutions = input.substitutions.map(nodes => nodes.map(node => node.textContent ?? '').join()); |
| 111 | + const evaluations = input.evaluations.map(nodes => nodes.map(node => node.textContent ?? '').join()); |
| 112 | + const result = input.finalResult?.map(node => node.textContent ?? '').join(); |
| 113 | + assert.deepEqual(substitutions, []); |
| 114 | + assert.deepEqual(evaluations, []); |
| 115 | + assert.deepEqual(result, value); |
| 116 | + } |
| 117 | + }); |
| 118 | + |
| 119 | + it('does not have substitutions yet', async () => { |
| 120 | + const {matchedStyles, stylesPane} = await setUpStyles(); |
| 121 | + const {property, treeElement} = |
| 122 | + await getTreeElement(matchedStyles, stylesPane, 'width', 'var(--w)', {'--w': {value: '40em'}}); |
| 123 | + const input = await showTrace(property, matchedStyles, treeElement); |
| 124 | + const substitutions = input.substitutions.map(nodes => nodes.map(node => node.textContent ?? '').join()); |
| 125 | + const evaluations = input.evaluations.map(nodes => nodes.map(node => node.textContent ?? '').join()); |
| 126 | + const result = input.finalResult?.map(node => node.textContent ?? '').join(); |
| 127 | + // TODO(pfaffe) once vars actually substitute this needs to show the first line |
| 128 | + assert.deepEqual(substitutions, ['var(--w)']); |
| 129 | + assert.deepEqual(evaluations, []); |
| 130 | + assert.deepEqual(result, 'var(--w)'); |
| 131 | + }); |
| 132 | + |
| 133 | + it('shows intermediate evaluation steps', async () => { |
| 134 | + const {matchedStyles, stylesPane} = await setUpStyles(); |
| 135 | + const {property, treeElement} = await getTreeElement( |
| 136 | + matchedStyles, stylesPane, 'fond-size', 'calc(clamp(16px, calc(1vw + 1em), 24px) + 3.2px)'); |
| 137 | + const resolveValuesSpy = sinon.spy(treeElement.parentPane().cssModel()!.resolveValues); |
| 138 | + const input = await showTrace(property, matchedStyles, treeElement); |
| 139 | + const substitutions = input.substitutions.map(nodes => nodes.map(node => node.textContent ?? '').join('')); |
| 140 | + const evaluations = input.evaluations.map(nodes => nodes.map(node => node.textContent ?? '').join('')); |
| 141 | + const result = input.finalResult?.map(node => node.textContent ?? '').join(''); |
| 142 | + await Promise.all(resolveValuesSpy.returnValues); |
| 143 | + assert.deepEqual(substitutions, []); |
| 144 | + assert.deepEqual(evaluations, [ |
| 145 | + 'calc(clamp(16px, calc(9.8px + 16px), 24px) + 3.2px)', |
| 146 | + 'calc(clamp(16px, 24.53px, 24px) + 3.2px)', |
| 147 | + 'calc(24px + 3.2px)', |
| 148 | + ]); |
| 149 | + assert.deepEqual(result, '27.7px'); |
23 | 150 | }); |
24 | 151 | }); |
0 commit comments