@weborigami/language 0.6.9 → 0.6.10
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/index.ts +19 -4
- package/main.js +5 -2
- package/package.json +2 -2
- package/src/compiler/compile.js +11 -3
- package/src/compiler/origami.pegjs +4 -2
- package/src/compiler/parse.js +77 -68
- package/src/compiler/parserHelpers.js +13 -13
- package/src/handlers/getPackedPath.js +17 -0
- package/src/handlers/jpeg_handler.js +5 -0
- package/src/handlers/js_handler.js +3 -3
- package/src/handlers/json_handler.js +3 -1
- package/src/handlers/tsv_handler.js +1 -1
- package/src/handlers/yaml_handler.js +1 -1
- package/src/project/jsGlobals.js +3 -3
- package/src/protocols/package.js +3 -3
- package/src/runtime/asyncStorage.js +7 -0
- package/src/runtime/codeFragment.js +4 -3
- package/src/runtime/errors.js +82 -129
- package/src/runtime/evaluate.js +8 -77
- package/src/runtime/execute.js +82 -0
- package/src/runtime/explainReferenceError.js +248 -0
- package/src/runtime/explainTraverseError.js +77 -0
- package/src/runtime/expressionFunction.js +8 -7
- package/src/runtime/expressionObject.js +4 -3
- package/src/runtime/handleExtension.js +22 -8
- package/src/runtime/internal.js +1 -1
- package/src/runtime/interop.js +15 -0
- package/src/runtime/ops.js +24 -19
- package/src/runtime/symbols.js +0 -1
- package/src/runtime/typos.js +22 -3
- package/test/compiler/compile.test.js +7 -103
- package/test/compiler/parse.test.js +38 -31
- package/test/project/fixtures/withPackageJson/subfolder/README.md +1 -0
- package/test/runtime/errors.test.js +296 -0
- package/test/runtime/evaluate.test.js +110 -34
- package/test/runtime/execute.test.js +41 -0
- package/test/runtime/expressionObject.test.js +3 -3
- package/test/runtime/ops.test.js +36 -35
- package/test/runtime/typos.test.js +2 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import coreGlobals from "../../src/project/coreGlobals.js";
|
|
4
|
+
import { formatError } from "../../src/runtime/errors.js";
|
|
5
|
+
import evaluate from "../../src/runtime/evaluate.js";
|
|
6
|
+
|
|
7
|
+
const globals = await coreGlobals();
|
|
8
|
+
|
|
9
|
+
describe("formatError", () => {
|
|
10
|
+
describe("ReferenceError", () => {
|
|
11
|
+
test("identifies an undefined function", async () => {
|
|
12
|
+
await assertError(
|
|
13
|
+
`doesntExist()`,
|
|
14
|
+
`ReferenceError: Couldn't find the function or map to execute.
|
|
15
|
+
"doesntExist" is not in scope.
|
|
16
|
+
evaluating: \x1b[31mdoesntExist\x1b[0m`,
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("identifies the argument that produced an error", async () => {
|
|
21
|
+
await assertError(
|
|
22
|
+
`Tree.map(foo, (_) => _)`,
|
|
23
|
+
`ReferenceError: Tree.map: The map argument wasn't defined.
|
|
24
|
+
"foo" is not in scope.
|
|
25
|
+
For documentation, see https://weborigami.org/builtins/Tree/map
|
|
26
|
+
evaluating: \x1b[31mfoo\x1b[0m`,
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("references the Origami file that produced the error", async () => {
|
|
31
|
+
await assertError(
|
|
32
|
+
{
|
|
33
|
+
text: `foo()`,
|
|
34
|
+
name: "test.ori",
|
|
35
|
+
url: "file:///path/to/test.ori",
|
|
36
|
+
},
|
|
37
|
+
`ReferenceError: Couldn't find the function or map to execute.
|
|
38
|
+
"foo" is not in scope.
|
|
39
|
+
evaluating: \x1b[31mfoo\x1b[0m
|
|
40
|
+
at /path/to/test.ori:1:1`,
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("proposes typos using globals", async () => {
|
|
45
|
+
await assertError(
|
|
46
|
+
`Mat.max(1, 2)`,
|
|
47
|
+
`ReferenceError: Couldn't find the function or map to execute.
|
|
48
|
+
"Mat.max" is not in scope.
|
|
49
|
+
Perhaps you intended one of these: Map, Math
|
|
50
|
+
evaluating: \x1B[31mMat.max\x1B[0m`,
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("proposes typos using inherited keys", async () => {
|
|
55
|
+
await assertError(
|
|
56
|
+
`
|
|
57
|
+
{
|
|
58
|
+
data: 1
|
|
59
|
+
sub: {
|
|
60
|
+
date: 2
|
|
61
|
+
more: {
|
|
62
|
+
result: datu.toString()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}.sub.more.result
|
|
66
|
+
`,
|
|
67
|
+
`ReferenceError: Couldn't find the function or map to execute.
|
|
68
|
+
"datu.toString" is not in scope.
|
|
69
|
+
Perhaps you intended one of these: data, date
|
|
70
|
+
evaluating: \x1B[31mdatu.toString\x1B[0m
|
|
71
|
+
at line 7, column 25`,
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("proposes typos using scope keys", async () => {
|
|
76
|
+
const parent = {
|
|
77
|
+
"index.ori": "Index page",
|
|
78
|
+
};
|
|
79
|
+
await assertError(
|
|
80
|
+
`index.orj(1, 2, 3)`,
|
|
81
|
+
`ReferenceError: Couldn't find the function or map to execute.
|
|
82
|
+
"index.orj" is not in scope.
|
|
83
|
+
Perhaps you intended: index.ori
|
|
84
|
+
evaluating: \x1B[31mindex.orj\x1B[0m`,
|
|
85
|
+
{ parent },
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("proposes typos using stack keys", async () => {
|
|
90
|
+
await assertError(
|
|
91
|
+
`((userName) => userNme.toString())("Alice")`,
|
|
92
|
+
`ReferenceError: Couldn't find the function or map to execute.
|
|
93
|
+
"userNme.toString" is not in scope.
|
|
94
|
+
Perhaps you intended: userName
|
|
95
|
+
evaluating: \x1B[31muserNme.toString\x1B[0m`,
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("suggests spaces around math operations", async () => {
|
|
100
|
+
await assertError(
|
|
101
|
+
`(1+2).toString()`,
|
|
102
|
+
`ReferenceError: Tried to get a property of something that doesn't exist.
|
|
103
|
+
"1+2" is not in scope.
|
|
104
|
+
If you intended a math operation, Origami requires spaces around the operator: "1 + 2"
|
|
105
|
+
evaluating: \x1B[31m1+2\x1B[0m`,
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("suggests angle brackets for global with extensions", async () => {
|
|
110
|
+
// code with error: [ops.property, Performance, "ori"]
|
|
111
|
+
await assertError(
|
|
112
|
+
`performance.ori()`,
|
|
113
|
+
`ReferenceError: Couldn't find the function or map to execute.
|
|
114
|
+
"performance" is a global, but "ori" looks like a file extension.
|
|
115
|
+
If you intended to reference a file, use angle brackets: <performance.ori>
|
|
116
|
+
evaluating: \x1B[31mperformance.ori\x1B[0m`,
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("suggests angle brackets for property of global with extensions", async () => {
|
|
121
|
+
// code with error: [ops.property, Performance, "html"]
|
|
122
|
+
await assertError(
|
|
123
|
+
`(performance.html).toString()`,
|
|
124
|
+
`ReferenceError: Tried to get a property of something that doesn't exist.
|
|
125
|
+
"performance" is a global, but "html" looks like a file extension.
|
|
126
|
+
If you intended to reference a file, use angle brackets: <performance.html>
|
|
127
|
+
evaluating: \x1B[31mperformance.html\x1B[0m`,
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("suggests angle brackets for accidental local key", async () => {
|
|
132
|
+
// code with error: [ops.property, [[ops.inherited, 0], "posts"], ".ori"]
|
|
133
|
+
await assertError(
|
|
134
|
+
`{
|
|
135
|
+
posts: {}
|
|
136
|
+
index.html: posts.ori()
|
|
137
|
+
}`,
|
|
138
|
+
`ReferenceError: Couldn't find the function or map to execute.
|
|
139
|
+
"posts.ori" looks like a file reference, but is matching the local object property "posts".
|
|
140
|
+
If you intended to reference a file, use angle brackets: <posts.ori>
|
|
141
|
+
evaluating: \x1B[31mposts.ori\x1B[0m
|
|
142
|
+
at line 3, column 21`,
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("suggests angle brackets for accidental local parameter", async () => {
|
|
147
|
+
// code with error: [ops.property, [[ops.inherited, 0], "posts"], ".ori"]
|
|
148
|
+
await assertError(
|
|
149
|
+
`((posts) => posts.ori())(1)`,
|
|
150
|
+
`ReferenceError: Couldn't find the function or map to execute.
|
|
151
|
+
"posts.ori" looks like a file reference, but is matching the local parameter "posts".
|
|
152
|
+
If you intended to reference a file, use angle brackets: <posts.ori>
|
|
153
|
+
evaluating: \x1B[31mposts.ori\x1B[0m`,
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("suggests angle brackets for property of accidental local key", async () => {
|
|
158
|
+
// code with error: [ops.property, [[ops.inherited, 0], "posts"], "md"]
|
|
159
|
+
await assertError(
|
|
160
|
+
`{
|
|
161
|
+
posts: {}
|
|
162
|
+
index.html: (posts.md).toString()
|
|
163
|
+
}`,
|
|
164
|
+
`ReferenceError: Tried to get a property of something that doesn't exist.
|
|
165
|
+
"posts.md" looks like a file reference, but is matching the local object property "posts".
|
|
166
|
+
If you intended to reference a file, use angle brackets: <posts.md>
|
|
167
|
+
evaluating: \x1B[31mposts.md\x1B[0m
|
|
168
|
+
at line 3, column 22`,
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("handles a traversal failure inside a reference error", async () => {
|
|
173
|
+
const parent = {
|
|
174
|
+
post1: {
|
|
175
|
+
title: "First post",
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
await assertError(
|
|
179
|
+
`(post1/totle).toUpperCase()`,
|
|
180
|
+
`ReferenceError: Tried to get a property of something that doesn't exist.
|
|
181
|
+
This path returned undefined: post1/totle
|
|
182
|
+
evaluating: \x1B[31mpost1/totle\x1B[0m`,
|
|
183
|
+
{ parent },
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("suggests a fully-qualified name", async () => {
|
|
188
|
+
await assertError(
|
|
189
|
+
`repeat(3, "hi")`,
|
|
190
|
+
`ReferenceError: Couldn't find the function or map to execute.
|
|
191
|
+
"repeat" is not in scope.
|
|
192
|
+
Perhaps you intended: Origami.repeat
|
|
193
|
+
evaluating: \x1B[31mrepeat\x1B[0m`,
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe("TraverseError", () => {
|
|
199
|
+
test("suggests typos for failed path", async () => {
|
|
200
|
+
const parent = {
|
|
201
|
+
a: {
|
|
202
|
+
b: {
|
|
203
|
+
sub: {
|
|
204
|
+
// need 2+ characters for typos
|
|
205
|
+
c: 1,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
await assertError(
|
|
211
|
+
`a/b/sup/c`,
|
|
212
|
+
`TraverseError: A path hit a null or undefined value.
|
|
213
|
+
The path traversal ended unexpectedly at: a/b/sup/
|
|
214
|
+
Perhaps you intended: sub/
|
|
215
|
+
evaluating: \x1B[31msup/\x1B[0m`,
|
|
216
|
+
{ parent },
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("identifies when a numeric key failed", async () => {
|
|
221
|
+
const parent = {
|
|
222
|
+
map: new Map([[1, new Map([["a", true]])]]),
|
|
223
|
+
};
|
|
224
|
+
await assertError(
|
|
225
|
+
`map/1/a`,
|
|
226
|
+
`TraverseError: A path hit a null or undefined value.
|
|
227
|
+
The path traversal ended unexpectedly at: map/1/
|
|
228
|
+
Slash-separated keys are searched as strings. Here there's no string "1" key, but there is a number 1 key.
|
|
229
|
+
To get the value for that number key, use parentheses: map/(1)
|
|
230
|
+
evaluating: \x1B[31m1/\x1B[0m`,
|
|
231
|
+
{ parent },
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("identifies when a value couldn't be unpacked due to a missing extension handler", async () => {
|
|
236
|
+
const parent = {
|
|
237
|
+
"file.foo": Uint8Array.from("hello"),
|
|
238
|
+
};
|
|
239
|
+
await assertError(
|
|
240
|
+
`file.foo/bar`,
|
|
241
|
+
`TraverseError: A path hit binary file data that can't be unpacked.
|
|
242
|
+
The path traversal ended unexpectedly at: file.foo/
|
|
243
|
+
The value couldn't be unpacked because no file extension handler is registered for ".foo".
|
|
244
|
+
evaluating: \x1B[31mfile.foo/\x1B[0m`,
|
|
245
|
+
{ parent },
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("identifies when a value didn't need to be unpacked", async () => {
|
|
250
|
+
const parent = {
|
|
251
|
+
a: {
|
|
252
|
+
b: 1,
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
await assertError(
|
|
256
|
+
`a/b/`,
|
|
257
|
+
`TraverseError: A path tried to unpack data that's already unpacked.
|
|
258
|
+
The path traversal ended unexpectedly at: a/b/
|
|
259
|
+
You can drop the trailing slash and just use: b
|
|
260
|
+
evaluating: \x1B[31mb/\x1B[0m`,
|
|
261
|
+
{ parent },
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("identifies when a value didn't exist to be unpacked", async () => {
|
|
266
|
+
const parent = {
|
|
267
|
+
a: {},
|
|
268
|
+
};
|
|
269
|
+
await assertError(
|
|
270
|
+
`a/b/`,
|
|
271
|
+
`TraverseError: A path tried to unpack a value that doesn't exist.
|
|
272
|
+
The path traversal ended unexpectedly at: a/b/
|
|
273
|
+
evaluating: \x1B[31mb/\x1B[0m`,
|
|
274
|
+
{ parent },
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
async function assertError(source, expectedMessage, options) {
|
|
281
|
+
try {
|
|
282
|
+
await evaluate(source, {
|
|
283
|
+
globals,
|
|
284
|
+
object: null,
|
|
285
|
+
parent: {},
|
|
286
|
+
stack: [],
|
|
287
|
+
...options,
|
|
288
|
+
});
|
|
289
|
+
} catch (/** @type {any} */ error) {
|
|
290
|
+
const actualMessage = await formatError(error);
|
|
291
|
+
assert.strictEqual(actualMessage, expectedMessage);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
throw new Error("Expected an error to be thrown");
|
|
296
|
+
}
|
|
@@ -1,41 +1,117 @@
|
|
|
1
|
+
import { Tree } from "@weborigami/async-tree";
|
|
1
2
|
import assert from "node:assert";
|
|
2
3
|
import { describe, test } from "node:test";
|
|
3
|
-
|
|
4
|
-
import { SyncMap } from "@weborigami/async-tree";
|
|
5
4
|
import evaluate from "../../src/runtime/evaluate.js";
|
|
6
|
-
|
|
5
|
+
|
|
6
|
+
const globals = {
|
|
7
|
+
concat: (...args) => args.join(""),
|
|
8
|
+
greet: (name) => `Hello, ${name}!`,
|
|
9
|
+
name: "Alice",
|
|
10
|
+
};
|
|
7
11
|
|
|
8
12
|
describe("evaluate", () => {
|
|
9
|
-
test("
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
13
|
+
test("array", async () => {
|
|
14
|
+
await assertEvaluation("[]", []);
|
|
15
|
+
await assertEvaluation("[ 1, 2, 3, ]", [1, 2, 3]);
|
|
16
|
+
await assertEvaluation("[\n'a'\n'b'\n'c'\n]", ["a", "b", "c"]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("functionComposition", async () => {
|
|
20
|
+
await assertEvaluation("greet()", "Hello, undefined!");
|
|
21
|
+
await assertEvaluation("greet(name)", "Hello, Alice!");
|
|
22
|
+
await assertEvaluation("greet 'world'", "Hello, world!", { mode: "shell" });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("function call with spread", async () => {
|
|
26
|
+
await assertEvaluation(
|
|
27
|
+
`concat("Hello", ...[", ", name], "!")`,
|
|
28
|
+
"Hello, Alice!",
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("angle bracket path", async () => {
|
|
33
|
+
await assertEvaluation("<data>", "Bob", {
|
|
34
|
+
parent: {
|
|
35
|
+
data: "Bob",
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("object literal", async () => {
|
|
41
|
+
await assertEvaluation("{ message = greet(name) }", {
|
|
42
|
+
message: "Hello, Alice!",
|
|
43
|
+
});
|
|
44
|
+
await assertEvaluation(
|
|
45
|
+
"{ message = greet(name) }",
|
|
46
|
+
{
|
|
47
|
+
message: "Hello, Alice!",
|
|
48
|
+
},
|
|
49
|
+
{ mode: "shell" },
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("object with computed property key", async () => {
|
|
54
|
+
await assertEvaluation("{ [name] = greet(name) }", {
|
|
55
|
+
Alice: "Hello, Alice!",
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("number", async () => {
|
|
60
|
+
await assertEvaluation("1", 1);
|
|
61
|
+
await assertEvaluation("3.14159", 3.14159);
|
|
62
|
+
await assertEvaluation("-1", -1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("sync object", async () => {
|
|
66
|
+
await assertEvaluation("{a:1, b:2}", { a: 1, b: 2 });
|
|
67
|
+
await assertEvaluation("{ a: { b: { c: 0 } } }", { a: { b: { c: 0 } } });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("templateLiteral", async () => {
|
|
71
|
+
await assertEvaluation("`Hello, ${name}!`", "Hello, Alice!");
|
|
72
|
+
await assertEvaluation(
|
|
73
|
+
"`escape characters with \\`backslash\\``",
|
|
74
|
+
"escape characters with `backslash`",
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("merge", async () => {
|
|
79
|
+
{
|
|
80
|
+
assertEvaluation(
|
|
81
|
+
`
|
|
82
|
+
{
|
|
83
|
+
a: 1
|
|
84
|
+
...more
|
|
85
|
+
c: a
|
|
86
|
+
}
|
|
87
|
+
`,
|
|
88
|
+
{
|
|
89
|
+
a: 1,
|
|
90
|
+
b: 2,
|
|
91
|
+
c: 1,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
globals: {
|
|
95
|
+
more: {
|
|
96
|
+
b: 2,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
}
|
|
40
102
|
});
|
|
41
103
|
});
|
|
104
|
+
|
|
105
|
+
async function assertEvaluation(text, expected, options = {}) {
|
|
106
|
+
let result = await evaluate(text, {
|
|
107
|
+
globals,
|
|
108
|
+
...options,
|
|
109
|
+
});
|
|
110
|
+
if (result instanceof Function) {
|
|
111
|
+
result = await result();
|
|
112
|
+
}
|
|
113
|
+
if (Tree.isMaplike(result)) {
|
|
114
|
+
result = await Tree.plain(result);
|
|
115
|
+
}
|
|
116
|
+
assert.deepEqual(result, expected);
|
|
117
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
|
|
4
|
+
import { SyncMap } from "@weborigami/async-tree";
|
|
5
|
+
import execute from "../../src/runtime/execute.js";
|
|
6
|
+
import { createCode } from "../compiler/codeHelpers.js";
|
|
7
|
+
|
|
8
|
+
describe("execute", () => {
|
|
9
|
+
test("if object in function position isn't a function, can unpack it", async () => {
|
|
10
|
+
const fn = (...args) => args.join(",");
|
|
11
|
+
const packed = new String();
|
|
12
|
+
/** @type {any} */ (packed).unpack = async () => fn;
|
|
13
|
+
const code = createCode([packed, "a", "b", "c"]);
|
|
14
|
+
const result = await execute(code);
|
|
15
|
+
assert.equal(result, "a,b,c");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("if function has needsState, it gets the state", async () => {
|
|
19
|
+
const fn = (state) => {
|
|
20
|
+
return state;
|
|
21
|
+
};
|
|
22
|
+
fn.needsState = true;
|
|
23
|
+
const state = {};
|
|
24
|
+
const code = createCode([fn]);
|
|
25
|
+
const result = await execute(code, state);
|
|
26
|
+
assert.equal(result, state);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("if function has parentAsTarget, it gets bound to state.container", async () => {
|
|
30
|
+
/** @this {import("@weborigami/async-tree").SyncOrAsyncMap} */
|
|
31
|
+
const fn = function () {
|
|
32
|
+
return this;
|
|
33
|
+
};
|
|
34
|
+
fn.parentAsTarget = true;
|
|
35
|
+
const parent = new SyncMap();
|
|
36
|
+
const state = { parent };
|
|
37
|
+
const code = createCode([fn]);
|
|
38
|
+
const result = await execute(code, state);
|
|
39
|
+
assert.equal(result, parent);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -41,7 +41,7 @@ describe("expressionObject", () => {
|
|
|
41
41
|
test("can instantiate an Origami tree", async () => {
|
|
42
42
|
const entries = [
|
|
43
43
|
["name", "world"],
|
|
44
|
-
["message", [ops.
|
|
44
|
+
["message", [ops.deepText, "Hello, ", [[ops.inherited, 0], "name"], "!"]],
|
|
45
45
|
];
|
|
46
46
|
const context = new SyncMap();
|
|
47
47
|
const object = await expressionObject(entries, { object: context });
|
|
@@ -56,7 +56,7 @@ describe("expressionObject", () => {
|
|
|
56
56
|
const entries = [
|
|
57
57
|
[
|
|
58
58
|
[
|
|
59
|
-
ops.
|
|
59
|
+
ops.deepText,
|
|
60
60
|
[
|
|
61
61
|
[ops.inherited, 0],
|
|
62
62
|
[ops.literal, "name"], // references `name` on same object
|
|
@@ -103,7 +103,7 @@ describe("expressionObject", () => {
|
|
|
103
103
|
// Immediate maplike value, should have a slash
|
|
104
104
|
["object", [ops.object, ["b", [ops.literal, 2]]]],
|
|
105
105
|
// Computed key
|
|
106
|
-
[[ops.
|
|
106
|
+
[[ops.deepText, [ops.array, "data", ".json"]], 1],
|
|
107
107
|
];
|
|
108
108
|
const object = await expressionObject(entries);
|
|
109
109
|
assert.deepEqual(object[symbols.keys](), [
|