Skip to content
This repository has been archived by the owner on Sep 9, 2019. It is now read-only.

Commit

Permalink
feat(options): support assertBeforeCallbackName and assertAfterCallba…
Browse files Browse the repository at this point in the history
…ckName
  • Loading branch information
azu committed Aug 24, 2019
1 parent c542815 commit d2717eb
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 27 deletions.
82 changes: 69 additions & 13 deletions src/ast-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {
isIdentifier,
isLiteral,
isNullLiteral,
isDirective
isDirective,
callExpression,
identifier,
stringLiteral
} from "@babel/types";
import template from "@babel/template";

Expand Down Expand Up @@ -36,61 +39,114 @@ export const ERROR_COMMENT_PATTERN = /^([a-zA-Z]*?Error)/;
export const PROMISE_RESOLVE_COMMENT_PATTERN = /^Resolve:\s*(.*?)\s*$/;
export const PROMISE_REJECT_COMMENT_PATTERN = /^Reject:\s*(.*?)\s*$/;

export function wrapAssert(actualNode: any, expectedNode: any): any {
export interface wrapAssertOptions {
// callback name before assert
assertBeforeCallbackName?: string;
// callback name after assert
assertAfterCallbackName?: string;
}

export function wrapAssert(
{
actualNode,
expectedNode,
commentExpression,
id
}: { actualNode: any; expectedNode: any; commentExpression: string; id: string },
options: wrapAssertOptions
): any {
assert.notStrictEqual(typeof expectedNode, "undefined");
const ACTUAL_NODE = actualNode;
const EXPECTED_NODE = expectedNode;
const BEFORE_CALLBACK = options.assertBeforeCallbackName
? callExpression(identifier(options.assertBeforeCallbackName), [stringLiteral(id)])
: undefined;
const AFTER_CALLBACK = options.assertAfterCallbackName
? callExpression(identifier(options.assertAfterCallbackName), [stringLiteral(id)])
: undefined;
if (isConsole(actualNode)) {
const args = actualNode.arguments;
const firstArgument = args[0];
return wrapAssert(firstArgument, expectedNode);
return wrapAssert(
{
actualNode: firstArgument,
expectedNode,
commentExpression,
id
},
options
);
} else if (isIdentifier(expectedNode) && ERROR_COMMENT_PATTERN.test(expectedNode.name)) {
return template`assert.throws(function() {
return template`BEFORE_CALLBACK;assert.throws(function() {
ACTUAL_NODE
})`({
});AFTER_CALLBACK;`({
BEFORE_CALLBACK,
AFTER_CALLBACK,
ACTUAL_NODE
});
} else if (expectedNode.type === "Resolve") {
// getExpressionNodeFromCommentValue define the type
const ARGS = isConsole(actualNode) ? actualNode.arguments[0] : actualNode;
return template`Promise.resolve(ARGS).then(v => {
${wrapAssert({ type: "Identifier", name: "v" }, expectedNode.node)}
${wrapAssert(
{
actualNode: { type: "Identifier", name: "v" },
expectedNode: expectedNode.node,
commentExpression,
id
},
options
)}
return v;
});`({
ARGS
});
} else if (expectedNode.type === "Reject") {
const ARGS = isConsole(actualNode) ? actualNode.arguments[0] : actualNode;
return template`assert.rejects(ARGS)`({
return template`BEFORE_CALLBACK;assert.rejects(ARGS);AFTER_CALLBACK;`({
BEFORE_CALLBACK,
AFTER_CALLBACK,
ARGS
});
} else if (isIdentifier(expectedNode) && expectedNode.name === "NaN") {
return template`assert.ok(isNaN(ACTUAL_NODE));`({
return template`BEFORE_CALLBACK;assert.ok(isNaN(ACTUAL_NODE));AFTER_CALLBACK;`({
BEFORE_CALLBACK,
AFTER_CALLBACK,
ACTUAL_NODE
});
} else if (isNullLiteral(expectedNode)) {
return template`assert.strictEqual(ACTUAL_NODE, null)`({
return template`BEFORE_CALLBACK;assert.strictEqual(ACTUAL_NODE, null);AFTER_CALLBACK;`({
BEFORE_CALLBACK,
AFTER_CALLBACK,
ACTUAL_NODE
});
} else if (isIdentifier(expectedNode) && expectedNode.name === "undefined") {
return template`assert.strictEqual(ACTUAL_NODE, undefined)`({
return template`BEFORE_CALLBACK;assert.strictEqual(ACTUAL_NODE, undefined);AFTER_CALLBACK`({
BEFORE_CALLBACK,
AFTER_CALLBACK,
ACTUAL_NODE
});
} else if (isLiteral(expectedNode)) {
// Handle Directive Prorogue as string literal
if (isDirective(ACTUAL_NODE)) {
return template`assert.strictEqual(ACTUAL_NODE, EXPECTED_NODE)`({
return template`BEFORE_CALLBACK;assert.strictEqual(ACTUAL_NODE, EXPECTED_NODE);AFTER_CALLBACK;`({
BEFORE_CALLBACK,
AFTER_CALLBACK,
ACTUAL_NODE: (ACTUAL_NODE.value as any).extra.raw,
EXPECTED_NODE
});
} else {
return template`assert.strictEqual(ACTUAL_NODE, EXPECTED_NODE)`({
return template`BEFORE_CALLBACK;assert.strictEqual(ACTUAL_NODE, EXPECTED_NODE);AFTER_CALLBACK;`({
BEFORE_CALLBACK,
AFTER_CALLBACK,
ACTUAL_NODE,
EXPECTED_NODE
});
}
} else {
return template`assert.deepStrictEqual(ACTUAL_NODE, EXPECTED_NODE)`({
return template`BEFORE_CALLBACK;assert.deepStrictEqual(ACTUAL_NODE, EXPECTED_NODE);AFTER_CALLBACK;`({
BEFORE_CALLBACK,
AFTER_CALLBACK,
ACTUAL_NODE,
EXPECTED_NODE
});
Expand Down
32 changes: 23 additions & 9 deletions src/comment-to-assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
PROMISE_REJECT_COMMENT_PATTERN,
PROMISE_RESOLVE_COMMENT_PATTERN,
tryGetCodeFromComments,
wrapAssert
wrapAssert,
wrapAssertOptions
} from "./ast-utils";
import { transformFromAstSync } from "@babel/core";
import { identifier, isExpressionStatement, File } from "@babel/types";
Expand Down Expand Up @@ -48,9 +49,9 @@ function getExpressionNodeFromCommentValue(string: string): { type: string } & {
}
}

export interface toAssertFromSourceOptions {
export type toAssertFromSourceOptions = {
babel?: ParserOptions;
}
} & wrapAssertOptions;

/**
* transform code to asserted code
Expand All @@ -65,30 +66,43 @@ export function toAssertFromSource(code: string, options?: toAssertFromSourceOpt
if (!ast) {
throw new Error("Can not parse the code");
}
const output = toAssertFromAST(ast);
const output = toAssertFromAST(ast, options);
const babelFileResult = transformFromAstSync(output, code, { comments: true });
if (!babelFileResult) {
throw new Error("can not generate from ast: " + JSON.stringify(output));
}
return babelFileResult.code;
}

export interface toAssertFromASTOptions {}

/**
* transform AST to asserted AST.
*/
export function toAssertFromAST(ast: File, _options: toAssertFromASTOptions = {}) {
export function toAssertFromAST(ast: File, options: wrapAssertOptions = {}) {
const replaceSet = new Set();
let id = 0;
traverse(ast, {
exit(path) {
if (!replaceSet.has(path.node) && path.node.trailingComments) {
const commentExpression = tryGetCodeFromComments(path.node.trailingComments);
if (commentExpression) {
const commentExpressionNode = getExpressionNodeFromCommentValue(commentExpression);
const actualNode = isExpressionStatement(path.node) ? path.node.expression : path.node;
const replacement = wrapAssert(actualNode, commentExpressionNode);
path.replaceWith(replacement);
const replacement = wrapAssert(
{
actualNode: actualNode,
expectedNode: commentExpressionNode,
commentExpression,
id: String(`id:${id++}`)
},
options
);
if (Array.isArray(replacement)) {
// prevent ∞ loop
path.node.trailingComments = null;
path.replaceWithMultiple(replacement);
} else {
path.replaceWith(replacement);
}
replaceSet.add(path.node);
}
}
Expand Down
15 changes: 13 additions & 2 deletions test/snapshot-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,23 @@ ${JSON.stringify(actual)}
if (typeof actual !== "string") {
throw new Error("actual is not string");
}
if (options.asyncCallbackName) {
if (options.assertAfterCallbackName && options.assertBeforeCallbackName) {
// finish after all called
let actualCallCount = 0;
const totalCountOfAssert = actual.split(options.assertBeforeCallbackName).length - 1;
vm.runInContext(
actual,
vm.createContext({
assert,
done
[options.assertBeforeCallbackName]: () => {
// nope
},
[options.assertAfterCallbackName]: () => {
actualCallCount++;
if (actualCallCount === totalCountOfAssert) {
done();
}
}
})
);
} else {
Expand Down
2 changes: 1 addition & 1 deletion test/snapshots/Promise.reject/output.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
assert.rejects(Promise.reject(new Error("message"))); // => Reject: "message"
assert.rejects(Promise.reject(new Error("message"))); // => Reject: "message"
2 changes: 1 addition & 1 deletion test/snapshots/Promise.resolve/output.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Promise.resolve(Promise.resolve(1)).then(v => {
assert.strictEqual(v, 1);
return v;
}); // => Resolve: 1
}); // => Resolve: 1
1 change: 1 addition & 0 deletions test/snapshots/callback.NaN/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NaN; // => NaN
4 changes: 4 additions & 0 deletions test/snapshots/callback.NaN/options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
assertBeforeCallbackName: "beforeCallback",
assertAfterCallbackName: "afterCallback"
};
3 changes: 3 additions & 0 deletions test/snapshots/callback.NaN/output.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
beforeCallback("id:0");
assert.ok(isNaN(NaN));
afterCallback("id:0");
4 changes: 4 additions & 0 deletions test/snapshots/callback.multiple-assert/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
1; // => 1
"str"; // => "str"
[1, 2, 3]; // => [1,2,3]
Promise.resolve(1); // => Resolve: 1
4 changes: 4 additions & 0 deletions test/snapshots/callback.multiple-assert/options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
assertBeforeCallbackName: "beforeCallback",
assertAfterCallbackName: "afterCallback"
};
18 changes: 18 additions & 0 deletions test/snapshots/callback.multiple-assert/output.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
beforeCallback("id:0");
assert.strictEqual(1, 1);
afterCallback("id:0");
// => 1
beforeCallback("id:1");
assert.strictEqual("str", "str");
afterCallback("id:1");
// => "str"
beforeCallback("id:2");
assert.deepStrictEqual([1, 2, 3], [1, 2, 3]);
afterCallback("id:2");
// => [1,2,3]
Promise.resolve(Promise.resolve(1)).then(v => {
beforeCallback("id:3");
assert.strictEqual(v, 1);
afterCallback("id:3");
return v;
}); // => Resolve: 1
2 changes: 1 addition & 1 deletion test/snapshots/console.log.Promise.resolve/output.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Promise.resolve(Promise.resolve(1)).then(v => {
assert.strictEqual(v, 1);
return v;
}); // => Resolve: 1
}); // => Resolve: 1

0 comments on commit d2717eb

Please sign in to comment.