From 8d5a891e3f687b93d0c35a4fc0ae335e222c4a80 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sun, 5 Jan 2025 16:27:29 +0100 Subject: [PATCH 1/4] feat(learn): add section for dynamically generating test cases --- .../en/learn/test-runner/using-test-runner.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/apps/site/pages/en/learn/test-runner/using-test-runner.md b/apps/site/pages/en/learn/test-runner/using-test-runner.md index 84e2657d7b953..c7df13dcbad6b 100644 --- a/apps/site/pages/en/learn/test-runner/using-test-runner.md +++ b/apps/site/pages/en/learn/test-runner/using-test-runner.md @@ -64,6 +64,40 @@ Then for each setup, create a dedicated `setup` file (ensuring the base `setup.m Each example below was taken from real-world projects; they may not be appropriate/applicable to yours, but each demonstrate general concepts that are broadly applicable. +## Dynamically generating test cases + +Some times, you may want to dynamically generate test-cases. For instance, you want to test the same thing across a bunch of files. This is possible, albeit slightly arcane. You must use `test` (you cannot use `describe`) + `await testContext.test`: + +```js +import assert from 'node:assert/strict'; +import { globSync } from 'node:fs'; +import { test } from 'node:test'; +import { fileURLToPath } from 'node:url'; + +test('Check package.jsons', { concurrency: true }, async t => { + const pjsons = await Promise.all( + globSync( + // Get all the package.json files 1-level deep within ./workspaces + // ⚠️ Passing a file URL string, like from import.meta.resolve, causes glob* to fail silently + fileURLToPath(import.meta.resolve('./workspaces/*/package.json')) + ).map(path => import(path, { with: { type: 'json' } })) + ); + + // ⚠️ `t.test`, NOT `test` + const cases = pjsons.map(pjson => + t.test(`Ensure fields are properly set: ${pjson.name}`, () => { + assert.partialDeepStrictEqual(pjson.keywords, [ + 'node.js', + 'sliced bread', + ]); + }) + ); + + // Allow the cases to run concurrently. + await Promise.allSettled(cases); +}); +``` + ## ServiceWorker tests [`ServiceWorkerGlobalScope`](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope) contains very specific APIs that don't exist in other environments, and some of its APIs are seemingly similar to others (ex `fetch`) but have augmented behaviour. You do not want these to spill into unrelated tests. From 7c0f5dec6de5d38ff4723aea4716bd9638e56209 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sun, 5 Jan 2025 16:31:35 +0100 Subject: [PATCH 2/4] fixup!: adjust for prettier mangling --- apps/site/pages/en/learn/test-runner/using-test-runner.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/site/pages/en/learn/test-runner/using-test-runner.md b/apps/site/pages/en/learn/test-runner/using-test-runner.md index c7df13dcbad6b..17598c1fec588 100644 --- a/apps/site/pages/en/learn/test-runner/using-test-runner.md +++ b/apps/site/pages/en/learn/test-runner/using-test-runner.md @@ -83,8 +83,8 @@ test('Check package.jsons', { concurrency: true }, async t => { ).map(path => import(path, { with: { type: 'json' } })) ); - // ⚠️ `t.test`, NOT `test` const cases = pjsons.map(pjson => + // ⚠️ `t.test`, NOT `test` t.test(`Ensure fields are properly set: ${pjson.name}`, () => { assert.partialDeepStrictEqual(pjson.keywords, [ 'node.js', From 0844518ea536d6946d42658067f1d7cb98021c06 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sun, 2 Feb 2025 14:44:29 +0100 Subject: [PATCH 3/4] fixup!: simple & advanced examples --- .../en/learn/test-runner/using-test-runner.md | 106 +++++++++++++++--- 1 file changed, 91 insertions(+), 15 deletions(-) diff --git a/apps/site/pages/en/learn/test-runner/using-test-runner.md b/apps/site/pages/en/learn/test-runner/using-test-runner.md index 17598c1fec588..95fe7db4e24a8 100644 --- a/apps/site/pages/en/learn/test-runner/using-test-runner.md +++ b/apps/site/pages/en/learn/test-runner/using-test-runner.md @@ -66,30 +66,63 @@ Each example below was taken from real-world projects; they may not be appropria ## Dynamically generating test cases -Some times, you may want to dynamically generate test-cases. For instance, you want to test the same thing across a bunch of files. This is possible, albeit slightly arcane. You must use `test` (you cannot use `describe`) + `await testContext.test`: +Some times, you may want to dynamically generate test-cases. For instance, you want to test the same thing across a bunch of files. This is possible, albeit slightly arcane. You must use `test` (you cannot use `describe`) + `testContext.test`: -```js +```js displayName="simple example (prior to 23.8.0)" import assert from 'node:assert/strict'; -import { globSync } from 'node:fs'; import { test } from 'node:test'; -import { fileURLToPath } from 'node:url'; + +import { detectOsInUserAgent } from '…'; + +const userAgents = [ + { ua: '…', os: 'WIN' }, + // … +]; + +test('Detect OS via user-agent', { concurrency: true }, async t => { + const cases = userAgents.map(({ os, ua }) => { + t.test(ua, () => assert.equal(detectOsInUserAgent(ua), os)); + }); + + await Promise.allSettled(cases); +}); +``` + +```js displayName="simple example (23.8.0 and later)" +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import { detectOsInUserAgent } from '…'; + +const userAgents = [ + { ua: /* … */, os: 'WIN' }, + // … +]; + +test('Detect OS via user-agent', { concurrency: true }, t => { + for (const { os, ua } from userAgents) { + t.test(ua, () => assert.equal(detectOsInUserAgent(ua), os)); + } +}); +``` + + + +```js displayName="Advanced example (prior to 23.8.0)" +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import { getWorkspacePJSONs } from './getWorkspacePJSONs.mjs'; + +const requiredKeywords = ['node.js', 'sliced bread']; test('Check package.jsons', { concurrency: true }, async t => { - const pjsons = await Promise.all( - globSync( - // Get all the package.json files 1-level deep within ./workspaces - // ⚠️ Passing a file URL string, like from import.meta.resolve, causes glob* to fail silently - fileURLToPath(import.meta.resolve('./workspaces/*/package.json')) - ).map(path => import(path, { with: { type: 'json' } })) - ); + const pjsons = await getWorkspacePJSONs(); const cases = pjsons.map(pjson => // ⚠️ `t.test`, NOT `test` t.test(`Ensure fields are properly set: ${pjson.name}`, () => { - assert.partialDeepStrictEqual(pjson.keywords, [ - 'node.js', - 'sliced bread', - ]); + assert.partialDeepStrictEqual(pjson.keywords, requiredKeywords); }) ); @@ -98,6 +131,49 @@ test('Check package.jsons', { concurrency: true }, async t => { }); ``` +```js displayName="Advanced example (23.8.0 and later)" +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import { getWorkspacePJSONs } from './getWorkspacePJSONs.mjs'; + +const requiredKeywords = ['node.js', 'sliced bread']; + +test('Check package.jsons', { concurrency: true }, async t => { + const pjsons = await getWorkspacePJSONs(); + + for (const pjson of pjsons) { + // ⚠️ `t.test`, NOT `test` + t.test(`Ensure fields are properly set: ${pjson.name}`, () => { + assert.partialDeepStrictEqual(pjson.keywords, requiredKeywords); + }); + } +}); +``` + +```js displayName="./getWorkspacePJSONs.mjs" +import { globSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; + +// Note: This would be better implemented as an async generator, leveraging fs.glob (instead of fs.globSync); +// however, generators and especially async generators are much less understood, +// so this simplified example is provided for easier understanding. + +/** + * Get all the package.json files, by default 1-level deep within ./workspaces/ + */ +export function getWorkspacePJSONs(path = './workspaces/*/package.json') { + return Promise.all( + globSync( + // ⚠️ Passing a file URL string, like from import.meta.resolve, causes glob* to fail silently + fileURLToPath(import.meta.resolve(path)) + ).map(path => import(path, { with: { type: 'json' } })) + ); +} +``` + +> **Note**: Prior to version 23.8.0, the setup is quite different because `testContext.test` was not automatically awaited. + ## ServiceWorker tests [`ServiceWorkerGlobalScope`](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope) contains very specific APIs that don't exist in other environments, and some of its APIs are seemingly similar to others (ex `fetch`) but have augmented behaviour. You do not want these to spill into unrelated tests. From 9740c656558c9d83e9c745f869dd298ba294813a Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sun, 2 Feb 2025 15:03:39 +0100 Subject: [PATCH 4/4] fixup!: re-organise examples --- .../en/learn/test-runner/using-test-runner.md | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/apps/site/pages/en/learn/test-runner/using-test-runner.md b/apps/site/pages/en/learn/test-runner/using-test-runner.md index 95fe7db4e24a8..155e710b8eba0 100644 --- a/apps/site/pages/en/learn/test-runner/using-test-runner.md +++ b/apps/site/pages/en/learn/test-runner/using-test-runner.md @@ -68,47 +68,49 @@ Each example below was taken from real-world projects; they may not be appropria Some times, you may want to dynamically generate test-cases. For instance, you want to test the same thing across a bunch of files. This is possible, albeit slightly arcane. You must use `test` (you cannot use `describe`) + `testContext.test`: -```js displayName="simple example (prior to 23.8.0)" +### Simple example + +```js displayName="23.8.0 and later" import assert from 'node:assert/strict'; import { test } from 'node:test'; import { detectOsInUserAgent } from '…'; const userAgents = [ - { ua: '…', os: 'WIN' }, + { ua: /* … */, os: 'WIN' }, // … ]; -test('Detect OS via user-agent', { concurrency: true }, async t => { - const cases = userAgents.map(({ os, ua }) => { +test('Detect OS via user-agent', { concurrency: true }, t => { + for (const { os, ua } from userAgents) { t.test(ua, () => assert.equal(detectOsInUserAgent(ua), os)); - }); - - await Promise.allSettled(cases); + } }); ``` -```js displayName="simple example (23.8.0 and later)" +```js displayName="prior to 23.8.0" import assert from 'node:assert/strict'; import { test } from 'node:test'; import { detectOsInUserAgent } from '…'; const userAgents = [ - { ua: /* … */, os: 'WIN' }, + { ua: '…', os: 'WIN' }, // … ]; -test('Detect OS via user-agent', { concurrency: true }, t => { - for (const { os, ua } from userAgents) { +test('Detect OS via user-agent', { concurrency: true }, async t => { + const cases = userAgents.map(({ os, ua }) => { t.test(ua, () => assert.equal(detectOsInUserAgent(ua), os)); - } + }); + + await Promise.allSettled(cases); }); ``` - +### Advanced example -```js displayName="Advanced example (prior to 23.8.0)" +```js displayName="23.8.0 and later" import assert from 'node:assert/strict'; import { test } from 'node:test'; @@ -119,19 +121,16 @@ const requiredKeywords = ['node.js', 'sliced bread']; test('Check package.jsons', { concurrency: true }, async t => { const pjsons = await getWorkspacePJSONs(); - const cases = pjsons.map(pjson => + for (const pjson of pjsons) { // ⚠️ `t.test`, NOT `test` t.test(`Ensure fields are properly set: ${pjson.name}`, () => { assert.partialDeepStrictEqual(pjson.keywords, requiredKeywords); - }) - ); - - // Allow the cases to run concurrently. - await Promise.allSettled(cases); + }); + } }); ``` -```js displayName="Advanced example (23.8.0 and later)" +```js displayName="prior to 23.8.0" import assert from 'node:assert/strict'; import { test } from 'node:test'; @@ -142,12 +141,15 @@ const requiredKeywords = ['node.js', 'sliced bread']; test('Check package.jsons', { concurrency: true }, async t => { const pjsons = await getWorkspacePJSONs(); - for (const pjson of pjsons) { + const cases = pjsons.map(pjson => // ⚠️ `t.test`, NOT `test` t.test(`Ensure fields are properly set: ${pjson.name}`, () => { assert.partialDeepStrictEqual(pjson.keywords, requiredKeywords); - }); - } + }) + ); + + // Allow the cases to run concurrently. + await Promise.allSettled(cases); }); ```