feat(ses-ava): Include test title in special console error logging
MODIFY
packages/ses-ava/src/ses-ava-test.js
packages/ses-ava/src/ses-ava-test.js
import 'ses';
+const { stringify } = JSON;
const { defineProperty, freeze } = Object;
const { apply } = Reflect;
@@ -40,22 +41,22 @@ const isPromise = maybePromise =>
*
* @param {(...unknown) => unknown} func
* @param {unknown[]} args
- * @param {string} name
+ * @param {string} source
* @param {Logger} logger
*/
-const logErrorFirst = (func, args, name, logger) => {
+const logErrorFirst = (func, args, source, logger) => {
let result;
try {
result = apply(func, undefined, args);
} catch (err) {
- logger(`THROWN from ${name}:`, err);
+ logger(`THROWN from ${source}:`, err);
throw err;
}
if (isPromise(result)) {
return result.then(
v => v,
reason => {
- logger(`REJECTED from ${name}:`, reason);
+ logger(`REJECTED from ${source}:`, reason);
return result;
},
);
@@ -85,17 +86,39 @@ const augmentLogging = (testerFunc, logger) => {
const augmented = (...args) => {
// Align with ava argument parsing.
// https://github.com/avajs/ava/blob/c74934853db1d387c46ed1f953970c777feed6a0/lib/parse-test-args.js
- const maybeTitle = typeof args[0] === 'string' ? [args.shift()] : [];
+ const rawTitle = typeof args[0] === 'string' ? args.shift() : undefined;
const implFuncOrObj = args.shift();
+ const hasRawTitle = typeof rawTitle === 'string';
+ let resolvedTitle;
+ // Successful test declaration must be possible before `lockdown` allows
+ // `harden` to function, but `freeze(arrowFunc)` is a suitable replacement
+ // because all objects reachable from the result are intrinsics hardened by
+ // lockdown.
+ const wrapBuildTitle = (buildTitle, thisObj) => {
+ const wrappedBuildTitle = (...titleArgs) => {
+ resolvedTitle = apply(buildTitle, thisObj, titleArgs);
+ return resolvedTitle;
+ };
+ return freeze(wrappedBuildTitle);
+ };
const wrapImplFunc = fn => {
const wrappedFunc = t => {
+ // `harden` should be functional by the time a test callback is invoked.
harden(t);
- return logErrorFirst(fn, [t, ...args], testerFuncName, logger);
+ // Format source like `test("$rawTitle") "$resolvedTitle"`.
+ const quotedRawTitle = hasRawTitle ? stringify(rawTitle) : '';
+ const quotedResolvedTitle =
+ typeof resolvedTitle === 'string' && resolvedTitle !== rawTitle
+ ? ` ${stringify(resolvedTitle)}`
+ : '';
+ const source = `${testerFuncName}(${quotedRawTitle})${quotedResolvedTitle}`;
+ return logErrorFirst(fn, [t, ...args], source, logger);
};
- if (fn.title) {
- wrappedFunc.title = fn.title;
+ const buildTitle = fn.title;
+ if (buildTitle) {
+ wrappedFunc.title = wrapBuildTitle(buildTitle, fn);
}
- return wrappedFunc;
+ return freeze(wrappedFunc);
};
let implArg;
if (typeof implFuncOrObj === 'function') {
@@ -104,16 +127,21 @@ const augmentLogging = (testerFunc, logger) => {
} else if (typeof implFuncOrObj === 'object' && implFuncOrObj) {
// Handle cases like `test(title, test.macro(...), ...)`.
// Note that this will need updating if a future version of ava adds an alternative to `exec`.
- implArg = freeze({
+ implArg = {
...implFuncOrObj,
exec: wrapImplFunc(implFuncOrObj.exec),
- });
+ };
+ const buildTitle = implArg.title;
+ if (buildTitle) {
+ implArg.title = wrapBuildTitle(buildTitle, implArg);
+ }
+ freeze(implArg);
} else {
// Let ava handle this bad input.
implArg = implFuncOrObj;
}
// @ts-expect-error these spreads are acceptable
- return testerFunc(...maybeTitle, implArg, ...args);
+ return testerFunc(...(hasRawTitle ? [rawTitle] : []), implArg, ...args);
};
// re-use other properties (e.g. `.always`)
// https://github.com/endojs/endo/issues/647#issuecomment-809010961

feat(ses-ava): Include test title in special console error logging (#1477)