Skip to content

Commit fdd0253

Browse files
jtgobermskelton
andauthored
feat(expect-expect): Support regex patterns (#390)
* updated expect-expect to use regex. Added tests * remove package.json * Cleanup * Cleanup --------- Co-authored-by: Mark Skelton <[email protected]>
1 parent ed7c44d commit fdd0253

File tree

3 files changed

+95
-5
lines changed

3 files changed

+95
-5
lines changed

docs/rules/expect-expect.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ test('should work with callbacks/async', async () => {
3535
"playwright/expect-expect": [
3636
"error",
3737
{
38-
"assertFunctionNames": ["assertCustomCondition"]
38+
"assertFunctionNames": ["assertCustomCondition"],
39+
"assertFunctionPatterns": ["^assert.*", "^verify.*"]
3940
}
4041
]
4142
}
@@ -57,3 +58,30 @@ test('should scroll', async ({ page }) => {
5758
await assertScrolledToBottom(page)
5859
})
5960
```
61+
62+
### `assertFunctionPatterns`
63+
64+
This array option specifies regular expression patterns that should match
65+
function names to be considered as asserting functions. This is useful when you
66+
have multiple assertion functions following a naming convention.
67+
68+
```ts
69+
/* eslint playwright/expect-expect: ["error", { "assertFunctionPatterns": ["^assert.*", "^verify.*"] }] */
70+
71+
function assertScrolledToBottom(page) {
72+
// ...
73+
}
74+
75+
function verifyPageLoaded(page) {
76+
// ...
77+
}
78+
79+
test('should scroll', async ({ page }) => {
80+
await assertScrolledToBottom(page)
81+
await verifyPageLoaded(page)
82+
})
83+
```
84+
85+
You can use both `assertFunctionNames` and `assertFunctionPatterns` together.
86+
The rule will consider a function as an assertion if it matches either an exact
87+
name or a pattern.

src/rules/expect-expect.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ runRuleTester('expect-expect', rule, {
2929
name: 'Custom assert function',
3030
options: [{ assertFunctionNames: ['wayComplexCustomCondition'] }],
3131
},
32+
{
33+
code: javascript`
34+
test('should fail', async ({ page }) => {
35+
await assertCustomCondition(page)
36+
})
37+
`,
38+
errors: [{ messageId: 'noAssertions', type: 'Identifier' }],
39+
name: 'Custom assert function pattern mismatch',
40+
options: [{ assertFunctionPatterns: ['^verify.*', '^check.*'] }],
41+
},
3242
{
3343
code: 'it("should pass", () => hi(true).toBeDefined())',
3444
errors: [{ messageId: 'noAssertions', type: 'Identifier' }],
@@ -110,6 +120,40 @@ runRuleTester('expect-expect', rule, {
110120
name: 'Custom assert class method',
111121
options: [{ assertFunctionNames: ['assertCustomCondition'] }],
112122
},
123+
{
124+
code: javascript`
125+
test('should pass', async ({ page }) => {
126+
await verifyElementVisible(page.locator('button'))
127+
})
128+
`,
129+
name: 'Custom assert function matching regex pattern',
130+
options: [{ assertFunctionPatterns: ['^verify.*'] }],
131+
},
132+
{
133+
code: javascript`
134+
test('should pass', async ({ page }) => {
135+
await page.checkTextContent('Hello')
136+
await validateUserLoggedIn(page)
137+
})
138+
`,
139+
name: 'Multiple custom assert functions matching different regex patterns',
140+
options: [{ assertFunctionPatterns: ['^check.*', '^validate.*'] }],
141+
},
142+
{
143+
code: javascript`
144+
test('should pass', async ({ page }) => {
145+
await myCustomAssert(page)
146+
await anotherAssertion(true)
147+
})
148+
`,
149+
name: 'Mixed string and regex pattern matching',
150+
options: [
151+
{
152+
assertFunctionNames: ['myCustomAssert'],
153+
assertFunctionPatterns: ['.*Assertion$'],
154+
},
155+
],
156+
},
113157
{
114158
code: 'it("should pass", () => expect(true).toBeDefined())',
115159
name: 'Global alias - test',

src/rules/expect-expect.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ export default createRule({
77
create(context) {
88
const options = {
99
assertFunctionNames: [] as string[],
10+
assertFunctionPatterns: [] as string[],
1011
...((context.options?.[0] as Record<string, unknown>) ?? {}),
1112
}
1213

14+
const patterns = options.assertFunctionPatterns.map(
15+
(pattern) => new RegExp(pattern),
16+
)
17+
1318
const unchecked: ESTree.CallExpression[] = []
1419

1520
function checkExpressions(nodes: ESTree.Node[]) {
@@ -24,16 +29,25 @@ export default createRule({
2429
}
2530
}
2631

32+
function matches(node: ESTree.CallExpression) {
33+
if (options.assertFunctionNames.some((name) => dig(node.callee, name))) {
34+
return true
35+
}
36+
37+
if (patterns.some((pattern) => dig(node.callee, pattern))) {
38+
return true
39+
}
40+
41+
return false
42+
}
43+
2744
return {
2845
CallExpression(node) {
2946
const call = parseFnCall(context, node)
3047

3148
if (call?.type === 'test') {
3249
unchecked.push(node)
33-
} else if (
34-
call?.type === 'expect' ||
35-
options.assertFunctionNames.find((name) => dig(node.callee, name))
36-
) {
50+
} else if (call?.type === 'expect' || matches(node)) {
3751
const ancestors = context.sourceCode.getAncestors(node)
3852
checkExpressions(ancestors)
3953
}
@@ -63,6 +77,10 @@ export default createRule({
6377
items: [{ type: 'string' }],
6478
type: 'array',
6579
},
80+
assertFunctionPatterns: {
81+
items: [{ type: 'string' }],
82+
type: 'array',
83+
},
6684
},
6785
type: 'object',
6886
},

0 commit comments

Comments
 (0)