diff --git a/src/configs/poku.ts b/src/configs/poku.ts index 8d4cf95c..6672c2bf 100644 --- a/src/configs/poku.ts +++ b/src/configs/poku.ts @@ -1,4 +1,4 @@ -import { cwd } from 'node:process'; +import { env, cwd } from 'node:process'; export const results = { success: 0, @@ -13,4 +13,7 @@ export const deepOptions: string[] = []; export const GLOBAL = { cwd: cwd(), + runAsOnly: false, + isPoku: typeof env?.POKU_FILE === 'string' && env?.POKU_FILE.length > 0, + FILE: env.POKU_FILE, }; diff --git a/src/modules/essentials/poku.ts b/src/modules/essentials/poku.ts index 6ca2bf32..b26cbf76 100644 --- a/src/modules/essentials/poku.ts +++ b/src/modules/essentials/poku.ts @@ -44,7 +44,6 @@ export async function poku( const concurrency = await Promise.all(promises); if (concurrency.some((result) => !result)) code = 1; - } catch { } finally { const end = process.hrtime(start); const total = (end[0] * 1e3 + end[1] / 1e6).toFixed(6); diff --git a/src/modules/helpers/describe.ts b/src/modules/helpers/describe.ts index 0c5b91f0..a81e0960 100644 --- a/src/modules/helpers/describe.ts +++ b/src/modules/helpers/describe.ts @@ -1,10 +1,12 @@ import type { DescribeOptions } from '../../@types/describe.js'; -import { hrtime, env } from 'node:process'; +import { hrtime } from 'node:process'; import { format } from '../../services/format.js'; import { Write } from '../../services/write.js'; import { indentation } from '../../configs/indentation.js'; import { todo, skip, onlyDescribe } from './modifiers.js'; -import { hasDescribeOnly, hasOnly } from '../../parsers/get-arg.js'; +import { hasOnly } from '../../parsers/get-arg.js'; +import { checkOnly } from '../../parsers/callback.js'; +import { GLOBAL } from '../../configs/poku.js'; export async function describeBase( arg1: string | (() => unknown | Promise), @@ -14,9 +16,6 @@ export async function describeBase( let cb: (() => unknown | Promise) | undefined; let options: DescribeOptions | undefined; - const isPoku = typeof env?.FILE === 'string' && env?.FILE.length > 0; - const FILE = env.POKU_FILE; - if (typeof arg1 === 'string') { title = arg1; @@ -31,7 +30,7 @@ export async function describeBase( indentation.hasDescribe = true; const { background, icon } = options ?? {}; - const message = `${cb ? format('◌').dim() : (icon ?? '☰')} ${cb ? format(isPoku ? `${title} › ${format(`${FILE}`).italic().gray()}` : title).dim() : format(title).bold()}`; + const message = `${cb ? format('◌').dim() : (icon ?? '☰')} ${cb ? format(title).dim() : format(title).bold()}`; const noBackground = !background; if (noBackground) Write.log(format(message).bold()); @@ -56,6 +55,7 @@ export async function describeBase( const total = (end[0] * 1e3 + end[1] / 1e6).toFixed(6); + GLOBAL.runAsOnly = false; indentation.hasDescribe = false; Write.log( `${format(`● ${title}`).success().bold()} ${format(`› ${total}ms`).success().dim()}` @@ -77,7 +77,18 @@ async function describeCore( if (typeof messageOrCb === 'string' && typeof cbOrOptions !== 'function') return describeBase(messageOrCb, cbOrOptions); - if (hasOnly || hasDescribeOnly) return; + if (hasOnly) { + const hasItOnly = checkOnly( + typeof messageOrCb === 'function' ? messageOrCb : cbOrOptions + ); + + if (!hasItOnly) return; + + if (typeof messageOrCb === 'string' && typeof cbOrOptions === 'function') + return describeBase(messageOrCb, cbOrOptions); + + if (typeof messageOrCb === 'function') return describeBase(messageOrCb); + } if (typeof messageOrCb === 'string' && typeof cbOrOptions === 'function') return describeBase(messageOrCb, cbOrOptions); diff --git a/src/modules/helpers/it/core.ts b/src/modules/helpers/it/core.ts index 682973e1..79a9bded 100644 --- a/src/modules/helpers/it/core.ts +++ b/src/modules/helpers/it/core.ts @@ -1,10 +1,11 @@ -import { hrtime, env } from 'node:process'; +import { hrtime } from 'node:process'; import { each } from '../../../configs/each.js'; import { indentation } from '../../../configs/indentation.js'; import { format } from '../../../services/format.js'; import { Write } from '../../../services/write.js'; import { todo, skip, onlyIt } from '../modifiers.js'; -import { hasItOnly, hasOnly } from '../../../parsers/get-arg.js'; +import { hasOnly } from '../../../parsers/get-arg.js'; +import { GLOBAL } from '../../../configs/poku.js'; export async function itBase( ...args: [ @@ -16,10 +17,6 @@ export async function itBase( let message: string | undefined; let cb: () => unknown | Promise; - const isPoku = - typeof env?.POKU_FILE === 'string' && env?.POKU_FILE.length > 0; - const FILE = env.POKU_FILE; - if (typeof args[0] === 'string') { message = args[0]; cb = args[1] as () => unknown | Promise; @@ -29,9 +26,7 @@ export async function itBase( indentation.hasItOrTest = true; Write.log( - isPoku - ? `${indentation.hasDescribe ? ' ' : ''}${format(`◌ ${message} › ${format(`${FILE}`).italic().gray()}`).dim()}` - : `${indentation.hasDescribe ? ' ' : ''}${format(`◌ ${message}`).dim()}` + `${indentation.hasDescribe ? ' ' : ''}${format(`◌ ${message}`).dim()}` ); } @@ -86,7 +81,14 @@ async function itCore( messageOrCb: string | (() => unknown) | (() => Promise), cb?: (() => unknown) | (() => Promise) ): Promise { - if (hasOnly || hasItOnly) return; + if (hasOnly) { + if (!GLOBAL.runAsOnly) return; + + if (typeof messageOrCb === 'string' && typeof cb === 'function') + return itBase(messageOrCb, cb); + + if (typeof messageOrCb === 'function') return itBase(messageOrCb); + } if (typeof messageOrCb === 'string' && cb) return itBase(messageOrCb, cb); if (typeof messageOrCb === 'function') return itBase(messageOrCb); diff --git a/src/modules/helpers/modifiers.ts b/src/modules/helpers/modifiers.ts index 061d05bf..47edfd22 100644 --- a/src/modules/helpers/modifiers.ts +++ b/src/modules/helpers/modifiers.ts @@ -1,10 +1,12 @@ +import { exit } from 'node:process'; import { Write } from '../../services/write.js'; import { indentation } from '../../configs/indentation.js'; import { format } from '../../services/format.js'; import { itBase } from './it/core.js'; import { describeBase } from './describe.js'; -import { hasDescribeOnly, hasItOnly, hasOnly } from '../../parsers/get-arg.js'; -import { exit } from 'node:process'; +import { hasOnly } from '../../parsers/get-arg.js'; +import { CheckNoOnly } from '../../parsers/callback.js'; +import { GLOBAL } from '../../configs/poku.js'; export function todo(message: string): void; export async function todo( @@ -50,15 +52,19 @@ export async function onlyDescribe( messageOrCb: string | (() => unknown) | (() => Promise), cb?: (() => unknown) | (() => Promise) ): Promise { - if (!(hasOnly || hasDescribeOnly)) { + if (!hasOnly) { Write.log( - format( - "Can't run `describe.only` tests without `--only` or `--only=describe` flags" - ).fail() + format("Can't run `describe.only` tests without `--only` flag").fail() ); exit(1); } + const noItOnly = CheckNoOnly( + typeof messageOrCb === 'function' ? messageOrCb : cb + ); + + if (noItOnly) GLOBAL.runAsOnly = true; + if (typeof messageOrCb === 'string' && cb) return describeBase(messageOrCb, cb); if (typeof messageOrCb === 'function') return describeBase(messageOrCb); @@ -75,10 +81,10 @@ export async function onlyIt( messageOrCb: string | (() => unknown) | (() => Promise), cb?: (() => unknown) | (() => Promise) ): Promise { - if (!(hasOnly || hasItOnly)) { + if (!hasOnly) { Write.log( format( - "Can't run `it.only` and `test.only` tests without `--only`, `--only=it` or `--only=test` flags" + "Can't run `it.only` and `test.only` tests without `--only` flag" ).fail() ); exit(1); diff --git a/src/modules/helpers/skip.ts b/src/modules/helpers/skip.ts index 130def10..8c680a19 100644 --- a/src/modules/helpers/skip.ts +++ b/src/modules/helpers/skip.ts @@ -1,11 +1,10 @@ -import { exit, env } from 'node:process'; +import { exit } from 'node:process'; import { Write } from '../../services/write.js'; import { format } from '../../services/format.js'; +import { GLOBAL } from '../../configs/poku.js'; export const skip = (message = 'Skipping') => { - const isPoku = - typeof env?.POKU_FILE === 'string' && env?.POKU_FILE.length > 0; - const FILE = env.POKU_FILE; + const { isPoku, FILE } = GLOBAL; if (message) Write.log( diff --git a/src/parsers/callback.ts b/src/parsers/callback.ts new file mode 100644 index 00000000..88e3ed35 --- /dev/null +++ b/src/parsers/callback.ts @@ -0,0 +1,23 @@ +export const checkOnly = (cb: unknown): boolean => { + if (typeof cb !== 'function') return false; + + const body = cb.toString(); + + return ( + body.includes('it.only') || + body.includes('test.only') || + body.includes('describe.only') + ); +}; + +export const CheckNoOnly = (cb: unknown): boolean => { + if (typeof cb !== 'function') return false; + + const body = cb.toString(); + + return !( + body.includes('it.only') || + body.includes('test.only') || + body.includes('describe.only') + ); +}; diff --git a/src/parsers/get-arg.ts b/src/parsers/get-arg.ts index 8ff9fb9d..e299a79c 100644 --- a/src/parsers/get-arg.ts +++ b/src/parsers/get-arg.ts @@ -56,10 +56,4 @@ export const argToArray = ( .filter((a) => a); }; -const only = getArg('only'); - -export const hasOnly = hasArg('only') && !only; - -export const hasDescribeOnly = only === 'describe'; - -export const hasItOnly = only && ['it', 'test'].includes(only); +export const hasOnly = hasArg('only'); diff --git a/src/services/assert.ts b/src/services/assert.ts index cf9271b8..102b685f 100644 --- a/src/services/assert.ts +++ b/src/services/assert.ts @@ -13,10 +13,7 @@ const { cwd } = GLOBAL; const regexFile = /file:(\/\/)?/; const assertProcessor = () => { - const isPoku = - typeof process.env?.POKU_FILE === 'string' && - process.env?.POKU_FILE.length > 0; - const FILE = process.env.POKU_FILE; + const { isPoku, FILE } = GLOBAL; let preIdentation = ''; diff --git a/src/services/run-tests.ts b/src/services/run-tests.ts index fdde64bb..33a89801 100644 --- a/src/services/run-tests.ts +++ b/src/services/run-tests.ts @@ -8,14 +8,12 @@ import { runTestFile } from './run-test-file.js'; import { isQuiet } from '../parsers/output.js'; import { deepOptions, GLOBAL, results } from '../configs/poku.js'; import { availableParallelism } from '../polyfills/os.js'; -import { hasOnly, hasDescribeOnly, hasItOnly } from '../parsers/get-arg.js'; +import { hasOnly } from '../parsers/get-arg.js'; const { cwd } = GLOBAL; const failFastError = ` ${format('ℹ').fail()} ${format('failFast').bold()} is enabled`; -if (hasDescribeOnly) deepOptions.push('--only=describe'); -else if (hasItOnly) deepOptions.push('--only=it'); -else if (hasOnly) deepOptions.push('--only'); +if (hasOnly) deepOptions.push('--only'); export const runTests = async ( dir: string, diff --git a/test/__fixtures__/e2e/only/--describe-only/basic-logs.test.ts b/test/__fixtures__/e2e/only/--describe-only/basic-logs.test.ts deleted file mode 100644 index b65ed1f0..00000000 --- a/test/__fixtures__/e2e/only/--describe-only/basic-logs.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { assert } from '../../../../../src/modules/essentials/assert.js'; -import { describe } from '../../../../../src/modules/helpers/describe.js'; -import { it } from '../../../../../src/modules/helpers/it/core.js'; - -let counter = 0; - -it('Should skip', () => { - counter++; -}); - -describe.only('Should run', () => { - it('Should run', () => { - counter++; - }); -}); - -describe('Should skip', () => { - counter++; - - it('Should never be called', () => { - counter++; - }); - - it.only('Should never be called', () => { - counter++; - }); -}); - -assert.strictEqual(counter, 2); diff --git a/test/__fixtures__/e2e/only/--it-only/basic-logs.test.ts b/test/__fixtures__/e2e/only/--it-only/basic-logs.test.ts deleted file mode 100644 index c798af77..00000000 --- a/test/__fixtures__/e2e/only/--it-only/basic-logs.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { assert } from '../../../../../src/modules/essentials/assert.js'; -import { describe } from '../../../../../src/modules/helpers/describe.js'; -import { it } from '../../../../../src/modules/helpers/it/core.js'; - -let counter = 0; - -it('Should skip', () => { - counter++; -}); - -it.only('Should run', () => { - counter++; -}); - -describe('Should skip', () => { - it('Should skip', () => { - counter++; - }); - - it.only('Should run', () => { - counter++; - }); -}); - -describe('Should run', () => { - it('Should skip', () => { - counter++; - }); - - it.only('Should run', () => { - counter++; - }); -}); - -describe('Should run', () => { - it.only('Should run', () => { - counter++; - }); -}); - -assert.strictEqual(counter, 4); diff --git a/test/__fixtures__/e2e/only/--only/basic-logs.test.ts b/test/__fixtures__/e2e/only/--only/basic-logs.test.ts index 8e14d03a..10f1df69 100644 --- a/test/__fixtures__/e2e/only/--only/basic-logs.test.ts +++ b/test/__fixtures__/e2e/only/--only/basic-logs.test.ts @@ -2,44 +2,100 @@ import { assert } from '../../../../../src/modules/essentials/assert.js'; import { describe } from '../../../../../src/modules/helpers/describe.js'; import { it } from '../../../../../src/modules/helpers/it/core.js'; -let counter = 0; +(async () => { + describe('1', () => { + it('2', () => { + assert(true); + }); -it('Should skip', () => { - counter++; -}); + it.only('3', () => { + assert(true); + }); + }); -it.only('Should run', () => { - counter++; -}); + describe.only('4', () => { + assert(true); + }); -it('Should skip', () => { - counter++; -}); + it.only('5', () => { + assert(true); + }); -describe('Should skip', () => { - it('Should never be called', () => { - counter++; + describe('6', () => { + it('7', () => { + assert(true); + }); }); - it.only('Should never be called', () => { - counter++; + describe.only('8', () => { + it('9', () => { + assert(true); + }); + + it('10', () => { + assert(true); + }); + + it('11', () => { + assert(true); + }); }); -}); -describe.only('Should run', () => { - it('Should skip', () => { - counter++; + describe('12', () => { + it('13', () => { + assert(true); + }); + + it('14', () => { + assert(true); + }); }); - it.only('Should run', () => { - counter++; + describe.only('15', () => { + it('16', () => { + assert(true); + }); + + it.only('17', () => { + assert(true); + }); + + it('18', () => { + assert(true); + }); }); -}); -describe.only('Should run', () => { - it.only('Should run', () => { - counter++; + await describe.only('19', async () => { + await it('20', async () => { + await Promise.resolve(true); + assert(true); + }); + + await it('21', async () => { + await Promise.resolve(true); + assert(true); + }); + + await it('22', async () => { + await Promise.resolve(true); + assert(true); + }); }); -}); -assert.strictEqual(counter, 3); + await describe.only('23', async () => { + await it('24', async () => { + await Promise.resolve(true); + assert(true); + }); + + await it.only('25', async () => { + await Promise.resolve(true); + assert(true); + }); + + await it('26', async () => { + await Promise.resolve(true); + assert(true); + }); + }); +})(); diff --git a/test/__fixtures__/e2e/only/--only/no-logs.test.ts b/test/__fixtures__/e2e/only/--only/no-logs.test.ts index ba8f8ad7..d16590df 100644 --- a/test/__fixtures__/e2e/only/--only/no-logs.test.ts +++ b/test/__fixtures__/e2e/only/--only/no-logs.test.ts @@ -2,44 +2,100 @@ import { assert } from '../../../../../src/modules/essentials/assert.js'; import { describe } from '../../../../../src/modules/helpers/describe.js'; import { it } from '../../../../../src/modules/helpers/it/core.js'; -let counter = 0; +(async () => { + describe(() => { + it(() => { + assert(true); + }); -it(() => { - counter++; -}); + it.only(() => { + assert(true); + }); + }); -it.only(() => { - counter++; -}); + describe.only(() => { + assert(true); + }); -it(() => { - counter++; -}); + it.only(() => { + assert(true); + }); -describe(() => { - it(() => { - counter++; + describe(() => { + it(() => { + assert(true); + }); }); - it.only(() => { - counter++; + describe.only(() => { + it(() => { + assert(true); + }); + + it(() => { + assert(true); + }); + + it(() => { + assert(true); + }); }); -}); -describe.only(() => { - it(() => { - counter++; + describe(() => { + it(() => { + assert(true); + }); + + it(() => { + assert(true); + }); }); - it.only(() => { - counter++; + describe.only(() => { + it(() => { + assert(true); + }); + + it.only(() => { + assert(true); + }); + + it(() => { + assert(true); + }); }); -}); -describe.only(() => { - it.only(() => { - counter++; + await describe.only(async () => { + await it(async () => { + await Promise.resolve(true); + assert(true); + }); + + await it(async () => { + await Promise.resolve(true); + assert(true); + }); + + await it(async () => { + await Promise.resolve(true); + assert(true); + }); }); -}); -assert.strictEqual(counter, 3); + await describe.only(async () => { + await it(async () => { + await Promise.resolve(true); + assert(true); + }); + + await it.only(async () => { + await Promise.resolve(true); + assert(true); + }); + + await it(async () => { + await Promise.resolve(true); + assert(true); + }); + }); +})(); diff --git a/test/__fixtures__/e2e/only/describe.test.ts b/test/__fixtures__/e2e/only/describe.test.ts new file mode 100644 index 00000000..35559dbf --- /dev/null +++ b/test/__fixtures__/e2e/only/describe.test.ts @@ -0,0 +1,6 @@ +import { assert } from '../../../../src/modules/essentials/assert.js'; +import { describe } from '../../../../src/modules/helpers/describe.js'; + +describe.only('1', () => { + assert(true); +}); diff --git a/test/__fixtures__/e2e/only/examples/only-it.test.ts b/test/__fixtures__/e2e/only/hooks.test.ts similarity index 52% rename from test/__fixtures__/e2e/only/examples/only-it.test.ts rename to test/__fixtures__/e2e/only/hooks.test.ts index 05bc6fc8..ae75780e 100644 --- a/test/__fixtures__/e2e/only/examples/only-it.test.ts +++ b/test/__fixtures__/e2e/only/hooks.test.ts @@ -1,24 +1,21 @@ -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'; +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'; + +let counter = 0; +let beforeHookCounter = 0; +let afterHookCounter = 0; beforeEach(() => { - // It will run normally before all `it.only` and `test.only`. + beforeHookCounter++; }); afterEach(() => { - // It will run normally after all `it.only` and `test.only`. + afterHookCounter++; }); -let counter = 0; - -// ⬇️ `describe` scopes ⬇️ - describe('1', () => { counter++; // ✅ `describe` scope will be executed as it's in "native" JavaScript flow @@ -39,9 +36,8 @@ describe('1', () => { }); }); -// ⬇️ Top-level or non-`describe` scopes ⬇️ - counter++; // ✅ Will be executed as it's in "native" JavaScript flow +assert.strictEqual(counter, 4, 'Ensure JavaScript natural flow'); test('6', () => { counter++; // ⏭️ `test` will be skipped @@ -59,8 +55,6 @@ 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); +assert.strictEqual(counter, 6, '1 describe + 2 it + 2 test + 1 "natural"'); +assert.strictEqual(beforeHookCounter, 4, '2 test + 2 it'); +assert.strictEqual(afterHookCounter, 4, '2 test + 2 it'); diff --git a/test/__fixtures__/e2e/only/it.test.ts b/test/__fixtures__/e2e/only/it.test.ts new file mode 100644 index 00000000..86f3138d --- /dev/null +++ b/test/__fixtures__/e2e/only/it.test.ts @@ -0,0 +1,6 @@ +import { assert } from '../../../../src/modules/essentials/assert.js'; +import { it } from '../../../../src/modules/helpers/it/core.js'; + +it.only('1', () => { + assert(true); +}); diff --git a/test/__fixtures__/e2e/skip/skip-helper.test.ts b/test/__fixtures__/e2e/skip/skip-helper.test.ts new file mode 100644 index 00000000..0bdc0c66 --- /dev/null +++ b/test/__fixtures__/e2e/skip/skip-helper.test.ts @@ -0,0 +1,9 @@ +import { exit } from 'node:process'; +import { skip } from '../../../../src/modules/helpers/skip.js'; +import { GLOBAL } from '../../../../src/configs/poku.js'; + +// Mock +GLOBAL.isPoku = false; + +skip('Testing'); +exit(1); diff --git a/test/__fixtures__/e2e/skip/skip-modifier.test.ts b/test/__fixtures__/e2e/skip/skip-modifier.test.ts new file mode 100644 index 00000000..ca98bd1c --- /dev/null +++ b/test/__fixtures__/e2e/skip/skip-modifier.test.ts @@ -0,0 +1,28 @@ +import { describe } from '../../../../src/modules/helpers/describe.js'; +import { test } from '../../../../src/modules/helpers/test.js'; +import { it } from '../../../../src/modules/helpers/it/core.js'; +import { exit } from 'node:process'; + +describe.skip('1', () => { + exit(1); +}); + +describe.skip(() => { + exit(1); +}); + +it.skip('2', () => { + exit(1); +}); + +it.skip(() => { + exit(1); +}); + +test.skip('3', () => { + exit(1); +}); + +test.skip(() => { + exit(1); +}); diff --git a/test/compatibility/by-docker-compose/node-12.test.ts b/test/compatibility/by-docker-compose/node-12.test.ts deleted file mode 100644 index 002dda0f..00000000 --- a/test/compatibility/by-docker-compose/node-12.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { test } from '../../../src/modules/helpers/test.js'; -import { assert } from '../../../src/modules/essentials/assert.js'; -import { docker } from '../../../src/modules/helpers/container.js'; - -const projectName = 'poku'; -const serviceName = 'node-12'; - -test(`Compatibility Tests: ${serviceName}`, async () => { - const dockerfile = docker.dockerfile({ - containerName: serviceName, - tagName: `${projectName}-${serviceName}`, - }); - - await dockerfile.remove(); - - const compose = docker.compose({ - build: true, - cwd: './test/__docker__', - detach: false, - serviceName, - projectName, - // verbose: true, - }); - - const result = await compose.up(); - - if (!result) { - assert.fail(`See the logs by running \`docker logs ${serviceName}\``); - } - - await dockerfile.remove(); -}); diff --git a/test/e2e/only.test.ts b/test/e2e/only.test.ts index 64eb7886..851f81d2 100644 --- a/test/e2e/only.test.ts +++ b/test/e2e/only.test.ts @@ -12,18 +12,9 @@ describe('Only', async () => { cwd: 'test/__fixtures__/e2e/only/--only', }); - if (results.exitCode !== 0) { - console.log(results.stdout); - console.log(results.stderr); - } - - assert.strictEqual(results.exitCode, 0, 'Passed'); - }); - - await it('--only=it', async () => { - const results = await inspectPoku('--only=it --debug', { - cwd: 'test/__fixtures__/e2e/only/--it-only', - }); + const actual = results.stdout.split('\n'); + const offset = + actual.findIndex((line) => line.includes('Running Tests')) + 1; if (results.exitCode !== 0) { console.log(results.stdout); @@ -31,12 +22,50 @@ describe('Only', async () => { } assert.strictEqual(results.exitCode, 0, 'Passed'); + + assert.match(actual[offset + 1], /1/); + assert.match(actual[offset + 2], /3/); + assert.match(actual[offset + 3], /3/); + assert.match(actual[offset + 4], /1/); + + assert.match(actual[offset + 5], /4/); + assert.match(actual[offset + 6], /4/); + assert.match(actual[offset + 7], /5/); + assert.match(actual[offset + 8], /5/); + + assert.match(actual[offset + 9], /8/); + assert.match(actual[offset + 10], /9/); + assert.match(actual[offset + 11], /9/); + assert.match(actual[offset + 12], /10/); + assert.match(actual[offset + 13], /10/); + assert.match(actual[offset + 14], /11/); + assert.match(actual[offset + 15], /11/); + assert.match(actual[offset + 16], /8/); + + assert.match(actual[offset + 17], /15/); + assert.match(actual[offset + 18], /17/); + assert.match(actual[offset + 19], /17/); + assert.match(actual[offset + 20], /15/); + + assert.match(actual[offset + 21], /19/); + assert.match(actual[offset + 22], /20/); + assert.match(actual[offset + 23], /20/); + assert.match(actual[offset + 24], /21/); + assert.match(actual[offset + 25], /21/); + assert.match(actual[offset + 26], /22/); + assert.match(actual[offset + 27], /22/); + assert.match(actual[offset + 28], /19/); + + assert.match(actual[offset + 29], /23/); + assert.match(actual[offset + 30], /25/); + assert.match(actual[offset + 31], /25/); + assert.match(actual[offset + 32], /23/); }); - await it('--only=test', async () => { - const results = await inspectPoku('--only=test --debug', { - cwd: 'test/__fixtures__/e2e/only/--it-only', - }); + await it('No Poku Runner', async () => { + const results = await inspectCLI( + `${cmd} ./test/__fixtures__/e2e/only/--only/basic-logs.test.${ext} --only` + ); if (results.exitCode !== 0) { console.log(results.stdout); @@ -46,9 +75,9 @@ describe('Only', async () => { assert.strictEqual(results.exitCode, 0, 'Passed'); }); - await it('Should fail without `--only` (it)', async () => { + await it('Should fail without `--only`', async () => { const results = await inspectPoku('--debug', { - cwd: 'test/__fixtures__/e2e/only/--it-only', + cwd: 'test/__fixtures__/e2e/only/--only', }); if (results.exitCode !== 1) { @@ -59,23 +88,10 @@ describe('Only', async () => { assert.strictEqual(results.exitCode, 1, 'Failed'); }); - await it('--only=describe', async () => { - const results = await inspectPoku('--only=describe --debug', { - cwd: 'test/__fixtures__/e2e/only/--describe-only', - }); - - if (results.exitCode !== 0) { - console.log(results.stdout); - console.log(results.stderr); - } - - assert.strictEqual(results.exitCode, 0, 'Passed'); - }); - - await it('Should fail without `--only` (describe)', async () => { - const results = await inspectPoku('--debug', { - cwd: 'test/__fixtures__/e2e/only/--describe-only', - }); + await it('No Poku Runner should fail without `--only` (describe)', async () => { + const results = await inspectCLI( + `${cmd} ./test/__fixtures__/e2e/only/describe.test.${ext}` + ); if (results.exitCode !== 1) { console.log(results.stdout); @@ -85,24 +101,11 @@ describe('Only', async () => { assert.strictEqual(results.exitCode, 1, 'Failed'); }); - await it('No Poku Runner', async () => { + await it('No Poku Runner should fail without `--only` (it)', async () => { const results = await inspectCLI( - `${cmd} ./test/__fixtures__/e2e/only/--only/basic-logs.test.${ext} --only` + `${cmd} ./test/__fixtures__/e2e/only/it.test.${ext}` ); - if (results.exitCode !== 0) { - console.log(results.stdout); - console.log(results.stderr); - } - - assert.strictEqual(results.exitCode, 0, 'Passed'); - }); - - await it('Should fail without `--only`', async () => { - const results = await inspectPoku('--debug', { - cwd: 'test/__fixtures__/e2e/only/--only', - }); - if (results.exitCode !== 1) { console.log(results.stdout); console.log(results.stderr); @@ -124,10 +127,10 @@ 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', - }); + await it('Check hooks when using .only modifier', async () => { + const results = await inspectCLI( + `${cmd} ./test/__fixtures__/e2e/only/hooks.test.${ext} --only` + ); if (results.exitCode !== 0) { console.log(results.stdout); diff --git a/test/e2e/skip.test.ts b/test/e2e/skip.test.ts new file mode 100644 index 00000000..1e2c8e89 --- /dev/null +++ b/test/e2e/skip.test.ts @@ -0,0 +1,51 @@ +import { describe } from '../../src/modules/helpers/describe.js'; +import { it } from '../../src/modules/helpers/it/core.js'; +import { assert } from '../../src/modules/essentials/assert.js'; +import { ext, inspectPoku, inspectCLI } from '../__utils__/capture-cli.test.js'; +import { runner } from '../../src/parsers/get-runner.js'; + +describe('Skip', async () => { + const cmd = runner(`_.${ext}`).join(' '); + + await it('Using Poku', async () => { + const results = await inspectPoku('--debug', { + cwd: 'test/__fixtures__/e2e/skip', + }); + + if (results.exitCode !== 0) { + console.log(results.stdout); + console.log(results.stderr); + } + + assert.strictEqual(results.exitCode, 0, 'Passed'); + assert.match(results.stdout, /PASS › 2/, 'Passed 2'); + assert.match(results.stdout, /FAIL › 0/, 'Failed 0'); + assert.match(results.stdout, /SKIP › 7/, 'Skipped 7'); + }); + + await it('No Poku Runner (Modifier)', async () => { + const results = await inspectCLI( + `${cmd} ./test/__fixtures__/e2e/skip/skip-modifier.test.${ext}` + ); + + if (results.exitCode !== 0) { + console.log(results.stdout); + console.log(results.stderr); + } + + assert.strictEqual(results.exitCode, 0, 'Passed'); + }); + + await it('No Poku Runner (Helper)', async () => { + const results = await inspectCLI( + `${cmd} ./test/__fixtures__/e2e/skip/skip-helper.test.${ext}` + ); + + if (results.exitCode !== 0) { + console.log(results.stdout); + console.log(results.stderr); + } + + assert.strictEqual(results.exitCode, 0, 'Passed'); + }); +}); diff --git a/test/unit/parse-callbacks.test.ts b/test/unit/parse-callbacks.test.ts new file mode 100644 index 00000000..25b9bfe4 --- /dev/null +++ b/test/unit/parse-callbacks.test.ts @@ -0,0 +1,293 @@ +import { describe } from '../../src/modules/helpers/describe.js'; +import { test } from '../../src/modules/helpers/test.js'; +import { it } from '../../src/modules/helpers/it/core.js'; +import { assert } from '../../src/modules/essentials/assert.js'; +import { checkOnly, CheckNoOnly } from '../../src/parsers/callback.js'; + +const cbWithOnly = { + function: function cb() { + describe.only(() => {}); + }, + function2: function cb() { + it.only(() => {}); + }, + function3: function cb() { + test.only(() => {}); + }, + anon: function () { + describe.only(() => {}); + }, + anon2: function () { + it.only(() => {}); + }, + anon3: function () { + test.only(() => {}); + }, + arrow: () => { + describe.only(() => {}); + }, + arrow2: () => { + it.only(() => {}); + }, + arrow3: () => { + test.only(() => {}); + }, +}; + +const cbWithoutOnly = { + function: function cb() { + describe(() => {}); + }, + function2: function cb() { + it(() => {}); + }, + function3: function cb() { + test(() => {}); + }, + anon: function () { + describe(() => {}); + }, + anon2: function () { + it(() => {}); + }, + anon3: function () { + test(() => {}); + }, + arrow: () => { + describe(() => {}); + }, + arrow2: () => { + it(() => {}); + }, + arrow3: () => { + test(() => {}); + }, +}; + +describe('Parse Callbacks: checkOnly — true', () => { + assert.strictEqual(checkOnly(undefined), false, 'No function'); + + assert.strictEqual( + checkOnly(cbWithOnly.function), + true, + 'Classic Function: describe.only' + ); + + assert.strictEqual( + checkOnly(cbWithOnly.function2), + true, + 'Classic Function: it.only' + ); + + assert.strictEqual( + checkOnly(cbWithOnly.function3), + true, + 'Classic Function: test.only' + ); + + assert.strictEqual( + checkOnly(cbWithOnly.anon), + true, + 'Anonymous Function: describe.only' + ); + + assert.strictEqual( + checkOnly(cbWithOnly.anon2), + true, + 'Anonymous Function: it.only' + ); + + assert.strictEqual( + checkOnly(cbWithOnly.anon3), + true, + 'Anonymous Function: test.only' + ); + + assert.strictEqual( + checkOnly(cbWithOnly.arrow), + true, + 'Arrow Function: describe.only' + ); + + assert.strictEqual( + checkOnly(cbWithOnly.arrow2), + true, + 'Arrow Function: it.only' + ); + + assert.strictEqual( + checkOnly(cbWithOnly.arrow3), + true, + 'Arrow Function: test.only' + ); +}); + +describe('Parse Callbacks: checkOnly — false', () => { + assert.strictEqual( + checkOnly(cbWithoutOnly.function), + false, + 'Classic Function: describe.only' + ); + + assert.strictEqual( + checkOnly(cbWithoutOnly.function2), + false, + 'Classic Function: it.only' + ); + + assert.strictEqual( + checkOnly(cbWithoutOnly.function3), + false, + 'Classic Function: test.only' + ); + + assert.strictEqual( + checkOnly(cbWithoutOnly.anon), + false, + 'Anonymous Function: describe.only' + ); + + assert.strictEqual( + checkOnly(cbWithoutOnly.anon2), + false, + 'Anonymous Function: it.only' + ); + + assert.strictEqual( + checkOnly(cbWithoutOnly.anon3), + false, + 'Anonymous Function: test.only' + ); + + assert.strictEqual( + checkOnly(cbWithoutOnly.arrow), + false, + 'Arrow Function: describe.only' + ); + + assert.strictEqual( + checkOnly(cbWithoutOnly.arrow2), + false, + 'Arrow Function: it.only' + ); + + assert.strictEqual( + checkOnly(cbWithoutOnly.arrow3), + false, + 'Arrow Function: test.only' + ); +}); + +describe('Parse Callbacks: CheckNoOnly — true', () => { + assert.strictEqual(CheckNoOnly(undefined), false, 'No function'); + + assert.strictEqual( + CheckNoOnly(cbWithoutOnly.function), + true, + 'Classic Function: describe.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithoutOnly.function2), + true, + 'Classic Function: it.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithoutOnly.function3), + true, + 'Classic Function: test.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithoutOnly.anon), + true, + 'Anonymous Function: describe.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithoutOnly.anon2), + true, + 'Anonymous Function: it.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithoutOnly.anon3), + true, + 'Anonymous Function: test.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithoutOnly.arrow), + true, + 'Arrow Function: describe.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithoutOnly.arrow2), + true, + 'Arrow Function: it.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithoutOnly.arrow3), + true, + 'Arrow Function: test.only' + ); +}); + +describe('Parse Callbacks: CheckNoOnly — false', () => { + assert.strictEqual( + CheckNoOnly(cbWithOnly.function), + false, + 'Classic Function: describe.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithOnly.function2), + false, + 'Classic Function: it.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithOnly.function3), + false, + 'Classic Function: test.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithOnly.anon), + false, + 'Anonymous Function: describe.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithOnly.anon2), + false, + 'Anonymous Function: it.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithOnly.anon3), + false, + 'Anonymous Function: test.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithOnly.arrow), + false, + 'Arrow Function: describe.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithOnly.arrow2), + false, + 'Arrow Function: it.only' + ); + + assert.strictEqual( + CheckNoOnly(cbWithOnly.arrow3), + false, + 'Arrow Function: test.only' + ); +}); diff --git a/website/docs/documentation/helpers/only.mdx b/website/docs/documentation/helpers/only.mdx index 6f4f2a00..d3b43776 100644 --- a/website/docs/documentation/helpers/only.mdx +++ b/website/docs/documentation/helpers/only.mdx @@ -8,19 +8,25 @@ import { Stability } from '@site/src/components/Stability'; # 🌌 only -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. +The `.only` modifier 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. + From{' '} + + 3.0.0-rc.1 + {' '} + onwards. + } /> Add only modifier to describe,{' '} @@ -28,257 +34,77 @@ The `.only` helper enables selective execution of tests, allowing you to focus o , ], }, + { + version: '2.7.0', + changes: [ + <> + Add only modifier to describe,{' '} + it and test methods (experimental). + , + ], + }, ]} /> ## Usage -To enable the `.only` helper, you must to pass one of the following flags to enable it selectively: - -### `--only` - -Enables the `.only` helper for `describe`, `it` and `test` methods. +To enable the `.only` modifier, you must to pass the `--only` flag. -
- -- ✅ `describe.only` -- ✅ `it.only` -- ✅ `test.only` -- ⏭️ `describe` _(it will be skipped)_ -- ⏭️ `it` _(it will be skipped)_ -- ⏭️ `test` _(it will be skipped)_ - -
- -```ts -import { describe, it, test } from 'poku'; - -describe.only(() => { - it.only(() => { - // ... - }); - - test.only(() => { - // ... - }); -}); +:::tip -test.only(() => { - // ... -}); -``` +You can pass the `--only` flag either using the test runner (**Poku**) or the runner you prefer, for example: -```bash +```sh npx poku --only +npx tsx test/my-test.test.ts --only +npm test -- --only +node test/my-test.test.js --only ``` -:::note - -- `describe`, `it` and `test` methods without `.only` will be skipped. - ::: -### `--only=describe` - -Enables the `.only` helper for `describe` method. - -
- -- ✅ `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`)_ - -
+### Common Examples ```ts import { describe, it, test } from 'poku'; describe.only(() => { + // ✅ Will be executed + it(() => { - // ... + // ✅ Will be executed }); test(() => { - // ... + // ✅ Will be executed }); }); -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. - -
- -- ✅ `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(() => { - // ... - }); + // ⏭️ Will be skipped - test.only(() => { - // ... + it(() => { + // ⏭️ Will be skipped }); -}); - -test.only(() => { - // ... -}); -``` - -```bash -npx poku --only=it -``` - -:::note - -- `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. -::: - ---- - -## Common issues - -### `.only` vs. scope - -If a `.only` method is inside a skipped method, it won't be executed, for example: - -```ts -import { describe, it, test } from 'poku'; - -describe.only(() => { - it.only(() => { - // ... ✅ + test(() => { + // ⏭️ Will be skipped }); - - // it(() => { - // // ... - // }); - - // test(() => { - // // ... - // }); }); -// 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`: +describe(() => { + // ✅ Will be executed -```ts -import { describe, it } from 'poku'; + it(() => { + // ⏭️ Will be skipped + }); -describe(() => { it.only(() => { - // ... ✅ + // ✅ Will be executed }); - // 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(() => { - // ... ❌ + test(() => { + // ⏭️ Will be skipped }); }); ``` @@ -287,76 +113,15 @@ describe(() => { 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, beforeEach, afterEach } 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`. -}); - -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 -}); +:::tip -// describe.only('10', () => { -// counter++; // ❌ It would force a failure since `describe.only` is not enabled in `--only=it` -// }); +- The `.only` modifier 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's not possible to run `.only` modifiers without `--only` flag. -assert.strictEqual(counter, 6); -``` +::: -```bash -npx poku --only=it -``` +:::note +It's important to recall that **Poku** respects conventional **JavaScript** syntax in tests and doesn't change the order of the executions. +:::