-
Notifications
You must be signed in to change notification settings - Fork 162
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
💫 Added withNamedArgs method to the Chai emit matcher (#456)
- Loading branch information
1 parent
4ce739d
commit 62dd2f9
Showing
9 changed files
with
309 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@ethereum-waffle/chai": patch | ||
--- | ||
|
||
Added withNamedArgs method to the Chai emit matcher |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export const isStruct = (arr: any[]) => { | ||
if (!Array.isArray(arr)) return false; | ||
const keys = Object.keys(arr); | ||
const hasAlphaNumericKeys = keys.some((key) => key.match(/^[a-zA-Z0-9]*[a-zA-Z]+[a-zA-Z0-9]*$/)); | ||
const hasNumericKeys = keys.some((key) => key.match(/^\d+$/)); | ||
return hasAlphaNumericKeys && hasNumericKeys; | ||
}; | ||
|
||
export const convertStructToPlainObject = (struct: any[]): any => { | ||
const keys = Object.keys(struct).filter((key: any) => isNaN(key)); | ||
return keys.reduce( | ||
(acc: any, key: any) => ({ | ||
...acc, | ||
[key]: isStruct(struct[key]) | ||
? convertStructToPlainObject(struct[key]) | ||
: struct[key] | ||
}), | ||
{} | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import {BytesLike, utils} from 'ethers'; | ||
import {Hexable} from 'ethers/lib/utils'; | ||
import {convertStructToPlainObject, isStruct} from './misc/struct'; | ||
|
||
/** | ||
* Used for testing the arguments of events or custom errors, naming the arguments. | ||
* Can test the subset of all arguments. | ||
* Should be used after .emit matcher. | ||
*/ | ||
export function supportWithNamedArgs(Assertion: Chai.AssertionStatic) { | ||
const assertArgsObjectEqual = (context: any, expectedArgs: Record<string, unknown>, arg: any) => { | ||
const logDescription = (context.contract.interface as utils.Interface).parseLog(arg); | ||
const actualArgs = logDescription.args; | ||
|
||
for (const [key, expectedValue] of Object.entries(expectedArgs)) { | ||
const paramIndex = logDescription.eventFragment.inputs.findIndex(input => input.name === key); | ||
new Assertion(paramIndex, `"${key}" argument in the "${context.eventName}" event not found`).gte(0); | ||
if (Array.isArray(expectedValue)) { | ||
for (let j = 0; j < expectedValue.length; j++) { | ||
new Assertion( | ||
actualArgs[paramIndex][j], | ||
`"${key}" value at index "${j}" on "${context.eventName}" event`) | ||
.equal(expectedValue[j]); | ||
} | ||
} else { | ||
if (actualArgs[paramIndex].hash !== undefined && actualArgs[paramIndex]._isIndexed) { | ||
const expectedArgBytes = utils.isHexString(expectedValue) | ||
? utils.arrayify(expectedValue as BytesLike | Hexable | number) | ||
: utils.toUtf8Bytes(expectedValue as string); | ||
new Assertion( | ||
actualArgs[paramIndex].hash, | ||
`value of indexed "${key}" argument in the "${context.eventName}" event ` + | ||
`to be hash of or equal to "${expectedValue}"` | ||
).to.be.oneOf([expectedValue, utils.keccak256(expectedArgBytes)]); | ||
} else { | ||
if (isStruct(actualArgs[paramIndex])) { | ||
new Assertion( | ||
convertStructToPlainObject(actualArgs[paramIndex]) | ||
).to.deep.equal(expectedValue); | ||
return; | ||
} | ||
new Assertion(actualArgs[paramIndex], | ||
`value of "${key}" argument in the "${context.eventName}" event`) | ||
.equal(expectedValue); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
const tryAssertArgsObjectEqual = (context: any, expectedArgs: Record<string, unknown>, args: any[]) => { | ||
if (args.length === 1) return assertArgsObjectEqual(context, expectedArgs, args[0]); | ||
if (context.txMatcher !== 'emit') { | ||
throw new Error('Wrong format of arguments'); | ||
} | ||
for (const index in args) { | ||
try { | ||
assertArgsObjectEqual(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('withNamedArgs', function (this: any, expectedArgs: Record<string, unknown>) { | ||
if (!('txMatcher' in this) || !('callPromise' in this)) { | ||
throw new Error('withNamedArgs() must be used after emit()'); | ||
} | ||
const isNegated = this.__flags.negate === true; | ||
this.callPromise = this.callPromise.then(() => { | ||
const isCurrentlyNegated = this.__flags.negate === true; | ||
this.__flags.negate = isNegated; | ||
tryAssertArgsObjectEqual(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; | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import {describeMockProviderCases} from './MockProviderCases'; | ||
import {eventsTest} from './eventsTest'; | ||
import {eventsTest, eventsWithNamedArgs} from './eventsTest'; | ||
|
||
describeMockProviderCases('INTEGRATION: Events', (provider) => { | ||
eventsTest(provider); | ||
eventsWithNamedArgs(provider); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters