Files
react-native-devtools-frontend/front_end/console_test_runner/ConsoleTestRunner.js
T
Patrick Brosset e65aaac861 Enable the no-return-assign ESLint rule
This rule disallows assigning a value in a return statement.
It also works in one-liner arrow functions.

For instance, a => a = 2 will trigger a warning.
This is to avoid cases where you actually meant to compare a to 2, not
assign the value 2 (maybe you wanted to return a boolean in a filter
callback).
These sorts of issues can easily pass review and cause weird problems
that are hard to track down later.

The rule is enabled in this CL, and all warnings are fixed with this CL.

More info about the rule: https://eslint.org/docs/rules/no-return-assign

Bug: 1093379
Change-Id: I38b98ea59ecaedc83a7f69073b17980808f9a3bf
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2242761
Reviewed-by: Patrick Brosset <patrick.brosset@microsoft.com>
Reviewed-by: Jose Leal <joselea@microsoft.com>
Reviewed-by: Tim van der Lippe <tvanderlippe@chromium.org>
Commit-Queue: Patrick Brosset <patrick.brosset@microsoft.com>
2020-06-22 08:34:42 +00:00

696 lines
23 KiB
JavaScript

// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview using private properties isn't a Closure violation in tests.
* @suppress {accessControls}
*/
/** @typedef {function(!Element, !SDK.ConsoleMessage=):string} */
ConsoleTestRunner.Formatter;
/**
* @param {boolean=} printOriginatingCommand
* @param {boolean=} dumpClassNames
* @param {!ConsoleTestRunner.Formatter=} formatter
*/
ConsoleTestRunner.dumpConsoleMessages = async function(printOriginatingCommand, dumpClassNames, formatter) {
TestRunner.addResults(
await ConsoleTestRunner.dumpConsoleMessagesIntoArray(printOriginatingCommand, dumpClassNames, formatter));
};
/**
* @param {boolean=} printOriginatingCommand
* @param {boolean=} dumpClassNames
* @param {!ConsoleTestRunner.Formatter=} formatter
* @return {!Promise<!Array<string>>}
*/
ConsoleTestRunner.dumpConsoleMessagesIntoArray = async function(printOriginatingCommand, dumpClassNames, formatter) {
formatter = formatter || ConsoleTestRunner.prepareConsoleMessageText;
const result = [];
const consoleView = Console.ConsoleView.instance();
const originalViewportStyle = consoleView._viewport.element.style;
const originalSize = {width: originalViewportStyle.width, height: originalViewportStyle.height};
ConsoleTestRunner.disableConsoleViewport();
if (consoleView._needsFullUpdate) {
consoleView._updateMessageList();
}
const viewMessages = consoleView._visibleViewMessages;
for (let i = 0; i < viewMessages.length; ++i) {
const uiMessage = viewMessages[i];
const message = uiMessage.consoleMessage();
const element = uiMessage.element();
// Retrieving the message element triggered rendering, now wait for
// the live location within to be resolved initially.
await TestRunner.waitForPendingLiveLocationUpdates();
let classNames;
if (dumpClassNames) {
classNames = [''];
for (let node = element.firstChild; node; node = node.traverseNextNode(element)) {
if (node.nodeType === Node.ELEMENT_NODE && node.className) {
let depth = 0;
let depthTest = node;
while (depthTest !== element) {
if (depthTest.nodeType === Node.ELEMENT_NODE && depthTest.className) {
depth++;
}
depthTest = depthTest.parentNodeOrShadowHost();
}
classNames.push(
' '.repeat(depth) +
node.className.replace('platform-linux', 'platform-*')
.replace('platform-mac', 'platform-*')
.replace('platform-windows', 'platform-*'));
}
}
}
if (ConsoleTestRunner.dumpConsoleTableMessage(uiMessage, false, result)) {
if (dumpClassNames) {
result.push(classNames.join('\n'));
}
} else {
let messageText = formatter(element, message);
messageText = messageText.replace(/VM\d+/g, 'VM');
result.push(messageText + (dumpClassNames ? ' ' + classNames.join('\n') : ''));
}
if (printOriginatingCommand && uiMessage.consoleMessage().originatingMessage()) {
result.push('Originating from: ' + uiMessage.consoleMessage().originatingMessage().messageText);
}
}
consoleView._viewport.element.style.width = originalSize.width;
consoleView._viewport.element.style.height = originalSize.height;
return result;
};
/**
* @param {!Element} messageElement
* @return {string}
*/
ConsoleTestRunner.prepareConsoleMessageText = function(messageElement) {
let messageText = messageElement.deepTextContent().replace(/\u200b/g, '');
// Replace scriptIds with generic scriptId string to avoid flakiness.
messageText = messageText.replace(/VM\d+/g, 'VM');
// Remove line and column of evaluate method.
messageText = messageText.replace(/(at eval \(eval at evaluate) \(:\d+:\d+\)/, '$1');
if (messageText.startsWith('Navigated to')) {
const fileName = messageText.split(' ').pop().split('/').pop();
messageText = 'Navigated to ' + fileName;
}
// The message might be extremely long in case of dumping stack overflow message.
messageText = messageText.substring(0, 1024);
return messageText;
};
/**
* @param {!Console.ConsoleViewMessage} viewMessage
* @param {boolean} forceInvalidate
* @param {!Array<string>} results
* @return {boolean}
*/
ConsoleTestRunner.dumpConsoleTableMessage = function(viewMessage, forceInvalidate, results) {
if (forceInvalidate) {
Console.ConsoleView.instance()._viewport.invalidate();
}
const table = viewMessage.element();
const headers = table.querySelectorAll('th > div:first-child');
if (!headers.length) {
return false;
}
let headerLine = '';
for (let i = 0; i < headers.length; i++) {
headerLine += headers[i].textContent + ' | ';
}
addResult('HEADER ' + headerLine);
const rows = table.querySelectorAll('.data-container tr');
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
let rowLine = '';
const items = row.querySelectorAll('td > span');
for (let j = 0; j < items.length; j++) {
rowLine += items[j].textContent + ' | ';
}
if (rowLine.trim()) {
addResult('ROW ' + rowLine);
}
}
/**
* @param {string} x
*/
function addResult(x) {
if (results) {
results.push(x);
} else {
TestRunner.addResult(x);
}
}
return true;
};
ConsoleTestRunner.disableConsoleViewport = function() {
ConsoleTestRunner.fixConsoleViewportDimensions(600, 2000);
};
/**
* @param {number} width
* @param {number} height
*/
ConsoleTestRunner.fixConsoleViewportDimensions = function(width, height) {
const viewport = Console.ConsoleView.instance()._viewport;
viewport.element.style.width = width + 'px';
viewport.element.style.height = height + 'px';
viewport.element.style.position = 'absolute';
viewport.invalidate();
};
ConsoleTestRunner.selectMainExecutionContext = function() {
const executionContexts = TestRunner.runtimeModel.executionContexts();
for (const context of executionContexts) {
if (context.isDefault) {
self.UI.context.setFlavor(SDK.ExecutionContext, context);
return;
}
}
};
/**
* @param {string} code
* @param {!Function=} callback
* @param {boolean=} dontForceMainContext
*/
ConsoleTestRunner.evaluateInConsole = function(code, callback, dontForceMainContext) {
if (!dontForceMainContext) {
ConsoleTestRunner.selectMainExecutionContext();
}
callback = TestRunner.safeWrap(callback);
const consoleView = Console.ConsoleView.instance();
consoleView._prompt._appendCommand(code, true);
ConsoleTestRunner.addConsoleViewSniffer(function(commandResult) {
const element = commandResult.toMessageElement();
// Only call the callback once the live location within the
// message element is resolved initially.
TestRunner.waitForPendingLiveLocationUpdates().then(() => {
callback(element.deepTextContent());
});
});
};
/**
* @param {string} code
* @param {boolean=} dontForceMainContext
* @return {!Promise}
*/
ConsoleTestRunner.evaluateInConsolePromise = function(code, dontForceMainContext) {
return new Promise(fulfill => ConsoleTestRunner.evaluateInConsole(code, fulfill, dontForceMainContext));
};
/**
* @param {!Function} override
* @param {boolean=} opt_sticky
*/
ConsoleTestRunner.addConsoleViewSniffer = function(override, opt_sticky) {
TestRunner.addSniffer(Console.ConsoleView.prototype, '_consoleMessageAddedForTest', override, opt_sticky);
};
ConsoleTestRunner.waitForPendingViewportUpdates = async function() {
const refreshPromise = Console.ConsoleView.instance()._scheduledRefreshPromiseForTest || Promise.resolve();
await refreshPromise;
};
/**
* @param {string} code
* @param {!Function=} callback
* @param {boolean=} dontForceMainContext
*/
ConsoleTestRunner.evaluateInConsoleAndDump = function(code, callback, dontForceMainContext) {
callback = TestRunner.safeWrap(callback);
/**
* @param {string} text
*/
function mycallback(text) {
text = text.replace(/\bVM\d+/g, 'VM');
TestRunner.addResult(code + ' = ' + text);
callback(text);
}
ConsoleTestRunner.evaluateInConsole(code, mycallback, dontForceMainContext);
};
/**
* @param {string} code
* @param {boolean=} dontForceMainContext
* @return {!Promise}
*/
ConsoleTestRunner.evaluateInConsoleAndDumpPromise = function(code, dontForceMainContext) {
return new Promise(fulfill => ConsoleTestRunner.evaluateInConsoleAndDump(code, fulfill, dontForceMainContext));
};
/**
* @return {number}
*/
ConsoleTestRunner.consoleMessagesCount = function() {
const consoleView = Console.ConsoleView.instance();
return consoleView._consoleMessages.length;
};
/**
* @param {function(!Element):string|undefined} messageFormatter
* @param {!Element} node
* @return {string}
*/
ConsoleTestRunner.formatterIgnoreStackFrameUrls = function(messageFormatter, node) {
/**
* @param {string} string
*/
function isNotEmptyLine(string) {
return string.trim().length > 0;
}
/**
* @param {string} string
*/
function ignoreStackFrameAndMutableData(string) {
let buffer = string.replace(/\u200b/g, '');
buffer = buffer.replace(/VM\d+/g, 'VM');
return buffer.replace(/^\s+at [^\]]+(]?)$/, '$1');
}
messageFormatter = messageFormatter || TestRunner.textContentWithLineBreaks;
const buffer = messageFormatter(node);
return buffer.split('\n').map(ignoreStackFrameAndMutableData).filter(isNotEmptyLine).join('\n');
};
/**
* @param {!Element} element
* @param {!SDK.ConsoleMessage} message
* @return {string}
*/
ConsoleTestRunner.simpleFormatter = function(element, message) {
return message.messageText + ':' + message.line + ':' + message.column;
};
/**
* @param {boolean=} printOriginatingCommand
* @param {boolean=} dumpClassNames
* @param {!ConsoleTestRunner.Formatter=} messageFormatter
*/
ConsoleTestRunner.dumpConsoleMessagesIgnoreErrorStackFrames = async function(
printOriginatingCommand, dumpClassNames, messageFormatter) {
TestRunner.addResults(await ConsoleTestRunner.dumpConsoleMessagesIntoArray(
printOriginatingCommand, dumpClassNames,
ConsoleTestRunner.formatterIgnoreStackFrameUrls.bind(this, messageFormatter)));
};
ConsoleTestRunner.dumpConsoleMessagesWithStyles = function() {
const messageViews = Console.ConsoleView.instance()._visibleViewMessages;
for (let i = 0; i < messageViews.length; ++i) {
const element = messageViews[i].element();
const messageText = ConsoleTestRunner.prepareConsoleMessageText(element);
TestRunner.addResult(messageText);
const spans = element.querySelectorAll('.console-message-text *');
for (let j = 0; j < spans.length; ++j) {
TestRunner.addResult('Styled text #' + j + ': ' + (spans[j].style.cssText || 'NO STYLES DEFINED'));
}
}
};
/**
* @param {boolean=} sortMessages
*/
ConsoleTestRunner.dumpConsoleMessagesWithClasses = async function(sortMessages) {
const result = [];
const messageViews = Console.ConsoleView.instance()._visibleViewMessages;
for (let i = 0; i < messageViews.length; ++i) {
const element = messageViews[i].element();
const contentElement = messageViews[i].contentElement();
await TestRunner.waitForPendingLiveLocationUpdates();
const messageText = ConsoleTestRunner.prepareConsoleMessageText(element);
result.push(messageText + ' ' + element.getAttribute('class') + ' > ' + contentElement.getAttribute('class'));
}
if (sortMessages) {
result.sort();
}
TestRunner.addResults(result);
};
ConsoleTestRunner.dumpConsoleClassesBrief = async function() {
const messageViews = Console.ConsoleView.instance()._visibleViewMessages;
for (let i = 0; i < messageViews.length; ++i) {
const repeatText = messageViews[i].repeatCount() > 1 ? (' x' + messageViews[i].repeatCount()) : '';
const element = messageViews[i].toMessageElement();
await TestRunner.waitForPendingLiveLocationUpdates();
TestRunner.addResult(element.className + repeatText);
}
};
ConsoleTestRunner.dumpConsoleCounters = async function() {
const counter = ConsoleCounters.WarningErrorCounter._instanceForTest;
if (counter._updatingForTest) {
await TestRunner.addSnifferPromise(counter, '_updatedForTest');
}
if (counter._titles) {
TestRunner.addResult(counter._titles);
}
await ConsoleTestRunner.dumpConsoleClassesBrief();
};
/**
* @param {!Function} callback
* @param {function(!Element):boolean} deepFilter
* @param {function(!ObjectUI.ObjectPropertiesSection):boolean} sectionFilter
*/
ConsoleTestRunner.expandConsoleMessages = function(callback, deepFilter, sectionFilter) {
Console.ConsoleView.instance()._invalidateViewport();
const messageViews = Console.ConsoleView.instance()._visibleViewMessages;
// Initiate round-trips to fetch necessary data for further rendering.
for (let i = 0; i < messageViews.length; ++i) {
messageViews[i].element();
}
TestRunner.deprecatedRunAfterPendingDispatches(expandTreeElements);
function expandTreeElements() {
for (let i = 0; i < messageViews.length; ++i) {
const element = messageViews[i].element();
for (let node = element; node; node = node.traverseNextNode(element)) {
if (node.treeElementForTest) {
node.treeElementForTest.expand();
}
if (node._expandStackTraceForTest) {
node._expandStackTraceForTest();
}
if (!node._section) {
continue;
}
if (sectionFilter && !sectionFilter(node._section)) {
continue;
}
node._section.expand();
if (!deepFilter) {
continue;
}
const treeElements = node._section.rootElement().children();
for (let j = 0; j < treeElements.length; ++j) {
for (let treeElement = treeElements[j]; treeElement;
treeElement = treeElement.traverseNextTreeElement(true, null, true)) {
if (deepFilter(treeElement)) {
treeElement.expand();
}
}
}
}
}
TestRunner.deprecatedRunAfterPendingDispatches(callback);
}
};
/**
* @param {function(!Element):boolean} deepFilter
* @param {function(!ObjectUI.ObjectPropertiesSection):boolean} sectionFilter
* @return {!Promise}
*/
ConsoleTestRunner.expandConsoleMessagesPromise = function(deepFilter, sectionFilter) {
return new Promise(fulfill => ConsoleTestRunner.expandConsoleMessages(fulfill, deepFilter, sectionFilter));
};
/**
* @param {!Function} callback
*/
ConsoleTestRunner.expandGettersInConsoleMessages = function(callback) {
const messageViews = Console.ConsoleView.instance()._visibleViewMessages;
const properties = [];
let propertiesCount = 0;
TestRunner.addSniffer(ObjectUI.ObjectPropertyTreeElement.prototype, '_updateExpandable', propertyExpandableUpdated);
for (let i = 0; i < messageViews.length; ++i) {
const element = messageViews[i].element();
for (let node = element; node; node = node.traverseNextNode(element)) {
if (node.classList && node.classList.contains('object-value-calculate-value-button')) {
++propertiesCount;
node.click();
properties.push(node.parentElement.parentElement);
}
}
}
function propertyExpandableUpdated() {
--propertiesCount;
if (propertiesCount === 0) {
for (let i = 0; i < properties.length; ++i) {
properties[i].click();
}
TestRunner.deprecatedRunAfterPendingDispatches(callback);
} else {
TestRunner.addSniffer(
ObjectUI.ObjectPropertyTreeElement.prototype, '_updateExpandable', propertyExpandableUpdated);
}
}
};
/**
* @param {!Function} callback
*/
ConsoleTestRunner.expandConsoleMessagesErrorParameters = function(callback) {
const messageViews = Console.ConsoleView.instance()._visibleViewMessages;
// Initiate round-trips to fetch necessary data for further rendering.
for (let i = 0; i < messageViews.length; ++i) {
messageViews[i].element();
}
TestRunner.deprecatedRunAfterPendingDispatches(callback);
};
/**
* @param {!Function} callback
*/
ConsoleTestRunner.waitForRemoteObjectsConsoleMessages = function(callback) {
const messages = Console.ConsoleView.instance()._visibleViewMessages;
for (let i = 0; i < messages.length; ++i) {
messages[i].toMessageElement();
}
TestRunner.deprecatedRunAfterPendingDispatches(callback);
};
/**
* @return {!Promise}
*/
ConsoleTestRunner.waitForRemoteObjectsConsoleMessagesPromise = function() {
return new Promise(resolve => ConsoleTestRunner.waitForRemoteObjectsConsoleMessages(resolve));
};
/**
* @return {!Promise}
*/
ConsoleTestRunner.waitUntilConsoleEditorLoaded = function() {
let fulfill;
const promise = new Promise(x => {
fulfill = x;
});
const prompt = Console.ConsoleView.instance()._prompt;
if (prompt._editor) {
fulfill(prompt._editor);
} else {
TestRunner.addSniffer(Console.ConsolePrompt.prototype, '_editorSetForTest', _ => fulfill(prompt._editor));
}
return promise;
};
/**
* @param {!Function} callback
*/
ConsoleTestRunner.waitUntilMessageReceived = function(callback) {
TestRunner.addSniffer(self.SDK.consoleModel, 'addMessage', callback, false);
};
/**
* @return {!Promise}
*/
ConsoleTestRunner.waitUntilMessageReceivedPromise = function() {
return new Promise(fulfill => ConsoleTestRunner.waitUntilMessageReceived(fulfill));
};
/**
* @param {number} count
* @param {!Function} callback
*/
ConsoleTestRunner.waitUntilNthMessageReceived = function(count, callback) {
function override() {
if (--count === 0) {
TestRunner.safeWrap(callback)();
} else {
TestRunner.addSniffer(self.SDK.consoleModel, 'addMessage', override, false);
}
}
TestRunner.addSniffer(self.SDK.consoleModel, 'addMessage', override, false);
};
/**
* @param {number} count
* @return {!Promise}
*/
ConsoleTestRunner.waitUntilNthMessageReceivedPromise = function(count) {
return new Promise(fulfill => ConsoleTestRunner.waitUntilNthMessageReceived(count, fulfill));
};
/**
* @param {string} namePrefix
*/
ConsoleTestRunner.changeExecutionContext = function(namePrefix) {
const selector = Console.ConsoleView.instance()._consoleContextSelector;
for (const executionContext of selector._items) {
if (selector.titleFor(executionContext).startsWith(namePrefix)) {
self.UI.context.setFlavor(SDK.ExecutionContext, executionContext);
return;
}
}
TestRunner.addResult('FAILED: context with prefix: ' + namePrefix + ' not found in the context list');
};
/**
* @param {number} expectedCount
* @param {!Function} callback
*/
ConsoleTestRunner.waitForConsoleMessages = function(expectedCount, callback) {
const consoleView = Console.ConsoleView.instance();
checkAndReturn();
function checkAndReturn() {
if (consoleView._visibleViewMessages.length === expectedCount) {
TestRunner.addResult('Message count: ' + expectedCount);
callback();
} else {
TestRunner.addSniffer(consoleView, '_messageAppendedForTests', checkAndReturn);
}
}
};
/**
* @param {number} expectedCount
* @return {!Promise}
*/
ConsoleTestRunner.waitForConsoleMessagesPromise = async function(expectedCount) {
await new Promise(fulfill => ConsoleTestRunner.waitForConsoleMessages(expectedCount, fulfill));
await TestRunner.waitForPendingLiveLocationUpdates();
return ConsoleTestRunner.waitForPendingViewportUpdates();
};
/**
* @param {number} fromMessage
* @param {number} fromTextOffset
* @param {number} toMessage
* @param {number} toTextOffset
* @suppressGlobalPropertiesCheck
*/
ConsoleTestRunner.selectConsoleMessages = async function(fromMessage, fromTextOffset, toMessage, toTextOffset) {
const consoleView = Console.ConsoleView.instance();
const fromElement = consoleView.itemElement(fromMessage).element();
const toElement = consoleView.itemElement(toMessage).element();
await TestRunner.waitForPendingLiveLocationUpdates();
const from = selectionContainerAndOffset(fromElement, fromTextOffset);
const to = selectionContainerAndOffset(toElement, toTextOffset);
window.getSelection().setBaseAndExtent(from.container, from.offset, to.container, to.offset);
/**
* @param {!Node} container
* @param {number} offset
* @return {?{container: !Node, offset: number}}
*/
function selectionContainerAndOffset(container, offset) {
/** @type {?Node} */
let node = container;
if (offset === 0 && container.nodeType !== Node.TEXT_NODE) {
container = /** @type {!Node} */ (container.traverseNextTextNode());
node = container;
}
let charCount = 0;
while ((node = node.traverseNextTextNode(container))) {
const length = node.textContent.length;
if (charCount + length >= offset) {
return {container: node, offset: offset - charCount};
}
charCount += length;
}
return null;
}
};
/**
* @param {!Function} override
* @param {boolean=} opt_sticky
*/
ConsoleTestRunner.addConsoleSniffer = function(override, opt_sticky) {
TestRunner.addSniffer(SDK.ConsoleModel.prototype, 'addMessage', override, opt_sticky);
};
/**
* @param {!Function} func
* @return {!Function}
*/
ConsoleTestRunner.wrapListener = function(func) {
/**
* @this {*}
*/
async function wrapper() {
await Promise.resolve();
func.apply(this, arguments);
}
return wrapper;
};
ConsoleTestRunner.dumpStackTraces = function() {
const viewMessages = Console.ConsoleView.instance()._visibleViewMessages;
for (let i = 0; i < viewMessages.length; ++i) {
const m = viewMessages[i].consoleMessage();
TestRunner.addResult(
'Message[' + i + ']: ' + Bindings.displayNameForURL(m.url || '') + ':' + m.line + ' ' + m.messageText);
const trace = m.stackTrace ? m.stackTrace.callFrames : null;
if (!trace) {
TestRunner.addResult('FAIL: no stack trace attached to message #' + i);
} else {
TestRunner.addResult('Stack Trace:\n');
TestRunner.addResult(' url: ' + trace[0].url);
TestRunner.addResult(' function: ' + trace[0].functionName);
TestRunner.addResult(' line: ' + trace[0].lineNumber);
}
}
};
/**
* @return {!{first: number, last: number, count: number}}
*/
ConsoleTestRunner.visibleIndices = function() {
const consoleView = Console.ConsoleView.instance();
const viewport = consoleView._viewport;
const viewportRect = viewport.element.getBoundingClientRect();
let first = -1;
let last = -1;
let count = 0;
for (let i = 0; i < consoleView._visibleViewMessages.length; i++) {
// Created message elements may have a bounding rect, but not be connected to DOM.
const item = consoleView._visibleViewMessages[i];
if (!item._element || !item._element.isConnected) {
continue;
}
const itemRect = item._element.getBoundingClientRect();
const isVisible = (itemRect.bottom > viewportRect.top + 1) && (itemRect.top <= viewportRect.bottom - 1);
if (isVisible) {
first = first === -1 ? i : first;
last = i;
count++;
}
}
return {first, last, count};
};