diff --git a/src/rules/__tests__/prefer-expect-assertions.test.ts b/src/rules/__tests__/prefer-expect-assertions.test.ts index e46826423..bd88fbdac 100644 --- a/src/rules/__tests__/prefer-expect-assertions.test.ts +++ b/src/rules/__tests__/prefer-expect-assertions.test.ts @@ -101,6 +101,180 @@ ruleTester.run('prefer-expect-assertions', rule, { `, parserOptions: { sourceType: 'module' }, }, + dedent` + beforeEach(() => expect.hasAssertions()); + + it('responds ok', function () { + client.get('/user', response => { + expect(response.status).toBe(200); + }); + }); + + it("is a number that is greater than four", () => { + expect(number).toBeGreaterThan(4); + }); + `, + dedent` + afterEach(() => { + expect.hasAssertions(); + }); + + it('responds ok', function () { + client.get('/user', response => { + expect(response.status).toBe(200); + }); + }); + + it("is a number that is greater than four", () => { + expect(number).toBeGreaterThan(4); + }); + `, + dedent` + afterEach(() => { + expect.hasAssertions(); + }); + + it('responds ok', function () { + client.get('/user', response => { + expect(response.status).toBe(200); + }); + }); + + it("is a number that is greater than four", () => { + expect.hasAssertions(); + + expect(number).toBeGreaterThan(4); + }); + `, + dedent` + beforeEach(() => { expect.hasAssertions(); }); + + describe('my tests', () => { + it('responds ok', function () { + client.get('/user', response => { + expect(response.status).toBe(200); + }); + }); + + it("is a number that is greater than four", () => { + expect.hasAssertions(); + + expect(number).toBeGreaterThan(4); + }); + }); + `, + dedent` + describe('my tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + + describe('left', () => { + describe('inner', () => { + it('responds ok', function () { + client.get('/user', response => { + expect(response.status).toBe(200); + }); + }); + }); + }); + + describe('right', () => { + it("is a number that is greater than four", () => { + expect(number).toBeGreaterThan(4); + }); + }); + }); + `, + dedent` + describe('my tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + + describe('left', () => { + it('responds ok', function () { + client.get('/user', response => { + expect(response.status).toBe(200); + }); + }); + }); + + describe('right', () => { + it("is a number that is greater than four", () => { + expect(number).toBeGreaterThan(4); + }); + }); + }); + `, + dedent` + describe('my tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + + describe('left', () => { + beforeEach(() => { expect.hasAssertions(); }); + + it('responds ok', function () { + client.get('/user', response => { + expect(response.status).toBe(200); + }); + }); + }); + + describe('right', () => { + it("is a number that is greater than four", () => { + expect(number).toBeGreaterThan(4); + }); + }); + }); + `, + dedent` + describe('my tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + + describe('left', () => { + afterEach(() => { expect.hasAssertions(); }); + + it('responds ok', function () { + client.get('/user', response => { + expect(response.status).toBe(200); + }); + }); + }); + + describe('right', () => { + it("is a number that is greater than four", () => { + expect(number).toBeGreaterThan(4); + }); + }); + }); + `, + dedent` + describe('my tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + + it('responds ok', function () { + client.get('/user', response => { + expect(response.status).toBe(200); + }); + }); + + it("is a number that is greater than four", () => { + expect.hasAssertions(); + + expect(number).toBeGreaterThan(4); + }); + }); + `, + // todo: this probably should not be considered valid + // (we should only respect expect.hasAssertions in the immediate of beforeEach) + dedent` + beforeEach(() => { + setTimeout(() => expect.hasAssertions(), 5000); + }); + + it('only returns numbers that are greater than six', () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(6); + } + }); + `, ], invalid: [ { @@ -178,32 +352,337 @@ ruleTester.run('prefer-expect-assertions', rule, { }, { code: dedent` - it("it1", function() { - someFunctionToDo(); - someFunctionToDo2(); + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + `, + errors: [ + { + messageId: 'haveExpectAssertions', + column: 1, + line: 1, + suggestions: [ + { + messageId: 'suggestAddingHasAssertions', + output: dedent` + it("it1", function() {expect.hasAssertions(); + someFunctionToDo(); + someFunctionToDo2(); + }); + `, + }, + { + messageId: 'suggestAddingAssertions', + output: dedent` + it("it1", function() {expect.assertions(); + someFunctionToDo(); + someFunctionToDo2(); + }); + `, + }, + ], + }, + ], + }, + { + code: dedent` + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + + describe('some tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + `, + errors: [ + { + messageId: 'haveExpectAssertions', + column: 1, + line: 1, + suggestions: [ + { + messageId: 'suggestAddingHasAssertions', + output: dedent` + it("it1", function() {expect.hasAssertions(); + someFunctionToDo(); + someFunctionToDo2(); + }); + + describe('some tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + `, + }, + { + messageId: 'suggestAddingAssertions', + output: dedent` + it("it1", function() {expect.assertions(); + someFunctionToDo(); + someFunctionToDo2(); + }); + + describe('some tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + `, + }, + ], + }, + ], + }, + { + code: dedent` + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + + describe('some tests', () => { + afterEach(() => { expect.hasAssertions(); }); + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + `, + errors: [ + { + messageId: 'haveExpectAssertions', + column: 1, + line: 1, + suggestions: [ + { + messageId: 'suggestAddingHasAssertions', + output: dedent` + it("it1", function() {expect.hasAssertions(); + someFunctionToDo(); + someFunctionToDo2(); + }); + + describe('some tests', () => { + afterEach(() => { expect.hasAssertions(); }); + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + `, + }, + { + messageId: 'suggestAddingAssertions', + output: dedent` + it("it1", function() {expect.assertions(); + someFunctionToDo(); + someFunctionToDo2(); + }); + + describe('some tests', () => { + afterEach(() => { expect.hasAssertions(); }); + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + `, + }, + ], + }, + ], + }, + { + // todo: this should be considered valid, as hooks are evaluated before tests are run + code: dedent` + describe('some tests', () => { + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + + beforeEach(() => { expect.hasAssertions(); }); + + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + `, + errors: [ + { + messageId: 'haveExpectAssertions', + column: 3, + line: 2, + suggestions: [ + { + messageId: 'suggestAddingHasAssertions', + output: dedent` + describe('some tests', () => { + it("it1", function() {expect.hasAssertions(); + someFunctionToDo(); + someFunctionToDo2(); + }); + + beforeEach(() => { expect.hasAssertions(); }); + + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + `, + }, + { + messageId: 'suggestAddingAssertions', + output: dedent` + describe('some tests', () => { + it("it1", function() {expect.assertions(); + someFunctionToDo(); + someFunctionToDo2(); + }); + + beforeEach(() => { expect.hasAssertions(); }); + + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + `, + }, + ], + }, + ], + }, + { + code: dedent` + describe('some tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + `, + errors: [ + { + messageId: 'haveExpectAssertions', + column: 1, + line: 9, + suggestions: [ + { + messageId: 'suggestAddingHasAssertions', + output: dedent` + describe('some tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + + it("it1", function() {expect.hasAssertions(); + someFunctionToDo(); + someFunctionToDo2(); + }); + `, + }, + { + messageId: 'suggestAddingAssertions', + output: dedent` + describe('some tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + + it("it1", function() {expect.assertions(); + someFunctionToDo(); + someFunctionToDo2(); + }); + `, + }, + ], + }, + ], + }, + { + code: dedent` + describe('some tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + + describe('more tests', () => { + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); }); `, errors: [ { messageId: 'haveExpectAssertions', - column: 1, - line: 1, + column: 3, + line: 10, suggestions: [ { messageId: 'suggestAddingHasAssertions', output: dedent` - it("it1", function() {expect.hasAssertions(); - someFunctionToDo(); - someFunctionToDo2(); + describe('some tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + + describe('more tests', () => { + it("it1", function() {expect.hasAssertions(); + someFunctionToDo(); + someFunctionToDo2(); + }); }); `, }, { messageId: 'suggestAddingAssertions', output: dedent` - it("it1", function() {expect.assertions(); - someFunctionToDo(); - someFunctionToDo2(); + describe('some tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + it("it1", function() { + someFunctionToDo(); + someFunctionToDo2(); + }); + }); + + describe('more tests', () => { + it("it1", function() {expect.assertions(); + someFunctionToDo(); + someFunctionToDo2(); + }); }); `, }, @@ -286,6 +765,70 @@ ruleTester.run('prefer-expect-assertions', rule, { }, ], }, + { + code: 'beforeEach(() => { expect.hasAssertions("1") })', + errors: [ + { + messageId: 'hasAssertionsTakesNoArguments', + column: 27, + line: 1, + suggestions: [ + { + messageId: 'suggestRemovingExtraArguments', + output: 'beforeEach(() => { expect.hasAssertions() })', + }, + ], + }, + ], + }, + { + code: 'beforeEach(() => expect.hasAssertions("1"))', + errors: [ + { + messageId: 'hasAssertionsTakesNoArguments', + column: 25, + line: 1, + suggestions: [ + { + messageId: 'suggestRemovingExtraArguments', + output: 'beforeEach(() => expect.hasAssertions())', + }, + ], + }, + ], + }, + { + code: 'afterEach(() => { expect.hasAssertions("1") })', + errors: [ + { + messageId: 'hasAssertionsTakesNoArguments', + column: 26, + line: 1, + suggestions: [ + { + messageId: 'suggestRemovingExtraArguments', + output: 'afterEach(() => { expect.hasAssertions() })', + }, + ], + }, + ], + }, + { + code: 'afterEach(() => expect.hasAssertions("1"))', + errors: [ + { + messageId: 'hasAssertionsTakesNoArguments', + column: 24, + line: 1, + suggestions: [ + { + messageId: 'suggestRemovingExtraArguments', + output: 'afterEach(() => expect.hasAssertions())', + }, + ], + }, + ], + }, { code: 'it("it1", function() {expect.hasAssertions("1");})', errors: [ @@ -518,6 +1061,133 @@ ruleTester.run('prefer-expect-assertions', rule, { } }); + it("returns numbers that are greater than five", () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(5); + } + }); + `, + }, + ], + }, + ], + }, + // todo: it probably makes sense to report use in beforeAll and afterAll hooks + { + code: dedent` + beforeAll(() => { expect.hasAssertions(); }); + + it("returns numbers that are greater than four", async () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + + it("returns numbers that are greater than five", () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(5); + } + }); + `, + options: [{ onlyFunctionsWithAsyncKeyword: true }], + errors: [ + { + messageId: 'haveExpectAssertions', + column: 1, + line: 3, + suggestions: [ + { + messageId: 'suggestAddingHasAssertions', + output: dedent` + beforeAll(() => { expect.hasAssertions(); }); + + it("returns numbers that are greater than four", async () => {expect.hasAssertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + + it("returns numbers that are greater than five", () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(5); + } + }); + `, + }, + { + messageId: 'suggestAddingAssertions', + output: dedent` + beforeAll(() => { expect.hasAssertions(); }); + + it("returns numbers that are greater than four", async () => {expect.assertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + + it("returns numbers that are greater than five", () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(5); + } + }); + `, + }, + ], + }, + ], + }, + { + code: dedent` + afterAll(() => { expect.hasAssertions(); }); + + it("returns numbers that are greater than four", async () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + + it("returns numbers that are greater than five", () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(5); + } + }); + `, + options: [{ onlyFunctionsWithAsyncKeyword: true }], + errors: [ + { + messageId: 'haveExpectAssertions', + column: 1, + line: 3, + suggestions: [ + { + messageId: 'suggestAddingHasAssertions', + output: dedent` + afterAll(() => { expect.hasAssertions(); }); + + it("returns numbers that are greater than four", async () => {expect.hasAssertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + + it("returns numbers that are greater than five", () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(5); + } + }); + `, + }, + { + messageId: 'suggestAddingAssertions', + output: dedent` + afterAll(() => { expect.hasAssertions(); }); + + it("returns numbers that are greater than four", async () => {expect.assertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + it("returns numbers that are greater than five", () => { for (const number of getNumbers()) { expect(number).toBeGreaterThan(5); @@ -572,6 +1242,29 @@ ruleTester.run('prefer-expect-assertions (loops)', rule, { `, options: [{ onlyFunctionsWithExpectInLoop: true }], }, + // todo: ideally this should be considered valid + // { + // code: dedent` + // describe('my tests', () => { + // beforeEach(expect.hasAssertions); + // + // it("is a number that is greater than four", () => { + // expect(number).toBeGreaterThan(4); + // }); + // + // it("returns numbers that are greater than four", () => { + // for (const number of getNumbers()) { + // expect(number).toBeGreaterThan(4); + // } + // }); + // }); + // + // it("returns numbers that are greater than five", () => { + // expect(number).toBeGreaterThan(5); + // }); + // `, + // options: [{ onlyFunctionsWithExpectInLoop: true }], + // }, ], invalid: [ { @@ -792,13 +1485,76 @@ ruleTester.run('prefer-expect-assertions (loops)', rule, { { messageId: 'suggestAddingAssertions', output: dedent` - it("is wrong"); + it("is wrong"); + + it("is a test", () => {expect.assertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + `, + }, + ], + }, + ], + }, + { + code: dedent` + it("is a number that is greater than four", () => { + expect(number).toBeGreaterThan(4); + }); + + it("returns numbers that are greater than four", () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + + it("returns numbers that are greater than five", () => { + expect(number).toBeGreaterThan(5); + }); + `, + options: [{ onlyFunctionsWithExpectInLoop: true }], + errors: [ + { + messageId: 'haveExpectAssertions', + column: 1, + line: 5, + suggestions: [ + { + messageId: 'suggestAddingHasAssertions', + output: dedent` + it("is a number that is greater than four", () => { + expect(number).toBeGreaterThan(4); + }); + + it("returns numbers that are greater than four", () => {expect.hasAssertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + + it("returns numbers that are greater than five", () => { + expect(number).toBeGreaterThan(5); + }); + `, + }, + { + messageId: 'suggestAddingAssertions', + output: dedent` + it("is a number that is greater than four", () => { + expect(number).toBeGreaterThan(4); + }); - it("is a test", () => {expect.assertions(); + it("returns numbers that are greater than four", () => {expect.assertions(); for (const number of getNumbers()) { expect(number).toBeGreaterThan(4); } }); + + it("returns numbers that are greater than five", () => { + expect(number).toBeGreaterThan(5); + }); `, }, ], @@ -807,14 +1563,19 @@ ruleTester.run('prefer-expect-assertions (loops)', rule, { }, { code: dedent` - it("is a number that is greater than four", () => { - expect(number).toBeGreaterThan(4); + describe('my tests', () => { + beforeEach(expect.hasAssertions); + it("is a number that is greater than four", () => { + expect(number).toBeGreaterThan(4); + }); }); - it("returns numbers that are greater than four", () => { - for (const number of getNumbers()) { - expect(number).toBeGreaterThan(4); - } + describe('more tests', () => { + it("returns numbers that are greater than four", () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); }); it("returns numbers that are greater than five", () => { @@ -825,20 +1586,25 @@ ruleTester.run('prefer-expect-assertions (loops)', rule, { errors: [ { messageId: 'haveExpectAssertions', - column: 1, - line: 5, + column: 3, + line: 9, suggestions: [ { messageId: 'suggestAddingHasAssertions', output: dedent` - it("is a number that is greater than four", () => { - expect(number).toBeGreaterThan(4); + describe('my tests', () => { + beforeEach(expect.hasAssertions); + it("is a number that is greater than four", () => { + expect(number).toBeGreaterThan(4); + }); }); - it("returns numbers that are greater than four", () => {expect.hasAssertions(); - for (const number of getNumbers()) { - expect(number).toBeGreaterThan(4); - } + describe('more tests', () => { + it("returns numbers that are greater than four", () => {expect.hasAssertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); }); it("returns numbers that are greater than five", () => { @@ -849,14 +1615,19 @@ ruleTester.run('prefer-expect-assertions (loops)', rule, { { messageId: 'suggestAddingAssertions', output: dedent` - it("is a number that is greater than four", () => { - expect(number).toBeGreaterThan(4); + describe('my tests', () => { + beforeEach(expect.hasAssertions); + it("is a number that is greater than four", () => { + expect(number).toBeGreaterThan(4); + }); }); - it("returns numbers that are greater than four", () => {expect.assertions(); - for (const number of getNumbers()) { - expect(number).toBeGreaterThan(4); - } + describe('more tests', () => { + it("returns numbers that are greater than four", () => {expect.assertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); }); it("returns numbers that are greater than five", () => { @@ -1249,6 +2020,144 @@ ruleTester.run('prefer-expect-assertions (loops)', rule, { }, ], }, + { + code: dedent` + describe('my tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + + it("it1", async () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + }); + + it("it1", () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + `, + options: [{ onlyFunctionsWithExpectInLoop: true }], + errors: [ + { + messageId: 'haveExpectAssertions', + column: 1, + line: 11, + suggestions: [ + { + messageId: 'suggestAddingHasAssertions', + output: dedent` + describe('my tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + + it("it1", async () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + }); + + it("it1", () => {expect.hasAssertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + `, + }, + { + messageId: 'suggestAddingAssertions', + output: dedent` + describe('my tests', () => { + beforeEach(() => { expect.hasAssertions(); }); + + it("it1", async () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + }); + + it("it1", () => {expect.assertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + `, + }, + ], + }, + ], + }, + { + code: dedent` + describe('my tests', () => { + afterEach(() => { expect.hasAssertions(); }); + + it("it1", async () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + }); + + it("it1", () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + `, + options: [{ onlyFunctionsWithExpectInLoop: true }], + errors: [ + { + messageId: 'haveExpectAssertions', + column: 1, + line: 11, + suggestions: [ + { + messageId: 'suggestAddingHasAssertions', + output: dedent` + describe('my tests', () => { + afterEach(() => { expect.hasAssertions(); }); + + it("it1", async () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + }); + + it("it1", () => {expect.hasAssertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + `, + }, + { + messageId: 'suggestAddingAssertions', + output: dedent` + describe('my tests', () => { + afterEach(() => { expect.hasAssertions(); }); + + it("it1", async () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + }); + + it("it1", () => {expect.assertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + `, + }, + ], + }, + ], + }, { code: dedent` it.skip.each\`\`("it1", async () => { @@ -1362,6 +2271,75 @@ ruleTester.run('prefer-expect-assertions (loops)', rule, { } }); + it("it1", () => { + expect.hasAssertions(); + + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + `, + }, + ], + }, + ], + }, + { + code: dedent` + describe('my tests', () => { + it("it1", async () => { + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + }); + + it("it1", () => { + expect.hasAssertions(); + + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + `, + options: [{ onlyFunctionsWithExpectInLoop: true }], + errors: [ + { + messageId: 'haveExpectAssertions', + column: 3, + line: 2, + suggestions: [ + { + messageId: 'suggestAddingHasAssertions', + output: dedent` + describe('my tests', () => { + it("it1", async () => {expect.hasAssertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + }); + + it("it1", () => { + expect.hasAssertions(); + + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + `, + }, + { + messageId: 'suggestAddingAssertions', + output: dedent` + describe('my tests', () => { + it("it1", async () => {expect.assertions(); + for (const number of getNumbers()) { + expect(number).toBeGreaterThan(4); + } + }); + }); + it("it1", () => { expect.hasAssertions(); @@ -1410,6 +2388,22 @@ ruleTester.run('prefer-expect-assertions (callbacks)', rule, { `, options: [{ onlyFunctionsWithExpectInCallback: true }], }, + { + code: dedent` + beforeEach(() => expect.hasAssertions()); + + it('returns numbers that are greater than two', function () { + const expectNumbersToBeGreaterThan = (numbers, value) => { + for (let number of numbers) { + expect(number).toBeGreaterThan(value); + } + }; + + expectNumbersToBeGreaterThan(getNumbers(), 2); + }); + `, + options: [{ onlyFunctionsWithExpectInCallback: true }], + }, { code: dedent` it("returns numbers that are greater than five", function () { @@ -1490,6 +2484,50 @@ ruleTester.run('prefer-expect-assertions (callbacks)', rule, { `, options: [{ onlyFunctionsWithExpectInCallback: true }], }, + { + code: dedent` + it('responds ok', function () { + expect.assertions(1); + + client.get('/user', response => { + expect(response.status).toBe(200); + }); + }); + + describe('my test', () => { + beforeEach(() => expect.hasAssertions()); + + it('responds ok', function () { + client.get('/user', response => { + expect(response.status).toBe(200); + }); + }); + }); + `, + options: [{ onlyFunctionsWithExpectInCallback: true }], + }, + { + code: dedent` + it('responds ok', function () { + expect.assertions(1); + + client.get('/user', response => { + expect(response.status).toBe(200); + }); + }); + + describe('my test', () => { + afterEach(() => expect.hasAssertions()); + + it('responds ok', function () { + client.get('/user', response => { + expect(response.status).toBe(200); + }); + }); + }); + `, + options: [{ onlyFunctionsWithExpectInCallback: true }], + }, ], invalid: [ { diff --git a/src/rules/prefer-expect-assertions.ts b/src/rules/prefer-expect-assertions.ts index a0ae6ae00..878f5cbff 100644 --- a/src/rules/prefer-expect-assertions.ts +++ b/src/rules/prefer-expect-assertions.ts @@ -8,7 +8,6 @@ import { createRule, getAccessorValue, isFunction, - isTypeOfJestFnCall, parseJestFnCall, removeExtraArgumentsFixer, } from './utils'; @@ -103,6 +102,7 @@ export default createRule<[RuleOptions], MessageIds>({ ], create(context, [options]) { let expressionDepth = 0; + let describeDepth = 0; let hasExpectInCallback = false; let hasExpectInLoop = false; let hasExpectAssertionsAsFirstStatement = false; @@ -190,6 +190,15 @@ export default createRule<[RuleOptions], MessageIds>({ const enterForLoop = () => (inForLoop = true); const exitForLoop = () => (inForLoop = false); + let inEachHook = false; + + // when set to a non-negative value, all expect calls within describes whose depth + // are equal to or higher than this value are covered by an expect.hasAssertions set + // up within a beforeEach or afterEach hook + // + // when the describe depth is lower than the current value, it gets reset to -1 + let coveredByHookAtDepth = -1; + return { FunctionExpression: enterExpression, 'FunctionExpression:exit': exitExpression, @@ -204,12 +213,32 @@ export default createRule<[RuleOptions], MessageIds>({ CallExpression(node) { const jestFnCall = parseJestFnCall(node, context); + if (jestFnCall?.type === 'describe') { + describeDepth += 1; + } + + if (jestFnCall?.type === 'hook' && jestFnCall.name.endsWith('Each')) { + inEachHook = true; + } + if (jestFnCall?.type === 'test') { inTestCaseCall = true; return; } + if ( + jestFnCall?.type === 'expect' && + inEachHook && + getAccessorValue(jestFnCall.members[0]) === 'hasAssertions' + ) { + checkExpectHasAssertions(jestFnCall, node); + + if (coveredByHookAtDepth < 0) { + coveredByHookAtDepth = describeDepth; + } + } + if (jestFnCall?.type === 'expect' && inTestCaseCall) { if ( expressionDepth === 1 && @@ -232,7 +261,23 @@ export default createRule<[RuleOptions], MessageIds>({ } }, 'CallExpression:exit'(node: TSESTree.CallExpression) { - if (!isTypeOfJestFnCall(node, context, ['test'])) { + const jestFnCall = parseJestFnCall(node, context); + + if (jestFnCall?.type === 'describe') { + describeDepth -= 1; + + // clear the "covered by each hook" flag if we have left the describe + // depth that it applies to + if (coveredByHookAtDepth > describeDepth) { + coveredByHookAtDepth = -1; + } + } + + if (jestFnCall?.type === 'hook' && jestFnCall.name.endsWith('Each')) { + inEachHook = false; + } + + if (jestFnCall?.type !== 'test') { return; } @@ -257,6 +302,13 @@ export default createRule<[RuleOptions], MessageIds>({ return; } + if ( + coveredByHookAtDepth >= 0 && + coveredByHookAtDepth <= describeDepth + ) { + return; + } + const suggestions: Array<[MessageIds, string]> = []; if (testFn.body.type === AST_NODE_TYPES.BlockStatement) {