@ethereum-waffle/chai
Version:
A sweet set of chai matchers for your blockchain testing needs.
94 lines (90 loc) • 3.91 kB
text/typescript
import {utils} from 'ethers';
import {convertStructToPlainObject, isStruct} from './misc/struct';
/**
* Used for testing the arguments of events or custom errors.
* Should be used after .emit or .revertedWith matchers respectively.
*/
export function supportWithArgs(Assertion: Chai.AssertionStatic) {
const assertArgsArraysEqual = (context: any, expectedArgs: any[], arg: any) => {
let actualArgs: utils.Result;
let wrongNumberOfArgsMsg: string;
if (context.txMatcher === 'emit') {
actualArgs = (context.contract.interface as utils.Interface).parseLog(arg).args;
wrongNumberOfArgsMsg = `Expected "${context.eventName}" event to have ${expectedArgs.length} argument(s), ` +
`but it has ${actualArgs.length}`;
} else if (context.txMatcher === 'revertedWith') {
actualArgs = arg;
wrongNumberOfArgsMsg = `Expected "${context.txErrorName}" event to have ${expectedArgs.length} argument(s), ` +
`but it has ${actualArgs.length}`;
} else {
throw new Error('Unknown txMatcher');
}
context.assert(
actualArgs.length === expectedArgs.length,
wrongNumberOfArgsMsg,
'Do not combine .not. with .withArgs()',
expectedArgs.length,
actualArgs.length
);
for (let index = 0; index < expectedArgs.length; index++) {
if (expectedArgs[index]?.length !== undefined && typeof expectedArgs[index] !== 'string') {
context.assert(
actualArgs[index].length === expectedArgs[index].length,
`Expected ${actualArgs[index]} to equal ${expectedArgs[index]}, ` +
'but they have different lengths',
'Do not combine .not. with .withArgs()'
);
for (let j = 0; j < expectedArgs[index].length; j++) {
new Assertion(actualArgs[index][j]).equal(expectedArgs[index][j]);
}
} else {
if (actualArgs[index].hash !== undefined && actualArgs[index]._isIndexed === true) {
const expectedArgBytes = utils.isHexString(expectedArgs[index])
? utils.arrayify(expectedArgs[index]) : utils.toUtf8Bytes(expectedArgs[index]);
new Assertion(actualArgs[index].hash).to.be.oneOf(
[expectedArgs[index], utils.keccak256(expectedArgBytes)]
);
} else {
if (isStruct(actualArgs[index])) {
new Assertion(
convertStructToPlainObject(actualArgs[index])
).to.deep.equal(expectedArgs[index]);
return;
}
new Assertion(actualArgs[index]).equal(expectedArgs[index]);
}
}
}
};
const tryAssertArgsArraysEqual = (context: any, expectedArgs: any[], args: any[]) => {
if (args.length === 1) return assertArgsArraysEqual(context, expectedArgs, args[0]);
if (context.txMatcher !== 'emit') {
throw new Error('Wrong format of arguments');
}
for (const index in args) {
try {
assertArgsArraysEqual(context, expectedArgs, args[index]);
return;
} catch {}
}
context.assert(false,
`Specified args not emitted in any of ${context.args.length} emitted "${context.eventName}" events`,
'Do not combine .not. with .withArgs()'
);
};
Assertion.addMethod('withArgs', function (this: any, ...expectedArgs: any[]) {
if (!('txMatcher' in this) || !('callPromise' in this)) {
throw new Error('withArgs() must be used after emit() or revertedWith()');
}
const isNegated = this.__flags.negate === true;
this.callPromise = this.callPromise.then(() => {
const isCurrentlyNegated = this.__flags.negate === true;
this.__flags.negate = isNegated;
tryAssertArgsArraysEqual(this, expectedArgs, this.args);
this.__flags.negate = isCurrentlyNegated;
});
this.then = this.callPromise.then.bind(this.callPromise);
this.catch = this.callPromise.catch.bind(this.callPromise);
return this;
});
}