blob: 9f0bef654749b6d6ce8ffea787c5de5124f6acc8 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as UI from '../ui/legacy/legacy.js';
import {raf, removeChildren, setColorScheme, TEST_CONTAINER_ID} from './DOMHelpers.js';
const documentBodyElements = new Set<Element>();
function removeElementOrWidget(node: Node, parent = document.body) {
const widget = UI.Widget.Widget.get(node);
if (widget) {
widget.detach();
} else {
parent.removeChild(node);
}
}
/**
* If a widget creates a glass pane, it can get orphaned and not cleaned up correctly.
*/
function removeGlassPanes() {
for (const pane of document.body.querySelectorAll('[data-devtools-glass-pane]')) {
removeElementOrWidget(pane);
}
}
/**
* If a text editor is created we create a special parent for the tooltip
* This does not get cleared after render, but it's internals do.
* So we need to manually remove it
*/
function removeTextEditorTooltip() {
// Found in front_end/ui/components/text_editor/config.ts
for (const tooltip of document.body.querySelectorAll('.editor-tooltip-host')) {
removeElementOrWidget(tooltip);
}
}
function removeAnnouncer() {
UI.ARIAUtils.LiveAnnouncer.removeAnnouncerElements(document.body);
}
/**
* If a test calls localEvalCSS, an element is created on demand for this
* purpose. This element is not removed from the DOM and will leak between tests
* if not removed.
*/
function removeCSSEvaluationElement() {
// Found in front_end/core/sdk/CSSPropertyParserMatchers.ts
const element = document.getElementById('css-evaluation-element');
if (element) {
document.body.removeChild(element);
}
}
/**
* Completely cleans out the test DOM to ensure it's empty for the next test run.
* This is run automatically between tests - you should not be manually calling this yourself.
**/
export const cleanTestDOM = (testName = '') => {
const previousContainer = document.getElementById(TEST_CONTAINER_ID);
if (previousContainer) {
removeChildren(previousContainer);
previousContainer.remove();
}
removeGlassPanes();
removeTextEditorTooltip();
removeAnnouncer();
removeCSSEvaluationElement();
// Verify that nothing was left behind
for (const child of document.body.children) {
if (!documentBodyElements.has(child)) {
console.error(`Test "${testName}" left DOM in document.body:`);
console.error(child);
}
}
};
/**
* Sets up the DOM for testing,
* If not clean logs an error and cleans itself
**/
export const setupTestDOM = async () => {
for (const child of document.body.children) {
documentBodyElements.add(child);
}
const previousContainer = document.getElementById(TEST_CONTAINER_ID);
if (previousContainer) {
// This should not be reachable, unless the
// AfterEach hook fails before cleaning the DOM.
// Clean it here and report
console.error('Non clean test state found!');
cleanTestDOM();
await raf();
}
// Tests are run in light mode by default.
setColorScheme('light');
const newContainer = document.createElement('div');
newContainer.id = TEST_CONTAINER_ID;
// eslint-disable-next-line @devtools/no-document-body-mutation
document.body.appendChild(newContainer);
};