@webstudio-is/react-sdk 0.78.0 → 0.80.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.
Files changed (40) hide show
  1. package/lib/cjs/context.js +8 -2
  2. package/lib/cjs/css/normalize.js +23 -43
  3. package/lib/cjs/css/presets.js +1 -111
  4. package/lib/cjs/embed-template.js +45 -16
  5. package/lib/cjs/expression.js +137 -22
  6. package/lib/cjs/index.js +6 -2
  7. package/lib/cjs/props.js +32 -2
  8. package/lib/cjs/tree/create-elements-tree.js +7 -2
  9. package/lib/cjs/tree/root.js +15 -7
  10. package/lib/context.js +8 -2
  11. package/lib/css/normalize.js +23 -33
  12. package/lib/css/presets.js +1 -111
  13. package/lib/embed-template.js +45 -16
  14. package/lib/expression.js +137 -22
  15. package/lib/index.js +13 -5
  16. package/lib/props.js +32 -2
  17. package/lib/tree/create-elements-tree.js +10 -3
  18. package/lib/tree/root.js +16 -8
  19. package/lib/types/components/component-meta.d.ts +30 -0
  20. package/lib/types/context.d.ts +5 -2
  21. package/lib/types/css/normalize.d.ts +0 -520
  22. package/lib/types/css/presets.d.ts +0 -282
  23. package/lib/types/embed-template.d.ts +38 -0
  24. package/lib/types/expression.d.ts +12 -4
  25. package/lib/types/index.d.ts +1 -1
  26. package/lib/types/props.d.ts +10 -0
  27. package/lib/types/tree/create-elements-tree.d.ts +6 -5
  28. package/lib/types/tree/root.d.ts +5 -5
  29. package/package.json +17 -16
  30. package/src/context.tsx +17 -4
  31. package/src/css/normalize.ts +23 -32
  32. package/src/css/presets.ts +0 -110
  33. package/src/embed-template.test.ts +84 -4
  34. package/src/embed-template.ts +51 -18
  35. package/src/expression.test.ts +106 -12
  36. package/src/expression.ts +157 -26
  37. package/src/index.ts +6 -2
  38. package/src/props.ts +33 -3
  39. package/src/tree/create-elements-tree.tsx +19 -10
  40. package/src/tree/root.ts +24 -13
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  // webstudio custom opinionated presets
19
- import * as presets from "./presets";
19
+ import { borders, outline } from "./presets";
20
20
  import type { EmbedTemplateStyleDecl } from "../embed-template";
21
21
 
22
22
  export type Styles = EmbedTemplateStyleDecl[];
@@ -37,21 +37,13 @@ const boxSizing = {
37
37
  * box-sizing: border-box;
38
38
  }
39
39
  */
40
- const baseStyle = [
41
- boxSizing,
42
- ...presets.borders,
43
- ...presets.outline,
44
- ] satisfies Styles;
40
+ const baseStyle = [boxSizing, ...borders, ...outline] satisfies Styles;
45
41
 
46
42
  export const div = baseStyle;
47
43
  export const address = baseStyle;
48
44
  export const article = baseStyle;
49
45
  export const aside = baseStyle;
50
- export const blockquote = [
51
- ...baseStyle,
52
- ...presets.blockquote,
53
- ] satisfies Styles;
54
- export const figure = [...baseStyle, ...presets.margins] satisfies Styles;
46
+ export const figure = baseStyle;
55
47
  export const footer = baseStyle;
56
48
  export const header = baseStyle;
57
49
  export const main = baseStyle;
@@ -60,12 +52,12 @@ export const section = baseStyle;
60
52
  export const form = baseStyle;
61
53
  export const label = baseStyle;
62
54
 
63
- export const h1 = [...baseStyle, ...presets.h1] satisfies Styles;
64
- export const h2 = [...baseStyle, ...presets.h2] satisfies Styles;
65
- export const h3 = [...baseStyle, ...presets.h3] satisfies Styles;
66
- export const h4 = [...baseStyle, ...presets.h4] satisfies Styles;
67
- export const h5 = [...baseStyle, ...presets.h5] satisfies Styles;
68
- export const h6 = [...baseStyle, ...presets.h6] satisfies Styles;
55
+ export const h1 = baseStyle;
56
+ export const h2 = baseStyle;
57
+ export const h3 = baseStyle;
58
+ export const h4 = baseStyle;
59
+ export const h5 = baseStyle;
60
+ export const h6 = baseStyle;
69
61
 
70
62
  export const i = baseStyle;
71
63
 
@@ -76,7 +68,7 @@ export const li = baseStyle;
76
68
  export const ul = baseStyle;
77
69
  export const ol = baseStyle;
78
70
 
79
- export const p = [...baseStyle, ...presets.verticalMargins];
71
+ export const p = baseStyle;
80
72
  export const span = baseStyle;
81
73
 
82
74
  // @todo for now not applied to html, as we don't have html element
@@ -102,7 +94,7 @@ export const html = [
102
94
  value: { type: "unit", value: 4, unit: "number" },
103
95
  },
104
96
  boxSizing,
105
- ...presets.borders,
97
+ ...borders,
106
98
  ] satisfies Styles;
107
99
 
108
100
  /**
@@ -132,7 +124,7 @@ export const body = [
132
124
  property: "fontFamily",
133
125
  value: {
134
126
  type: "keyword",
135
- value: "Arial, sans-serif",
127
+ value: "Arial, Roboto, sans-serif",
136
128
  },
137
129
  },
138
130
  {
@@ -144,7 +136,7 @@ export const body = [
144
136
  value: { type: "unit", unit: "number", value: 1.2 },
145
137
  },
146
138
  boxSizing,
147
- ...presets.borders,
139
+ ...borders,
148
140
  ] satisfies Styles;
149
141
 
150
142
  /**
@@ -163,8 +155,7 @@ export const hr = [
163
155
  value: { type: "keyword", value: "inherit" },
164
156
  },
165
157
  boxSizing,
166
- ...presets.borders,
167
- ...presets.margins,
158
+ ...borders,
168
159
  ] satisfies Styles;
169
160
 
170
161
  /**
@@ -186,7 +177,7 @@ export const b = [
186
177
  value: { type: "keyword", value: "700" },
187
178
  },
188
179
  boxSizing,
189
- ...presets.borders,
180
+ ...borders,
190
181
  ] satisfies Styles;
191
182
  export const strong = b;
192
183
 
@@ -209,7 +200,7 @@ export const code = [
209
200
  value: { type: "unit", value: 1, unit: "em" },
210
201
  },
211
202
  boxSizing,
212
- ...presets.borders,
203
+ ...borders,
213
204
  ] satisfies Styles;
214
205
 
215
206
  export const kbd = code;
@@ -226,7 +217,7 @@ export const small = [
226
217
  value: { type: "unit", value: 80, unit: "%" },
227
218
  },
228
219
  boxSizing,
229
- ...presets.borders,
220
+ ...borders,
230
221
  ] satisfies Styles;
231
222
 
232
223
  /**
@@ -251,7 +242,7 @@ const subSupBase = [
251
242
  value: { type: "keyword", value: "baseline" },
252
243
  },
253
244
  boxSizing,
254
- ...presets.borders,
245
+ ...borders,
255
246
  ] satisfies Styles;
256
247
 
257
248
  export const sub = [
@@ -286,7 +277,7 @@ export const table = [
286
277
  property: "textIndent",
287
278
  value: { type: "unit", value: 0, unit: "number" },
288
279
  },
289
- ...presets.borders,
280
+ ...borders,
290
281
  /* 2 */
291
282
  {
292
283
  property: "borderTopColor",
@@ -349,7 +340,7 @@ const buttonBase = [
349
340
  value: { type: "unit", value: 0, unit: "number" },
350
341
  },
351
342
  boxSizing,
352
- ...presets.borders,
343
+ ...borders,
353
344
  ] satisfies Styles;
354
345
 
355
346
  export const input = buttonBase;
@@ -436,7 +427,7 @@ export const legend = [
436
427
  value: { type: "unit", value: 0, unit: "number" },
437
428
  },
438
429
  boxSizing,
439
- ...presets.borders,
430
+ ...borders,
440
431
  ] satisfies Styles;
441
432
 
442
433
  /**
@@ -449,7 +440,7 @@ export const progress = [
449
440
  value: { type: "keyword", value: "baseline" },
450
441
  },
451
442
  boxSizing,
452
- ...presets.borders,
443
+ ...borders,
453
444
  ] satisfies Styles;
454
445
 
455
446
  /**
@@ -512,5 +503,5 @@ export const summary = [
512
503
  value: { type: "keyword", value: "list-item" },
513
504
  },
514
505
  boxSizing,
515
- ...presets.borders,
506
+ ...borders,
516
507
  ] satisfies Styles;
@@ -25,113 +25,3 @@ export const outline: Styles = [
25
25
  value: { type: "unit", value: 1, unit: "px" },
26
26
  },
27
27
  ];
28
-
29
- export const margins = [
30
- {
31
- property: "marginTop",
32
- value: { type: "unit", value: 0, unit: "px" },
33
- },
34
- {
35
- property: "marginRight",
36
- value: { type: "unit", value: 0, unit: "px" },
37
- },
38
- {
39
- property: "marginBottom",
40
- value: { type: "unit", value: 0, unit: "px" },
41
- },
42
- {
43
- property: "marginLeft",
44
- value: { type: "unit", value: 0, unit: "px" },
45
- },
46
- ] satisfies Styles;
47
-
48
- export const verticalMargins = [
49
- {
50
- property: "marginTop",
51
- value: { type: "unit", value: 0, unit: "px" },
52
- },
53
- {
54
- property: "marginBottom",
55
- value: { type: "unit", value: 0, unit: "px" },
56
- },
57
- ] satisfies Styles;
58
-
59
- export const blockquote = [
60
- ...margins,
61
- {
62
- property: "paddingTop",
63
- value: { type: "unit", value: 10, unit: "px" },
64
- },
65
- {
66
- property: "paddingBottom",
67
- value: { type: "unit", value: 10, unit: "px" },
68
- },
69
- {
70
- property: "paddingLeft",
71
- value: { type: "unit", value: 20, unit: "px" },
72
- },
73
- {
74
- property: "paddingRight",
75
- value: { type: "unit", value: 20, unit: "px" },
76
- },
77
- {
78
- property: "borderLeftWidth",
79
- value: { type: "unit", value: 5, unit: "px" },
80
- },
81
- {
82
- property: "borderLeftStyle",
83
- value: { type: "keyword", value: "solid" },
84
- },
85
- {
86
- property: "borderLeftColor",
87
- value: { type: "rgb", r: 226, g: 226, b: 226, alpha: 1 },
88
- },
89
- ] satisfies Styles;
90
-
91
- export const h1 = [
92
- ...verticalMargins,
93
- {
94
- property: "fontSize",
95
- value: { type: "unit", value: 38, unit: "px" },
96
- },
97
- ] satisfies Styles;
98
-
99
- export const h2 = [
100
- ...verticalMargins,
101
- {
102
- property: "fontSize",
103
- value: { type: "unit", value: 32, unit: "px" },
104
- },
105
- ] satisfies Styles;
106
-
107
- export const h3 = [
108
- ...verticalMargins,
109
- {
110
- property: "fontSize",
111
- value: { type: "unit", value: 24, unit: "px" },
112
- },
113
- ] satisfies Styles;
114
-
115
- export const h4 = [
116
- ...verticalMargins,
117
- {
118
- property: "fontSize",
119
- value: { type: "unit", value: 18, unit: "px" },
120
- },
121
- ] satisfies Styles;
122
-
123
- export const h5 = [
124
- ...verticalMargins,
125
- {
126
- property: "fontSize",
127
- value: { type: "unit", value: 14, unit: "px" },
128
- },
129
- ] satisfies Styles;
130
-
131
- export const h6 = [
132
- ...verticalMargins,
133
- {
134
- property: "fontSize",
135
- value: { type: "unit", value: 12, unit: "px" },
136
- },
137
- ] satisfies Styles;
@@ -307,10 +307,7 @@ test("generate data for embedding from props bound to data source expressions",
307
307
  type: "string",
308
308
  name: "state",
309
309
  value: "initial",
310
- dataSourceRef: {
311
- type: "variable",
312
- name: "boxState",
313
- },
310
+ dataSourceRef: { type: "variable", name: "boxState" },
314
311
  },
315
312
  ],
316
313
  children: [],
@@ -384,3 +381,86 @@ test("generate data for embedding from props bound to data source expressions",
384
381
  styles: [],
385
382
  });
386
383
  });
384
+
385
+ test("generate data for embedding from action props", () => {
386
+ expect(
387
+ generateDataFromEmbedTemplate(
388
+ [
389
+ {
390
+ type: "instance",
391
+ component: "Box1",
392
+ props: [
393
+ {
394
+ type: "string",
395
+ name: "state",
396
+ value: "initial",
397
+ dataSourceRef: { type: "variable", name: "boxState" },
398
+ },
399
+ ],
400
+ children: [
401
+ {
402
+ type: "instance",
403
+ component: "Box2",
404
+ props: [
405
+ {
406
+ type: "action",
407
+ name: "onClick",
408
+ value: [{ type: "execute", code: `boxState = 'success'` }],
409
+ },
410
+ ],
411
+ children: [],
412
+ },
413
+ ],
414
+ },
415
+ ],
416
+ defaultBreakpointId
417
+ )
418
+ ).toEqual({
419
+ children: [{ type: "id", value: expectString }],
420
+ instances: [
421
+ {
422
+ type: "instance",
423
+ id: expectString,
424
+ component: "Box1",
425
+ children: [{ type: "id", value: expectString }],
426
+ },
427
+ { type: "instance", id: expectString, component: "Box2", children: [] },
428
+ ],
429
+ props: [
430
+ {
431
+ id: expectString,
432
+ instanceId: expectString,
433
+ type: "dataSource",
434
+ name: "state",
435
+ value: expectString,
436
+ },
437
+ {
438
+ id: expectString,
439
+ instanceId: expectString,
440
+ type: "action",
441
+ name: "onClick",
442
+ value: [
443
+ {
444
+ type: "execute",
445
+ code: expect.stringMatching(/\$ws\$dataSource\$\w+ = 'success'/),
446
+ },
447
+ ],
448
+ },
449
+ ],
450
+ dataSources: [
451
+ {
452
+ type: "variable",
453
+ id: expectString,
454
+ scopeInstanceId: expectString,
455
+ name: "boxState",
456
+ value: {
457
+ type: "string",
458
+ value: "initial",
459
+ },
460
+ },
461
+ ],
462
+ styleSourceSelections: [],
463
+ styleSources: [],
464
+ styles: [],
465
+ });
466
+ });
@@ -21,11 +21,13 @@ const EmbedTemplateText = z.object({
21
21
 
22
22
  type EmbedTemplateText = z.infer<typeof EmbedTemplateText>;
23
23
 
24
+ const DataSourceVariableRef = z.object({
25
+ type: z.literal("variable"),
26
+ name: z.string(),
27
+ });
28
+
24
29
  const DataSourceRef = z.union([
25
- z.object({
26
- type: z.literal("variable"),
27
- name: z.string(),
28
- }),
30
+ DataSourceVariableRef,
29
31
  z.object({
30
32
  type: z.literal("expression"),
31
33
  name: z.string(),
@@ -58,6 +60,16 @@ const EmbedTemplateProp = z.union([
58
60
  dataSourceRef: z.optional(DataSourceRef),
59
61
  value: z.array(z.string()),
60
62
  }),
63
+ z.object({
64
+ type: z.literal("action"),
65
+ name: z.string(),
66
+ value: z.array(
67
+ z.object({
68
+ type: z.literal("execute"),
69
+ code: z.string(),
70
+ })
71
+ ),
72
+ }),
61
73
  ]);
62
74
 
63
75
  type EmbedTemplateProp = z.infer<typeof EmbedTemplateProp>;
@@ -124,6 +136,29 @@ const createInstancesFromTemplate = (
124
136
  if (item.props) {
125
137
  for (const prop of item.props) {
126
138
  const propId = nanoid();
139
+ // action cannot be bound to data source
140
+ if (prop.type === "action") {
141
+ props.push({
142
+ id: propId,
143
+ instanceId,
144
+ type: "action",
145
+ name: prop.name,
146
+ value: prop.value.map((value) => {
147
+ return {
148
+ type: "execute",
149
+ // replace all references with variable names
150
+ code: validateExpression(value.code, {
151
+ effectful: true,
152
+ transformIdentifier: (ref) => {
153
+ const id = dataSourceByRef.get(ref)?.id ?? ref;
154
+ return encodeDataSourceVariable(id);
155
+ },
156
+ }),
157
+ };
158
+ }),
159
+ });
160
+ continue;
161
+ }
127
162
  if (prop.dataSourceRef === undefined) {
128
163
  props.push({ id: propId, instanceId, ...prop });
129
164
  continue;
@@ -148,7 +183,13 @@ const createInstancesFromTemplate = (
148
183
  id,
149
184
  scopeInstanceId: instanceId,
150
185
  name: dataSourceRef.name,
151
- code: dataSourceRef.code,
186
+ // replace all references with variable names
187
+ code: validateExpression(dataSourceRef.code, {
188
+ transformIdentifier: (ref) => {
189
+ const id = dataSourceByRef.get(ref)?.id ?? ref;
190
+ return encodeDataSourceVariable(id);
191
+ },
192
+ }),
152
193
  };
153
194
  dataSourceByRef.set(dataSourceRef.name, dataSource);
154
195
  } else {
@@ -246,25 +287,17 @@ export const generateDataFromEmbedTemplate = (
246
287
  defaultBreakpointId
247
288
  );
248
289
 
249
- // replace all references with variable names
250
- const dataSources: DataSource[] = [];
251
- for (const dataSource of dataSourceByRef.values()) {
252
- if (dataSource.type === "expression") {
253
- dataSource.code = validateExpression(dataSource.code, (ref) => {
254
- const id = dataSourceByRef.get(ref)?.id ?? ref;
255
- return encodeDataSourceVariable(id);
256
- });
257
- }
258
- dataSources.push(dataSource);
259
- }
260
-
261
290
  return {
262
291
  children,
263
292
  instances,
264
293
  props,
265
- dataSources,
294
+ dataSources: Array.from(dataSourceByRef.values()),
266
295
  styleSourceSelections,
267
296
  styleSources,
268
297
  styles,
269
298
  };
270
299
  };
300
+
301
+ export type EmbedTemplateData = ReturnType<
302
+ typeof generateDataFromEmbedTemplate
303
+ >;
@@ -2,8 +2,10 @@ import { expect, test } from "@jest/globals";
2
2
  import {
3
3
  decodeDataSourceVariable,
4
4
  encodeDataSourceVariable,
5
- executeExpressions,
6
- generateExpressionsComputation,
5
+ executeComputingExpressions,
6
+ executeEffectfulExpression,
7
+ generateComputingExpressions,
8
+ generateEffectfulExpression,
7
9
  validateExpression,
8
10
  } from "./expression";
9
11
 
@@ -17,6 +19,13 @@ test("allow unary and binary expressions", () => {
17
19
  expect(validateExpression(`[-1, 1 + 1]`)).toEqual(`[-1, 1 + 1]`);
18
20
  });
19
21
 
22
+ test("optionally allow assignment expressions", () => {
23
+ expect(() => {
24
+ validateExpression(`a = 2`);
25
+ }).toThrowError(/Cannot use assignment in this expression/);
26
+ expect(validateExpression(`a = 2`, { effectful: true })).toEqual(`a = 2`);
27
+ });
28
+
20
29
  test("forbid member expressions", () => {
21
30
  expect(() => {
22
31
  validateExpression("var1 + obj.param");
@@ -44,6 +53,21 @@ test("forbid ternary", () => {
44
53
  }).toThrowError(/Ternary operator is not supported/);
45
54
  });
46
55
 
56
+ test("forbid increment and decrement", () => {
57
+ expect(() => {
58
+ validateExpression("++var1");
59
+ }).toThrowError(/"\+\+" operator is not supported/);
60
+ expect(() => {
61
+ validateExpression("var1++");
62
+ }).toThrowError(/"\+\+" operator is not supported/);
63
+ expect(() => {
64
+ validateExpression("--var1");
65
+ }).toThrowError(/"--" operator is not supported/);
66
+ expect(() => {
67
+ validateExpression("var1--");
68
+ }).toThrowError(/"--" operator is not supported/);
69
+ });
70
+
47
71
  test("forbid multiple expressions", () => {
48
72
  expect(() => {
49
73
  validateExpression("a b");
@@ -57,12 +81,14 @@ test("forbid multiple expressions", () => {
57
81
  });
58
82
 
59
83
  test("transform identifiers", () => {
60
- expect(validateExpression(`a + b`, (id) => `$ws$${id}`)).toEqual(
61
- `$ws$a + $ws$b`
62
- );
84
+ expect(
85
+ validateExpression(`a + b`, {
86
+ transformIdentifier: (id) => `$ws$${id}`,
87
+ })
88
+ ).toEqual(`$ws$a + $ws$b`);
63
89
  });
64
90
 
65
- test("generate expressions computation", () => {
91
+ test("generate computing expressions", () => {
66
92
  const variables = new Set(["var0"]);
67
93
  const expressions = new Map([
68
94
  ["exp3", "exp2 + exp1"],
@@ -70,7 +96,7 @@ test("generate expressions computation", () => {
70
96
  ["exp2", "exp1"],
71
97
  ["exp4", "exp2"],
72
98
  ]);
73
- expect(generateExpressionsComputation(variables, expressions))
99
+ expect(generateComputingExpressions(expressions, variables))
74
100
  .toMatchInlineSnapshot(`
75
101
  "const var0 = _variables.get('var0');
76
102
  const exp1 = (var0);
@@ -86,10 +112,22 @@ test("generate expressions computation", () => {
86
112
  `);
87
113
  });
88
114
 
115
+ test("add only used variables in computing expression", () => {
116
+ const expressions = new Map([["exp1", "var0"]]);
117
+ expect(generateComputingExpressions(expressions, new Set(["var0", "var1"])))
118
+ .toMatchInlineSnapshot(`
119
+ "const var0 = _variables.get('var0');
120
+ const exp1 = (var0);
121
+ return new Map([
122
+ ['exp1', exp1],
123
+ ]);"
124
+ `);
125
+ });
126
+
89
127
  test("execute expression", () => {
90
128
  const variables = new Map();
91
129
  const expressions = new Map([["exp1", "1 + 1"]]);
92
- expect(executeExpressions(variables, expressions)).toEqual(
130
+ expect(executeComputingExpressions(expressions, variables)).toEqual(
93
131
  new Map([["exp1", 2]])
94
132
  );
95
133
  });
@@ -97,7 +135,7 @@ test("execute expression", () => {
97
135
  test("execute expression dependent on variables", () => {
98
136
  const variables = new Map([["var1", 5]]);
99
137
  const expressions = new Map([["exp1", "var1 + 1"]]);
100
- expect(executeExpressions(variables, expressions)).toEqual(
138
+ expect(executeComputingExpressions(expressions, variables)).toEqual(
101
139
  new Map([["exp1", 6]])
102
140
  );
103
141
  });
@@ -108,7 +146,7 @@ test("execute expression dependent on another expressions", () => {
108
146
  ["exp1", "exp0 + 1"],
109
147
  ["exp0", "var1 + 2"],
110
148
  ]);
111
- expect(executeExpressions(variables, expressions)).toEqual(
149
+ expect(executeComputingExpressions(expressions, variables)).toEqual(
112
150
  new Map([
113
151
  ["exp1", 6],
114
152
  ["exp0", 5],
@@ -124,7 +162,7 @@ test("forbid circular expressions", () => {
124
162
  ["exp2", "exp1 + 3"],
125
163
  ]);
126
164
  expect(() => {
127
- executeExpressions(variables, expressions);
165
+ executeComputingExpressions(expressions, variables);
128
166
  }).toThrowError(/Cannot access 'exp0' before initialization/);
129
167
  });
130
168
 
@@ -132,7 +170,7 @@ test("make sure dependency exists", () => {
132
170
  const variables = new Map();
133
171
  const expressions = new Map([["exp1", "var1 + 1"]]);
134
172
  expect(() => {
135
- executeExpressions(variables, expressions);
173
+ executeComputingExpressions(expressions, variables);
136
174
  }).toThrowError(/Unknown dependency "var1"/);
137
175
  });
138
176
 
@@ -145,3 +183,59 @@ test("encode/decode variable names", () => {
145
183
  );
146
184
  expect(decodeDataSourceVariable("myVarName")).toEqual(undefined);
147
185
  });
186
+
187
+ test("generate effectful expression", () => {
188
+ expect(
189
+ generateEffectfulExpression(`var0 = var0 + var1`, new Set(["var0", "var1"]))
190
+ ).toMatchInlineSnapshot(`
191
+ "let var0 = _variables.get('var0');
192
+ let var1 = _variables.get('var1');
193
+ var0 = var0 + var1;
194
+ return new Map([
195
+ ['var0', var0],
196
+ ]);"
197
+ `);
198
+
199
+ expect(
200
+ generateEffectfulExpression(`var0 = var1 + 1`, new Set(["var0", "var1"]))
201
+ ).toMatchInlineSnapshot(`
202
+ "let var1 = _variables.get('var1');
203
+ let var0;
204
+ var0 = var1 + 1;
205
+ return new Map([
206
+ ['var0', var0],
207
+ ]);"
208
+ `);
209
+ });
210
+
211
+ test("add only used variables in effectful expression", () => {
212
+ expect(
213
+ generateEffectfulExpression(
214
+ `var0 = var1 + 1`,
215
+ new Set(["var0", "var1", "var2"])
216
+ )
217
+ ).toMatchInlineSnapshot(`
218
+ "let var1 = _variables.get('var1');
219
+ let var0;
220
+ var0 = var1 + 1;
221
+ return new Map([
222
+ ['var0', var0],
223
+ ]);"
224
+ `);
225
+ });
226
+
227
+ test("forbid not allowed variables in effectful expression", () => {
228
+ expect(() => {
229
+ generateEffectfulExpression(`var0 = var0 + var1`, new Set(["var0"]));
230
+ }).toThrowError(/Unknown dependency "var1"/);
231
+ });
232
+
233
+ test("execute effectful expression", () => {
234
+ const variables = new Map([
235
+ ["var0", 2],
236
+ ["var1", 3],
237
+ ]);
238
+ expect(executeEffectfulExpression(`var0 = var0 + var1`, variables)).toEqual(
239
+ new Map([["var0", 5]])
240
+ );
241
+ });