@hyperfrontend/testing/consumerconsumer
A packed package installed outside the workspace and driven through plain Node child processes, so a suite checks what a consumer installs rather than what the workspace resolves.
Usage
usePackedConsumer registers the node:test hooks that install the tarball once for the suite and delete the directory afterwards. The handle it returns always points at the suite's consumer, so capture it once at the top of the spec or in the describe body:
import { describe, expect, it } from '@hyperfrontend/testing'
import { usePackedConsumer } from '@hyperfrontend/testing/consumer'
const consumer = usePackedConsumer({ tarball: process.env.MY_LIB_TARBALL })
describe('my-lib as a consumer sees it', () => {
it('imports every export entry under plain Node', () => {
expect(consumer.entries('import').filter((entry) => !entry.ok)).toEqual([])
})
it('hashes through the node entry', () => {
const hash = consumer.run(async () => {
const { createHash } = await import('my-lib/node')
return createHash('abc')
})
expect(hash).toBe('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad')
})
})
entries reads the installed manifest's exports map and loads each subpath in a child process of its own, with import or require as the map allows, so an entry that resolves only through a bundler, a path alias or a loader hook fails here with the child's error attached.
run serialises the function it is given with toString(), writes it into a script inside the consumer and runs that script with the Node binary running the suite. The function therefore closes over nothing from the spec: import the package inside the body and pass values through args, which travel as JSON, as the result does. Pass kind: 'require' to run the body as CommonJS. A child that exits non-zero makes run throw with everything the child wrote to stderr.
write and node are the pieces underneath, for a script that is more than one function, such as a build driven by the installed package. peers installs the packages a consumer is expected to bring, such as an optional peer dependency. node points the children at another Node binary, so a suite running on a current release can check the package on the oldest one it declares. For a consumer one test owns, createPackedConsumer installs straight away and leaves removal to the caller.
Design
The install uses the npm cache the suite's own project install just filled, so it is fast and works offline; the consumer directory sits under the system temporary directory, outside any workspace, with nothing installed above it to fall back on. The result of run goes to a file rather than stdout so whatever the package prints cannot corrupt it.
API Reference§
ƒ Functions
Nothing of the test process reaches the children: no loader hooks, no path aliases, no hoisted
node_modules. A package that only works through those fails here, the way it would for a consumer. The caller removes the directory; for one a whole suite shares, usePackedConsumer registers the hooks itself.Parameters
| Name | Type | Description |
|---|---|---|
§options | PackedConsumerOptions | The tarball, its peers, the install timeout and the Node binary for the children. |
Returns
PackedConsumerExample
Loading every entry of a packed package
const consumer = createPackedConsumer({ tarball: '/tmp/my-lib-1.0.0.tgz' })
try {
expect(consumer.entries('import').filter((entry) => !entry.ok)).toEqual([])
} finally {
consumer.remove()
}node:test hooks that do it. Call it in a
describe body or at the top of a spec file. The returned handle forwards to the consumer current at the time of each call, so it can be captured once and used from any test or hook. Using it outside a test throws, since nothing is installed then. Functions handed to
run are serialised with toString() and executed in the child, so they can close over nothing from the spec: pass values through args and import the package inside the body.Parameters
| Name | Type | Description |
|---|---|---|
§options | PackedConsumerOptions | The tarball, its peers, the install timeout and the Node binary for the children. |
Returns
PackedConsumerExample
Calling the packed package the way a consumer does
const consumer = usePackedConsumer({ tarball: process.env.MY_LIB_TARBALL })
it('imports every entry under plain Node', () => {
expect(consumer.entries('import').filter((entry) => !entry.ok)).toEqual([])
})
it('hashes through the node entry', () => {
const hash = consumer.run(async () => {
const { createHash } = await import('my-lib/node')
return createHash('abc')
})
expect(hash).toBe('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad')
})◆ Types
type ModuleKind = "import" | "require"