@webstudio-is/react-sdk 0.79.0 → 0.81.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/lib/cjs/context.js +8 -2
- package/lib/cjs/css/normalize.js +1 -1
- package/lib/cjs/embed-template.js +45 -16
- package/lib/cjs/expression.js +137 -22
- package/lib/cjs/index.js +6 -2
- package/lib/cjs/props.js +32 -2
- package/lib/cjs/tree/create-elements-tree.js +7 -2
- package/lib/cjs/tree/root.js +15 -7
- package/lib/context.js +8 -2
- package/lib/css/normalize.js +1 -1
- package/lib/embed-template.js +45 -16
- package/lib/expression.js +137 -22
- package/lib/index.js +13 -5
- package/lib/props.js +32 -2
- package/lib/tree/create-elements-tree.js +10 -3
- package/lib/tree/root.js +16 -8
- package/lib/types/components/component-meta.d.ts +30 -0
- package/lib/types/context.d.ts +5 -2
- package/lib/types/embed-template.d.ts +38 -0
- package/lib/types/expression.d.ts +12 -4
- package/lib/types/index.d.ts +1 -1
- package/lib/types/props.d.ts +10 -0
- package/lib/types/tree/create-elements-tree.d.ts +6 -5
- package/lib/types/tree/root.d.ts +5 -5
- package/package.json +17 -16
- package/src/context.tsx +17 -4
- package/src/css/normalize.ts +1 -1
- package/src/embed-template.test.ts +84 -4
- package/src/embed-template.ts +51 -18
- package/src/expression.test.ts +106 -12
- package/src/expression.ts +157 -26
- package/src/index.ts +6 -2
- package/src/props.ts +33 -3
- package/src/tree/create-elements-tree.tsx +19 -10
- package/src/tree/root.ts +24 -13
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { type ComponentProps } from "react";
|
|
2
2
|
import type { ReadableAtom } from "nanostores";
|
|
3
3
|
import type { Assets } from "@webstudio-is/asset-uploader";
|
|
4
|
-
import type {
|
|
4
|
+
import type { Instance, Instances } from "@webstudio-is/project-build";
|
|
5
5
|
import type { Components } from "../components/components-utils";
|
|
6
|
-
import { type Params } from "../context";
|
|
6
|
+
import { type Params, type DataSourceValues } from "../context";
|
|
7
7
|
import type { Pages, PropsByInstanceId } from "../props";
|
|
8
8
|
import type { WebstudioComponent } from "./webstudio-component";
|
|
9
|
-
export declare const createElementsTree: ({ renderer, imageBaseUrl, assetBaseUrl, instances, rootInstanceId, propsByInstanceIdStore, assetsStore, pagesStore, dataSourceValuesStore, onDataSourceUpdate, Component, components, }: Params & {
|
|
9
|
+
export declare const createElementsTree: ({ renderer, imageBaseUrl, assetBaseUrl, instances, rootInstanceId, propsByInstanceIdStore, assetsStore, pagesStore, dataSourceValuesStore, executeEffectfulExpression, onDataSourceUpdate, Component, components, }: Params & {
|
|
10
10
|
instances: Map<string, {
|
|
11
11
|
type: "instance";
|
|
12
12
|
id: string;
|
|
@@ -24,8 +24,9 @@ export declare const createElementsTree: ({ renderer, imageBaseUrl, assetBaseUrl
|
|
|
24
24
|
propsByInstanceIdStore: ReadableAtom<PropsByInstanceId>;
|
|
25
25
|
assetsStore: ReadableAtom<Assets>;
|
|
26
26
|
pagesStore: ReadableAtom<Pages>;
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
executeEffectfulExpression: (expression: string, values: DataSourceValues) => DataSourceValues;
|
|
28
|
+
dataSourceValuesStore: ReadableAtom<DataSourceValues>;
|
|
29
|
+
onDataSourceUpdate: (newValues: DataSourceValues) => void;
|
|
29
30
|
Component: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
|
|
30
31
|
components: Components;
|
|
31
32
|
}) => JSX.Element | null;
|
package/lib/types/tree/root.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type ComponentProps } from "react";
|
|
2
|
-
import { type Build, type Page
|
|
2
|
+
import { type Build, type Page } from "@webstudio-is/project-build";
|
|
3
3
|
import type { Asset } from "@webstudio-is/asset-uploader";
|
|
4
4
|
import { WebstudioComponent } from "./webstudio-component";
|
|
5
5
|
import type { Components } from "../components/components-utils";
|
|
6
|
-
import type { Params } from "../context";
|
|
6
|
+
import type { Params, DataSourceValues } from "../context";
|
|
7
7
|
export type Data = {
|
|
8
8
|
page: Page;
|
|
9
9
|
pages: Array<Page>;
|
|
@@ -14,12 +14,12 @@ export type Data = {
|
|
|
14
14
|
export type RootPropsData = Omit<Data, "build"> & {
|
|
15
15
|
build: Pick<Data["build"], "instances" | "props" | "dataSources">;
|
|
16
16
|
};
|
|
17
|
-
type DataSourceValues = Map<DataSource["id"], unknown>;
|
|
18
17
|
type RootProps = {
|
|
19
18
|
data: RootPropsData;
|
|
20
|
-
|
|
19
|
+
executeComputingExpressions: (values: DataSourceValues) => DataSourceValues;
|
|
20
|
+
executeEffectfulExpression: (expression: string, values: DataSourceValues) => DataSourceValues;
|
|
21
21
|
Component?: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
|
|
22
22
|
components: Components;
|
|
23
23
|
};
|
|
24
|
-
export declare const InstanceRoot: ({ data,
|
|
24
|
+
export declare const InstanceRoot: ({ data, executeComputingExpressions, executeEffectfulExpression, Component, components, }: RootProps) => JSX.Element | null;
|
|
25
25
|
export {};
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webstudio-is/react-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.81.0",
|
|
4
4
|
"description": "Webstudio JavaScript / TypeScript API",
|
|
5
5
|
"author": "Webstudio <github@webstudio.is>",
|
|
6
6
|
"homepage": "https://webstudio.is",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"devDependencies": {
|
|
9
|
-
"@jest/globals": "^29.
|
|
10
|
-
"@remix-run/react": "^1.
|
|
9
|
+
"@jest/globals": "^29.6.1",
|
|
10
|
+
"@remix-run/react": "^1.18.1",
|
|
11
11
|
"@types/react": "^18.0.35",
|
|
12
12
|
"@types/react-dom": "^18.0.11",
|
|
13
|
-
"jest": "^29.
|
|
13
|
+
"jest": "^29.6.1",
|
|
14
14
|
"react": "^18.2.0",
|
|
15
15
|
"react-dom": "^18.2.0",
|
|
16
16
|
"type-fest": "^3.7.1",
|
|
@@ -21,25 +21,26 @@
|
|
|
21
21
|
"@webstudio-is/tsconfig": "^1.0.6"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"@remix-run/react": "^1.
|
|
24
|
+
"@remix-run/react": "^1.18.0",
|
|
25
25
|
"react": "^18.2.0",
|
|
26
26
|
"react-dom": "^18.2.0",
|
|
27
27
|
"zod": "^3.19.1"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@
|
|
30
|
+
"@jsep-plugin/assignment": "^1.2.1",
|
|
31
|
+
"@nanostores/react": "^0.7.1",
|
|
31
32
|
"detect-font": "^0.1.5",
|
|
32
|
-
"html-tags": "^3.
|
|
33
|
+
"html-tags": "^3.3.1",
|
|
33
34
|
"jsep": "^1.3.8",
|
|
34
|
-
"nanoevents": "^
|
|
35
|
-
"nanoid": "^
|
|
36
|
-
"nanostores": "^0.
|
|
37
|
-
"@webstudio-is/asset-uploader": "^0.
|
|
38
|
-
"@webstudio-is/css-data": "^0.
|
|
39
|
-
"@webstudio-is/css-engine": "^0.
|
|
40
|
-
"@webstudio-is/fonts": "^0.
|
|
41
|
-
"@webstudio-is/generate-arg-types": "^0.
|
|
42
|
-
"@webstudio-is/project-build": "^0.
|
|
35
|
+
"nanoevents": "^8.0.0",
|
|
36
|
+
"nanoid": "^4.0.2",
|
|
37
|
+
"nanostores": "^0.9.3",
|
|
38
|
+
"@webstudio-is/asset-uploader": "^0.81.0",
|
|
39
|
+
"@webstudio-is/css-data": "^0.81.0",
|
|
40
|
+
"@webstudio-is/css-engine": "^0.81.0",
|
|
41
|
+
"@webstudio-is/fonts": "^0.81.0",
|
|
42
|
+
"@webstudio-is/generate-arg-types": "^0.81.0",
|
|
43
|
+
"@webstudio-is/project-build": "^0.81.0"
|
|
43
44
|
},
|
|
44
45
|
"exports": {
|
|
45
46
|
".": {
|
package/src/context.tsx
CHANGED
|
@@ -31,13 +31,20 @@ export type Params = {
|
|
|
31
31
|
assetBaseUrl: string;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
export type DataSourceValues = Map<DataSource["id"], unknown>;
|
|
35
|
+
|
|
34
36
|
export const ReactSdkContext = createContext<
|
|
35
37
|
Params & {
|
|
36
38
|
propsByInstanceIdStore: ReadableAtom<PropsByInstanceId>;
|
|
37
39
|
assetsStore: ReadableAtom<Assets>;
|
|
38
40
|
pagesStore: ReadableAtom<Pages>;
|
|
39
|
-
dataSourceValuesStore: ReadableAtom<
|
|
40
|
-
|
|
41
|
+
dataSourceValuesStore: ReadableAtom<DataSourceValues>;
|
|
42
|
+
executeEffectfulExpression: (
|
|
43
|
+
expression: string,
|
|
44
|
+
values: DataSourceValues
|
|
45
|
+
) => DataSourceValues;
|
|
46
|
+
setDataSourceValues: (newValues: DataSourceValues) => void;
|
|
47
|
+
setBoundDataSourceValue: (
|
|
41
48
|
instanceId: Instance["id"],
|
|
42
49
|
prop: Prop["name"],
|
|
43
50
|
value: unknown
|
|
@@ -50,7 +57,13 @@ export const ReactSdkContext = createContext<
|
|
|
50
57
|
assetsStore: atom(new Map()),
|
|
51
58
|
pagesStore: atom(new Map()),
|
|
52
59
|
dataSourceValuesStore: atom(new Map()),
|
|
53
|
-
|
|
54
|
-
throw Error("React SDK
|
|
60
|
+
executeEffectfulExpression: () => {
|
|
61
|
+
throw Error("React SDK executeEffectfulExpression is not implemented");
|
|
62
|
+
},
|
|
63
|
+
setDataSourceValues: () => {
|
|
64
|
+
throw Error("React SDK setBoundDataSourceValue is not implemented");
|
|
65
|
+
},
|
|
66
|
+
setBoundDataSourceValue: () => {
|
|
67
|
+
throw Error("React SDK setBoundDataSourceValue is not implemented");
|
|
55
68
|
},
|
|
56
69
|
});
|
package/src/css/normalize.ts
CHANGED
|
@@ -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
|
+
});
|
package/src/embed-template.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
+
>;
|
package/src/expression.test.ts
CHANGED
|
@@ -2,8 +2,10 @@ import { expect, test } from "@jest/globals";
|
|
|
2
2
|
import {
|
|
3
3
|
decodeDataSourceVariable,
|
|
4
4
|
encodeDataSourceVariable,
|
|
5
|
-
|
|
6
|
-
|
|
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(
|
|
61
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
});
|