@mirta/testing 0.4.12 → 0.5.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/dist/index.d.mts +6 -15
- package/dist/index.mjs +123 -54
- package/package.json +8 -5
package/dist/index.d.mts
CHANGED
|
@@ -3,20 +3,15 @@ interface SimulatorInstance {
|
|
|
3
3
|
reset(): void;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
interface
|
|
7
|
-
|
|
8
|
-
* Отключает защиту от расхождения в поведении с настоящей функцией `defineRule`.
|
|
9
|
-
*
|
|
10
|
-
* Контроллер не отправляет одно и то же значение дважды при использовании `whenChanged`.
|
|
11
|
-
*/
|
|
12
|
-
allowSameValue?: boolean;
|
|
6
|
+
interface RunOptions {
|
|
7
|
+
force?: boolean;
|
|
13
8
|
}
|
|
14
9
|
interface DefineRuleSimulator extends SimulatorInstance {
|
|
15
10
|
/** Отправляет одно или несколько сообщений. */
|
|
16
|
-
run(payload: WbRules.MqttMessage | WbRules.MqttMessage[]): void;
|
|
11
|
+
run(payload: WbRules.MqttMessage | WbRules.MqttMessage[], options?: RunOptions): void;
|
|
17
12
|
}
|
|
18
13
|
/** Имитатор конструкции defineRule. */
|
|
19
|
-
declare function useDefineRule(
|
|
14
|
+
declare function useDefineRule(): DefineRuleSimulator;
|
|
20
15
|
|
|
21
16
|
interface WithDevice {
|
|
22
17
|
publish(controlId: string, value: WbRules.MqttValue): WithDevice;
|
|
@@ -67,12 +62,8 @@ interface CoreSimulator extends SimulatorInstance {
|
|
|
67
62
|
get trackMqtt(): TrackMqttSimulator;
|
|
68
63
|
defineZigbeeDevice(deviceId: string): ZigbeeDevice;
|
|
69
64
|
}
|
|
70
|
-
interface CoreSimulatorOptions {
|
|
71
|
-
/** Параметры имитатора `defineRule`. */
|
|
72
|
-
defineRule?: DefineRuleOptions;
|
|
73
|
-
}
|
|
74
65
|
/** Единая точка входа для настройки симуляции. */
|
|
75
|
-
declare function useSimulator(
|
|
66
|
+
declare function useSimulator(): CoreSimulator;
|
|
76
67
|
|
|
77
68
|
export { defineZigbeeDevice, useDefineRule, useGetControl, useGetDevice, useSimulator, useTrackMqtt };
|
|
78
|
-
export type { ControlValue,
|
|
69
|
+
export type { ControlValue, DefineRuleSimulator, GetControlSimulator, GetDeviceSimulator, TrackMqttSimulator, ZigbeeDevice };
|
package/dist/index.mjs
CHANGED
|
@@ -1,57 +1,106 @@
|
|
|
1
|
-
import { useEvent } from 'mirta';
|
|
1
|
+
import { useEvent, isObject } from 'mirta';
|
|
2
2
|
import { mock } from 'vitest-mock-extended';
|
|
3
3
|
|
|
4
|
-
class
|
|
5
|
-
constructor(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
+ ` got "${message.value.toString()}".`
|
|
9
|
-
+ `\nYou can suppress this error by passing 'allowSameValue: true' option to 'useDefineRule' call.`;
|
|
10
|
-
super(text);
|
|
4
|
+
class DevSetValueError extends Error {
|
|
5
|
+
constructor(property) {
|
|
6
|
+
super(`[dev] Missing control name in "${property}"`);
|
|
7
|
+
Error.captureStackTrace(this, DevSetValueError);
|
|
11
8
|
}
|
|
12
9
|
}
|
|
13
|
-
|
|
10
|
+
// Используем простой объект в качестве базового хранилища.
|
|
11
|
+
let state;
|
|
12
|
+
function setValueSilent(controlPath, value) {
|
|
13
|
+
state[controlPath] = value;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Создаёт прокси для объекта `dev`, который автоматически реагирует
|
|
17
|
+
* на изменения значений, запуская соответствующие симуляторы.
|
|
18
|
+
*
|
|
19
|
+
* @param options Объект с зависимостями симуляторов.
|
|
20
|
+
*/
|
|
21
|
+
function createDev({ trackMqtt, defineRule }) {
|
|
22
|
+
const handler = {
|
|
23
|
+
get(_target, prop) {
|
|
24
|
+
// Прозрачное чтение значения.
|
|
25
|
+
return state[prop];
|
|
26
|
+
},
|
|
27
|
+
set(_target, prop, value) {
|
|
28
|
+
const changed = state[prop] !== value;
|
|
29
|
+
// Устанавливаем новое значение в хранилище.
|
|
30
|
+
if (changed)
|
|
31
|
+
state[prop] = value;
|
|
32
|
+
const [deviceName, controlName] = prop.split('/');
|
|
33
|
+
if (!controlName)
|
|
34
|
+
throw new DevSetValueError(prop);
|
|
35
|
+
// Оповещаем о любом обновлении.
|
|
36
|
+
trackMqtt.publish({
|
|
37
|
+
topic: `/devices/${deviceName}/controls/${controlName}`,
|
|
38
|
+
value,
|
|
39
|
+
});
|
|
40
|
+
// Отправляем только изменённое значение.
|
|
41
|
+
if (changed)
|
|
42
|
+
defineRule.run({
|
|
43
|
+
topic: prop,
|
|
44
|
+
value,
|
|
45
|
+
}, {
|
|
46
|
+
force: true,
|
|
47
|
+
});
|
|
48
|
+
return true;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
function reset() {
|
|
52
|
+
state = {};
|
|
53
|
+
global.dev = new Proxy(state, handler);
|
|
54
|
+
}
|
|
55
|
+
reset();
|
|
56
|
+
// Возвращаем прокси, который будет использоваться как `global.dev`.
|
|
57
|
+
return {
|
|
58
|
+
reset,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Хранилище для экземпляра прокси, чтобы он был доступен при сбросе.
|
|
63
|
+
*/
|
|
64
|
+
let instance$4;
|
|
65
|
+
/**
|
|
66
|
+
* Возвращает инициализированный прокси для `dev`. Создает его, если он еще не существует.
|
|
67
|
+
* @param options Конфигурация с зависимостями.
|
|
68
|
+
*/
|
|
69
|
+
function useDev(options) {
|
|
70
|
+
return instance$4 ??= createDev(options);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function createInstance$2() {
|
|
14
74
|
let mqttEvent;
|
|
15
|
-
let values = {};
|
|
16
75
|
function reset() {
|
|
17
76
|
mqttEvent = useEvent();
|
|
18
|
-
values = {};
|
|
19
77
|
global.defineRule = (variantA, variantB) => {
|
|
20
78
|
const rule = typeof variantA !== 'string'
|
|
21
79
|
? variantA
|
|
22
80
|
: variantB;
|
|
23
81
|
if (!rule)
|
|
24
|
-
return;
|
|
82
|
+
return 0;
|
|
25
83
|
mqttEvent.on(({ topic, value }) => {
|
|
26
84
|
if (rule.whenChanged === topic)
|
|
27
85
|
rule.then(value);
|
|
28
86
|
});
|
|
87
|
+
return 0;
|
|
29
88
|
};
|
|
30
89
|
}
|
|
31
|
-
// Поведение движка wb-rules, отправляет только изменившиеся значения.
|
|
32
|
-
function isValueChanged(message) {
|
|
33
|
-
if (options.allowSameValue)
|
|
34
|
-
return true;
|
|
35
|
-
if (values[message.topic] === message.value) {
|
|
36
|
-
const error = new SameTopicValueError(message);
|
|
37
|
-
Error.captureStackTrace(error, isValueChanged);
|
|
38
|
-
throw error;
|
|
39
|
-
}
|
|
40
|
-
values[message.topic] = message.value;
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
90
|
/** Отправляет одно или несколько сообщений */
|
|
44
|
-
function run(payload) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
91
|
+
function run(payload, options = {}) {
|
|
92
|
+
const { force = false } = options;
|
|
93
|
+
payload = Array.isArray(payload) ? payload : [payload];
|
|
94
|
+
payload.forEach((item) => {
|
|
95
|
+
if (dev[item.topic] === item.value && !force) {
|
|
96
|
+
// Значение не изменилось, симуляторы не запускаем.
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Устанавливаем значение во внутреннее состояние.
|
|
100
|
+
setValueSilent(item.topic, item.value);
|
|
101
|
+
// Инициируем событие. Ивент не проверяет изменения, он просто вызывает коллбек.
|
|
102
|
+
mqttEvent.raise(item);
|
|
103
|
+
});
|
|
55
104
|
}
|
|
56
105
|
reset();
|
|
57
106
|
return {
|
|
@@ -61,10 +110,21 @@ function createInstance$2(options) {
|
|
|
61
110
|
}
|
|
62
111
|
// let instance: DefineRuleSimulator | undefined
|
|
63
112
|
/** Имитатор конструкции defineRule. */
|
|
64
|
-
function useDefineRule(
|
|
65
|
-
return /* instance ??= */ createInstance$2(
|
|
113
|
+
function useDefineRule() {
|
|
114
|
+
return /* instance ??= */ createInstance$2();
|
|
66
115
|
}
|
|
67
116
|
|
|
117
|
+
function parseDeviceTopic(topic) {
|
|
118
|
+
if (!topic.startsWith('/devices/'))
|
|
119
|
+
return;
|
|
120
|
+
const match = /^\/devices\/([^/]+)\/controls\/([^/]+)$/.exec(topic);
|
|
121
|
+
if (!match)
|
|
122
|
+
return;
|
|
123
|
+
return {
|
|
124
|
+
deviceName: match[1],
|
|
125
|
+
controlName: match[2],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
68
128
|
function createInstance$1() {
|
|
69
129
|
let mqttEvent;
|
|
70
130
|
function reset() {
|
|
@@ -77,17 +137,22 @@ function createInstance$1() {
|
|
|
77
137
|
};
|
|
78
138
|
}
|
|
79
139
|
function publish(payload) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
140
|
+
payload = Array.isArray(payload) ? payload : [payload];
|
|
141
|
+
payload.forEach((item) => {
|
|
142
|
+
const parts = parseDeviceTopic(item.topic);
|
|
143
|
+
if (parts) {
|
|
144
|
+
// Устанавливаем значение во внутреннее состояние.
|
|
145
|
+
setValueSilent(`${parts.deviceName}/${parts.controlName}`, item.value);
|
|
146
|
+
}
|
|
147
|
+
// Инициируем событие. Ивент не проверяет изменения, он просто вызывает коллбек.
|
|
148
|
+
mqttEvent.raise(item);
|
|
149
|
+
});
|
|
86
150
|
}
|
|
87
151
|
function withDevice(deviceId) {
|
|
88
152
|
return {
|
|
89
153
|
publish(controlId, value) {
|
|
90
|
-
|
|
154
|
+
// Устанавливаем значение через publish, чтобы не дублировать логику.
|
|
155
|
+
publish({
|
|
91
156
|
topic: `/devices/${deviceId}/controls/${controlId}`,
|
|
92
157
|
value,
|
|
93
158
|
});
|
|
@@ -128,21 +193,22 @@ function useGetDevice() {
|
|
|
128
193
|
}
|
|
129
194
|
|
|
130
195
|
function createInstance() {
|
|
131
|
-
let values = {};
|
|
132
196
|
function reset() {
|
|
133
|
-
|
|
134
|
-
global.getControl = (devicePath) => {
|
|
197
|
+
global.getControl = (controlPath) => {
|
|
135
198
|
return mock({
|
|
136
|
-
getValue: () =>
|
|
199
|
+
getValue: () => dev[controlPath],
|
|
200
|
+
setValue: rawValue => dev[controlPath] = isObject(rawValue)
|
|
201
|
+
? rawValue.value
|
|
202
|
+
: rawValue,
|
|
137
203
|
});
|
|
138
204
|
};
|
|
139
205
|
}
|
|
140
206
|
function defineValue(deviceId, controlId, value) {
|
|
141
|
-
|
|
207
|
+
dev[`${deviceId}/${controlId}`] = value;
|
|
142
208
|
}
|
|
143
209
|
function defineValues(presets) {
|
|
144
210
|
presets.forEach((preset) => {
|
|
145
|
-
|
|
211
|
+
dev[`${preset.deviceId}/${preset.controlId}`] = preset.value;
|
|
146
212
|
});
|
|
147
213
|
}
|
|
148
214
|
reset();
|
|
@@ -196,7 +262,7 @@ function defineZigbeeDevice(deviceId) {
|
|
|
196
262
|
};
|
|
197
263
|
}
|
|
198
264
|
|
|
199
|
-
function createSimulator(
|
|
265
|
+
function createSimulator() {
|
|
200
266
|
const simulators = {};
|
|
201
267
|
function reset() {
|
|
202
268
|
for (const key in simulators)
|
|
@@ -204,8 +270,11 @@ function createSimulator(options) {
|
|
|
204
270
|
}
|
|
205
271
|
const getDevice = (simulators.getDevice ??= useGetDevice());
|
|
206
272
|
const getControl = (simulators.getControl ??= useGetControl());
|
|
207
|
-
const defineRule = (simulators.defineRule ??= useDefineRule(
|
|
273
|
+
const defineRule = (simulators.defineRule ??= useDefineRule());
|
|
208
274
|
const trackMqtt = (simulators.trackMqtt ??= useTrackMqtt());
|
|
275
|
+
// Создаем и устанавливаем прокси на dev, передавая ему зависимости.
|
|
276
|
+
// Это должно быть сделано ПОСЛЕ инициализации defineRule и trackMqtt.
|
|
277
|
+
simulators.dev ??= useDev({ defineRule, trackMqtt });
|
|
209
278
|
return {
|
|
210
279
|
reset,
|
|
211
280
|
getDevice,
|
|
@@ -217,8 +286,8 @@ function createSimulator(options) {
|
|
|
217
286
|
}
|
|
218
287
|
let instance;
|
|
219
288
|
/** Единая точка входа для настройки симуляции. */
|
|
220
|
-
function useSimulator(
|
|
221
|
-
return instance ??= createSimulator(
|
|
289
|
+
function useSimulator() {
|
|
290
|
+
return instance ??= createSimulator();
|
|
222
291
|
}
|
|
223
292
|
|
|
224
293
|
export { defineZigbeeDevice, useDefineRule, useGetControl, useGetDevice, useSimulator, useTrackMqtt };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mirta/testing",
|
|
3
3
|
"description": "A unit-test runner equipped with built-in Wiren Board runtime emulation",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"license": "Unlicense",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mirta",
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"README.md"
|
|
15
15
|
],
|
|
16
16
|
"types": "./dist/index.d.mts",
|
|
17
|
+
"imports": {
|
|
18
|
+
"#*": "./src/*.js"
|
|
19
|
+
},
|
|
17
20
|
"exports": {
|
|
18
21
|
".": {
|
|
19
22
|
"import": {
|
|
@@ -39,12 +42,12 @@
|
|
|
39
42
|
},
|
|
40
43
|
"dependencies": {
|
|
41
44
|
"vitest-mock-extended": "^3.1.0",
|
|
42
|
-
"@mirta/env-loader": "0.
|
|
43
|
-
"@mirta/workspace": "0.
|
|
44
|
-
"mirta": "0.
|
|
45
|
+
"@mirta/env-loader": "0.5.0",
|
|
46
|
+
"@mirta/workspace": "0.5.0",
|
|
47
|
+
"mirta": "0.5.0"
|
|
45
48
|
},
|
|
46
49
|
"devDependencies": {
|
|
47
|
-
"@mirta/rollup": "0.
|
|
50
|
+
"@mirta/rollup": "0.5.0"
|
|
48
51
|
},
|
|
49
52
|
"scripts": {
|
|
50
53
|
"build:mono": "rollup -c"
|