Skip to content

Commit 36917a8

Browse files
committed
feat(prefer-web-first-assertions): Support allInnerTexts() and allTextContents()
Fixes #362
1 parent 5f4d2f2 commit 36917a8

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-0
lines changed

src/rules/prefer-web-first-assertions.test.ts

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,180 @@ runRuleTester('prefer-web-first-assertions', rule, {
601601
output: test('await expect(page.locator(".content")).toHaveText("Some content")'),
602602
},
603603

604+
// allTextContents
605+
{
606+
code: test('expect(await foo.allTextContents()).toBe("bar")'),
607+
errors: [{ messageId: 'useWebFirstAssertion' }],
608+
},
609+
{
610+
code: test(`
611+
const myText = page.locator('foo li').allTextContents();
612+
expect(myText).toEqual(['Alpha', 'Beta', 'Gamma'])
613+
`),
614+
errors: [{ messageId: 'useWebFirstAssertion' }],
615+
},
616+
{
617+
code: test('expect(await foo.allTextContents()).not.toBe("bar")'),
618+
errors: [{ messageId: 'useWebFirstAssertion' }],
619+
},
620+
{
621+
code: test('expect(await foo.allTextContents()).toEqual("bar")'),
622+
errors: [{ messageId: 'useWebFirstAssertion' }],
623+
},
624+
{
625+
code: test('expect.soft(await foo.allTextContents()).toBe("bar")'),
626+
errors: [{ messageId: 'useWebFirstAssertion' }],
627+
},
628+
{
629+
code: test('expect["soft"](await foo.allTextContents()).not.toEqual("bar")'),
630+
errors: [{ messageId: 'useWebFirstAssertion' }],
631+
},
632+
{
633+
code: test(`
634+
const fooLocator = page.locator('.fooClass');
635+
const fooLocatorText = await fooLocator.allTextContents();
636+
expect(fooLocatorText).toEqual('foo');
637+
`),
638+
errors: [{ messageId: 'useWebFirstAssertion' }],
639+
},
640+
{
641+
code: test(`
642+
const fooLocator = page.locator('.fooClass');
643+
let fooLocatorText = await fooLocator.allTextContents();
644+
expect(fooLocatorText).toEqual('foo');
645+
fooLocatorText = 'foo';
646+
expect(fooLocatorText).toEqual('foo');
647+
`),
648+
errors: [{ messageId: 'useWebFirstAssertion' }],
649+
},
650+
{
651+
code: test(`
652+
let fooLocatorText;
653+
const fooLocator = page.locator('.fooClass');
654+
fooLocatorText = 'Unrelated';
655+
fooLocatorText = await fooLocator.allTextContents();
656+
expect(fooLocatorText).toEqual('foo');
657+
`),
658+
errors: [{ messageId: 'useWebFirstAssertion' }],
659+
},
660+
{
661+
code: test(`
662+
let fooLocatorText;
663+
let fooLocatorText2;
664+
const fooLocator = page.locator('.fooClass');
665+
fooLocatorText = await fooLocator.allTextContents();
666+
fooLocatorText2 = await fooLocator.allTextContents();
667+
expect(fooLocatorText).toEqual('foo');
668+
`),
669+
errors: [{ messageId: 'useWebFirstAssertion' }],
670+
},
671+
{
672+
code: test(`
673+
let fooLocatorText;
674+
fooLocatorText = 'foo';
675+
expect(fooLocatorText).toEqual('foo');
676+
fooLocatorText = await page.locator('.fooClass').allTextContents();
677+
expect(fooLocatorText).toEqual('foo');
678+
`),
679+
errors: [{ messageId: 'useWebFirstAssertion' }],
680+
},
681+
{
682+
code: test(`
683+
const unrelatedAssignment = "unrelated";
684+
const fooLocatorText = await page.locator('.foo').allTextContents();
685+
expect(fooLocatorText).toEqual('foo');
686+
`),
687+
errors: [{ messageId: 'useWebFirstAssertion' }],
688+
},
689+
{
690+
code: test(`
691+
const locatorFoo = page.locator(".foo")
692+
const isBarText = await locatorFoo.locator(".bar").allTextContents()
693+
expect(isBarText).toBe("bar")
694+
`),
695+
errors: [{ messageId: 'useWebFirstAssertion' }],
696+
},
697+
{
698+
code: test(`
699+
const content = await foo.allTextContents();
700+
expect(content).toBe("bar")
701+
`),
702+
errors: [{ messageId: 'useWebFirstAssertion' }],
703+
},
704+
705+
// allInnerTexts
706+
{
707+
code: test('expect(await foo.allInnerTexts()).toBe("bar")'),
708+
errors: [{ messageId: 'useWebFirstAssertion' }],
709+
},
710+
{
711+
code: test('expect(await foo.allInnerTexts()).not.toBe("bar")'),
712+
errors: [{ messageId: 'useWebFirstAssertion' }],
713+
},
714+
{
715+
code: test('expect(await foo.allInnerTexts()).toEqual("bar")'),
716+
errors: [{ messageId: 'useWebFirstAssertion' }],
717+
},
718+
{
719+
code: test('expect.soft(await foo.allInnerTexts()).toBe("bar")'),
720+
errors: [{ messageId: 'useWebFirstAssertion' }],
721+
},
722+
{
723+
code: test('expect["soft"](await foo.allInnerTexts()).not.toEqual("bar")'),
724+
errors: [{ messageId: 'useWebFirstAssertion' }],
725+
},
726+
{
727+
code: test(`
728+
const fooLocator = page.locator('.fooClass');
729+
const fooLocatorText = await fooLocator.allInnerTexts();
730+
expect(fooLocatorText).toEqual('foo');
731+
`),
732+
errors: [{ messageId: 'useWebFirstAssertion' }],
733+
},
734+
{
735+
code: test(`
736+
let fooLocatorText;
737+
const fooLocator = page.locator('.fooClass');
738+
fooLocatorText = 'Unrelated';
739+
fooLocatorText = await fooLocator.allInnerTexts();
740+
expect(fooLocatorText).toEqual('foo');
741+
`),
742+
errors: [{ messageId: 'useWebFirstAssertion' }],
743+
},
744+
{
745+
code: test(`
746+
let fooLocatorText;
747+
fooLocatorText = 'foo';
748+
expect(fooLocatorText).toEqual('foo');
749+
fooLocatorText = await page.locator('.fooClass').allInnerTexts();
750+
expect(fooLocatorText).toEqual('foo');
751+
`),
752+
errors: [{ messageId: 'useWebFirstAssertion' }],
753+
},
754+
{
755+
code: test(`
756+
const unrelatedAssignment = "unrelated";
757+
const fooLocatorText = await page.locator('.foo').allInnerTexts();
758+
expect(fooLocatorText).toEqual('foo');
759+
`),
760+
errors: [{ messageId: 'useWebFirstAssertion' }],
761+
},
762+
{
763+
code: test(`
764+
const locatorFoo = page.locator(".foo")
765+
const isBarText = await locatorFoo.locator(".bar").allInnerTexts()
766+
expect(isBarText).toBe("bar")
767+
`),
768+
errors: [{ messageId: 'useWebFirstAssertion' }],
769+
},
770+
{
771+
code: test(`
772+
const content = await foo.allInnerTexts();
773+
expect(content).toBe("bar")
774+
`),
775+
errors: [{ messageId: 'useWebFirstAssertion' }],
776+
},
777+
604778
// isChecked
605779
{
606780
code: test('expect(await page.locator("howdy").isChecked()).toBe(true)'),
@@ -1041,6 +1215,7 @@ runRuleTester('prefer-web-first-assertions', rule, {
10411215
{ code: test('const value = await bar["inputValue"]()') },
10421216
{ code: test('const isEditable = await baz[`isEditable`]()') },
10431217
{ code: test('await expect(await locator.toString()).toBe("something")') },
1218+
{ code: test('const myText = page.locator("foo li").allTextContents()') },
10441219
{
10451220
code: dedent`
10461221
import { expect } from '@playwright/test';

src/rules/prefer-web-first-assertions.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,24 @@ import { createRule } from '../utils/createRule.js'
99
import { parseFnCall } from '../utils/parseFnCall.js'
1010

1111
type MethodConfig = {
12+
fixable?: boolean
1213
inverse?: string
1314
matcher: string
1415
prop?: string
1516
type: 'boolean' | 'string'
1617
}
1718

1819
const methods: Record<string, MethodConfig> = {
20+
allInnerTexts: {
21+
fixable: false,
22+
matcher: 'toHaveText',
23+
type: 'string',
24+
},
25+
allTextContents: {
26+
fixable: false,
27+
matcher: 'toHaveText',
28+
type: 'string',
29+
},
1930
getAttribute: {
2031
matcher: 'toHaveAttribute',
2132
type: 'string',
@@ -95,6 +106,17 @@ export default createRule({
95106
const newMatcher =
96107
(+!!notModifier ^ +isFalsy && methodConfig.inverse) || methodConfig.matcher
97108

109+
// Some methods are too complex to autofix, so we just report a warning
110+
// with a recommendation.
111+
if (methodConfig.fixable === false) {
112+
context.report({
113+
data: { matcher: methodConfig.matcher, method },
114+
messageId: 'useWebFirstAssertion',
115+
node: call.callee.property,
116+
})
117+
return
118+
}
119+
98120
const { callee } = call
99121
context.report({
100122
data: {

0 commit comments

Comments
 (0)