Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/rules/expect-expect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ runRuleTester('expect-expect', rule, {
name: 'Custom assert function',
options: [{ assertFunctionNames: ['wayComplexCustomCondition'] }],
},
{
code: javascript`
test('should fail', async ({ page }) => {
await assertCustomCondition(page)
})
`,
errors: [{ messageId: 'noAssertions', type: 'Identifier' }],
name: 'Custom assert function pattern mismatch',
options: [{ assertFunctionPatterns: ['^verify.*', '^check.*'] }],
},
{
code: 'it("should pass", () => hi(true).toBeDefined())',
errors: [{ messageId: 'noAssertions', type: 'Identifier' }],
Expand Down Expand Up @@ -110,6 +120,38 @@ runRuleTester('expect-expect', rule, {
name: 'Custom assert class method',
options: [{ assertFunctionNames: ['assertCustomCondition'] }],
},
{
code: javascript`
test('should pass', async ({ page }) => {
await verifyElementVisible(page.locator('button'))
})
`,
name: 'Custom assert function matching regex pattern',
options: [{ assertFunctionPatterns: ['^verify.*'] }],
},
{
code: javascript`
test('should pass', async ({ page }) => {
await page.checkTextContent('Hello')
await validateUserLoggedIn(page)
})
`,
name: 'Multiple custom assert functions matching different regex patterns',
options: [{ assertFunctionPatterns: ['^check.*', '^validate.*'] }],
},
{
code: javascript`
test('should pass', async ({ page }) => {
await myCustomAssert(page)
await anotherAssertion(true)
})
`,
name: 'Mixed string and regex pattern matching',
options: [{
assertFunctionNames: ['myCustomAssert'],
assertFunctionPatterns: ['.*Assertion$']
}],
},
{
code: 'it("should pass", () => expect(true).toBeDefined())',
name: 'Global alias - test',
Expand Down
38 changes: 37 additions & 1 deletion src/rules/expect-expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,68 @@ import { dig } from '../utils/ast.js'
import { createRule } from '../utils/createRule.js'
import { parseFnCall } from '../utils/parseFnCall.js'


export default createRule({
create(context) {
const options = {
assertFunctionNames: [] as string[],
assertFunctionPatterns: [] as string[],
...((context.options?.[0] as Record<string, unknown>) ?? {}),
}


const unchecked: ESTree.CallExpression[] = []


function checkExpressions(nodes: ESTree.Node[]) {
for (const node of nodes) {
const index =
node.type === 'CallExpression' ? unchecked.indexOf(node) : -1


if (index !== -1) {
unchecked.splice(index, 1)
break
}
}
}


function matchesAssertFunction(node: ESTree.CallExpression): boolean {
// Check exact string matches
if (options.assertFunctionNames.some((name) => dig(node.callee, name))) {
return true
}


// Check regex patterns
if (options.assertFunctionPatterns.some((pattern) => {
try {
const regex = new RegExp(pattern)
return dig(node.callee, regex)
} catch {
// Invalid regex pattern, skip it
return false
}
})) {
return true
}


return false
}


return {
CallExpression(node) {
const call = parseFnCall(context, node)


if (call?.type === 'test') {
unchecked.push(node)
} else if (
call?.type === 'expect' ||
options.assertFunctionNames.find((name) => dig(node.callee, name))
matchesAssertFunction(node)
) {
const ancestors = context.sourceCode.getAncestors(node)
checkExpressions(ancestors)
Expand Down Expand Up @@ -63,6 +95,10 @@ export default createRule({
items: [{ type: 'string' }],
type: 'array',
},
assertFunctionPatterns: {
items: [{ type: 'string' }],
type: 'array',
},
},
type: 'object',
},
Expand Down