@nudojs/cli 0.2.1 → 0.3.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/package.json +12 -7
- package/src/abs-route.ts +273 -0
- package/src/builtins/builtin-prototype.ts +279 -0
- package/src/builtins/builtin-static.ts +260 -0
- package/src/eval-binary.ts +113 -0
- package/src/evaluator-api.ts +3 -0
- package/src/evaluator.ts +174 -633
- package/src/index.ts +373 -270
- package/src/narrowing.ts +7 -0
- package/src/phi-from-test.ts +42 -0
- package/src/run-test.ts +92 -0
- package/src/term-registry.ts +96 -0
- package/CHANGELOG.md +0 -41
- package/dist/chunk-3HCPXPEW.js +0 -5286
- package/dist/evaluator-api.d.ts +0 -151
- package/dist/evaluator-api.js +0 -214
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -4035
- package/examples/real-world.js +0 -66
- package/examples/validate-real-world.ts +0 -160
- package/src/__tests__/builtin-advanced.test.ts +0 -292
- package/src/__tests__/builtin-api.test.ts +0 -308
- package/src/__tests__/builtin-functions.test.ts +0 -313
- package/src/__tests__/builtin-map.test.ts +0 -129
- package/src/__tests__/builtin-promise.test.ts +0 -150
- package/src/__tests__/builtin-regexp.test.ts +0 -133
- package/src/__tests__/builtin-set.test.ts +0 -163
- package/src/__tests__/builtin-url.test.ts +0 -256
- package/src/__tests__/builtin-web.test.ts +0 -340
- package/src/__tests__/combination-scenarios.test.ts +0 -384
- package/src/__tests__/edge-cases.test.ts +0 -749
- package/src/__tests__/evaluator-loops.test.ts +0 -283
- package/src/__tests__/evaluator-nullish-coalescing.test.ts +0 -86
- package/src/__tests__/evaluator-optional-chaining.test.ts +0 -37
- package/src/__tests__/evaluator-phase2.test.ts +0 -564
- package/src/__tests__/evaluator-phase3.test.ts +0 -864
- package/src/__tests__/evaluator-refined.test.ts +0 -121
- package/src/__tests__/evaluator-string-methods.test.ts +0 -151
- package/src/__tests__/evaluator.test.ts +0 -943
- package/src/__tests__/mock-arrow-function.test.ts +0 -169
- package/src/__tests__/mock-comparison.test.ts +0 -221
- package/src/__tests__/mock-comprehensive.test.ts +0 -250
- package/src/__tests__/mock-final-integration.test.ts +0 -263
- package/src/__tests__/mock-helpers.test.ts +0 -174
- package/src/__tests__/mock-realworld.test.ts +0 -191
- package/src/__tests__/mock-rest-debug.test.ts +0 -110
- package/src/__tests__/mock-rest-edge.test.ts +0 -176
- package/src/__tests__/mock-sinon-runtime.test.ts +0 -168
- package/src/__tests__/narrowing-discriminated.test.ts +0 -35
- package/src/__tests__/narrowing-in-operator.test.ts +0 -56
- package/src/__tests__/narrowing-switch.test.ts +0 -56
- package/src/__tests__/narrowing-truthiness.test.ts +0 -52
- package/src/__tests__/narrowing.test.ts +0 -82
- package/src/__tests__/nudo-mock-syntax.test.ts +0 -245
- package/src/__tests__/realworld-patterns.test.ts +0 -430
- package/src/__tests__/syntax-sugar.test.ts +0 -345
- package/src/builtins/index.ts +0 -61
- package/tsconfig.json +0 -9
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll } from "vitest";
|
|
2
|
-
import { parse, extractDirectives, type CaseDirective, parseTypeValueExpr } from "@nudojs/parser";
|
|
3
|
-
import { typeValueToString, createEnvironment, T } from "@nudojs/core";
|
|
4
|
-
import { evaluateFunctionFull } from "../evaluator.js";
|
|
5
|
-
|
|
6
|
-
function runTest(source: string): { name: string; caseName: string; result: string }[] {
|
|
7
|
-
const ast = parse(source);
|
|
8
|
-
const directives = extractDirectives(ast);
|
|
9
|
-
const env = createEnvironment();
|
|
10
|
-
const results: { name: string; caseName: string; result: string }[] = [];
|
|
11
|
-
|
|
12
|
-
for (const fn of directives) {
|
|
13
|
-
for (const d of fn.directives) {
|
|
14
|
-
if (d.kind === "mock") {
|
|
15
|
-
if (d.arrowFn) {
|
|
16
|
-
const fnType = T.fn(d.arrowFn.params, d.arrowFn.body, env);
|
|
17
|
-
(fnType as any)._paramPatterns = d.arrowFn.paramPatterns;
|
|
18
|
-
env.bind(d.name, fnType);
|
|
19
|
-
} else if (d.sinonExpr) {
|
|
20
|
-
const body = { type: "BlockStatement", body: [] };
|
|
21
|
-
const mockFn = T.fn(["...args"], body, env);
|
|
22
|
-
if (d.sinonExpr.returnValue) {
|
|
23
|
-
(mockFn as any)._directReturn = d.sinonExpr.returnValue;
|
|
24
|
-
} else if (d.sinonExpr.resolvedValue) {
|
|
25
|
-
(mockFn as any)._directReturn = T.promise(d.sinonExpr.resolvedValue);
|
|
26
|
-
} else {
|
|
27
|
-
(mockFn as any)._directReturn = T.unknown;
|
|
28
|
-
}
|
|
29
|
-
env.bind(d.name, mockFn);
|
|
30
|
-
} else if (d.expression) {
|
|
31
|
-
env.bind(d.name, parseTypeValueExpr(d.expression));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const caseDirectives = fn.directives.filter((d): d is CaseDirective => d.kind === "case");
|
|
37
|
-
for (const dir of caseDirectives) {
|
|
38
|
-
const result = evaluateFunctionFull(fn.node, dir.args, env);
|
|
39
|
-
results.push({
|
|
40
|
-
name: fn.name,
|
|
41
|
-
caseName: dir.name,
|
|
42
|
-
result: typeValueToString(result.value),
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return results;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
describe("Final Integration Test - All Mock Features", () => {
|
|
50
|
-
const source = `
|
|
51
|
-
// 1. 基础箭头函数 Mock - filter
|
|
52
|
-
// @nudo:mock predicate = (x) => x > 3
|
|
53
|
-
// @nudo:case "filter" ([1, 2, 3, 4, 5])
|
|
54
|
-
function filterGreater(arr) {
|
|
55
|
-
const result = [];
|
|
56
|
-
for (const item of arr) {
|
|
57
|
-
if (predicate(item)) result.push(item);
|
|
58
|
-
}
|
|
59
|
-
return result;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// 2. 基础箭头函数 Mock - map
|
|
63
|
-
// @nudo:mock transform = (x) => x * 2
|
|
64
|
-
// @nudo:case "map" ([1, 2, 3])
|
|
65
|
-
function doubleAll(arr) {
|
|
66
|
-
return arr.map(transform);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// 3. 基础箭头函数 Mock - reduce
|
|
70
|
-
// @nudo:mock reducer = (a, b) => a + b
|
|
71
|
-
// @nudo:case "reduce" ([1, 2, 3, 4])
|
|
72
|
-
function sumAll(arr) {
|
|
73
|
-
return arr.reduce(reducer, 0);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// 4. 多参数 Mock
|
|
77
|
-
// @nudo:mock combine = (a, b) => a + b * 10
|
|
78
|
-
// @nudo:case "combine" (3, 4)
|
|
79
|
-
function combineNums(a, b) {
|
|
80
|
-
return combine(a, b);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 5. 函数组合
|
|
84
|
-
// @nudo:mock f = (x) => x + 1
|
|
85
|
-
// @nudo:mock g = (x) => x * 2
|
|
86
|
-
// @nudo:case "compose" (5)
|
|
87
|
-
function compose(x) {
|
|
88
|
-
return f(g(x));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// 6. 回调模式
|
|
92
|
-
// @nudo:mock callback = (result) => result * 2
|
|
93
|
-
// @nudo:case "callback" (21)
|
|
94
|
-
function processWithCallback(x) {
|
|
95
|
-
return callback(x);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 7. 类型收窄 - string
|
|
99
|
-
// @nudo:mock processor = (x) => typeof x === "string" ? x.toUpperCase() : x
|
|
100
|
-
// @nudo:case "string" ("hello")
|
|
101
|
-
// @nudo:case "number" (42)
|
|
102
|
-
function process(x) {
|
|
103
|
-
return processor(x);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// 8. 对象返回
|
|
107
|
-
// @nudo:mock makeUser = (name, age) => ({ name, age, isAdult: age >= 18 })
|
|
108
|
-
// @nudo:case "user" ("Alice", 25)
|
|
109
|
-
function createUser(name, age) {
|
|
110
|
-
return makeUser(name, age);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// 9. 数组解构
|
|
114
|
-
// @nudo:mock swap = ([a, b]) => [b, a]
|
|
115
|
-
// @nudo:case "swap" ([1, 2])
|
|
116
|
-
function swapPair(pair) {
|
|
117
|
-
return swap(pair);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// 10. 对象解构
|
|
121
|
-
// @nudo:mock getFullName = ({first, last}) => first + " " + last
|
|
122
|
-
// @nudo:case "name" ({first: "John", last: "Doe"})
|
|
123
|
-
function fullName(obj) {
|
|
124
|
-
return getFullName(obj);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// 11. Rest 参数
|
|
128
|
-
// @nudo:mock sum = (...args) => args.length
|
|
129
|
-
// @nudo:case "rest" (1, 2, 3)
|
|
130
|
-
function countArgs() {
|
|
131
|
-
return sum(1, 2, 3);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 12. 条件逻辑
|
|
135
|
-
// @nudo:mock validator = (x) => x > 0 && x < 100
|
|
136
|
-
// @nudo:case "valid" (50)
|
|
137
|
-
// @nudo:case "invalid" (150)
|
|
138
|
-
function validate(x) {
|
|
139
|
-
return validator(x);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// 13. 嵌套函数调用
|
|
143
|
-
// @nudo:mock double = (x) => x * 2
|
|
144
|
-
// @nudo:mock addTen = (x) => x + 10
|
|
145
|
-
// @nudo:case "nested" (5)
|
|
146
|
-
function nestedCalls(x) {
|
|
147
|
-
return addTen(double(x));
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// 14. 无参数 Mock
|
|
151
|
-
// @nudo:mock getRandom = () => 42
|
|
152
|
-
// @nudo:case "noargs" ()
|
|
153
|
-
function getValue() {
|
|
154
|
-
return getRandom();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// 15. Sinon Mock
|
|
158
|
-
// @nudo:mock fetch = sinon.stub()
|
|
159
|
-
// @nudo:case "sinon" ("/api/data")
|
|
160
|
-
function getData(url) {
|
|
161
|
-
return fetch(url);
|
|
162
|
-
}
|
|
163
|
-
`;
|
|
164
|
-
|
|
165
|
-
let results: { name: string; caseName: string; result: string }[];
|
|
166
|
-
|
|
167
|
-
beforeAll(() => {
|
|
168
|
-
results = runTest(source);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("1. filter with predicate", () => {
|
|
172
|
-
const r = results.find(r => r.caseName === "filter")!;
|
|
173
|
-
expect(r.result).toBe("[4, 5]");
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("2. map with transform", () => {
|
|
177
|
-
const r = results.find(r => r.caseName === "map")!;
|
|
178
|
-
expect(r.result).toBe("[2, 4, 6]");
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("3. reduce with reducer", () => {
|
|
182
|
-
const r = results.find(r => r.caseName === "reduce")!;
|
|
183
|
-
expect(r.result).toBe("10");
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it("4. multi-parameter mock", () => {
|
|
187
|
-
const r = results.find(r => r.caseName === "combine")!;
|
|
188
|
-
expect(r.result).toBe("43");
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("5. function composition", () => {
|
|
192
|
-
const r = results.find(r => r.caseName === "compose")!;
|
|
193
|
-
expect(r.result).toBe("11");
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("6. callback pattern", () => {
|
|
197
|
-
const r = results.find(r => r.caseName === "callback")!;
|
|
198
|
-
expect(r.result).toBe("42");
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it("7a. type narrowing - string", () => {
|
|
202
|
-
const r = results.find(r => r.caseName === "string")!;
|
|
203
|
-
expect(r.result).toBe('"HELLO"');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it("7b. type narrowing - number", () => {
|
|
207
|
-
const r = results.find(r => r.caseName === "number")!;
|
|
208
|
-
expect(r.result).toBe("42");
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it("8. object return", () => {
|
|
212
|
-
const r = results.find(r => r.caseName === "user")!;
|
|
213
|
-
expect(r.result).toBe('{ name: "Alice", age: 25, isAdult: true }');
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("9. array destructuring", () => {
|
|
217
|
-
const r = results.find(r => r.caseName === "swap")!;
|
|
218
|
-
expect(r.result).toBe("[2, 1]");
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it("10. object destructuring", () => {
|
|
222
|
-
const r = results.find(r => r.caseName === "name")!;
|
|
223
|
-
expect(r.result).toBe('"John Doe"');
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it("11. rest parameters", () => {
|
|
227
|
-
const r = results.find(r => r.caseName === "rest")!;
|
|
228
|
-
expect(r.result).toBe("3");
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("12a. conditional - valid", () => {
|
|
232
|
-
const r = results.find(r => r.caseName === "valid")!;
|
|
233
|
-
expect(r.result).toBe("true");
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it("12b. conditional - invalid", () => {
|
|
237
|
-
const r = results.find(r => r.caseName === "invalid")!;
|
|
238
|
-
expect(r.result).toBe("false");
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it("13. nested function calls", () => {
|
|
242
|
-
const r = results.find(r => r.caseName === "nested")!;
|
|
243
|
-
expect(r.result).toBe("20");
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it("14. no-args mock", () => {
|
|
247
|
-
const r = results.find(r => r.caseName === "noargs")!;
|
|
248
|
-
expect(r.result).toBe("42");
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it("15. sinon stub (returns unknown)", () => {
|
|
252
|
-
const r = results.find(r => r.caseName === "sinon")!;
|
|
253
|
-
expect(r.result).toBe("unknown");
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it("all tests passed", () => {
|
|
257
|
-
console.log("\n=== 最终测试结果 ===");
|
|
258
|
-
for (const r of results) {
|
|
259
|
-
console.log(`${r.name} - ${r.caseName}: ${r.result}`);
|
|
260
|
-
}
|
|
261
|
-
console.log(`\n总计: ${results.length} 个测试用例`);
|
|
262
|
-
});
|
|
263
|
-
});
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { parse, extractDirectives, type CaseDirective, parseTypeValueExpr } from "@nudojs/parser";
|
|
3
|
-
import { typeValueToString, createEnvironment, T, stub, spy, mock, mockHelperToTypeValue } from "@nudojs/core";
|
|
4
|
-
import { evaluateFunctionFull } from "../evaluator.js";
|
|
5
|
-
|
|
6
|
-
function runNudoTest(source: string): { name: string; caseName: string; result: string }[] {
|
|
7
|
-
const ast = parse(source);
|
|
8
|
-
const directives = extractDirectives(ast);
|
|
9
|
-
const env = createEnvironment();
|
|
10
|
-
const results: { name: string; caseName: string; result: string }[] = [];
|
|
11
|
-
|
|
12
|
-
for (const fn of directives) {
|
|
13
|
-
for (const d of fn.directives) {
|
|
14
|
-
if (d.kind === "mock") {
|
|
15
|
-
if (d.arrowFn) {
|
|
16
|
-
const fnType = T.fn(d.arrowFn.params, d.arrowFn.body, env);
|
|
17
|
-
(fnType as any)._paramPatterns = d.arrowFn.paramPatterns;
|
|
18
|
-
env.bind(d.name, fnType);
|
|
19
|
-
} else if (d.nudoMock) {
|
|
20
|
-
const typeVal = mockHelperToTypeValue(d.nudoMock, env);
|
|
21
|
-
env.bind(d.name, typeVal);
|
|
22
|
-
} else if (d.expression) {
|
|
23
|
-
env.bind(d.name, parseTypeValueExpr(d.expression));
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const caseDirectives = fn.directives.filter((d): d is CaseDirective => d.kind === "case");
|
|
29
|
-
for (const dir of caseDirectives) {
|
|
30
|
-
const result = evaluateFunctionFull(fn.node, dir.args, env);
|
|
31
|
-
results.push({
|
|
32
|
-
name: fn.name,
|
|
33
|
-
caseName: dir.name,
|
|
34
|
-
result: typeValueToString(result.value),
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return results;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
describe("Mock Helper Functions", () => {
|
|
42
|
-
describe("mockHelperToTypeValue", () => {
|
|
43
|
-
it("stub() should create a function returning unknown", () => {
|
|
44
|
-
const env = createEnvironment();
|
|
45
|
-
const helper = stub();
|
|
46
|
-
const typeVal = mockHelperToTypeValue(helper, env);
|
|
47
|
-
expect(typeVal.kind).toBe("function");
|
|
48
|
-
expect((typeVal as any)._directReturn.kind).toBe("unknown");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("stub().returns(T.number) should create a function returning number", () => {
|
|
52
|
-
const env = createEnvironment();
|
|
53
|
-
const helper = stub.returns(T.number);
|
|
54
|
-
const typeVal = mockHelperToTypeValue(helper, env);
|
|
55
|
-
expect(typeVal.kind).toBe("function");
|
|
56
|
-
expect((typeVal as any)._directReturn.kind).toBe("primitive");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("stub().resolves(T.string) should create a function returning promise<string>", () => {
|
|
60
|
-
const env = createEnvironment();
|
|
61
|
-
const helper = stub.resolves(T.string);
|
|
62
|
-
const typeVal = mockHelperToTypeValue(helper, env);
|
|
63
|
-
expect(typeVal.kind).toBe("function");
|
|
64
|
-
expect((typeVal as any)._directReturn.kind).toBe("promise");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("spy() should create a function returning unknown", () => {
|
|
68
|
-
const env = createEnvironment();
|
|
69
|
-
const helper = spy();
|
|
70
|
-
const typeVal = mockHelperToTypeValue(helper, env);
|
|
71
|
-
expect(typeVal.kind).toBe("function");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("mock() should create a function returning unknown", () => {
|
|
75
|
-
const env = createEnvironment();
|
|
76
|
-
const helper = mock();
|
|
77
|
-
const typeVal = mockHelperToTypeValue(helper, env);
|
|
78
|
-
expect(typeVal.kind).toBe("function");
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe("Mock helpers through evaluator", () => {
|
|
83
|
-
it("stub().returns with arrow function mock should work", () => {
|
|
84
|
-
// The existing arrow function syntax already works - this is the baseline
|
|
85
|
-
const results = runNudoTest(`
|
|
86
|
-
// @nudo:mock handler = (x) => x * 2
|
|
87
|
-
// @nudo:case "double" (21)
|
|
88
|
-
function double(x) {
|
|
89
|
-
return handler(x);
|
|
90
|
-
}
|
|
91
|
-
`);
|
|
92
|
-
expect(results[0].result).toBe("42");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("stub with _directReturn should work in evaluator", () => {
|
|
96
|
-
// Simulate what happens when a mock helper is bound to an env var
|
|
97
|
-
const env = createEnvironment();
|
|
98
|
-
const helper = stub.returns(T.number);
|
|
99
|
-
const typeVal = mockHelperToTypeValue(helper, env);
|
|
100
|
-
env.bind("myStub", typeVal);
|
|
101
|
-
|
|
102
|
-
// The evaluator should see _directReturn and return number
|
|
103
|
-
const ast = parse(`
|
|
104
|
-
// @nudo:case "test" ()
|
|
105
|
-
function testFn() {
|
|
106
|
-
return myStub();
|
|
107
|
-
}
|
|
108
|
-
`);
|
|
109
|
-
const directives = extractDirectives(ast);
|
|
110
|
-
const fn = directives[0];
|
|
111
|
-
const caseDir = fn.directives.find((d): d is CaseDirective => d.kind === "case")!;
|
|
112
|
-
const result = evaluateFunctionFull(fn.node, caseDir.args, env);
|
|
113
|
-
expect(typeValueToString(result.value)).toBe("number");
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe("stub().callsFake executes the fake with actual args (sinon semantics)", () => {
|
|
118
|
-
it("mockHelperToTypeValue binds the fake function itself, not a direct return of it", () => {
|
|
119
|
-
const env = createEnvironment();
|
|
120
|
-
const fake = parseTypeValueExpr("(x) => ({ v: x })");
|
|
121
|
-
expect(fake.kind).toBe("function");
|
|
122
|
-
const typeVal = mockHelperToTypeValue(stub.callsFake(fake), env);
|
|
123
|
-
// 调用语义 = 以实参执行 fake:直接绑定 fake 本身(同 @nudo:mock 箭头机制),
|
|
124
|
-
// 而非把 fake 函数值当 _directReturn 传播
|
|
125
|
-
expect(typeVal).toBe(fake);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("stub().callsFake((x) => ({ v: x })) called with 21 yields { v: 21 }", () => {
|
|
129
|
-
const results = runNudoTest(`
|
|
130
|
-
// @nudo:mock transform = stub().callsFake((x) => ({ v: x }))
|
|
131
|
-
// @nudo:case "direct" (21)
|
|
132
|
-
function applyTransform(x) {
|
|
133
|
-
return transform(x);
|
|
134
|
-
}
|
|
135
|
-
`);
|
|
136
|
-
expect(results[0].result).toBe("{ v: 21 }");
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("sinon.stub().callsFake((x) => ({ v: x })) called with 21 yields { v: 21 }", () => {
|
|
140
|
-
const results = runNudoTest(`
|
|
141
|
-
// @nudo:mock transform = sinon.stub().callsFake((x) => ({ v: x }))
|
|
142
|
-
// @nudo:case "direct" (21)
|
|
143
|
-
function applyTransform(x) {
|
|
144
|
-
return transform(x);
|
|
145
|
-
}
|
|
146
|
-
`);
|
|
147
|
-
expect(results[0].result).toBe("{ v: 21 }");
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("callsFake propagates precisely through arr.map(transform) HOF", () => {
|
|
151
|
-
const results = runNudoTest(`
|
|
152
|
-
// @nudo:mock transform = stub().callsFake((x) => ({ v: x }))
|
|
153
|
-
// @nudo:case "hof" ([1, 2, 3])
|
|
154
|
-
function mapAll(arr) {
|
|
155
|
-
return arr.map(transform);
|
|
156
|
-
}
|
|
157
|
-
`);
|
|
158
|
-
expect(results[0].result).toBe("[{ v: 1 }, { v: 2 }, { v: 3 }]");
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("withArgs(...).returns(...) matched args take priority; unmatched fall back to stub default", () => {
|
|
162
|
-
const results = runNudoTest(`
|
|
163
|
-
// @nudo:mock handler = stub().withArgs(21).returns("hit")
|
|
164
|
-
// @nudo:case "match" (21)
|
|
165
|
-
// @nudo:case "miss" (22)
|
|
166
|
-
function call(x) {
|
|
167
|
-
return handler(x);
|
|
168
|
-
}
|
|
169
|
-
`);
|
|
170
|
-
expect(results[0].result).toBe('"hit"');
|
|
171
|
-
expect(results[1].result).toBe("unknown");
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
});
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { parse, extractDirectives, type CaseDirective, type MockDirective, parseTypeValueExpr } from "@nudojs/parser";
|
|
3
|
-
import { typeValueToString, createEnvironment, T } from "@nudojs/core";
|
|
4
|
-
import { evaluateFunctionFull } from "../evaluator.js";
|
|
5
|
-
|
|
6
|
-
function inferWithMocks(source: string): { name: string; caseName: string; result: string }[] {
|
|
7
|
-
const ast = parse(source);
|
|
8
|
-
const directives = extractDirectives(ast);
|
|
9
|
-
const env = createEnvironment();
|
|
10
|
-
const results: { name: string; caseName: string; result: string }[] = [];
|
|
11
|
-
|
|
12
|
-
for (const fn of directives) {
|
|
13
|
-
for (const d of fn.directives) {
|
|
14
|
-
if (d.kind === "mock") {
|
|
15
|
-
if (d.arrowFn) {
|
|
16
|
-
const fnType = T.fn(d.arrowFn.params, d.arrowFn.body, env);
|
|
17
|
-
(fnType as any)._paramPatterns = d.arrowFn.paramPatterns;
|
|
18
|
-
env.bind(d.name, fnType);
|
|
19
|
-
} else if (d.expression) {
|
|
20
|
-
env.bind(d.name, parseTypeValueExpr(d.expression));
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const caseDirectives = fn.directives.filter((d): d is CaseDirective => d.kind === "case");
|
|
26
|
-
for (const dir of caseDirectives) {
|
|
27
|
-
const result = evaluateFunctionFull(fn.node, dir.args, env);
|
|
28
|
-
results.push({
|
|
29
|
-
name: fn.name,
|
|
30
|
-
caseName: dir.name,
|
|
31
|
-
result: typeValueToString(result.value),
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return results;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
describe("Real-world Mock Tests", () => {
|
|
39
|
-
// 测试1: 基础 filter
|
|
40
|
-
it("filter with predicate", () => {
|
|
41
|
-
const results = inferWithMocks(`
|
|
42
|
-
// @nudo:mock predicate = (x) => x > 3
|
|
43
|
-
// @nudo:case "nums" ([1, 2, 3, 4, 5])
|
|
44
|
-
function filterGreater(arr) {
|
|
45
|
-
const result = [];
|
|
46
|
-
for (const item of arr) {
|
|
47
|
-
if (predicate(item)) result.push(item);
|
|
48
|
-
}
|
|
49
|
-
return result;
|
|
50
|
-
}
|
|
51
|
-
`);
|
|
52
|
-
expect(results[0].result).toBe("[4, 5]");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// 测试2: map
|
|
56
|
-
it("map with transform", () => {
|
|
57
|
-
const results = inferWithMocks(`
|
|
58
|
-
// @nudo:mock transform = (x) => x * 2
|
|
59
|
-
// @nudo:case "nums" ([1, 2, 3])
|
|
60
|
-
function doubleAll(arr) {
|
|
61
|
-
return arr.map(transform);
|
|
62
|
-
}
|
|
63
|
-
`);
|
|
64
|
-
expect(results[0].result).toBe("[2, 4, 6]");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// 测试3: reduce
|
|
68
|
-
it("reduce with reducer", () => {
|
|
69
|
-
const results = inferWithMocks(`
|
|
70
|
-
// @nudo:mock reducer = (a, b) => a + b
|
|
71
|
-
// @nudo:case "nums" ([1, 2, 3, 4])
|
|
72
|
-
function sumAll(arr) {
|
|
73
|
-
return arr.reduce(reducer, 0);
|
|
74
|
-
}
|
|
75
|
-
`);
|
|
76
|
-
expect(results[0].result).toBe("10");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// 测试4: 多参数
|
|
80
|
-
it("multi-parameter mock", () => {
|
|
81
|
-
const results = inferWithMocks(`
|
|
82
|
-
// @nudo:mock combine = (a, b) => a + b * 10
|
|
83
|
-
// @nudo:case "nums" (3, 4)
|
|
84
|
-
function combineNums(a, b) {
|
|
85
|
-
return combine(a, b);
|
|
86
|
-
}
|
|
87
|
-
`);
|
|
88
|
-
expect(results[0].result).toBe("43");
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// 测试5: 函数组合
|
|
92
|
-
it("function composition", () => {
|
|
93
|
-
const results = inferWithMocks(`
|
|
94
|
-
// @nudo:mock f = (x) => x + 1
|
|
95
|
-
// @nudo:mock g = (x) => x * 2
|
|
96
|
-
// @nudo:case "num" (5)
|
|
97
|
-
function compose(x) {
|
|
98
|
-
return f(g(x));
|
|
99
|
-
}
|
|
100
|
-
`);
|
|
101
|
-
expect(results[0].result).toBe("11");
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// 测试6: 回调模式
|
|
105
|
-
it("callback pattern", () => {
|
|
106
|
-
const results = inferWithMocks(`
|
|
107
|
-
// @nudo:mock callback = (result) => result * 2
|
|
108
|
-
// @nudo:case "num" (21)
|
|
109
|
-
function processWithCallback(x) {
|
|
110
|
-
return callback(x);
|
|
111
|
-
}
|
|
112
|
-
`);
|
|
113
|
-
expect(results[0].result).toBe("42");
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// 测试7: 字符串操作
|
|
117
|
-
it("string operations", () => {
|
|
118
|
-
const results = inferWithMocks(`
|
|
119
|
-
// @nudo:mock transform = (s) => s.toUpperCase()
|
|
120
|
-
// @nudo:case "str" ("hello")
|
|
121
|
-
function transformStr(s) {
|
|
122
|
-
return transform(s);
|
|
123
|
-
}
|
|
124
|
-
`);
|
|
125
|
-
expect(results[0].result).toBe('"HELLO"');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// 测试8: 条件逻辑
|
|
129
|
-
it("conditional logic", () => {
|
|
130
|
-
const results = inferWithMocks(`
|
|
131
|
-
// @nudo:mock validator = (x) => x > 0 && x < 100
|
|
132
|
-
// @nudo:case "valid" (50)
|
|
133
|
-
// @nudo:case "invalid" (150)
|
|
134
|
-
function validate(x) {
|
|
135
|
-
return validator(x);
|
|
136
|
-
}
|
|
137
|
-
`);
|
|
138
|
-
expect(results[0].result).toBe("true");
|
|
139
|
-
expect(results[1].result).toBe("false");
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// 测试9: 返回数组
|
|
143
|
-
it("return array", () => {
|
|
144
|
-
const results = inferWithMocks(`
|
|
145
|
-
// @nudo:mock generator = (n) => [n, n * 2, n * 3]
|
|
146
|
-
// @nudo:case "num" (5)
|
|
147
|
-
function generateTriples(n) {
|
|
148
|
-
return generator(n);
|
|
149
|
-
}
|
|
150
|
-
`);
|
|
151
|
-
expect(results[0].result).toBe("[5, 10, 15]");
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// 测试10: 类型收窄
|
|
155
|
-
it("type narrowing", () => {
|
|
156
|
-
const results = inferWithMocks(`
|
|
157
|
-
// @nudo:mock processor = (x) => typeof x === "string" ? x.toUpperCase() : x
|
|
158
|
-
// @nudo:case "str" ("hello")
|
|
159
|
-
// @nudo:case "num" (42)
|
|
160
|
-
function process(x) {
|
|
161
|
-
return processor(x);
|
|
162
|
-
}
|
|
163
|
-
`);
|
|
164
|
-
expect(results[0].result).toBe('"HELLO"');
|
|
165
|
-
expect(results[1].result).toBe("42");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// 测试11: 无参数 mock
|
|
169
|
-
it("no-parameter mock", () => {
|
|
170
|
-
const results = inferWithMocks(`
|
|
171
|
-
// @nudo:mock getter = () => 42
|
|
172
|
-
// @nudo:case "empty" ()
|
|
173
|
-
function getValue() {
|
|
174
|
-
return getter();
|
|
175
|
-
}
|
|
176
|
-
`);
|
|
177
|
-
expect(results[0].result).toBe("42");
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// 测试12: 复杂表达式
|
|
181
|
-
it("complex expressions", () => {
|
|
182
|
-
const results = inferWithMocks(`
|
|
183
|
-
// @nudo:mock calc = (a, b) => (a + b) * (a - b)
|
|
184
|
-
// @nudo:case "nums" (5, 3)
|
|
185
|
-
function calculate(a, b) {
|
|
186
|
-
return calc(a, b);
|
|
187
|
-
}
|
|
188
|
-
`);
|
|
189
|
-
expect(results[0].result).toBe("16");
|
|
190
|
-
});
|
|
191
|
-
});
|