@inquirer/testing 3.0.4 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -13
- package/dist/buffered-stream.d.ts +12 -0
- package/dist/buffered-stream.js +43 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +15 -33
- package/dist/jest.d.ts +19 -0
- package/dist/jest.js +119 -0
- package/dist/screen.d.ts +35 -0
- package/dist/screen.js +150 -0
- package/dist/terminal.d.ts +1 -0
- package/dist/terminal.js +15 -0
- package/dist/vitest.d.ts +20 -0
- package/dist/vitest.js +118 -0
- package/package.json +33 -5
package/README.md
CHANGED
|
@@ -27,13 +27,20 @@ yarn add @inquirer/testing --dev
|
|
|
27
27
|
</tr>
|
|
28
28
|
</table>
|
|
29
29
|
|
|
30
|
-
#
|
|
30
|
+
# Usage
|
|
31
|
+
|
|
32
|
+
This package provides two ways to test Inquirer prompts:
|
|
33
|
+
|
|
34
|
+
1. **Unit testing** with `render()` - Test individual prompts in isolation
|
|
35
|
+
2. **E2E testing** with `screen` - Test full CLI applications that use Inquirer
|
|
36
|
+
|
|
37
|
+
## Unit Testing with `render()`
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
The `render()` function creates and instruments a command line interface for testing a single prompt.
|
|
33
40
|
|
|
34
41
|
```ts
|
|
35
42
|
import { render } from '@inquirer/testing';
|
|
36
|
-
import input from '
|
|
43
|
+
import input from '@inquirer/input';
|
|
37
44
|
|
|
38
45
|
describe('input prompt', () => {
|
|
39
46
|
it('handle simple use case', async () => {
|
|
@@ -48,7 +55,6 @@ describe('input prompt', () => {
|
|
|
48
55
|
|
|
49
56
|
events.type('ohn');
|
|
50
57
|
events.keypress('enter');
|
|
51
|
-
// or events.keypress({ name: 'enter' })
|
|
52
58
|
|
|
53
59
|
await expect(answer).resolves.toEqual('John');
|
|
54
60
|
expect(getScreen()).toMatchInlineSnapshot(`"? What is your name John"`);
|
|
@@ -56,23 +62,132 @@ describe('input prompt', () => {
|
|
|
56
62
|
});
|
|
57
63
|
```
|
|
58
64
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
The core utility of `@inquirer/testing` is the `render()` function. This `render` function will create and instrument a command line like interface.
|
|
65
|
+
### `render()` API
|
|
62
66
|
|
|
63
67
|
`render` takes 2 arguments:
|
|
64
68
|
|
|
65
69
|
1. The Inquirer prompt to test (the return value of `createPrompt()`)
|
|
66
70
|
2. The prompt configuration (the first prompt argument)
|
|
67
71
|
|
|
68
|
-
`render`
|
|
72
|
+
`render` returns a promise that resolves once the prompt is rendered. This promise returns:
|
|
73
|
+
|
|
74
|
+
- `answer` (`Promise`) - Resolves when an answer is provided and valid
|
|
75
|
+
- `getScreen` (`({ raw?: boolean }) => string`) - Returns the current screen content. By default strips ANSI codes
|
|
76
|
+
- `events` - Utilities to interact with the prompt:
|
|
77
|
+
- `keypress(key: string | KeyObject)` - Trigger a keypress event
|
|
78
|
+
- `type(text: string)` - Type text into the prompt
|
|
79
|
+
- `getFullOutput` (`() => Promise<string>`) - Returns the full output interpreted through a virtual terminal, resolving ANSI escape sequences into the actual screen state
|
|
80
|
+
|
|
81
|
+
### Unit Testing Example
|
|
82
|
+
|
|
83
|
+
You can refer to the [`@inquirer/input` test suite](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/input/input.test.ts) for a comprehensive unit testing example using `render()`.
|
|
84
|
+
|
|
85
|
+
## E2E Testing with `screen`
|
|
86
|
+
|
|
87
|
+
For testing full CLI applications that use Inquirer prompts internally, use the framework-specific entry points:
|
|
88
|
+
|
|
89
|
+
### Vitest
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { describe, it, expect } from 'vitest';
|
|
93
|
+
import { screen } from '@inquirer/testing/vitest';
|
|
94
|
+
|
|
95
|
+
// Import your CLI AFTER @inquirer/testing/vitest
|
|
96
|
+
import { runMyCli } from './my-cli.js';
|
|
97
|
+
|
|
98
|
+
describe('my CLI', () => {
|
|
99
|
+
it('asks for name and confirms', async () => {
|
|
100
|
+
const result = runMyCli();
|
|
101
|
+
|
|
102
|
+
// First prompt is immediately available
|
|
103
|
+
expect(screen.getScreen()).toContain('What is your name?');
|
|
104
|
+
screen.type('John');
|
|
105
|
+
screen.keypress('enter');
|
|
106
|
+
|
|
107
|
+
// Wait for next prompt
|
|
108
|
+
await screen.next();
|
|
109
|
+
expect(screen.getScreen()).toContain('Confirm?');
|
|
110
|
+
screen.keypress('enter');
|
|
111
|
+
|
|
112
|
+
await result;
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Jest
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { screen } from '@inquirer/testing/jest';
|
|
121
|
+
import { runMyCli } from './my-cli.js';
|
|
122
|
+
|
|
123
|
+
describe('my CLI', () => {
|
|
124
|
+
it('asks for name and confirms', async () => {
|
|
125
|
+
const result = runMyCli();
|
|
126
|
+
|
|
127
|
+
// First prompt is immediately available
|
|
128
|
+
expect(screen.getScreen()).toContain('What is your name?');
|
|
129
|
+
screen.type('John');
|
|
130
|
+
screen.keypress('enter');
|
|
131
|
+
|
|
132
|
+
// Wait for next prompt
|
|
133
|
+
await screen.next();
|
|
134
|
+
expect(screen.getScreen()).toContain('Confirm?');
|
|
135
|
+
screen.keypress('enter');
|
|
136
|
+
|
|
137
|
+
await result;
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `screen` API
|
|
143
|
+
|
|
144
|
+
The `screen` object provides:
|
|
145
|
+
|
|
146
|
+
- `next()` - Wait for the next screen update (prompt transitions, validation errors, async updates). The initial prompt render is available immediately via `getScreen()` — no `next()` needed
|
|
147
|
+
- `getScreen({ raw?: boolean })` - Get the current prompt screen content. By default strips ANSI codes
|
|
148
|
+
- `getFullOutput({ raw?: boolean })` - Get all accumulated output interpreted through a virtual terminal (returns a `Promise`). By default resolves ANSI escape sequences into actual screen state
|
|
149
|
+
- `type(text)` - Type text (writes to stream AND emits keypresses)
|
|
150
|
+
- `keypress(key)` - Send a keypress event
|
|
151
|
+
- `clear()` - Reset screen state (called automatically before each test)
|
|
152
|
+
|
|
153
|
+
### Mocking Third-Party Prompts
|
|
154
|
+
|
|
155
|
+
All `@inquirer/*` prompts are mocked automatically. To mock a third-party or custom prompt package, use `wrapPrompt` in your own mock call:
|
|
156
|
+
|
|
157
|
+
#### Vitest
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { screen, wrapPrompt } from '@inquirer/testing/vitest';
|
|
161
|
+
|
|
162
|
+
vi.mock('@my-company/custom-prompt', async (importOriginal) => {
|
|
163
|
+
const actual = await importOriginal<typeof import('@my-company/custom-prompt')>();
|
|
164
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Jest
|
|
169
|
+
|
|
170
|
+
In Jest, `jest.mock()` factories are hoisted before imports, so `wrapPrompt` must be accessed via `jest.requireActual()` inside the factory:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { screen } from '@inquirer/testing/jest';
|
|
174
|
+
|
|
175
|
+
jest.mock('@my-company/custom-prompt', () => {
|
|
176
|
+
const { wrapPrompt } = jest.requireActual('@inquirer/testing/jest');
|
|
177
|
+
const actual = jest.requireActual('@my-company/custom-prompt');
|
|
178
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Important Notes
|
|
183
|
+
|
|
184
|
+
1. **Import order matters**: Import `@inquirer/testing/vitest` or `@inquirer/testing/jest` BEFORE importing modules that use Inquirer prompts
|
|
185
|
+
2. **Editor prompt**: The external editor is mocked — `screen.type()` buffers text, and `screen.keypress('enter')` submits it (same pattern as other prompts). Works with both `waitForUserInput: true` and `false`
|
|
186
|
+
3. **Sequential prompts**: Multiple prompts are supported, but they must run sequentially (not concurrently)
|
|
69
187
|
|
|
70
|
-
|
|
71
|
-
2. `getScreen` (`({ raw: boolean }) => string`) This function returns the state of what is printed on the command line screen at any given time. You can use its return value to validate your prompt is properly rendered. By default this function will strip the ANSI codes (used for colors.)
|
|
72
|
-
3. `events` (`{ keypress: (name | Key) => void, type: (string) => void }`) Is the utilities allowing you to interact with the prompt. Use it to trigger keypress events, or typing any input.
|
|
73
|
-
4. `getFullOutput` (`() => string`) Return a raw dump of everything that got sent on the output stream.
|
|
188
|
+
### E2E Testing Example
|
|
74
189
|
|
|
75
|
-
You can refer to
|
|
190
|
+
You can refer to the [`@inquirer/demo` test suite](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/demo/demo.test.ts) for a comprehensive E2E testing example using `screen`.
|
|
76
191
|
|
|
77
192
|
# License
|
|
78
193
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Stream } from 'node:stream';
|
|
2
|
+
export declare class BufferedStream extends Stream.Writable {
|
|
3
|
+
#private;
|
|
4
|
+
columns: number;
|
|
5
|
+
get writeCount(): number;
|
|
6
|
+
_write(chunk: Buffer, _encoding: string, callback: () => void): void;
|
|
7
|
+
getLastChunk({ raw }?: {
|
|
8
|
+
raw?: boolean;
|
|
9
|
+
}): string;
|
|
10
|
+
getFullOutput(): string;
|
|
11
|
+
clear(): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Stream } from 'node:stream';
|
|
2
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
3
|
+
export class BufferedStream extends Stream.Writable {
|
|
4
|
+
// Expose a large column width so cli-width (used by @inquirer/core's breakLines)
|
|
5
|
+
// doesn't hard-wrap output at 80 columns. This prevents artificial line breaks
|
|
6
|
+
// that would break assertions like toContain() in tests.
|
|
7
|
+
columns = 10_000;
|
|
8
|
+
#fullOutput = '';
|
|
9
|
+
#chunks = [];
|
|
10
|
+
#rawChunks = [];
|
|
11
|
+
#writeCount = 0;
|
|
12
|
+
get writeCount() {
|
|
13
|
+
return this.#writeCount;
|
|
14
|
+
}
|
|
15
|
+
_write(chunk, _encoding, callback) {
|
|
16
|
+
const str = chunk.toString();
|
|
17
|
+
this.#fullOutput += str;
|
|
18
|
+
// Keep track of every chunk sent through.
|
|
19
|
+
this.#rawChunks.push(str);
|
|
20
|
+
// Stripping the ANSI codes here because Inquirer will push commands ANSI (like cursor move.)
|
|
21
|
+
// This is probably fine since we don't care about those for testing; but this could become
|
|
22
|
+
// an issue if we ever want to test for those.
|
|
23
|
+
if (stripVTControlCharacters(str).trim().length > 0) {
|
|
24
|
+
this.#chunks.push(str);
|
|
25
|
+
this.#writeCount++;
|
|
26
|
+
this.emit('render');
|
|
27
|
+
}
|
|
28
|
+
callback();
|
|
29
|
+
}
|
|
30
|
+
getLastChunk({ raw } = {}) {
|
|
31
|
+
const chunks = raw ? this.#rawChunks : this.#chunks;
|
|
32
|
+
const lastChunk = chunks.at(-1);
|
|
33
|
+
return lastChunk ?? '';
|
|
34
|
+
}
|
|
35
|
+
getFullOutput() {
|
|
36
|
+
return this.#fullOutput;
|
|
37
|
+
}
|
|
38
|
+
clear() {
|
|
39
|
+
this.#fullOutput = '';
|
|
40
|
+
this.#chunks = [];
|
|
41
|
+
this.#rawChunks = [];
|
|
42
|
+
}
|
|
43
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import MuteStream from 'mute-stream';
|
|
2
2
|
import type { Prompt, Context } from '@inquirer/type';
|
|
3
|
-
|
|
3
|
+
type RenderOptions = Omit<Context, 'input' | 'output'>;
|
|
4
|
+
export declare function render<Value, const Config>(prompt: Prompt<Value, Config>, config: Config, options?: RenderOptions): Promise<{
|
|
4
5
|
answer: Promise<Value>;
|
|
5
6
|
input: MuteStream;
|
|
6
7
|
events: {
|
|
@@ -15,5 +16,8 @@ export declare function render<Value, const Config>(prompt: Prompt<Value, Config
|
|
|
15
16
|
getScreen: ({ raw }?: {
|
|
16
17
|
raw?: boolean;
|
|
17
18
|
}) => string;
|
|
18
|
-
getFullOutput: (
|
|
19
|
+
getFullOutput: ({ raw }?: {
|
|
20
|
+
raw?: boolean;
|
|
21
|
+
}) => Promise<string>;
|
|
19
22
|
}>;
|
|
23
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,40 +1,19 @@
|
|
|
1
|
-
import { Stream } from 'node:stream';
|
|
2
1
|
import { stripVTControlCharacters } from 'node:util';
|
|
3
2
|
import MuteStream from 'mute-stream';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
#_chunks = [];
|
|
7
|
-
#_rawChunks = [];
|
|
8
|
-
_write(chunk, _encoding, callback) {
|
|
9
|
-
const str = chunk.toString();
|
|
10
|
-
this.#_fullOutput += str;
|
|
11
|
-
// Keep track of every chunk send through.
|
|
12
|
-
this.#_rawChunks.push(str);
|
|
13
|
-
// Stripping the ANSI codes here because Inquirer will push commands ANSI (like cursor move.)
|
|
14
|
-
// This is probably fine since we don't care about those for testing; but this could become
|
|
15
|
-
// an issue if we ever want to test for those.
|
|
16
|
-
if (stripVTControlCharacters(str).trim().length > 0) {
|
|
17
|
-
this.#_chunks.push(str);
|
|
18
|
-
}
|
|
19
|
-
callback();
|
|
20
|
-
}
|
|
21
|
-
getLastChunk({ raw }) {
|
|
22
|
-
const chunks = raw ? this.#_rawChunks : this.#_chunks;
|
|
23
|
-
const lastChunk = chunks.at(-1);
|
|
24
|
-
return lastChunk ?? '';
|
|
25
|
-
}
|
|
26
|
-
getFullOutput() {
|
|
27
|
-
return this.#_fullOutput;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
3
|
+
import { BufferedStream } from './buffered-stream.js';
|
|
4
|
+
import { interpretTerminalOutput } from './terminal.js';
|
|
30
5
|
export async function render(prompt, config, options) {
|
|
31
6
|
const input = new MuteStream();
|
|
32
7
|
input.unmute();
|
|
33
8
|
const output = new BufferedStream();
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
9
|
+
const firstRender = new Promise((resolve) => output.once('render', resolve));
|
|
10
|
+
const answer = prompt(config, { ...options, input, output });
|
|
11
|
+
// The first render is synchronous. If our BufferedStream received a write, we're ready.
|
|
12
|
+
if (output.writeCount === 0) {
|
|
13
|
+
// Our BufferedStream didn't receive a write yet. This happens when the prompt
|
|
14
|
+
// errored before rendering. Race against the answer promise to handle that case.
|
|
15
|
+
await Promise.race([firstRender, answer.catch(() => { })]);
|
|
16
|
+
}
|
|
38
17
|
const events = {
|
|
39
18
|
keypress(key) {
|
|
40
19
|
if (typeof key === 'string') {
|
|
@@ -59,8 +38,11 @@ export async function render(prompt, config, options) {
|
|
|
59
38
|
const lastScreen = output.getLastChunk({ raw: Boolean(raw) });
|
|
60
39
|
return raw ? lastScreen : stripVTControlCharacters(lastScreen).trim();
|
|
61
40
|
},
|
|
62
|
-
getFullOutput: () => {
|
|
63
|
-
|
|
41
|
+
getFullOutput: async ({ raw } = {}) => {
|
|
42
|
+
const fullOutput = output.getFullOutput();
|
|
43
|
+
if (raw)
|
|
44
|
+
return fullOutput;
|
|
45
|
+
return interpretTerminalOutput(fullOutput);
|
|
64
46
|
},
|
|
65
47
|
};
|
|
66
48
|
}
|
package/dist/jest.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Prompt } from '@inquirer/type';
|
|
2
|
+
import { Screen } from './screen.js';
|
|
3
|
+
declare const screenInstance: Screen;
|
|
4
|
+
export { screenInstance as screen };
|
|
5
|
+
/**
|
|
6
|
+
* Wrap a prompt function to use the shared screen I/O.
|
|
7
|
+
* Use this in your own `jest.mock()` calls to mock third-party prompts.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* jest.mock('@my-company/custom-prompt', () => {
|
|
12
|
+
* const { wrapPrompt } = jest.requireActual('@inquirer/testing/jest');
|
|
13
|
+
* const actual = jest.requireActual('@my-company/custom-prompt');
|
|
14
|
+
* return { ...actual, default: wrapPrompt(actual.default) };
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function wrapPrompt<Value, Config>(prompt: Prompt<Value, Config>): Prompt<Value, Config>;
|
|
19
|
+
export { Screen, type KeypressEvent } from './screen.js';
|
package/dist/jest.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access */
|
|
2
|
+
// Note: Jest's requireActual returns `any` by design, unlike Vitest's typed importOriginal
|
|
3
|
+
import { Screen } from './screen.js';
|
|
4
|
+
// Global screen instance - exported for tests
|
|
5
|
+
const screenInstance = new Screen();
|
|
6
|
+
export { screenInstance as screen };
|
|
7
|
+
// Reset before each test (Jest's beforeEach)
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
screenInstance.clear();
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Wrap a prompt function to use the shared screen I/O.
|
|
13
|
+
* Use this in your own `jest.mock()` calls to mock third-party prompts.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* jest.mock('@my-company/custom-prompt', () => {
|
|
18
|
+
* const { wrapPrompt } = jest.requireActual('@inquirer/testing/jest');
|
|
19
|
+
* const actual = jest.requireActual('@my-company/custom-prompt');
|
|
20
|
+
* return { ...actual, default: wrapPrompt(actual.default) };
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function wrapPrompt(prompt) {
|
|
25
|
+
return (config, context) => {
|
|
26
|
+
const output = screenInstance.createOutput();
|
|
27
|
+
const promise = prompt(config, {
|
|
28
|
+
...context,
|
|
29
|
+
input: screenInstance.input,
|
|
30
|
+
output,
|
|
31
|
+
});
|
|
32
|
+
screenInstance.setActivePromise(promise);
|
|
33
|
+
return promise;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Mock individual prompt packages (covers `import input from '@inquirer/input'` style)
|
|
37
|
+
jest.mock('@inquirer/input', () => {
|
|
38
|
+
const actual = jest.requireActual('@inquirer/input');
|
|
39
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
40
|
+
});
|
|
41
|
+
jest.mock('@inquirer/select', () => {
|
|
42
|
+
const actual = jest.requireActual('@inquirer/select');
|
|
43
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
44
|
+
});
|
|
45
|
+
jest.mock('@inquirer/confirm', () => {
|
|
46
|
+
const actual = jest.requireActual('@inquirer/confirm');
|
|
47
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
48
|
+
});
|
|
49
|
+
jest.mock('@inquirer/checkbox', () => {
|
|
50
|
+
const actual = jest.requireActual('@inquirer/checkbox');
|
|
51
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
52
|
+
});
|
|
53
|
+
jest.mock('@inquirer/password', () => {
|
|
54
|
+
const actual = jest.requireActual('@inquirer/password');
|
|
55
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
56
|
+
});
|
|
57
|
+
jest.mock('@inquirer/expand', () => {
|
|
58
|
+
const actual = jest.requireActual('@inquirer/expand');
|
|
59
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
60
|
+
});
|
|
61
|
+
jest.mock('@inquirer/rawlist', () => {
|
|
62
|
+
const actual = jest.requireActual('@inquirer/rawlist');
|
|
63
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
64
|
+
});
|
|
65
|
+
jest.mock('@inquirer/number', () => {
|
|
66
|
+
const actual = jest.requireActual('@inquirer/number');
|
|
67
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
68
|
+
});
|
|
69
|
+
jest.mock('@inquirer/search', () => {
|
|
70
|
+
const actual = jest.requireActual('@inquirer/search');
|
|
71
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
72
|
+
});
|
|
73
|
+
jest.mock('@inquirer/editor', () => {
|
|
74
|
+
const actual = jest.requireActual('@inquirer/editor');
|
|
75
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
76
|
+
});
|
|
77
|
+
// Mock @inquirer/prompts barrel re-exports (covers `import { input } from '@inquirer/prompts'` style).
|
|
78
|
+
// Jest's module mock for individual packages doesn't propagate through barrel re-exports.
|
|
79
|
+
jest.mock('@inquirer/prompts', () => {
|
|
80
|
+
const actual = jest.requireActual('@inquirer/prompts');
|
|
81
|
+
return {
|
|
82
|
+
...actual,
|
|
83
|
+
input: wrapPrompt(actual.input),
|
|
84
|
+
select: wrapPrompt(actual.select),
|
|
85
|
+
confirm: wrapPrompt(actual.confirm),
|
|
86
|
+
checkbox: wrapPrompt(actual.checkbox),
|
|
87
|
+
password: wrapPrompt(actual.password),
|
|
88
|
+
expand: wrapPrompt(actual.expand),
|
|
89
|
+
rawlist: wrapPrompt(actual.rawlist),
|
|
90
|
+
number: wrapPrompt(actual.number),
|
|
91
|
+
search: wrapPrompt(actual.search),
|
|
92
|
+
editor: wrapPrompt(actual.editor),
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
// Mock the external editor to capture typed input instead of spawning a real editor.
|
|
96
|
+
// Buffers all screen.type() calls and submits on screen.keypress('enter'), matching
|
|
97
|
+
// the interaction pattern of other prompts (type → enter).
|
|
98
|
+
jest.mock('@inquirer/external-editor', () => ({
|
|
99
|
+
editAsync: (_text, callback) => {
|
|
100
|
+
let buffer = '';
|
|
101
|
+
const typeSpy = jest
|
|
102
|
+
.spyOn(screenInstance, 'type')
|
|
103
|
+
.mockImplementation((text) => {
|
|
104
|
+
buffer += text;
|
|
105
|
+
});
|
|
106
|
+
const keypressSpy = jest
|
|
107
|
+
.spyOn(screenInstance, 'keypress')
|
|
108
|
+
.mockImplementation((key) => {
|
|
109
|
+
const name = typeof key === 'string' ? key : key.name;
|
|
110
|
+
if (name === 'enter' || name === 'return') {
|
|
111
|
+
typeSpy.mockRestore();
|
|
112
|
+
keypressSpy.mockRestore();
|
|
113
|
+
process.nextTick(() => callback(undefined, buffer));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
}));
|
|
118
|
+
// Re-export Screen class and KeypressEvent type for advanced use cases
|
|
119
|
+
export { Screen } from './screen.js';
|
package/dist/screen.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import MuteStream from 'mute-stream';
|
|
2
|
+
import { BufferedStream } from './buffered-stream.js';
|
|
3
|
+
export type KeypressEvent = {
|
|
4
|
+
name?: string;
|
|
5
|
+
ctrl?: boolean;
|
|
6
|
+
meta?: boolean;
|
|
7
|
+
shift?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare class Screen {
|
|
10
|
+
#private;
|
|
11
|
+
constructor();
|
|
12
|
+
get input(): MuteStream;
|
|
13
|
+
createOutput(): BufferedStream;
|
|
14
|
+
setActivePromise(promise: Promise<unknown>): void;
|
|
15
|
+
/**
|
|
16
|
+
* Wait for the next screen update.
|
|
17
|
+
*
|
|
18
|
+
* Handles re-renders within the same prompt (e.g., validation errors,
|
|
19
|
+
* async updates) and prompt transitions in multi-prompt flows
|
|
20
|
+
* (automatically waits for the next prompt).
|
|
21
|
+
*
|
|
22
|
+
* Note: The initial prompt render is available immediately via getScreen()
|
|
23
|
+
* — no next() call is needed before reading it.
|
|
24
|
+
*/
|
|
25
|
+
next(): Promise<void>;
|
|
26
|
+
getScreen({ raw }?: {
|
|
27
|
+
raw?: boolean;
|
|
28
|
+
}): string;
|
|
29
|
+
getFullOutput({ raw }?: {
|
|
30
|
+
raw?: boolean;
|
|
31
|
+
}): Promise<string>;
|
|
32
|
+
keypress(key: string | KeypressEvent): void;
|
|
33
|
+
type(text: string): void;
|
|
34
|
+
clear(): void;
|
|
35
|
+
}
|
package/dist/screen.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
2
|
+
import MuteStream from 'mute-stream';
|
|
3
|
+
import { BufferedStream } from './buffered-stream.js';
|
|
4
|
+
import { interpretTerminalOutput } from './terminal.js';
|
|
5
|
+
export class Screen {
|
|
6
|
+
#input;
|
|
7
|
+
#outputs = [];
|
|
8
|
+
#currentOutput = null;
|
|
9
|
+
#activePromise = null;
|
|
10
|
+
#rendersConsumed = 0;
|
|
11
|
+
#renderResolve = null;
|
|
12
|
+
constructor() {
|
|
13
|
+
this.#input = new MuteStream();
|
|
14
|
+
this.#input.unmute();
|
|
15
|
+
}
|
|
16
|
+
get input() {
|
|
17
|
+
return this.#input;
|
|
18
|
+
}
|
|
19
|
+
createOutput() {
|
|
20
|
+
const output = new BufferedStream();
|
|
21
|
+
this.#outputs.push(output);
|
|
22
|
+
this.#currentOutput = output;
|
|
23
|
+
this.#rendersConsumed = 0;
|
|
24
|
+
// Forward render events for cross-output listening
|
|
25
|
+
output.on('render', () => {
|
|
26
|
+
if (this.#renderResolve) {
|
|
27
|
+
this.#renderResolve();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return output;
|
|
31
|
+
}
|
|
32
|
+
setActivePromise(promise) {
|
|
33
|
+
this.#activePromise = promise;
|
|
34
|
+
// Auto-consume the initial render. Since createPrompt renders synchronously,
|
|
35
|
+
// getScreen() works immediately after starting a prompt — no next() needed.
|
|
36
|
+
this.#rendersConsumed = this.#currentOutput?.writeCount ?? 0;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Wait for the next screen update.
|
|
40
|
+
*
|
|
41
|
+
* Handles re-renders within the same prompt (e.g., validation errors,
|
|
42
|
+
* async updates) and prompt transitions in multi-prompt flows
|
|
43
|
+
* (automatically waits for the next prompt).
|
|
44
|
+
*
|
|
45
|
+
* Note: The initial prompt render is available immediately via getScreen()
|
|
46
|
+
* — no next() call is needed before reading it.
|
|
47
|
+
*/
|
|
48
|
+
async next() {
|
|
49
|
+
if (this.#activePromise) {
|
|
50
|
+
const currentPromise = this.#activePromise;
|
|
51
|
+
// Consume any renders that happened synchronously (e.g., loading state).
|
|
52
|
+
// We want to wait for the next meaningful state change, not an intermediate render.
|
|
53
|
+
this.#rendersConsumed = this.#currentOutput?.writeCount ?? 0;
|
|
54
|
+
// Race: a future render (validation error, async update) vs the promise settling
|
|
55
|
+
// (prompt completed, possibly synchronously before any new render arrives).
|
|
56
|
+
const renderPromise = this.#waitForNextRender();
|
|
57
|
+
const settlePromise = currentPromise.then(() => 'settled', () => 'settled');
|
|
58
|
+
const result = await Promise.race([
|
|
59
|
+
renderPromise.then(() => 'render'),
|
|
60
|
+
settlePromise,
|
|
61
|
+
]);
|
|
62
|
+
if (result === 'settled') {
|
|
63
|
+
if (this.#activePromise !== currentPromise) {
|
|
64
|
+
// New prompt already started — its render was caught by the race's renderPromise.
|
|
65
|
+
this.#rendersConsumed = this.#currentOutput?.writeCount ?? 0;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Prompt settled but no new prompt yet. Wait for the next prompt's first render.
|
|
69
|
+
await this.#waitForNextRender();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Got a render. Check if the prompt also completed (making this a "done" render).
|
|
74
|
+
// Microtasks (promise settlement) always drain before macrotasks (setImmediate),
|
|
75
|
+
// so if the prompt resolved, its .then wins the race.
|
|
76
|
+
const settled = await Promise.race([
|
|
77
|
+
currentPromise.then(() => true, () => true),
|
|
78
|
+
new Promise((resolve) => setImmediate(() => resolve(false))),
|
|
79
|
+
]);
|
|
80
|
+
if (settled) {
|
|
81
|
+
if (this.#activePromise !== currentPromise) {
|
|
82
|
+
// New prompt already started — its render was caught by the race.
|
|
83
|
+
this.#rendersConsumed = this.#currentOutput?.writeCount ?? 0;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Prompt completed but no new prompt yet. Wait for it.
|
|
87
|
+
await this.#waitForNextRender();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// No active promise — wait for a render (e.g., prompt hasn't started yet)
|
|
94
|
+
await this.#waitForNextRender();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async #waitForNextRender() {
|
|
98
|
+
const writeCount = this.#currentOutput?.writeCount ?? 0;
|
|
99
|
+
if (writeCount > this.#rendersConsumed) {
|
|
100
|
+
this.#rendersConsumed = writeCount;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await new Promise((resolve) => {
|
|
104
|
+
const handler = () => {
|
|
105
|
+
this.#renderResolve = null;
|
|
106
|
+
resolve();
|
|
107
|
+
};
|
|
108
|
+
// Listen on current output (for re-renders within same prompt)
|
|
109
|
+
this.#currentOutput?.once('render', handler);
|
|
110
|
+
// Also set up cross-output listener (for prompt transitions and initial render)
|
|
111
|
+
this.#renderResolve = handler;
|
|
112
|
+
});
|
|
113
|
+
this.#rendersConsumed = this.#currentOutput?.writeCount ?? 0;
|
|
114
|
+
}
|
|
115
|
+
getScreen({ raw } = {}) {
|
|
116
|
+
const lastScreen = this.#currentOutput?.getLastChunk({ raw: Boolean(raw) }) ?? '';
|
|
117
|
+
return raw ? lastScreen : stripVTControlCharacters(lastScreen).trim();
|
|
118
|
+
}
|
|
119
|
+
async getFullOutput({ raw } = {}) {
|
|
120
|
+
const output = this.#outputs.map((o) => o.getFullOutput()).join('');
|
|
121
|
+
if (raw)
|
|
122
|
+
return output;
|
|
123
|
+
return interpretTerminalOutput(output);
|
|
124
|
+
}
|
|
125
|
+
keypress(key) {
|
|
126
|
+
if (typeof key === 'string') {
|
|
127
|
+
this.#input.emit('keypress', null, { name: key });
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
this.#input.emit('keypress', null, key);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
type(text) {
|
|
134
|
+
this.#input.write(text);
|
|
135
|
+
for (const char of text) {
|
|
136
|
+
this.#input.emit('keypress', null, { name: char });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
clear() {
|
|
140
|
+
// Recreate input stream to ensure clean state
|
|
141
|
+
this.#input = new MuteStream();
|
|
142
|
+
this.#input.unmute();
|
|
143
|
+
// Reset output tracking
|
|
144
|
+
this.#outputs = [];
|
|
145
|
+
this.#currentOutput = null;
|
|
146
|
+
this.#activePromise = null;
|
|
147
|
+
this.#rendersConsumed = 0;
|
|
148
|
+
this.#renderResolve = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function interpretTerminalOutput(rawOutput: string, cols?: number, rows?: number): Promise<string>;
|
package/dist/terminal.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Terminal } from '@xterm/headless';
|
|
2
|
+
export async function interpretTerminalOutput(rawOutput, cols = 10_000, rows = 4000) {
|
|
3
|
+
const term = new Terminal({ cols, rows, allowProposedApi: true, convertEol: true });
|
|
4
|
+
await new Promise((resolve) => term.write(rawOutput, resolve));
|
|
5
|
+
const lines = [];
|
|
6
|
+
for (let i = 0; i < term.rows; i++) {
|
|
7
|
+
lines.push(term.buffer.active.getLine(i)?.translateToString(true) ?? '');
|
|
8
|
+
}
|
|
9
|
+
term.dispose();
|
|
10
|
+
// Trim trailing empty lines
|
|
11
|
+
while (lines.length > 0 && lines.at(-1) === '') {
|
|
12
|
+
lines.pop();
|
|
13
|
+
}
|
|
14
|
+
return lines.join('\n');
|
|
15
|
+
}
|
package/dist/vitest.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Prompt } from '@inquirer/type';
|
|
2
|
+
import { Screen } from './screen.js';
|
|
3
|
+
declare const screenInstance: Screen;
|
|
4
|
+
export { screenInstance as screen };
|
|
5
|
+
/**
|
|
6
|
+
* Wrap a prompt function to use the shared screen I/O.
|
|
7
|
+
* Use this in your own `vi.mock()` calls to mock third-party prompts.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { wrapPrompt } from '@inquirer/testing/vitest';
|
|
12
|
+
*
|
|
13
|
+
* vi.mock('@my-company/custom-prompt', async (importOriginal) => {
|
|
14
|
+
* const actual = await importOriginal<typeof import('@my-company/custom-prompt')>();
|
|
15
|
+
* return { ...actual, default: wrapPrompt(actual.default) };
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function wrapPrompt<Value, Config>(prompt: Prompt<Value, Config>): Prompt<Value, Config>;
|
|
20
|
+
export { Screen, type KeypressEvent } from './screen.js';
|
package/dist/vitest.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { vi, beforeEach } from 'vitest';
|
|
2
|
+
import { Screen } from './screen.js';
|
|
3
|
+
// Global screen instance - exported for tests
|
|
4
|
+
const screenInstance = new Screen();
|
|
5
|
+
export { screenInstance as screen };
|
|
6
|
+
// Reset before each test
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
screenInstance.clear();
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Wrap a prompt function to use the shared screen I/O.
|
|
12
|
+
* Use this in your own `vi.mock()` calls to mock third-party prompts.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { wrapPrompt } from '@inquirer/testing/vitest';
|
|
17
|
+
*
|
|
18
|
+
* vi.mock('@my-company/custom-prompt', async (importOriginal) => {
|
|
19
|
+
* const actual = await importOriginal<typeof import('@my-company/custom-prompt')>();
|
|
20
|
+
* return { ...actual, default: wrapPrompt(actual.default) };
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function wrapPrompt(prompt) {
|
|
25
|
+
return (config, context) => {
|
|
26
|
+
const output = screenInstance.createOutput();
|
|
27
|
+
const promise = prompt(config, {
|
|
28
|
+
...context,
|
|
29
|
+
input: screenInstance.input,
|
|
30
|
+
output,
|
|
31
|
+
});
|
|
32
|
+
screenInstance.setActivePromise(promise);
|
|
33
|
+
return promise;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Mock individual prompt packages (covers `import input from '@inquirer/input'` style)
|
|
37
|
+
vi.mock('@inquirer/input', async (importOriginal) => {
|
|
38
|
+
const actual = await importOriginal();
|
|
39
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
40
|
+
});
|
|
41
|
+
vi.mock('@inquirer/select', async (importOriginal) => {
|
|
42
|
+
const actual = await importOriginal();
|
|
43
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
44
|
+
});
|
|
45
|
+
vi.mock('@inquirer/confirm', async (importOriginal) => {
|
|
46
|
+
const actual = await importOriginal();
|
|
47
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
48
|
+
});
|
|
49
|
+
vi.mock('@inquirer/checkbox', async (importOriginal) => {
|
|
50
|
+
const actual = await importOriginal();
|
|
51
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
52
|
+
});
|
|
53
|
+
vi.mock('@inquirer/password', async (importOriginal) => {
|
|
54
|
+
const actual = await importOriginal();
|
|
55
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
56
|
+
});
|
|
57
|
+
vi.mock('@inquirer/expand', async (importOriginal) => {
|
|
58
|
+
const actual = await importOriginal();
|
|
59
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
60
|
+
});
|
|
61
|
+
vi.mock('@inquirer/rawlist', async (importOriginal) => {
|
|
62
|
+
const actual = await importOriginal();
|
|
63
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
64
|
+
});
|
|
65
|
+
vi.mock('@inquirer/number', async (importOriginal) => {
|
|
66
|
+
const actual = await importOriginal();
|
|
67
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
68
|
+
});
|
|
69
|
+
vi.mock('@inquirer/search', async (importOriginal) => {
|
|
70
|
+
const actual = await importOriginal();
|
|
71
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
72
|
+
});
|
|
73
|
+
vi.mock('@inquirer/editor', async (importOriginal) => {
|
|
74
|
+
const actual = await importOriginal();
|
|
75
|
+
return { ...actual, default: wrapPrompt(actual.default) };
|
|
76
|
+
});
|
|
77
|
+
// Mock @inquirer/prompts barrel re-exports (covers `import { input } from '@inquirer/prompts'` style).
|
|
78
|
+
// While Vitest's module interception often propagates through ESM re-exports, an explicit mock
|
|
79
|
+
// ensures consistent behavior across all environments and bundler configurations.
|
|
80
|
+
vi.mock('@inquirer/prompts', async (importOriginal) => {
|
|
81
|
+
const actual = await importOriginal();
|
|
82
|
+
return {
|
|
83
|
+
...actual,
|
|
84
|
+
input: wrapPrompt(actual.input),
|
|
85
|
+
select: wrapPrompt(actual.select),
|
|
86
|
+
confirm: wrapPrompt(actual.confirm),
|
|
87
|
+
checkbox: wrapPrompt(actual.checkbox),
|
|
88
|
+
password: wrapPrompt(actual.password),
|
|
89
|
+
expand: wrapPrompt(actual.expand),
|
|
90
|
+
rawlist: wrapPrompt(actual.rawlist),
|
|
91
|
+
number: wrapPrompt(actual.number),
|
|
92
|
+
search: wrapPrompt(actual.search),
|
|
93
|
+
editor: wrapPrompt(actual.editor),
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
// Mock the external editor to capture typed input instead of spawning a real editor.
|
|
97
|
+
// Buffers all screen.type() calls and submits on screen.keypress('enter'), matching
|
|
98
|
+
// the interaction pattern of other prompts (type → enter).
|
|
99
|
+
vi.mock('@inquirer/external-editor', () => ({
|
|
100
|
+
editAsync: (_text, callback) => {
|
|
101
|
+
let buffer = '';
|
|
102
|
+
const typeSpy = vi
|
|
103
|
+
.spyOn(screenInstance, 'type')
|
|
104
|
+
.mockImplementation((text) => {
|
|
105
|
+
buffer += text;
|
|
106
|
+
});
|
|
107
|
+
const keypressSpy = vi.spyOn(screenInstance, 'keypress').mockImplementation((key) => {
|
|
108
|
+
const name = typeof key === 'string' ? key : key.name;
|
|
109
|
+
if (name === 'enter' || name === 'return') {
|
|
110
|
+
typeSpy.mockRestore();
|
|
111
|
+
keypressSpy.mockRestore();
|
|
112
|
+
process.nextTick(() => callback(undefined, buffer));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
}));
|
|
117
|
+
// Re-export Screen class and KeypressEvent type for advanced use cases
|
|
118
|
+
export { Screen } from './screen.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inquirer/testing",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Inquirer testing utilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"answer",
|
|
@@ -55,12 +55,25 @@
|
|
|
55
55
|
"dist"
|
|
56
56
|
],
|
|
57
57
|
"type": "module",
|
|
58
|
-
"sideEffects":
|
|
58
|
+
"sideEffects": [
|
|
59
|
+
"./src/vitest.ts",
|
|
60
|
+
"./src/jest.ts",
|
|
61
|
+
"./dist/vitest.js",
|
|
62
|
+
"./dist/jest.js"
|
|
63
|
+
],
|
|
59
64
|
"exports": {
|
|
60
65
|
".": {
|
|
61
66
|
"types": "./dist/index.d.ts",
|
|
62
67
|
"default": "./dist/index.js"
|
|
63
68
|
},
|
|
69
|
+
"./vitest": {
|
|
70
|
+
"types": "./dist/vitest.d.ts",
|
|
71
|
+
"default": "./dist/vitest.js"
|
|
72
|
+
},
|
|
73
|
+
"./jest": {
|
|
74
|
+
"types": "./dist/jest.d.ts",
|
|
75
|
+
"default": "./dist/jest.js"
|
|
76
|
+
},
|
|
64
77
|
"./package.json": "./package.json"
|
|
65
78
|
},
|
|
66
79
|
"publishConfig": {
|
|
@@ -71,19 +84,34 @@
|
|
|
71
84
|
},
|
|
72
85
|
"dependencies": {
|
|
73
86
|
"@inquirer/type": "^4.0.3",
|
|
87
|
+
"@xterm/headless": "^5.5.0",
|
|
74
88
|
"mute-stream": "^3.0.0"
|
|
75
89
|
},
|
|
76
90
|
"devDependencies": {
|
|
91
|
+
"@types/jest": "^29.5.0",
|
|
77
92
|
"@types/mute-stream": "^0.0.4",
|
|
78
93
|
"@types/node": "^25.0.2",
|
|
79
|
-
"typescript": "^5.9.3"
|
|
94
|
+
"typescript": "^5.9.3",
|
|
95
|
+
"vitest": "^3.0.0"
|
|
80
96
|
},
|
|
81
97
|
"peerDependencies": {
|
|
82
|
-
"@types/
|
|
98
|
+
"@types/jest": ">=29.0.0",
|
|
99
|
+
"@types/node": ">=18",
|
|
100
|
+
"jest": ">=29.0.0",
|
|
101
|
+
"vitest": ">=1.0.0"
|
|
83
102
|
},
|
|
84
103
|
"peerDependenciesMeta": {
|
|
104
|
+
"@types/jest": {
|
|
105
|
+
"optional": true
|
|
106
|
+
},
|
|
85
107
|
"@types/node": {
|
|
86
108
|
"optional": true
|
|
109
|
+
},
|
|
110
|
+
"jest": {
|
|
111
|
+
"optional": true
|
|
112
|
+
},
|
|
113
|
+
"vitest": {
|
|
114
|
+
"optional": true
|
|
87
115
|
}
|
|
88
116
|
},
|
|
89
117
|
"engines": {
|
|
@@ -91,5 +119,5 @@
|
|
|
91
119
|
},
|
|
92
120
|
"main": "./dist/index.js",
|
|
93
121
|
"types": "./dist/index.d.ts",
|
|
94
|
-
"gitHead": "
|
|
122
|
+
"gitHead": "53dbf6c492883546f6f9f2d5b9a78cbc00bd434c"
|
|
95
123
|
}
|