@mudah-cli/testing 0.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.
@@ -0,0 +1 @@
1
+ export { TestApp, TestResult, type TestAppOptions } from './test-app.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { TestApp, TestResult } from './test-app.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAuB,MAAM,eAAe,CAAC"}
@@ -0,0 +1,56 @@
1
+ import { Application } from '@mudah-cli/core';
2
+ import { Output } from '@mudah-cli/ui';
3
+ import { type CommandModule } from '@mudah-cli/console';
4
+ export interface TestAppOptions {
5
+ /** App root containing `mudah.json`. */
6
+ cwd: string;
7
+ /** Extra command modules to register (in addition to discovered ones). */
8
+ commands?: CommandModule[];
9
+ /** Environment map for capability detection. */
10
+ env?: NodeJS.ProcessEnv;
11
+ }
12
+ /**
13
+ * An in-process test harness around a real Mudah application.
14
+ *
15
+ * ```ts
16
+ * const app = await TestApp.create({ cwd: fixtureApp });
17
+ * const result = await app.dispatch(['hello', 'world']);
18
+ * result.exit(0).outContains('hello world');
19
+ * ```
20
+ *
21
+ * Boot (providers + discovery) happens once per `TestApp`; every
22
+ * `dispatch()` clears the captured output first.
23
+ */
24
+ export declare class TestApp {
25
+ readonly app: Application;
26
+ readonly output: Output;
27
+ private readonly kernel;
28
+ private outBuffer;
29
+ private errBuffer;
30
+ private booted;
31
+ private constructor();
32
+ static create(options: TestAppOptions): Promise<TestApp>;
33
+ /** Captured stdout since the last dispatch. */
34
+ outText(): string;
35
+ /** Captured stderr since the last dispatch. */
36
+ errText(): string;
37
+ clear(): void;
38
+ /**
39
+ * Dispatch argv in-process. Returns a {@link TestResult} with the exit
40
+ * code and chained assertions. Never throws for expected CLI failures
41
+ * (usage errors, unknown commands) — those become exit codes.
42
+ */
43
+ dispatch(argv: string[]): Promise<TestResult>;
44
+ }
45
+ /** Chained assertions over a dispatch result. */
46
+ export declare class TestResult {
47
+ readonly code: number;
48
+ private readonly app;
49
+ constructor(code: number, app: TestApp);
50
+ /** Assert the exit code, with full output in the failure message. */
51
+ exit(expected: number): this;
52
+ outContains(text: string): this;
53
+ outNotContains(text: string): this;
54
+ errContains(text: string): this;
55
+ errNotContains(text: string): this;
56
+ }
@@ -0,0 +1,133 @@
1
+ import { Application } from '@mudah-cli/core';
2
+ import { detectCapabilities } from '@mudah-cli/terminal';
3
+ import { Output, resolveTheme } from '@mudah-cli/ui';
4
+ import { ConsoleKernel, renderError } from '@mudah-cli/console';
5
+ /**
6
+ * An in-process test harness around a real Mudah application.
7
+ *
8
+ * ```ts
9
+ * const app = await TestApp.create({ cwd: fixtureApp });
10
+ * const result = await app.dispatch(['hello', 'world']);
11
+ * result.exit(0).outContains('hello world');
12
+ * ```
13
+ *
14
+ * Boot (providers + discovery) happens once per `TestApp`; every
15
+ * `dispatch()` clears the captured output first.
16
+ */
17
+ export class TestApp {
18
+ app;
19
+ output;
20
+ kernel;
21
+ outBuffer = '';
22
+ errBuffer = '';
23
+ booted = false;
24
+ constructor(app, output, kernel) {
25
+ this.app = app;
26
+ this.output = output;
27
+ this.kernel = kernel;
28
+ }
29
+ static async create(options) {
30
+ const caps = detectCapabilities({ env: options.env });
31
+ const app = new Application(options.cwd);
32
+ const output = new Output({
33
+ stream: { write: () => { } },
34
+ errorStream: { write: () => { } },
35
+ theme: resolveTheme(app.manifest.ui?.theme),
36
+ colorLevel: caps.colorLevel,
37
+ unicode: caps.unicode,
38
+ osc9: caps.osc9,
39
+ });
40
+ const kernel = new ConsoleKernel(app, output);
41
+ const testApp = new TestApp(app, output, kernel);
42
+ await app.discoverProviders();
43
+ await app.boot();
44
+ await app.evaluateLazy();
45
+ const modules = [...(await app.discoverCommandModules()), ...(options.commands ?? [])];
46
+ for (const mod of modules) {
47
+ kernel.register(mod);
48
+ }
49
+ testApp.booted = true;
50
+ return testApp;
51
+ }
52
+ /** Captured stdout since the last dispatch. */
53
+ outText() {
54
+ return this.outBuffer;
55
+ }
56
+ /** Captured stderr since the last dispatch. */
57
+ errText() {
58
+ return this.errBuffer;
59
+ }
60
+ clear() {
61
+ this.outBuffer = '';
62
+ this.errBuffer = '';
63
+ }
64
+ /**
65
+ * Dispatch argv in-process. Returns a {@link TestResult} with the exit
66
+ * code and chained assertions. Never throws for expected CLI failures
67
+ * (usage errors, unknown commands) — those become exit codes.
68
+ */
69
+ async dispatch(argv) {
70
+ if (!this.booted)
71
+ throw new Error('[testing] TestApp is not ready; use TestApp.create().');
72
+ this.clear();
73
+ // Route output through the buffers for this dispatch.
74
+ this.output.redirect({
75
+ write: (data) => {
76
+ this.outBuffer += data;
77
+ },
78
+ }, {
79
+ write: (data) => {
80
+ this.errBuffer += data;
81
+ },
82
+ });
83
+ let code;
84
+ try {
85
+ code = await this.kernel.dispatch(argv);
86
+ }
87
+ catch (error) {
88
+ code = renderError(error, this.output);
89
+ }
90
+ return new TestResult(code, this);
91
+ }
92
+ }
93
+ /** Chained assertions over a dispatch result. */
94
+ export class TestResult {
95
+ code;
96
+ app;
97
+ constructor(code, app) {
98
+ this.code = code;
99
+ this.app = app;
100
+ }
101
+ /** Assert the exit code, with full output in the failure message. */
102
+ exit(expected) {
103
+ if (this.code !== expected) {
104
+ throw new Error(`[testing] Expected exit code ${expected}, got ${this.code}.\n--- stdout ---\n${this.app.outText()}\n--- stderr ---\n${this.app.errText()}`);
105
+ }
106
+ return this;
107
+ }
108
+ outContains(text) {
109
+ if (!this.app.outText().includes(text)) {
110
+ throw new Error(`[testing] stdout missing "${text}".\n--- stdout ---\n${this.app.outText()}`);
111
+ }
112
+ return this;
113
+ }
114
+ outNotContains(text) {
115
+ if (this.app.outText().includes(text)) {
116
+ throw new Error(`[testing] stdout unexpectedly contains "${text}".\n--- stdout ---\n${this.app.outText()}`);
117
+ }
118
+ return this;
119
+ }
120
+ errContains(text) {
121
+ if (!this.app.errText().includes(text)) {
122
+ throw new Error(`[testing] stderr missing "${text}".\n--- stderr ---\n${this.app.errText()}`);
123
+ }
124
+ return this;
125
+ }
126
+ errNotContains(text) {
127
+ if (this.app.errText().includes(text)) {
128
+ throw new Error(`[testing] stderr unexpectedly contains "${text}".\n--- stderr ---\n${this.app.errText()}`);
129
+ }
130
+ return this;
131
+ }
132
+ }
133
+ //# sourceMappingURL=test-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-app.js","sourceRoot":"","sources":["../src/test-app.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@mudah-cli/testing",
3
+ "version": "0.1.0",
4
+ "description": "In-process command test runner with captured output, mocked prompts, and JSON assertions.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=26"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": false,
20
+ "dependencies": {
21
+ "@mudah-cli/animation": "^0.1.0",
22
+ "@mudah-cli/console": "^0.1.0",
23
+ "@mudah-cli/core": "^0.1.0",
24
+ "@mudah-cli/terminal": "^0.1.0",
25
+ "@mudah-cli/ui": "^0.1.0"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ }
30
+ }