From ee8ac1edf18b76d00158ef949587b7e272fdad8a Mon Sep 17 00:00:00 2001 From: Ryan Cebulko Date: Thu, 25 Feb 2021 11:21:53 -0500 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Attach=20firstElement=20an?= =?UTF-8?q?d=20messageArray=20to=20assert=20errors=20(#32887)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Attach firstElement and messageArray to assert errors * Add tests * Use string#replace insteadof iterating over messageArgs --- src/assert.js | 40 ++++++++++++++++++++++++++++++++-------- test/unit/test-assert.js | 13 +++++++++++++ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/assert.js b/src/assert.js index 9e448c7e0b88..8b84f9f8fe2d 100644 --- a/src/assert.js +++ b/src/assert.js @@ -53,6 +53,15 @@ export class UserError extends Error { /** * Throws a provided error if the second argument isn't trueish. + * + * Supports argument substitution into the message via %s placeholders. + * + * Throws an error object that has two extra properties: + * - associatedElement: This is the first element provided in the var args. + * It can be used for improved display of error messages. + * - messageArray: The elements of the substituted message as non-stringified + * elements in an array. When e.g. passed to console.error this yields + * native displays of things like HTML elements. * @param {Object} errorCls * @param {T} shouldBeTruthy * @param {string} opt_message @@ -65,16 +74,31 @@ function assertion(errorCls, shouldBeTruthy, opt_message, var_args) { return shouldBeTruthy; } + // Skip the first 3 arguments to isolate format params + const messageArgs = Array.prototype.slice.call(arguments, 3); + const messageArray = []; + let firstElement; + // Substitute provided values into format string in message - const message = Array.prototype.slice - // Skip the first 3 arguments to isolate format params - .call(arguments, 3) - .reduce( - (msg, subValue) => msg.replace('%s', subValue), - opt_message || 'Assertion failed' - ); + const message = (opt_message || 'Assertion failed').replace(/%s/g, () => { + const subValue = messageArgs.shift(); + + if (subValue != '') { + messageArray.push(subValue); + } + + // If an element is provided, add it to the error object + if (!firstElement && subValue?.nodeType == 1) { + firstElement = subValue; + } + + return subValue; + }); - throw new errorCls(message); + const error = new errorCls(message); + error.messageArray = messageArray; + error.associatedElement = firstElement; + throw error; } /** diff --git a/test/unit/test-assert.js b/test/unit/test-assert.js index 205551562486..264e98189795 100644 --- a/test/unit/test-assert.js +++ b/test/unit/test-assert.js @@ -61,4 +61,17 @@ describes.sandboxed('assertions', {}, () => { `1 a 2 b 3${USER_ERROR_SENTINEL}` ); }); + + it('should add element and message info', () => { + const div = document.createElement('div'); + let error; + try { + devAssert(false, '%s a %s b %s', div, 2, 3); + } catch (e) { + error = e; + } + + expect(error.associatedElement).to.equal(div); + expect(error.messageArray).to.deep.equal([div, 2, 3]); + }); });