iobroker.statechart 0.1.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/LICENSE +21 -21
- package/README.md +59 -438
- package/admin/build/tab.js +133 -130
- package/admin/statechart.png +0 -0
- package/admin/{style.css → tab.css} +0 -2
- package/admin/tab_m.html +1 -1
- package/build/lib/chart-analysis.js +367 -0
- package/build/lib/chart-analysis.js.map +7 -0
- package/build/lib/chart-codegen.js +405 -0
- package/build/lib/chart-codegen.js.map +7 -0
- package/build/lib/chart-helpers.js +102 -0
- package/build/lib/chart-helpers.js.map +7 -0
- package/build/lib/chart-model.js +477 -0
- package/build/lib/chart-model.js.map +7 -0
- package/build/lib/chart-runtime.js +1521 -0
- package/build/lib/chart-runtime.js.map +7 -0
- package/build/lib/chart-structure.js +172 -0
- package/build/lib/chart-structure.js.map +7 -0
- package/build/lib/chart-validation.js +372 -0
- package/build/lib/chart-validation.js.map +7 -0
- package/build/main.js +638 -0
- package/build/main.js.map +7 -0
- package/io-package.json +31 -34
- package/package.json +21 -17
- package/admin/i18n/de.json +0 -4
- package/admin/i18n/en.json +0 -4
- package/admin/i18n/es.json +0 -4
- package/admin/i18n/fr.json +0 -4
- package/admin/i18n/it.json +0 -4
- package/admin/i18n/nl.json +0 -4
- package/admin/i18n/pl.json +0 -4
- package/admin/i18n/pt.json +0 -4
- package/admin/i18n/ru.json +0 -4
- package/admin/i18n/uk.json +0 -4
- package/admin/i18n/zh-cn.json +0 -4
- package/admin/jsonConfig.json +0 -19
- package/admin/statechart.svg +0 -38
- package/lib/adapter-config.d.ts +0 -19
- package/lib/chart-codegen.js +0 -436
- package/lib/chart-codegen.test.js +0 -132
- package/lib/chart-helpers.js +0 -74
- package/lib/chart-helpers.test.js +0 -38
- package/lib/chart-model.js +0 -577
- package/lib/chart-model.test.js +0 -189
- package/lib/chart-runtime.js +0 -797
- package/lib/chart-runtime.test.js +0 -699
- package/lib/chart-structure.js +0 -106
- package/lib/chart-validation.js +0 -328
- package/lib/chart-validation.test.js +0 -149
- package/main.js +0 -279
package/lib/chart-model.test.js
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const { expect } = require("chai");
|
|
4
|
-
const { createChart, createExport, nameToId, parseExport, parseTransitionLabel } = require("./chart-model");
|
|
5
|
-
|
|
6
|
-
/** @returns {import("./chart-model").Chart} */
|
|
7
|
-
function createTestChart() {
|
|
8
|
-
return {
|
|
9
|
-
version: 1,
|
|
10
|
-
name: "Light",
|
|
11
|
-
enabled: true,
|
|
12
|
-
initial: "off",
|
|
13
|
-
symbols: [
|
|
14
|
-
{ name: "motion", scope: "input", stateId: "hm-rpc.0.ABC.MOTION" },
|
|
15
|
-
{ name: "count", scope: "local", initial: 0 },
|
|
16
|
-
],
|
|
17
|
-
states: [
|
|
18
|
-
{ id: "off", name: "Off", x: 0, y: 0 },
|
|
19
|
-
{ id: "on", name: "On", entry: "count++;", x: 200, y: 0 },
|
|
20
|
-
{ id: "dim", name: "Dim", parent: "on", x: 10, y: 40 },
|
|
21
|
-
],
|
|
22
|
-
transitions: [{ id: "t1", source: "off", target: "on", label: "motion[motion]", priority: 1 }],
|
|
23
|
-
functions: [{ id: "f1", code: "function f() {}", x: 0, y: 200 }],
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
describe("chart-model => export and import", () => {
|
|
28
|
-
it("imports what was exported", () => {
|
|
29
|
-
const chart = createTestChart();
|
|
30
|
-
const [imported] = parseExport(JSON.stringify(createExport([chart])));
|
|
31
|
-
expect(imported).to.deep.equal({
|
|
32
|
-
...chart,
|
|
33
|
-
states: chart.states.map(s => ({ parent: null, ...s })),
|
|
34
|
-
// charts without notes get an empty list
|
|
35
|
-
notes: [],
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("imports files exported before the adapter was renamed", () => {
|
|
40
|
-
const old = { ...createExport([createTestChart()]), format: "iobroker.state-machine" };
|
|
41
|
-
expect(parseExport(JSON.stringify(old)).map(c => c.name)).to.deep.equal(["Light"]);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("accepts a single chart without the export wrapper", () => {
|
|
45
|
-
const charts = parseExport(JSON.stringify(createTestChart()));
|
|
46
|
-
expect(charts.map(c => c.name)).to.deep.equal(["Light"]);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("fills in defaults of old or hand written charts", () => {
|
|
50
|
-
const [chart] = parseExport(
|
|
51
|
-
JSON.stringify({ states: [{ id: "a" }], transitions: [], symbols: [], initial: "unknown" }),
|
|
52
|
-
);
|
|
53
|
-
expect(chart).to.deep.include({ name: "Chart", initial: undefined, functions: [], version: 1 });
|
|
54
|
-
expect(chart.states).to.deep.equal([{ id: "a", name: "a", parent: null, x: 0, y: 0 }]);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* @param {(chart: any) => void} modify
|
|
59
|
-
* @returns {string} the error message
|
|
60
|
-
*/
|
|
61
|
-
const importError = modify => {
|
|
62
|
-
const chart = createTestChart();
|
|
63
|
-
modify(chart);
|
|
64
|
-
try {
|
|
65
|
-
parseExport(JSON.stringify(createExport([chart])));
|
|
66
|
-
} catch (err) {
|
|
67
|
-
return err.message;
|
|
68
|
-
}
|
|
69
|
-
return "no error";
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
it("rejects invalid files", () => {
|
|
73
|
-
expect(() => parseExport("{ no json")).to.throw(/No valid JSON/);
|
|
74
|
-
expect(() => parseExport('{"format": "something else"}')).to.throw(/contains no charts/);
|
|
75
|
-
expect(() => parseExport(JSON.stringify({ ...createExport([]), version: 99 }))).to.throw(/newer version/);
|
|
76
|
-
expect(() => parseExport(JSON.stringify(createExport([])))).to.throw(/contains no charts/);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("rejects invalid charts and tells where the problem is", () => {
|
|
80
|
-
expect(importError(c => (c.version = 2))).to.match(/^Chart 1: created with a newer version/);
|
|
81
|
-
expect(importError(c => delete c.states)).to.equal('Chart 1: "states" is missing or no list');
|
|
82
|
-
expect(importError(c => c.states.push({ id: "on" }))).to.equal(
|
|
83
|
-
"Chart 1: state on: the ID is used more than once",
|
|
84
|
-
);
|
|
85
|
-
expect(importError(c => (c.functions[0].id = "off"))).to.match(
|
|
86
|
-
/function block off: the ID is used more than once/,
|
|
87
|
-
);
|
|
88
|
-
expect(importError(c => (c.transitions[0].target = "gone"))).to.equal(
|
|
89
|
-
'Chart 1: transition t1: unknown target state "gone"',
|
|
90
|
-
);
|
|
91
|
-
expect(importError(c => (c.states[2].parent = "gone"))).to.equal(
|
|
92
|
-
'Chart 1: state dim: unknown parent state "gone"',
|
|
93
|
-
);
|
|
94
|
-
expect(importError(c => (c.states[1].parent = "dim"))).to.match(/the parent states form a cycle/);
|
|
95
|
-
expect(importError(c => (c.symbols[1].name = "motion"))).to.match(
|
|
96
|
-
/variable motion: the name is used more than once/,
|
|
97
|
-
);
|
|
98
|
-
expect(importError(c => (c.symbols[0].name = "1x"))).to.match(/variable 1: invalid name/);
|
|
99
|
-
expect(importError(c => (c.symbols[0].scope = "magic"))).to.match(/invalid kind "magic"/);
|
|
100
|
-
expect(importError(c => (c.states[1].entry = 5))).to.match(/state on: "entry" is no text/);
|
|
101
|
-
expect(importError(c => (c.notes = [{ id: "on", text: "x" }]))).to.match(
|
|
102
|
-
/note on: the ID is used more than once/,
|
|
103
|
-
);
|
|
104
|
-
expect(importError(c => (c.notes = [{ id: "n1", text: "x", parent: "gone" }]))).to.match(
|
|
105
|
-
/note n1: unknown parent state "gone"/,
|
|
106
|
-
);
|
|
107
|
-
expect(importError(c => (c.transitions[0].comment = 3))).to.match(/transition t1: "comment" is no text/);
|
|
108
|
-
expect(importError(c => (c.symbols[0].trigger = "sometimes"))).to.match(/invalid trigger "sometimes"/);
|
|
109
|
-
expect(importError(c => (c.symbols[0].ack = "maybe"))).to.match(/invalid ack filter "maybe"/);
|
|
110
|
-
expect(importError(c => (c.symbols[0].debounceMs = -5))).to.match(/"debounceMs" is no number of 0 or more/);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("imports notes and comments", () => {
|
|
114
|
-
const chart = createTestChart();
|
|
115
|
-
chart.comment = "About the chart";
|
|
116
|
-
chart.states[0].comment = "Light is off";
|
|
117
|
-
chart.notes = [{ id: "n1", text: "Remember", parent: "on", x: 5, y: 5 }];
|
|
118
|
-
const [imported] = parseExport(JSON.stringify(createExport([chart])));
|
|
119
|
-
expect(imported.comment).to.equal("About the chart");
|
|
120
|
-
expect(imported.states[0].comment).to.equal("Light is off");
|
|
121
|
-
expect(imported.notes).to.deep.equal([{ id: "n1", text: "Remember", parent: "on", x: 5, y: 5 }]);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe("chart-model => parseTransitionLabel", () => {
|
|
126
|
-
it("returns an empty result for an empty label", () => {
|
|
127
|
-
expect(parseTransitionLabel("")).to.deep.equal({});
|
|
128
|
-
expect(parseTransitionLabel(undefined)).to.deep.equal({});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("parses event, condition and action", () => {
|
|
132
|
-
expect(parseTransitionLabel("motion[motion == true]{light = true;}")).to.deep.equal({
|
|
133
|
-
event: "motion",
|
|
134
|
-
condition: "motion == true",
|
|
135
|
-
action: "light = true;",
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("parses a condition only", () => {
|
|
140
|
-
expect(parseTransitionLabel("[temp > 20 && window]")).to.deep.equal({ condition: "temp > 20 && window" });
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("allows nested brackets", () => {
|
|
144
|
-
expect(parseTransitionLabel("[[1, 2].includes(mode)]{ if (x) { y = 1; } }")).to.deep.equal({
|
|
145
|
-
condition: "[1, 2].includes(mode)",
|
|
146
|
-
action: "if (x) { y = 1; }",
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("parses labels spanning multiple lines", () => {
|
|
151
|
-
expect(
|
|
152
|
-
parseTransitionLabel("motion\n[motion &&\n !blocked]\n{\n light = true;\n count++;\n}"),
|
|
153
|
-
).to.deep.equal({
|
|
154
|
-
event: "motion",
|
|
155
|
-
condition: "motion &&\n !blocked",
|
|
156
|
-
action: "light = true;\n count++;",
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("parses temporal transitions", () => {
|
|
161
|
-
expect(parseTransitionLabel("after(5, sec)")).to.deep.equal({ afterMs: 5000 });
|
|
162
|
-
expect(parseTransitionLabel("after(1.5,min)[!motion]")).to.deep.equal({
|
|
163
|
-
afterMs: 90_000,
|
|
164
|
-
condition: "!motion",
|
|
165
|
-
});
|
|
166
|
-
expect(parseTransitionLabel("after(200, msec)")).to.deep.equal({ afterMs: 200 });
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it("rejects invalid labels", () => {
|
|
170
|
-
expect(() => parseTransitionLabel("after(5, hours)")).to.throw(/Invalid temporal expression/);
|
|
171
|
-
expect(() => parseTransitionLabel("[a > 1")).to.throw(/Missing/);
|
|
172
|
-
expect(() => parseTransitionLabel("a b")).to.throw(/Unexpected/);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe("chart-model => createChart", () => {
|
|
177
|
-
it("creates an empty, paused chart", () => {
|
|
178
|
-
expect(createChart("New")).to.include({ name: "New", enabled: false });
|
|
179
|
-
expect(createChart("New").states).to.deep.equal([]);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
describe("chart-model => nameToId", () => {
|
|
184
|
-
it("creates valid object IDs", () => {
|
|
185
|
-
expect(nameToId("Licht Flur")).to.equal("licht_flur");
|
|
186
|
-
expect(nameToId("Größe/Höhe")).to.equal("groesse_hoehe");
|
|
187
|
-
expect(nameToId("...")).to.equal("chart");
|
|
188
|
-
});
|
|
189
|
-
});
|