Skip to content
Merged
99 changes: 41 additions & 58 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@
"provenance": true
},
"peerDependencies": {
"poku": ">=4.2.0"
"poku": ">=4.3.0"
},
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
"@types/node": "^25.5.0",
"poku": "file:../poku",
"poku": "^4.3.0",
"prettier": "^3.6.2",
"rimraf": "^6.0.1",
"tsx": "^4.21.0",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SCOPE_HOOKS_KEY } from 'poku/plugins';
const SCOPE_HOOKS_KEY = Symbol.for('@pokujs/poku.test-scope-hooks');

export type ScopeHookHolder = { scope: unknown };

Expand Down
79 changes: 79 additions & 0 deletions tests/__fixtures__/integration/scope-hooks/integration.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { AsyncLocalStorage } from 'node:async_hooks';
import { assert, describe, it } from 'poku';
import { composeScopeHooks } from '../../../../src/index.ts';

const als = new AsyncLocalStorage<number>();
const events: string[] = [];
const seenIds: number[] = [];
let idSeed = 0;

composeScopeHooks({
name: '@pokujs/scope-hooks.integration-fixture',
createHolder: () => ({ scope: undefined }),
runScoped: async (holder, fn) => {
const id = ++idSeed;
holder.scope = { id };
events.push(`before:${id}`);

await als.run(id, async () => {
const result = fn();
if (result instanceof Promise) await result;
});

events.push(`after:${id}`);
},
});

await describe('scope hooks integration fixture', async () => {
const runs = [
it('first test executes inside the composed scope', async () => {
const id = als.getStore();
assert.ok(typeof id === 'number', 'ALS store exists for the first test');
seenIds.push(id as number);

await Promise.resolve();

assert.strictEqual(
als.getStore(),
id,
'ALS store remains stable for the first test'
);

events.push(`test:${id}`);
}),

it('second test executes inside the composed scope', async () => {
const id = als.getStore();
assert.ok(typeof id === 'number', 'ALS store exists for the second test');
seenIds.push(id as number);

await Promise.resolve();

assert.strictEqual(
als.getStore(),
id,
'ALS store remains stable for the second test'
);

events.push(`test:${id}`);
}),
];

await Promise.all(runs);
});

assert.strictEqual(seenIds.length, 2, 'Both poku it callbacks executed');
assert.ok(
seenIds[0] !== seenIds[1],
'Each poku it callback received an isolated scope id'
);

for (const id of seenIds) {
const beforeIndex = events.indexOf(`before:${id}`);
const testIndex = events.indexOf(`test:${id}`);
const afterIndex = events.indexOf(`after:${id}`);

assert.ok(beforeIndex >= 0, `before hook ran for scope ${id}`);
assert.ok(testIndex > beforeIndex, `test body ran after before hook for scope ${id}`);
assert.ok(afterIndex > testIndex, `after hook ran after test body for scope ${id}`);
}
104 changes: 4 additions & 100 deletions tests/poku-integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,110 +1,14 @@
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { join, relative } from 'node:path';
import process from 'node:process';
import { afterEach, assert, test } from 'poku';
import { assert, test } from 'poku';

test('scope hooks integrate with poku it execution', async () => {
const repoDir = process.cwd();
const tempDir = await mkdtemp(join(repoDir, '.poku-scope-hooks-'));
const fixturePath = join(tempDir, 'scope-hooks-it.fixture.ts');
const fixtureRelativePath = relative(repoDir, fixturePath).replaceAll(
'\\',
'/'
);
const scopeHooksModuleUrl = new URL('../src/index.ts', import.meta.url).href;

const fixtureSource = `
import { AsyncLocalStorage } from 'node:async_hooks';
import { assert, describe, it } from 'poku';
import { composeScopeHooks } from '${scopeHooksModuleUrl}';

const als = new AsyncLocalStorage<number>();
const events: string[] = [];
const seenIds: number[] = [];
let idSeed = 0;

composeScopeHooks({
name: '@pokujs/scope-hooks.integration-fixture',
createHolder: () => ({ scope: undefined }),
runScoped: async (holder, fn) => {
const id = ++idSeed;
holder.scope = { id };
events.push(\`before:\${id}\`);

await als.run(id, async () => {
const result = fn();
if (result instanceof Promise) await result;
});

events.push(\`after:\${id}\`);
},
});

await describe('scope hooks integration fixture', async () => {
const runs = [
it('first test executes inside the composed scope', async () => {
const id = als.getStore();
assert.ok(typeof id === 'number', 'ALS store exists for the first test');
seenIds.push(id as number);

await Promise.resolve();

assert.strictEqual(
als.getStore(),
id,
'ALS store remains stable for the first test'
);

events.push(\`test:\${id}\`);
}),

it('second test executes inside the composed scope', async () => {
const id = als.getStore();
assert.ok(typeof id === 'number', 'ALS store exists for the second test');
seenIds.push(id as number);

await Promise.resolve();

assert.strictEqual(
als.getStore(),
id,
'ALS store remains stable for the second test'
);

events.push(\`test:\${id}\`);
}),
];

await Promise.all(runs);
});

assert.strictEqual(seenIds.length, 2, 'Both poku it callbacks executed');
assert.ok(
seenIds[0] !== seenIds[1],
'Each poku it callback received an isolated scope id'
);

for (const id of seenIds) {
const beforeIndex = events.indexOf(\`before:\${id}\`);
const testIndex = events.indexOf(\`test:\${id}\`);
const afterIndex = events.indexOf(\`after:\${id}\`);

assert.ok(beforeIndex >= 0, \`before hook ran for scope \${id}\`);
assert.ok(testIndex > beforeIndex, \`test body ran after before hook for scope \${id}\`);
assert.ok(afterIndex > testIndex, \`after hook ran after test body for scope \${id}\`);
}
`;

afterEach(async () => {
await rm(tempDir, { recursive: true, force: true });
});

await writeFile(fixturePath, fixtureSource, 'utf8');
const fixturePath = 'tests/__fixtures__/integration/scope-hooks/integration.fixture.ts';

const { inspectPoku } = await import('poku/plugins');

const result = await inspectPoku({
command: `./${fixtureRelativePath} --showLogs`,
command: `${fixturePath} --showLogs`,
spawnOptions: { cwd: repoDir },
});

Expand All @@ -125,4 +29,4 @@ for (const id of seenIds) {
result.stdout.includes('second test executes inside the composed scope'),
'Second test executed through poku'
);
});
});
Loading
Loading