From 6ec0cf0da269c1f79a54e14fa821ccf7f7ed6b74 Mon Sep 17 00:00:00 2001 From: Muhammadyusuf Kurbonov Date: Tue, 8 Oct 2024 22:48:59 +0500 Subject: [PATCH 1/5] fix: set test state failed if error is thrown in `before` hook --- src/runner.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/runner.ts b/src/runner.ts index adb0d49..ae090aa 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -510,7 +510,11 @@ class CompiledFileTests { for (const item of items) { let candidate: vscode.TestItem | undefined = item; for (let i = 0; i < path.length && candidate; i++) { - candidate = candidate.children.get(path[i]); + const pathPart = path[i]; + if (pathPart.startsWith('"before all" hook') || pathPart.startsWith('"before each" hook')) { + break; + } + candidate = candidate.children.get(pathPart); } if (candidate !== undefined) { return candidate; From 1471a17c0c758706d029959a884d1a86ff87ad76 Mon Sep 17 00:00:00 2001 From: Muhammadyusuf Kurbonov Date: Sun, 13 Oct 2024 22:22:04 +0500 Subject: [PATCH 2/5] fix: add support for after hook --- src/runner.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/runner.ts b/src/runner.ts index ae090aa..efded01 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -511,7 +511,12 @@ class CompiledFileTests { let candidate: vscode.TestItem | undefined = item; for (let i = 0; i < path.length && candidate; i++) { const pathPart = path[i]; - if (pathPart.startsWith('"before all" hook') || pathPart.startsWith('"before each" hook')) { + if ( + pathPart.startsWith('"before all" hook') || + pathPart.startsWith('"before each" hook') || + pathPart.startsWith('"after all" hook') || + pathPart.startsWith('"after each" hook') + ) { break; } candidate = candidate.children.get(pathPart); From baf3519f061a8261abecb70c38c89d3d531dd29f Mon Sep 17 00:00:00 2001 From: Muhammadyusuf Kurbonov Date: Sun, 13 Oct 2024 22:22:29 +0500 Subject: [PATCH 3/5] tests: add tests for failed mocha hooks --- test-workspaces/with-hooks/.mocharc.js | 3 + test-workspaces/with-hooks/hello.test.js | 103 +++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 test-workspaces/with-hooks/.mocharc.js create mode 100644 test-workspaces/with-hooks/hello.test.js diff --git a/test-workspaces/with-hooks/.mocharc.js b/test-workspaces/with-hooks/.mocharc.js new file mode 100644 index 0000000..9df0b71 --- /dev/null +++ b/test-workspaces/with-hooks/.mocharc.js @@ -0,0 +1,3 @@ +module.exports = { + spec: '**/*.test.js' +}; \ No newline at end of file diff --git a/test-workspaces/with-hooks/hello.test.js b/test-workspaces/with-hooks/hello.test.js new file mode 100644 index 0000000..1b36c0c --- /dev/null +++ b/test-workspaces/with-hooks/hello.test.js @@ -0,0 +1,103 @@ +const { strictEqual } = require('node:assert'); + +describe('with beforeAll hook', () => { + before(() => { + console.log('Before hook executed once!'); + }); + + it('addition', async () => { + strictEqual(1 + 1, 2); + }); + + it('subtraction', async () => { + strictEqual(1 - 1, 0); + }); + it('failing', async () => { + strictEqual(1 * 1, 0); + }); +}); + +describe('with beforeEach hook', () => { + beforeEach(() => { + console.log('BeforeEach hook executed every time!'); + }); + + it('addition', async () => { + strictEqual(1 + 1, 2); + }); + + it('subtraction', async () => { + strictEqual(1 - 1, 0); + }); + it('failing', async () => { + strictEqual(1 * 1, 0); + }); +}); + +describe('with broken before hook (suite must be failed)', () => { + before(() => { + throw new Error('Before hook is broken!!!'); + }); + + it('addition (skipped)', async () => { + strictEqual(1 + 1, 2); + }); + + it('subtraction (skipped)', async () => { + strictEqual(1 - 1, 0); + }); + it('failing (skipped)', async () => { + strictEqual(1 * 1, 0); + }); +}); + +describe('with broken beforeEach hook (suite must be failed)', () => { + beforeEach(() => { + throw new Error('BeforeEach hook is broken!!!'); + }); + + it('addition (skipped)', async () => { + strictEqual(1 + 1, 2); + }); + + it('subtraction (skipped)', async () => { + strictEqual(1 - 1, 0); + }); + it('failing (skipped)', async () => { + strictEqual(1 * 1, 0); + }); +}); + +describe('with broken after hook (suite must be failed)', () => { + after(() => { + throw new Error('After hook is broken!!!'); + }); + + it('addition', async () => { + strictEqual(1 + 1, 2); + }); + + it('subtraction', async () => { + strictEqual(1 - 1, 0); + }); + it('failing', async () => { + strictEqual(1 * 1, 0); + }); +}); + +describe('with broken afterEach hook (suite must be failed)', () => { + afterEach(() => { + throw new Error('After each hook is broken!!!'); + }); + + it('addition (success)', async () => { + strictEqual(1 + 1, 2); + }); + + it('subtraction (skipped)', async () => { + strictEqual(1 - 1, 0); + }); + it('failing (skipped)', async () => { + strictEqual(1 * 1, 0); + }); +}); From 79065e494fd6fc838a39d87180379dc8e7d2dcc2 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 20 Oct 2024 18:52:42 +0200 Subject: [PATCH 4/5] test: Add actual test execution of hooks tests --- src/test/integration/with-hooks.test.ts | 125 ++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 src/test/integration/with-hooks.test.ts diff --git a/src/test/integration/with-hooks.test.ts b/src/test/integration/with-hooks.test.ts new file mode 100644 index 0000000..0760422 --- /dev/null +++ b/src/test/integration/with-hooks.test.ts @@ -0,0 +1,125 @@ +/** + * Copyright (C) Daniel Kuschny (Danielku15) and contributors. + * Copyright (C) Microsoft Corporation. All rights reserved. + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ + +import { expect } from 'chai'; +import * as vscode from 'vscode'; +import { captureTestRun, expectTestTree, getController } from '../util'; + +describe('with-hooks', () => { + it('discovers tests', async () => { + const c = await getController(); + + expectTestTree(c, [ + [ + 'hello.test.js', + [ + ['with beforeAll hook', [['addition'], ['failing'], ['subtraction']]], + ['with beforeEach hook', [['addition'], ['failing'], ['subtraction']]], + [ + 'with broken after hook (suite must be failed)', + [['addition'], ['failing'], ['subtraction']], + ], + [ + 'with broken afterEach hook (suite must be failed)', + [['addition (success)'], ['failing (skipped)'], ['subtraction (skipped)']], + ], + [ + 'with broken before hook (suite must be failed)', + [['addition (skipped)'], ['failing (skipped)'], ['subtraction (skipped)']], + ], + [ + 'with broken beforeEach hook (suite must be failed)', + [['addition (skipped)'], ['failing (skipped)'], ['subtraction (skipped)']], + ], + ], + ], + ]); + }); + + it('runs tests', async () => { + const c = await getController(); + const profiles = c.profiles; + expect(profiles).to.have.lengthOf(2); + + const run = await captureTestRun( + c, + new vscode.TestRunRequest( + undefined, + undefined, + profiles.find((p) => p.kind === vscode.TestRunProfileKind.Run), + ), + ); + + run.expectStates({ + 'hello.test.js/with beforeAll hook/addition': ['enqueued', 'started', 'passed'], + 'hello.test.js/with beforeAll hook/subtraction': ['enqueued', 'started', 'passed'], + 'hello.test.js/with beforeAll hook/failing': ['enqueued', 'started', 'failed'], + 'hello.test.js/with beforeEach hook/addition': ['enqueued', 'started', 'passed'], + 'hello.test.js/with beforeEach hook/subtraction': ['enqueued', 'started', 'passed'], + 'hello.test.js/with beforeEach hook/failing': ['enqueued', 'started', 'failed'], + 'hello.test.js/with broken before hook (suite must be failed)/addition (skipped)': [ + 'enqueued', + 'skipped', + ], + 'hello.test.js/with broken before hook (suite must be failed)/subtraction (skipped)': [ + 'enqueued', + 'skipped', + ], + 'hello.test.js/with broken before hook (suite must be failed)/failing (skipped)': [ + 'enqueued', + 'skipped', + ], + 'hello.test.js/with broken beforeEach hook (suite must be failed)/addition (skipped)': [ + 'enqueued', + 'started', + 'skipped', + ], + 'hello.test.js/with broken beforeEach hook (suite must be failed)/subtraction (skipped)': [ + 'enqueued', + 'skipped', + ], + 'hello.test.js/with broken beforeEach hook (suite must be failed)/failing (skipped)': [ + 'enqueued', + 'skipped', + ], + 'hello.test.js/with broken after hook (suite must be failed)/addition': [ + 'enqueued', + 'started', + 'passed', + ], + 'hello.test.js/with broken after hook (suite must be failed)/subtraction': [ + 'enqueued', + 'started', + 'passed', + ], + 'hello.test.js/with broken after hook (suite must be failed)/failing': [ + 'enqueued', + 'started', + 'failed', + ], + 'hello.test.js/with broken afterEach hook (suite must be failed)/addition (success)': [ + 'enqueued', + 'started', + 'passed', + ], + 'hello.test.js/with broken afterEach hook (suite must be failed)/subtraction (skipped)': [ + 'enqueued', + 'skipped', + ], + 'hello.test.js/with broken afterEach hook (suite must be failed)/failing (skipped)': [ + 'enqueued', + 'skipped', + ], + 'hello.test.js/with broken before hook (suite must be failed)': ['failed'], + 'hello.test.js/with broken beforeEach hook (suite must be failed)': ['failed'], + 'hello.test.js/with broken after hook (suite must be failed)': ['failed'], + 'hello.test.js/with broken afterEach hook (suite must be failed)': ['failed'], + }); + }); +}); From 1e207e74137f4d239da5773f8b21e880c7946caf Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 20 Oct 2024 19:10:33 +0200 Subject: [PATCH 5/5] fix: Handle hooks in test discovery --- README.md | 1 + package.json | 14 +++++++++++++- src/constants.ts | 1 + src/discoverer/evaluate.ts | 2 ++ src/discoverer/types.ts | 1 + 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a0ee40..d0a398b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ This extension automatically discovers and works with the `.mocharc.js/cjs/yaml/ - `syntax` Parse the file and try to extract the tests from the syntax tree. - The `extractTimeout` limiting how long the extraction of tests for a single file is allowed to take. - The `test` and `suite` identifiers the process extracts. Defaults to `["it", "test"]` and `["describe", "suite"]` respectively, covering Mocha's common interfaces. + - The `hooks` identifiers to avoid Mocha executing stuff on test discovery. Defaults to `["before", "after", "beforeEach", "afterEach"]`. - `mocha-vscode.debugOptions`: options, normally found in the launch.json, to pass when debugging the extension. See [the docs](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_launch-configuration-attributes) for a complete list of options. diff --git a/package.json b/package.json index 2de57ce..51cac66 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "title": "Mocha for VS Code", "properties": { "mocha-vscode.extractSettings": { - "markdownDescription": "Configures how tests get extracted. You can configure:\n\n- The `extractWith` mode, that specifies if tests are extracted.\n - `evaluation-cjs` (default) Translate the test file to CommonJS and evaluate it with all dependencies mocked.\n - `evaluation-cjs-full` Translate the test file to CommonJS and fully evaluate it with all dependencies.\n - `syntax` Parse the file and try to extract the tests from the syntax tree.\n- The `extractTimeout` limiting how long the extraction of tests for a single file is allowed to take.\n- The `test` and `suite` identifiers the process extracts. Defaults to `[\"it\", \"test\"]` and `[\"describe\", \"suite\"]` respectively, covering Mocha's common interfaces.\n\n- `mocha-vscode.debugOptions`: options, normally found in the launch.json, to pass when debugging the extension. See [the docs](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_launch-configuration-attributes) for a complete list of options.", + "markdownDescription": "Configures how tests get extracted. You can configure:\n\n- The `extractWith` mode, that specifies if tests are extracted.\n - `evaluation-cjs` (default) Translate the test file to CommonJS and evaluate it with all dependencies mocked.\n - `evaluation-cjs-full` Translate the test file to CommonJS and fully evaluate it with all dependencies.\n - `syntax` Parse the file and try to extract the tests from the syntax tree.\n- The `extractTimeout` limiting how long the extraction of tests for a single file is allowed to take.\n- The `test` and `suite` identifiers the process extracts. Defaults to `[\"it\", \"test\"]` and `[\"describe\", \"suite\"]` respectively, covering Mocha's common interfaces.\n- The `hooks` identifiers to avoid Mocha executing stuff on test discovery. Defaults to `[\"before\", \"after\", \"beforeEach\", \"afterEach\"]`\n\n- `mocha-vscode.debugOptions`: options, normally found in the launch.json, to pass when debugging the extension. See [the docs](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_launch-configuration-attributes) for a complete list of options.", "type": "object", "properties": { "suite": { @@ -53,6 +53,12 @@ "type": "string" } }, + "hooks": { + "type": "array", + "items": { + "type": "string" + } + }, "extractWith": { "type": "string", "enum": [ @@ -74,6 +80,12 @@ "it", "test" ], + "hooks": [ + "before", + "after", + "beforeEach", + "afterEach" + ], "extractWith": "evaluation-cjs", "extractTimeout": 10000 }, diff --git a/src/constants.ts b/src/constants.ts index ec88e48..b35c7f7 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,6 +16,7 @@ export const configFilePattern = '**/.mocharc.{js,cjs,yaml,yml,json,jsonc}'; export const defaultTestSymbols: IExtensionSettings = { suite: ['describe', 'suite'], test: ['it', 'test'], + hooks: ['before', 'after', 'beforeEach', 'afterEach'], extractWith: 'evaluation-cjs', extractTimeout: 10_000, }; diff --git a/src/discoverer/evaluate.ts b/src/discoverer/evaluate.ts index d0c36c5..d935c6a 100644 --- a/src/discoverer/evaluate.ts +++ b/src/discoverer/evaluate.ts @@ -203,6 +203,8 @@ export class EvaluationTestDiscoverer implements ITestDiscoverer { return suiteFunction; } else if (symbols.value.test.includes(prop as string)) { return testFunction; + } else if (symbols.value.hooks.includes(prop as string)) { + return placeholder(); } else if (prop in target) { return target[prop]; // top-level `var` defined get set on the contextObj } else if (prop in globalThis && !replacedGlobals.has(prop as string)) { diff --git a/src/discoverer/types.ts b/src/discoverer/types.ts index 347cc14..363310b 100644 --- a/src/discoverer/types.ts +++ b/src/discoverer/types.ts @@ -22,6 +22,7 @@ export interface IParsedNode { export interface IExtensionSettings { suite: readonly string[]; test: readonly string[]; + hooks: readonly string[]; extractWith: 'syntax' | 'evaluation-cjs' | 'evaluation-cjs-full'; extractTimeout: number; }