1// Copyright 2020 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5const {assert} = chai;
6
7import {renderElementIntoDOM, assertElements, assertElement} from './DOMHelpers.js';
8import {_normalizePositionData, drawGridAreaNames, drawGridLineNumbers, drawGridLineNames, CanvasSize, GridPositionNormalizedDataWithNames, NormalizePositionDataConfig} from '../../../../inspector_overlay/css_grid_label_helpers.js';
9import {AreaBounds, Bounds} from '../../../../inspector_overlay/common.js';
10import {gridStyle} from '../../../../inspector_overlay/highlight_grid_common.js';
11
12const GRID_LABEL_CONTAINER_ID = 'grid-label-container';
13const DEFAULT_GRID_LABEL_LAYER_ID = 'grid-labels';
14const GRID_LINE_NUMBER_LABEL_CONTAINER_CLASS = 'line-numbers';
15const GRID_LINE_NAME_LABEL_CONTAINER_CLASS = 'line-names';
16const GRID_LINE_AREA_LABEL_CONTAINER_CLASS = 'area-names';
17const GRID_TRACK_SIZES_LABEL_CONTAINER_CLASS = 'track-sizes';
18
19/**
20 * This does the same as initFrame but also prepares the DOM for testing grid labels.
21 */
22export function initFrameForGridLabels() {
23  const styleTag = document.createElement('style');
24  styleTag.textContent = gridStyle;
25  document.head.append(styleTag);
26  createGridLabelContainer();
27}
28
29export function initFrameForMultipleGridLabels(numGrids: number) {
30  for (let i = 1; i <= numGrids; i++) {
31    createGridLabelContainer(i);
32  }
33}
34
35export function createGridLabelContainer(layerId?: number) {
36  // Ensure main layer is created first
37  let el = document.getElementById(GRID_LABEL_CONTAINER_ID);
38  if (!el) {
39    el = document.createElement('div');
40    el.id = GRID_LABEL_CONTAINER_ID;
41  }
42
43  const layerEl = el.createChild('div');
44  layerEl.id = layerId ? `grid-${layerId}-labels` : DEFAULT_GRID_LABEL_LAYER_ID;
45  layerEl.createChild('div', GRID_LINE_NUMBER_LABEL_CONTAINER_CLASS);
46  layerEl.createChild('div', GRID_LINE_NAME_LABEL_CONTAINER_CLASS);
47  layerEl.createChild('div', GRID_LINE_AREA_LABEL_CONTAINER_CLASS);
48  layerEl.createChild('div', GRID_TRACK_SIZES_LABEL_CONTAINER_CLASS);
49
50  renderElementIntoDOM(el, {allowMultipleChildren: true});
51}
52
53export function getMainGridLabelContainer(): HTMLElement {
54  const el = document.getElementById(GRID_LABEL_CONTAINER_ID);
55  assertElement(el, HTMLElement);
56  return el;
57}
58
59export function getGridLabelContainer(layerId?: number): HTMLElement {
60  const id = layerId ? `grid-${layerId}-labels` : DEFAULT_GRID_LABEL_LAYER_ID;
61  const el = document.querySelector(`#${GRID_LABEL_CONTAINER_ID} #${CSS.escape(id)}`);
62  assertElement(el, HTMLElement);
63  return el;
64}
65
66export function getGridLineNumberLabelContainer(layerId?: number): HTMLElement {
67  const id = layerId ? `grid-${layerId}-labels` : DEFAULT_GRID_LABEL_LAYER_ID;
68  const el = document.querySelector(
69      `#${GRID_LABEL_CONTAINER_ID} #${CSS.escape(id)} .${GRID_LINE_NUMBER_LABEL_CONTAINER_CLASS}`);
70  assertElement(el, HTMLElement);
71  return el;
72}
73
74export function getGridLineNameLabelContainer(layerId?: number): HTMLElement {
75  const id = layerId ? `grid-${layerId}-labels` : DEFAULT_GRID_LABEL_LAYER_ID;
76  const el =
77      document.querySelector(`#${GRID_LABEL_CONTAINER_ID} #${CSS.escape(id)} .${GRID_LINE_NAME_LABEL_CONTAINER_CLASS}`);
78  assertElement(el, HTMLElement);
79  return el;
80}
81
82export function getGridTrackSizesLabelContainer(layerId?: number): HTMLElement {
83  const id = layerId ? `grid-${layerId}-labels` : DEFAULT_GRID_LABEL_LAYER_ID;
84  const el = document.querySelector(
85      `#${GRID_LABEL_CONTAINER_ID} #${CSS.escape(id)} .${GRID_TRACK_SIZES_LABEL_CONTAINER_CLASS}`);
86  assertElement(el, HTMLElement);
87  return el;
88}
89
90export function getGridAreaNameLabelContainer(layerId?: number): HTMLElement {
91  const id = layerId ? `grid-${layerId}-labels` : DEFAULT_GRID_LABEL_LAYER_ID;
92  const el =
93      document.querySelector(`#${GRID_LABEL_CONTAINER_ID} #${CSS.escape(id)} .${GRID_LINE_AREA_LABEL_CONTAINER_CLASS}`);
94  assertElement(el, HTMLElement);
95  return el;
96}
97
98interface Position {
99  x: number;
100  y: number;
101}
102
103interface TrackSize extends Position {
104  computedSize: number;
105}
106
107interface NamedLinePosition extends Position {
108  name: string;
109}
110
111interface ExpectedLayerLabel {
112  layerId: number;
113  expectedLabels: ExpectedLineNumberLabel[];
114}
115interface ExpectedLineNumberLabel {
116  className: string;
117  count: number;
118}
119
120interface ExpectedLineNameLabel {
121  type: string;
122  textContent: string;
123  x?: number;
124  y?: number;
125}
126
127interface ExpectedAreaNameLabel {
128  textContent: string;
129  left?: string;
130  top?: string;
131}
132
133export function drawGridLineNumbersAndAssertLabels(
134    config: NormalizePositionDataConfig&{writingMode?: string}, bounds: Bounds, canvasSize: CanvasSize, layerId: number,
135    expectedLabels: ExpectedLineNumberLabel[]) {
136  const el = getGridLineNumberLabelContainer(layerId);
137  const data = _normalizePositionData(config, bounds);
138
139  // Note that this test helper is focused on testing the number and orientation of the labels, not their exact position
140  // so we pass the identity matrix here in all cases, even when a different writing mode is provided.
141  drawGridLineNumbers(el, data, canvasSize, new DOMMatrix(), config.writingMode);
142  let totalLabelCount = 0;
143  for (const {className, count} of expectedLabels) {
144    const labels =
145        el.querySelectorAll(`.${GRID_LINE_NUMBER_LABEL_CONTAINER_CLASS} .grid-label-content.${CSS.escape(className)}`);
146    assert.strictEqual(labels.length, count, `Expected ${count} labels to be displayed for ${className}`);
147    totalLabelCount += count;
148  }
149
150  assert.strictEqual(
151      el.querySelectorAll(`.${GRID_LINE_NUMBER_LABEL_CONTAINER_CLASS} .grid-label-content`).length, totalLabelCount,
152      'The right total number of line number labels were displayed');
153}
154
155export function drawGridLineNamesAndAssertLabels(
156    config: NormalizePositionDataConfig, bounds: Bounds, canvasSize: CanvasSize, layerId: number,
157    expectedLabels: ExpectedLineNameLabel[]) {
158  const el = getGridLineNameLabelContainer(layerId);
159  const data = _normalizePositionData(config, bounds);
160  drawGridLineNames(el, data as GridPositionNormalizedDataWithNames, canvasSize);
161
162  const labels = el.querySelectorAll(`.${GRID_LINE_NAME_LABEL_CONTAINER_CLASS} .grid-label-content`);
163  assert.strictEqual(labels.length, expectedLabels.length, 'The right total number of line name labels were displayed');
164  assertElements(labels, HTMLElement);
165
166  const foundLabels: {textContent: string, x: number, y: number}[] = [];
167  labels.forEach(el => {
168    const width = el.offsetWidth;
169    const height = el.offsetHeight;
170    const top = parseInt(el.style.top, 10);
171    const left = parseInt(el.style.left, 10);
172
173    let rowOffset = height / 2;
174    if (el.classList.contains('right-top')) {
175      rowOffset = 0;
176    } else if (el.classList.contains('right-bottom')) {
177      rowOffset = height;
178    }
179
180    let columnOffset = width / 2;
181    if (el.classList.contains('bottom-left')) {
182      columnOffset = 0;
183    } else if (el.classList.contains('bottom-right')) {
184      columnOffset = width;
185    }
186
187    foundLabels.push({
188      textContent: el.textContent || '',
189      x: left + columnOffset,
190      y: top + rowOffset,
191    });
192  });
193
194  for (const expected of expectedLabels) {
195    const foundLabel = foundLabels.find(({textContent}) => textContent === expected.textContent);
196
197    if (!foundLabel) {
198      assert.fail(`Expected line name label with text content ${expected.textContent} not found`);
199      return;
200    }
201
202    if (expected.type === 'column' && typeof expected.x !== 'undefined') {
203      assert.strictEqual(
204          foundLabel.x, expected.x,
205          `Expected column line name label ${expected.textContent} to be positioned at ${expected.x}px`);
206    }
207    if (expected.type === 'row' && typeof expected.y !== 'undefined') {
208      assert.strictEqual(
209          foundLabel.y, expected.y,
210          `Expected row line name label ${expected.textContent} to be positioned at ${expected.y}px`);
211    }
212  }
213}
214
215export function drawGridAreaNamesAndAssertLabels(
216    areaNames: AreaBounds[], writingModeMatrix: DOMMatrix|undefined, writingMode: string|undefined,
217    expectedLabels: ExpectedAreaNameLabel[]) {
218  const el = getGridAreaNameLabelContainer();
219  drawGridAreaNames(el, areaNames, writingModeMatrix, writingMode);
220
221  const labels = el.querySelectorAll(`.${GRID_LINE_AREA_LABEL_CONTAINER_CLASS} .grid-label-content`);
222  assert.strictEqual(labels.length, expectedLabels.length, 'The right total number of area name labels were displayed');
223  assertElements(labels, HTMLElement);
224
225  const foundLabels: ExpectedAreaNameLabel[] = [];
226  labels.forEach(label => {
227    foundLabels.push({
228      textContent: label.textContent || '',
229      top: label.style.top,
230      left: label.style.left,
231    });
232  });
233  for (const expected of expectedLabels) {
234    const foundLabel = foundLabels.find(({textContent}) => textContent === expected.textContent);
235
236    if (!foundLabel) {
237      assert.fail(`Expected area label with text content ${expected.textContent} not found`);
238      return;
239    }
240
241    if (typeof expected.left !== 'undefined') {
242      assert.strictEqual(
243          foundLabel.left, expected.left,
244          `Expected label ${expected.textContent} to be left positioned to ${expected.left}`);
245    }
246    if (typeof expected.top !== 'undefined') {
247      assert.strictEqual(
248          foundLabel.top, expected.top,
249          `Expected label ${expected.textContent} to be top positioned to ${expected.top}`);
250    }
251  }
252}
253
254export function drawMultipleGridLineNumbersAndAssertLabels(
255    configs: Array<{config: NormalizePositionDataConfig, layerId: number}>, bounds: Bounds, canvasSize: CanvasSize,
256    expectedLabelList: ExpectedLayerLabel[]) {
257  for (const item of configs) {
258    const el = getGridLineNumberLabelContainer(item.layerId);
259    const data = _normalizePositionData(item.config, bounds);
260    drawGridLineNumbers(el, data, canvasSize);
261  }
262
263  let totalLabelCount = 0;
264  for (const {layerId, expectedLabels} of expectedLabelList) {
265    const el = getGridLineNumberLabelContainer(layerId);
266    for (const {className, count} of expectedLabels) {
267      const labels = el.querySelectorAll(
268          `.${GRID_LINE_NUMBER_LABEL_CONTAINER_CLASS} .grid-label-content.${CSS.escape(className)}`);
269      assert.strictEqual(labels.length, count, `Expected ${count} labels to be displayed for ${className}`);
270      totalLabelCount += count;
271    }
272  }
273
274  const mainLayerEl = getMainGridLabelContainer();
275  assert.strictEqual(
276      mainLayerEl.querySelectorAll(`.${GRID_LINE_NUMBER_LABEL_CONTAINER_CLASS} .grid-label-content`).length,
277      totalLabelCount, 'The right total number of line number labels were displayed');
278}
279