diff --git a/src/bin/enforce.ts b/src/bin/enforce.ts index 64868e02..544cbbbf 100644 --- a/src/bin/enforce.ts +++ b/src/bin/enforce.ts @@ -12,13 +12,11 @@ export const checkFlags = () => { '--denoAllow', '--denoCjs', '--denoDeny', - '--describeOnly', '--enforce', '--envFile', '--exclude', '--failFast', '--filter', - '--itOnly', '--killPid', '--killPort', '--killRange', diff --git a/src/bin/help.ts b/src/bin/help.ts index eee87300..b0c123a3 100644 --- a/src/bin/help.ts +++ b/src/bin/help.ts @@ -18,24 +18,21 @@ const summary: [string, string][] = [ ['--denoAllow', 'Allow permissions for Deno.'], ['--denoCjs', 'Support CommonJS in Deno.'], ['--denoDeny', 'Deny permissions for Deno.'], - ['--describeOnly', 'Only `describe` tests using `.only` will be run.'], ['--enforce, -x', 'Validate options before running tests.'], ['--envFile', 'Read and set an environment file.'], ['--exclude', 'Exclude by path using Regex to match files.'], ['--failFast', 'Stop tests at the first failure.'], ['--filter', 'Filter by path using Regex to match files.'], ['--help, -h', "Show Poku's CLI basic usage."], - ['--itOnly', 'Only `it` and `test` tests using `.only` will be run.'], ['--killPid', 'Terminate the specified processes.'], ['--killPort', 'Terminate the specified ports.'], ['--killRange', 'Terminate the specified port ranges.'], ['--listFiles', 'Display all the files returned in the terminal.'], ['--node', 'Enforce tests to run through Node.js.'], - ['--only', 'Only tests using `.only` will be run.'], + ['--only', 'Enable selective execution of tests.'], ['--parallel, -p', 'Run tests files in parallel.'], ['--platform', 'Enforce tests to run through a platform.'], ['--quiet, -q', 'Run tests with no logs.'], - ['--testOnly', 'Only `it` and `test` tests using `.only` will be run.'], ['--version, -v', "Show Poku's installed version."], ['--watch, -w', 'Watch for test events.'], ['--watchInterval', 'Set an interval for watch events.'], diff --git a/src/modules/helpers/modifiers.ts b/src/modules/helpers/modifiers.ts index 2185db18..061d05bf 100644 --- a/src/modules/helpers/modifiers.ts +++ b/src/modules/helpers/modifiers.ts @@ -53,7 +53,7 @@ export async function onlyDescribe( if (!(hasOnly || hasDescribeOnly)) { Write.log( format( - "Can't run `describe.only` tests without `--only` or `--describeOnly` flags" + "Can't run `describe.only` tests without `--only` or `--only=describe` flags" ).fail() ); exit(1); @@ -78,7 +78,7 @@ export async function onlyIt( if (!(hasOnly || hasItOnly)) { Write.log( format( - "Can't run `it.only` and `test.only` tests without `--only`, `--itOnly` or `--testOnly` flags" + "Can't run `it.only` and `test.only` tests without `--only`, `--only=it` or `--only=test` flags" ).fail() ); exit(1); diff --git a/src/parsers/get-arg.ts b/src/parsers/get-arg.ts index b6533e2f..fda6a33f 100644 --- a/src/parsers/get-arg.ts +++ b/src/parsers/get-arg.ts @@ -61,8 +61,10 @@ export const argToArray = ( .filter((a) => a); }; -export const hasOnly = hasArg('only'); +const only = getArg('only'); -export const hasItOnly = hasArg('itonly') || hasArg('testonly'); +export const hasOnly = hasArg('only') && !only; -export const hasDescribeOnly = hasArg('describeonly'); +export const hasDescribeOnly = only === 'describe'; + +export const hasItOnly = only && ['it', 'test'].includes(only); diff --git a/src/services/run-tests.ts b/src/services/run-tests.ts index 0974b6c7..41f88e48 100644 --- a/src/services/run-tests.ts +++ b/src/services/run-tests.ts @@ -14,9 +14,9 @@ import { hasOnly, hasDescribeOnly, hasItOnly } from '../parsers/get-arg.js'; const cwd = process.cwd(); -if (hasOnly) deepOptions.push('--only'); -if (hasDescribeOnly) deepOptions.push('--describeOnly'); -if (hasItOnly) deepOptions.push('--itOnly'); +if (hasDescribeOnly) deepOptions.push('--only=describe'); +else if (hasItOnly) deepOptions.push('--only=it'); +else if (hasOnly) deepOptions.push('--only'); export const runTests = async ( dir: string, diff --git a/test/__fixtures__/e2e/only/examples/only-it.test.ts b/test/__fixtures__/e2e/only/examples/only-it.test.ts new file mode 100644 index 00000000..05bc6fc8 --- /dev/null +++ b/test/__fixtures__/e2e/only/examples/only-it.test.ts @@ -0,0 +1,66 @@ +import { test } from '../../../../../src/modules/helpers/test.js'; +import { describe } from '../../../../../src/modules/helpers/describe.js'; +import { it } from '../../../../../src/modules/helpers/it/core.js'; +import { + beforeEach, + afterEach, +} from '../../../../../src/modules/helpers/each.js'; +import { assert } from '../../../../../src/modules/essentials/assert.js'; + +beforeEach(() => { + // It will run normally before all `it.only` and `test.only`. +}); + +afterEach(() => { + // It will run normally after all `it.only` and `test.only`. +}); + +let counter = 0; + +// ⬇️ `describe` scopes ⬇️ + +describe('1', () => { + counter++; // ✅ `describe` scope will be executed as it's in "native" JavaScript flow + + it.only('2', () => { + counter++; // ✅ `it.only` will be executed + }); + + it('3', () => { + counter++; // ⏭️ `it` will be skipped + }); + + test.only('4', () => { + counter++; // ✅ `test.only` will be executed + }); + + test('5', () => { + counter++; // ⏭️ `test` will be skipped + }); +}); + +// ⬇️ Top-level or non-`describe` scopes ⬇️ + +counter++; // ✅ Will be executed as it's in "native" JavaScript flow + +test('6', () => { + counter++; // ⏭️ `test` will be skipped +}); + +test.only('7', () => { + counter++; // ✅ `test.only` will be executed +}); + +it('8', () => { + counter++; // ⏭️ `it` will be skipped +}); + +it.only('9', () => { + counter++; // ✅ `it.only` will be executed +}); + +// describe.only('10', () => { +// counter++; // ❌ It would force a failure since `describe.only` is not enabled in `--only=it` +// }); + +assert.strictEqual(counter, 6); diff --git a/test/e2e/only.test.ts b/test/e2e/only.test.ts index 2eee999d..64eb7886 100644 --- a/test/e2e/only.test.ts +++ b/test/e2e/only.test.ts @@ -20,8 +20,8 @@ describe('Only', async () => { assert.strictEqual(results.exitCode, 0, 'Passed'); }); - await it('--itOnly', async () => { - const results = await inspectPoku('--itOnly --debug', { + await it('--only=it', async () => { + const results = await inspectPoku('--only=it --debug', { cwd: 'test/__fixtures__/e2e/only/--it-only', }); @@ -33,8 +33,8 @@ describe('Only', async () => { assert.strictEqual(results.exitCode, 0, 'Passed'); }); - await it('--testOnly', async () => { - const results = await inspectPoku('--testOnly --debug', { + await it('--only=test', async () => { + const results = await inspectPoku('--only=test --debug', { cwd: 'test/__fixtures__/e2e/only/--it-only', }); @@ -59,8 +59,8 @@ describe('Only', async () => { assert.strictEqual(results.exitCode, 1, 'Failed'); }); - await it('--describeOnly', async () => { - const results = await inspectPoku('--describeOnly --debug', { + await it('--only=describe', async () => { + const results = await inspectPoku('--only=describe --debug', { cwd: 'test/__fixtures__/e2e/only/--describe-only', }); @@ -123,4 +123,17 @@ describe('Only', async () => { assert.strictEqual(results.exitCode, 1, 'Failed'); }); + + await it('Ensure complex examples works', async () => { + const results = await inspectPoku('--only=it', { + cwd: 'test/__fixtures__/e2e/only/examples', + }); + + if (results.exitCode !== 0) { + console.log(results.stdout); + console.log(results.stderr); + } + + assert.strictEqual(results.exitCode, 0, 'Passed'); + }); }); diff --git a/website/docs/documentation/helpers/only.mdx b/website/docs/documentation/helpers/only.mdx index ea60eee0..431870a9 100644 --- a/website/docs/documentation/helpers/only.mdx +++ b/website/docs/documentation/helpers/only.mdx @@ -8,21 +8,12 @@ import { Stability } from '@site/src/components/Stability'; # 🌌 only -`.only` is an extended helper for `describe`, `it`, and `test` to assist you debug tests. +The `.only` helper enables selective execution of tests, allowing you to focus on specific `describe`, `it`, and/or `test` blocks by running only those marked with `.only`. See the [usage](#usage) to understand the different conditions and behaviors. -
- Poku doesn't map tests to run them, allowing native{' '} - JavaScript syntax in tests. -
-
- As a result, a different approach was adopted to allow{' '} - .only runs. -
- + 'This method can be altered according to suggestions and difficulties from users.' } /> @@ -42,130 +33,330 @@ import { Stability } from '@site/src/components/Stability'; ## Usage -You need to pass one of the temporary flags below to use the `.only` helper: - -> Look for "👋🏻" in the following examples. +To enable the `.only` helper, you must to pass one of the following flags to enable it selectively: ### `--only` -- Only tests using `.only` will be run. This applies to `describe`, `it` and `test` at the same time. -- If a common `describe` has a `it.only` in its scope, for example, the `it.only` will not be reached (see [**`--itOnly`**](#--itonly)). +Enables the `.only` helper for `describe`, `it` and `test` methods. -```ts -import { describe, it, test, assert } from 'poku'; +
-describe("I won't be executed", () => { - it("I won't be reached", async () => { - process.exit(1); - }); -}); +- ✅ `describe.only` +- ✅ `it.only` +- ✅ `test.only` +- ⏭️ `describe` _(it will be skipped)_ +- ⏭️ `it` _(it will be skipped)_ +- ⏭️ `test` _(it will be skipped)_ -describe.only("I'll be executed 👋🏻", () => { - it("I won't be executed", () => { - process.exit(1); - }); +
- it.only("I'll be executed 👋🏻", () => { - assert(true); - }); +```ts +import { describe, it, test } from 'poku'; - test.only("I'll be executed 👋🏻", () => { - assert(true); +describe.only(() => { + it.only(() => { + // ... }); -}); -describe("I won't be executed", () => { - it.only("I won't be reached, as `describe` is never executed", () => { - process.exit(1); + test.only(() => { + // ... }); }); -test.only("I'll be executed 👋🏻", () => { - assert(true); +test.only(() => { + // ... }); ``` ```bash npx poku --only - -# Same as: -npx poku --describeOnly --itOnly ``` :::note -By using `.only` without `--only`, `--describeOnly`, `--itOnly` or `--testOnly` flag, the test in question will force failure. + +- `describe`, `it` and `test` methods without `.only` will be skipped. + ::: -### `--itOnly` +### `--only=describe` + +Enables the `.only` helper for `describe` method. -> Alias: `--testOnly` +
-- Only `it` and `test` tests using `.only` will be run. This doens't apply to `describe`. +- ✅ `describe.only` +- ✅ `it` +- ✅ `test` +- ⏭️ `describe` _(it will be skipped)_ +- ❌ `it.only` _(it forces a failure since `it.only` is not enabled in `--only=describe`)_ +- ❌ `test.only` _(it forces a failure since `test.only` is not enabled in `--only=describe`)_ + +
```ts -import { test, describe, it, assert } from 'poku'; +import { describe, it, test } from 'poku'; + +describe.only(() => { + it(() => { + // ... + }); -test("I won't be executed", () => { - process.exit(1); + test(() => { + // ... + }); }); -test.only("I'll be executed 👋🏻", () => { - assert(true); +test(() => { + // ... }); +``` + +```bash +npx poku --only=describe +``` + +:::note + +- `describe` methods without `.only` will be skipped. +- `it` and `test` methods without `.only` will be executed normally, including outside the scope of `describe` (top-level). + +::: + +### `--only=it` + +> Alternative flag: `--only=test` + +Enables the `.only` helper for `it` and `test` methods. -describe("I'll be executed 👋🏻", () => { - it("I won't be executed", () => { - process.exit(1); +
+ +- ✅ `it.only` +- ✅ `test.only` +- ✅ `describe` +- ⏭️ `it` _(it will be skipped)_ +- ⏭️ `test` _(it will be skipped)_ +- ❌ `describe.only` _(it forces a failure since `describe.only` is not enabled in `--only=it`)_ + +
+ +```ts +import { describe, it, test } from 'poku'; + +describe(() => { + it.only(() => { + // ... }); - it.only("I'll be executed 👋🏻", () => { - assert(true); + test.only(() => { + // ... }); }); + +test.only(() => { + // ... +}); ``` ```bash -npx poku --itOnly +npx poku --only=it ``` :::note -By using `.only` in `describe` with `--itOnly` or `--testOnly` flag, the test in question will force failure. + +- `it` and `test` methods without `.only` will be skipped. +- `describe` methods without `.only` will be executed normally. + +::: + +--- + +:::tip + +- The `.only` helper works exactly as its respective `describe`, `it` and `test` methods (e.g., by running `beforeEach` and `afterEach` for the `test.only` or `it.only`). +- It works for both sequential and parallel executions normally, including synchronous and asynchronous tests. + +::: + +:::danger Important +It's important to recall that **Poku** respects conventional **JavaScript** syntax in tests and doesn't change the order of the executions. See the [examples](#complex-examples) to clarify it. ::: -### `--describeOnly` +--- + +## Common issues + +### `.only` vs. scope -- Only `describe` tests using `.only` will be run. This doens't apply to `it` and `test`. +If a `.only` method is inside an skipped method, it won't be executed, for example: ```ts -import { test, describe, it, assert } from 'poku'; +import { describe, it, test } from 'poku'; -describe("I won't be executed", () => { - it("I won't be reached", async () => { - process.exit(1); +describe.only(() => { + it.only(() => { + // ... ✅ }); + + // it(() => { + // // ... + // }); + + // test(() => { + // // ... + // }); }); -describe.only("I'll be executed 👋🏻", () => { - it("I'll be executed 👋🏻", () => { - assert(true); - }); +// describe(() => { +// it.only(() => { +// // ... ❌ +// }); +// +// test(() => { +// // ... +// }); +// }); +``` + +```bash +npx poku --only +``` + +--- + +## Migrating from other Test Runners + +In **Poku**, the `.only` helper works like a switch: + +- To enable the `.only` helper for both `describe`, `it` and `test` methods, you need to use the `--only` flag. +- To enable the `.only` helper for `describe` methods, you need to use the `--only=describe` flag. +- To enable the `.only` helper for `it` and `test` methods, you need to use the `--only=it` flag. + +An example running a `it.only` inside a `describe` method without `.only`: + +```ts +import { describe, it } from 'poku'; - test("I'll be executed 👋🏻", () => { - assert(true); +describe(() => { + it.only(() => { + // ... ✅ }); + + // it(() => { + // // ... + // }); +}); +``` + +```bash +npx poku --only=it +``` + +This way, you enable `.only` only for `it` and `test` methods, keeping `describe` methods with their default behavior. It means that `describe` methods will run even without `.only` due to `--only=it`, while `it` and `test` methods will only run if you use the `.only` helper. + +It's also important to note that the `--only` flag applies to all files to be tested and you can use the flag with or without `poku` command, for example: + +```bash +npx poku test/my-test.test.js --only +``` + +```bash +node test/my-test.test.js --only +``` + +```bash +npx tsx test/my-test.test.ts --only +``` + +--- + +## Mapped vs. non-mapped tests _(advanced concept)_ + +**Poku** doesn't map the tests to determine which ones will be run or not from appending `.only` tests, instead, it toggles which methods (`describe`, `it` and `test`) will be run according to the flags `--only`, `--only=describe` or `--only=it`. + +Why isn't `it.only` executed in the following example? + +```ts +describe(() => { + // it.only(() => { + // // ... ❌ + // }); +}); +``` + +```bash +npx poku --only +``` + +As the `describe` method isn't using the `.only` helper, it will be skipped, including everything within its scope, which includes the `it.only` in this example. + +--- + +## Complex examples + +### `--only=it` + +```ts +import { describe, it, test, assert } from 'poku'; + +beforeEach(() => { + // It will run normally before all `it.only` and `test.only`. +}); + +afterEach(() => { + // It will run normally after all `it.only` and `test.only`. }); -describe("I won't be executed", () => { - it.only("I won't be reached, as `describe` is never executed", () => { - process.exit(1); +let counter = 0; + +// ⬇️ `describe` scopes ⬇️ + +describe('1', () => { + counter++; // ✅ `describe` scope will be executed as it's in "native" JavaScript flow + + it.only('2', () => { + counter++; // ✅ `it.only` will be executed + }); + + it('3', () => { + counter++; // ⏭️ `it` will be skipped + }); + + test.only('4', () => { + counter++; // ✅ `test.only` will be executed }); + + test('5', () => { + counter++; // ⏭️ `test` will be skipped + }); +}); + +// ⬇️ Top-level or non-`describe` scopes ⬇️ + +counter++; // ✅ Will be executed as it's in "native" JavaScript flow + +test('6', () => { + counter++; // ⏭️ `test` will be skipped +}); + +test.only('7', () => { + counter++; // ✅ `test.only` will be executed }); -test("I'll be executed 👋🏻", () => { - assert(true); +it('8', () => { + counter++; // ⏭️ `it` will be skipped }); + +it.only('9', () => { + counter++; // ✅ `it.only` will be executed +}); + +// describe.only('10', () => { +// counter++; // ❌ It would force a failure since `describe.only` is not enabled in `--only=it` +// }); + +assert.strictEqual(counter, 6); ``` -:::note -By using `.only` in `it` or `test` with `--describeOnly` flag, the test in question will force failure. -::: +```bash +npx poku --only=it +```