@ray-js/lamp-schedule-core 1.0.1-beta-10 → 1.0.2-beta-1
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/conflict/ConflictResolver.js +48 -21
- package/lib/conflict/__test__/ConflictResolver.test.js +49 -0
- package/lib/conflict/__test__/scheduleDataManager.test.js +66 -0
- package/lib/conflict/__test__/transform.test.js +5 -1
- package/lib/conflict/index.js +27 -7
- package/lib/conflict/scheduleDataManager.js +28 -33
- package/lib/context/rhythms/actions.d.ts +13 -17
- package/lib/context/rhythms/reducer.d.ts +2 -23
- package/lib/context/rhythms/reducer.js +7 -8
- package/lib/context/schedule/reducer.d.ts +5 -2
- package/lib/dpParser/__test__/autoDispatch.test.d.ts +1 -0
- package/lib/dpParser/__test__/autoDispatch.test.js +45 -0
- package/lib/dpParser/__test__/rhythms.test.js +90 -14
- package/lib/dpParser/__test__/rtcTimer.test.js +35 -0
- package/lib/dpParser/__test__/sleep.test.js +17 -0
- package/lib/dpParser/__test__/wakeup.test.js +17 -4
- package/lib/dpParser/__test__/wakeupSigmesh.test.d.ts +1 -0
- package/lib/dpParser/__test__/wakeupSigmesh.test.js +126 -0
- package/lib/hooks/__test__/useBaseLightDp.test.d.ts +1 -0
- package/lib/hooks/__test__/useBaseLightDp.test.js +251 -0
- package/lib/hooks/__test__/useCommonSupport.test.d.ts +1 -0
- package/lib/hooks/__test__/useCommonSupport.test.js +204 -0
- package/lib/hooks/__test__/useTimerFlushList.test.d.ts +1 -0
- package/lib/hooks/__test__/useTimerFlushList.test.js +117 -0
- package/lib/hooks/__test__/useTimerOperate.test.js +239 -5
- package/lib/hooks/__test__/useTimerOperateLocal.test.js +161 -53
- package/lib/types/rhythms.d.ts +25 -13
- package/lib/types/ty.d.ts +3 -0
- package/lib/utils/ScheduleDataSync.js +4 -1
- package/lib/utils/ScheduleLogger.js +1 -1
- package/lib/utils/__test__/ScheduleDataSync.test.d.ts +11 -0
- package/lib/utils/__test__/ScheduleDataSync.test.js +137 -0
- package/lib/utils/__test__/ScheduleEmit.test.d.ts +1 -0
- package/lib/utils/__test__/ScheduleEmit.test.js +35 -0
- package/lib/utils/__test__/ScheduleSupport.test.js +288 -154
- package/package.json +1 -1
|
@@ -62,18 +62,28 @@ function doRangesOverlap(aStart, aEnd, bStart, bEnd) {
|
|
|
62
62
|
return true;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
// 情况2:
|
|
66
|
-
//
|
|
67
|
-
if (
|
|
65
|
+
// 情况2: 单时间点的特殊处理
|
|
66
|
+
// 如果两个时间范围都是单点(开始等于结束),且时间点相同,则认为冲突
|
|
67
|
+
if (aStartMin === aEndMin && bStartMin === bEndMin && aStartMin === bStartMin) {
|
|
68
68
|
return true;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
// 情况3:
|
|
72
|
-
//
|
|
73
|
-
|
|
71
|
+
// 情况3: 单点与时间范围的重叠判断
|
|
72
|
+
// 如果一个是单点,一个是范围,单点在范围内(包含边界)则冲突
|
|
73
|
+
if (aStartMin === aEndMin) {
|
|
74
|
+
// a是单点,判断是否在b范围内(考虑跨天场景)
|
|
75
|
+
const normalizedPoint = bIsCrossDay && aStartMin < bStartMin ? aStartMin + 1440 : aStartMin;
|
|
76
|
+
return normalizedPoint >= bStartMin && normalizedPoint <= bEndMin;
|
|
77
|
+
}
|
|
78
|
+
if (bStartMin === bEndMin) {
|
|
79
|
+
// b是单点,判断是否在a范围内(考虑跨天场景)
|
|
80
|
+
const normalizedPoint = aIsCrossDay && bStartMin < aStartMin ? bStartMin + 1440 : bStartMin;
|
|
81
|
+
return normalizedPoint >= aStartMin && normalizedPoint <= aEndMin;
|
|
82
|
+
}
|
|
74
83
|
|
|
75
|
-
// 情况4:
|
|
84
|
+
// 情况4: 标准区间重叠判断
|
|
76
85
|
// 使用标准区间重叠判断: [a1,a2] 和 [b1,b2] 重叠当且仅当 a2 > b1 && b2 > a1
|
|
86
|
+
// 注意:这里已经处理了跨天情况(通过标准化为连续分钟数)
|
|
77
87
|
return aEndMin > bStartMin && bEndMin > aStartMin;
|
|
78
88
|
}
|
|
79
89
|
|
|
@@ -127,18 +137,39 @@ function convertToTimeRanges(schedules) {
|
|
|
127
137
|
const endMinutes = parseTimeToMinutes(endTime);
|
|
128
138
|
const isCrossDay = endMinutes < startMinutes;
|
|
129
139
|
|
|
130
|
-
//
|
|
131
|
-
const
|
|
132
|
-
|
|
140
|
+
// 单次任务使用当前星期
|
|
141
|
+
const currentDayOfWeek = daysOfWeek[week];
|
|
142
|
+
const result = [];
|
|
143
|
+
|
|
144
|
+
// 添加当天的时间范围
|
|
145
|
+
result.push({
|
|
133
146
|
id: schedule.id,
|
|
134
147
|
type: schedule.type,
|
|
135
|
-
dayOfWeek:
|
|
148
|
+
dayOfWeek: currentDayOfWeek,
|
|
136
149
|
status: schedule.data.status,
|
|
137
150
|
startMinutes,
|
|
138
151
|
endMinutes,
|
|
139
152
|
originalSchedule: schedule,
|
|
140
153
|
isCrossDay
|
|
141
|
-
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// 如果是跨天任务,还需要添加下一天的时间范围(从午夜到结束时间)
|
|
157
|
+
if (isCrossDay) {
|
|
158
|
+
const nextDayOfWeek = daysOfWeek[(week + 1) % 7];
|
|
159
|
+
result.push({
|
|
160
|
+
id: schedule.id,
|
|
161
|
+
type: schedule.type,
|
|
162
|
+
dayOfWeek: nextDayOfWeek,
|
|
163
|
+
status: schedule.data.status,
|
|
164
|
+
startMinutes: 0,
|
|
165
|
+
// 从午夜开始
|
|
166
|
+
endMinutes: endMinutes,
|
|
167
|
+
// 到结束时间
|
|
168
|
+
originalSchedule: schedule,
|
|
169
|
+
isCrossDay: false // 这部分不再是跨天
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
142
173
|
} catch (error) {
|
|
143
174
|
scheduleLogger.error('Error processing single task:', error, schedule);
|
|
144
175
|
return [];
|
|
@@ -252,21 +283,17 @@ export function checkConflicts(scheduleList, currentSchedule) {
|
|
|
252
283
|
scheduleLogger.debug('checkConflicts otherRanges:', otherRanges);
|
|
253
284
|
currentRanges.forEach(current => {
|
|
254
285
|
otherRanges.forEach(other => {
|
|
255
|
-
var _rule$shouldIgnore, _current$originalSche
|
|
286
|
+
var _rule$shouldIgnore, _current$originalSche;
|
|
256
287
|
// 应用规则中的忽略条件
|
|
257
288
|
if ((_rule$shouldIgnore = rule.shouldIgnore) !== null && _rule$shouldIgnore !== void 0 && _rule$shouldIgnore.call(rule, current, other)) {
|
|
258
289
|
return;
|
|
259
290
|
}
|
|
260
291
|
const currentPreId = current === null || current === void 0 || (_current$originalSche = current.originalSchedule) === null || _current$originalSche === void 0 ? void 0 : _current$originalSche.prevId;
|
|
261
|
-
const currentId = current === null || current === void 0 || (_current$originalSche2 = current.originalSchedule) === null || _current$originalSche2 === void 0 ? void 0 : _current$originalSche2.id;
|
|
262
292
|
const otherId = other.id;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// FIXME:还是存在bug
|
|
268
|
-
// 如果当前定时自身修改 且 与自身冲突 并且其他定时不等于当前定时id 则忽略
|
|
269
|
-
if (currentPreId && currentId === currentPreId && otherId !== currentId) {
|
|
293
|
+
|
|
294
|
+
// 如果是更新操作,忽略与旧版本自身的冲突判断
|
|
295
|
+
// 这里通过 prevId 来标识这是一个更新操作,并且要忽略与旧版本的冲突
|
|
296
|
+
if (currentPreId && otherId === currentPreId) {
|
|
270
297
|
return;
|
|
271
298
|
}
|
|
272
299
|
if (current.dayOfWeek && current.status && other.status && current.dayOfWeek === other.dayOfWeek && rule.isConflict(current, other)) {
|
|
@@ -88,6 +88,55 @@ describe('ConflictResolver', () => {
|
|
|
88
88
|
const conflicts = checkConflicts([schedule1], schedule2);
|
|
89
89
|
expect(conflicts.length).toBe(1);
|
|
90
90
|
});
|
|
91
|
+
it('should detect conflicts when cross-day range overlaps single point schedule', () => {
|
|
92
|
+
const crossDaySchedule = {
|
|
93
|
+
id: 'timer_1',
|
|
94
|
+
type: EScheduleFunctionType.TIMER,
|
|
95
|
+
data: {
|
|
96
|
+
status: true,
|
|
97
|
+
weeks: [1, 0, 0, 0, 0, 0, 0],
|
|
98
|
+
startTime: '23:00',
|
|
99
|
+
endTime: '01:00'
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const singlePointSchedule = {
|
|
103
|
+
id: 'timer_2',
|
|
104
|
+
type: EScheduleFunctionType.TIMER,
|
|
105
|
+
data: {
|
|
106
|
+
status: true,
|
|
107
|
+
weeks: [0, 1, 0, 0, 0, 0, 0],
|
|
108
|
+
startTime: '00:30',
|
|
109
|
+
endTime: '00:30'
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const conflicts = checkConflicts([crossDaySchedule], singlePointSchedule);
|
|
113
|
+
expect(conflicts.length).toBe(1);
|
|
114
|
+
expect(conflicts[0].other).toBe(crossDaySchedule);
|
|
115
|
+
});
|
|
116
|
+
it('should not detect conflicts when single point lies outside cross-day range', () => {
|
|
117
|
+
const crossDaySchedule = {
|
|
118
|
+
id: 'timer_1',
|
|
119
|
+
type: EScheduleFunctionType.TIMER,
|
|
120
|
+
data: {
|
|
121
|
+
status: true,
|
|
122
|
+
weeks: [1, 0, 0, 0, 0, 0, 0],
|
|
123
|
+
startTime: '23:00',
|
|
124
|
+
endTime: '01:00'
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const singlePointSchedule = {
|
|
128
|
+
id: 'timer_2',
|
|
129
|
+
type: EScheduleFunctionType.TIMER,
|
|
130
|
+
data: {
|
|
131
|
+
status: true,
|
|
132
|
+
weeks: [0, 1, 0, 0, 0, 0, 0],
|
|
133
|
+
startTime: '01:30',
|
|
134
|
+
endTime: '01:30'
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const conflicts = checkConflicts([crossDaySchedule], singlePointSchedule);
|
|
138
|
+
expect(conflicts.length).toBe(0);
|
|
139
|
+
});
|
|
91
140
|
|
|
92
141
|
// 测试关闭状态的计划不会产生冲突
|
|
93
142
|
it('should not detect conflicts with schedules that are turned off', () => {
|
|
@@ -100,6 +100,72 @@ describe('ScheduleDataManager', () => {
|
|
|
100
100
|
expect(data.length).toBe(1);
|
|
101
101
|
expect(data[0]).toEqual(schedule2); // 应该是更新后的数据
|
|
102
102
|
});
|
|
103
|
+
it('should keep only the latest item when duplicate ids are provided in one call', () => {
|
|
104
|
+
const instance = ScheduleDataManager.getInstance();
|
|
105
|
+
const first = {
|
|
106
|
+
id: 'dup',
|
|
107
|
+
type: EScheduleFunctionType.TIMER,
|
|
108
|
+
data: {
|
|
109
|
+
status: true,
|
|
110
|
+
weeks: [1, 0, 0, 0, 0, 0, 0],
|
|
111
|
+
startTime: '08:00',
|
|
112
|
+
endTime: '08:30'
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const second = {
|
|
116
|
+
id: 'dup',
|
|
117
|
+
type: EScheduleFunctionType.TIMER,
|
|
118
|
+
data: {
|
|
119
|
+
status: true,
|
|
120
|
+
weeks: [1, 0, 0, 0, 0, 0, 0],
|
|
121
|
+
startTime: '09:00',
|
|
122
|
+
endTime: '09:30'
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
instance.setData([first, second]);
|
|
126
|
+
const data = instance.getData();
|
|
127
|
+
expect(data.length).toBe(1);
|
|
128
|
+
expect(data[0]).toEqual(second);
|
|
129
|
+
});
|
|
130
|
+
it('should flatten nested arrays and deduplicate by id with last occurrence winning', () => {
|
|
131
|
+
const instance = ScheduleDataManager.getInstance();
|
|
132
|
+
const version1 = {
|
|
133
|
+
id: 'nested',
|
|
134
|
+
type: EScheduleFunctionType.TIMER,
|
|
135
|
+
data: {
|
|
136
|
+
status: true,
|
|
137
|
+
weeks: [1, 0, 0, 0, 0, 0, 0],
|
|
138
|
+
startTime: '07:00',
|
|
139
|
+
endTime: '07:30'
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const version2 = {
|
|
143
|
+
id: 'nested',
|
|
144
|
+
type: EScheduleFunctionType.TIMER,
|
|
145
|
+
data: {
|
|
146
|
+
status: true,
|
|
147
|
+
weeks: [1, 0, 0, 0, 0, 0, 0],
|
|
148
|
+
startTime: '10:00',
|
|
149
|
+
endTime: '10:30'
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const another = {
|
|
153
|
+
id: 'another',
|
|
154
|
+
type: EScheduleFunctionType.TIMER,
|
|
155
|
+
data: {
|
|
156
|
+
status: true,
|
|
157
|
+
weeks: [0, 1, 0, 0, 0, 0, 0],
|
|
158
|
+
startTime: '12:00',
|
|
159
|
+
endTime: '12:30'
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
instance.setData([[version1], [version2, another]]);
|
|
163
|
+
const data = instance.getData();
|
|
164
|
+
expect(data.length).toBe(2);
|
|
165
|
+
expect(data).toContainEqual(version2);
|
|
166
|
+
expect(data).toContainEqual(another);
|
|
167
|
+
expect(data).not.toContainEqual(version1);
|
|
168
|
+
});
|
|
103
169
|
|
|
104
170
|
// 测试清空数据
|
|
105
171
|
it('should clear all data', () => {
|
|
@@ -48,7 +48,11 @@ describe('transform', () => {
|
|
|
48
48
|
status: true,
|
|
49
49
|
weeks: [1, 0, 0, 0, 0, 0, 0],
|
|
50
50
|
startTime: '10:00',
|
|
51
|
-
endTime: '10:00'
|
|
51
|
+
endTime: '10:00',
|
|
52
|
+
dps: {},
|
|
53
|
+
aliasName: 'aliasName',
|
|
54
|
+
isAppPush: false,
|
|
55
|
+
id: '1'
|
|
52
56
|
});
|
|
53
57
|
expect(result.id).toMatch(/^timer_/);
|
|
54
58
|
});
|
package/lib/conflict/index.js
CHANGED
|
@@ -30,6 +30,13 @@ export class Conflict {
|
|
|
30
30
|
const instance = ScheduleDataManager.getInstance();
|
|
31
31
|
const preList = instance.getData() || [];
|
|
32
32
|
const [cur] = transScheduleListToConflictList([current]);
|
|
33
|
+
if (!cur) {
|
|
34
|
+
scheduleLogger.error('Conflict.add: 无法转换当前日程', current);
|
|
35
|
+
return {
|
|
36
|
+
isConflict: false,
|
|
37
|
+
conflictList: []
|
|
38
|
+
};
|
|
39
|
+
}
|
|
33
40
|
scheduleLogger.debug('Conflict.add cur, current:', cur, current);
|
|
34
41
|
const conflictList = checkConflicts(preList, cur, Conflict.rule);
|
|
35
42
|
const isConflict = conflictList.length > 0;
|
|
@@ -69,17 +76,30 @@ export class Conflict {
|
|
|
69
76
|
const instance = ScheduleDataManager.getInstance();
|
|
70
77
|
const [cur, prev] = transScheduleListToConflictList([current, prevSchedule]);
|
|
71
78
|
scheduleLogger.debug('Conflict.update1:', cur, prev);
|
|
79
|
+
|
|
80
|
+
// 先删除旧数据,避免在冲突检测时干扰
|
|
72
81
|
const preList = instance.getData() || [];
|
|
73
|
-
|
|
82
|
+
let preItem;
|
|
83
|
+
if (prev && prev.id) {
|
|
84
|
+
preItem = preList.find(i => i.id === prev.id);
|
|
85
|
+
if (preItem) {
|
|
86
|
+
instance.deleteData(preItem);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 使用清理后的列表进行冲突检测
|
|
91
|
+
const updatedPreList = instance.getData() || [];
|
|
92
|
+
const _current = _objectSpread(_objectSpread({}, cur), prev && prev.id ? {
|
|
74
93
|
prevId: prev.id
|
|
75
|
-
});
|
|
76
|
-
const conflictList = checkConflicts(
|
|
94
|
+
} : {});
|
|
95
|
+
const conflictList = checkConflicts(updatedPreList, _current, Conflict.rule);
|
|
77
96
|
const isConflict = conflictList.length > 0;
|
|
78
|
-
if (!isConflict
|
|
79
|
-
//
|
|
80
|
-
const preItem = preList.find(i => i.id === prev.id);
|
|
81
|
-
preItem && instance.deleteData(preItem);
|
|
97
|
+
if (!isConflict) {
|
|
98
|
+
// 如果没有冲突,添加新数据
|
|
82
99
|
instance.setData(_current);
|
|
100
|
+
} else if (preItem) {
|
|
101
|
+
// 如果有冲突,恢复旧数据
|
|
102
|
+
instance.setData(preItem);
|
|
83
103
|
}
|
|
84
104
|
return {
|
|
85
105
|
isConflict,
|
|
@@ -18,42 +18,37 @@ export class ScheduleDataManager {
|
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (Array.isArray(item)) {
|
|
26
|
-
item.forEach(_item => {
|
|
27
|
-
if (!_item || !_item.id) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
// 如果跟之前id相同需要先删除
|
|
31
|
-
newList = newList.filter(i => i.id !== _item.id);
|
|
32
|
-
// 添加新项
|
|
33
|
-
newList.push(_item);
|
|
34
|
-
});
|
|
35
|
-
} else {
|
|
36
|
-
if (!item || !item.id) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
// 如果跟之前id相同需要先删除
|
|
40
|
-
newList = newList.filter(i => i.id !== item.id);
|
|
41
|
-
// 添加新项
|
|
42
|
-
newList.push(item);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
} else {
|
|
46
|
-
if (!value || !value.id) {
|
|
21
|
+
// 收集所有要处理的项目,并按照最后一次出现的顺序去重
|
|
22
|
+
const itemsMap = new Map();
|
|
23
|
+
const addItem = item => {
|
|
24
|
+
if (!item || !item.id) {
|
|
47
25
|
return;
|
|
48
26
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
27
|
+
if (itemsMap.has(item.id)) {
|
|
28
|
+
itemsMap.delete(item.id);
|
|
29
|
+
}
|
|
30
|
+
itemsMap.set(item.id, item);
|
|
31
|
+
};
|
|
32
|
+
const collectItems = input => {
|
|
33
|
+
if (Array.isArray(input)) {
|
|
34
|
+
input.forEach(subItem => {
|
|
35
|
+
if (Array.isArray(subItem)) {
|
|
36
|
+
collectItems(subItem);
|
|
37
|
+
} else {
|
|
38
|
+
addItem(subItem);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
} else {
|
|
42
|
+
addItem(input);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
collectItems(value);
|
|
46
|
+
if (itemsMap.size === 0) {
|
|
47
|
+
return;
|
|
53
48
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
ScheduleDataManager.list =
|
|
49
|
+
const idsToAdd = new Set(itemsMap.keys());
|
|
50
|
+
const filteredList = ScheduleDataManager.list.filter(item => !idsToAdd.has(item.id));
|
|
51
|
+
ScheduleDataManager.list = [...filteredList, ...itemsMap.values()];
|
|
57
52
|
}
|
|
58
53
|
clearData() {
|
|
59
54
|
ScheduleDataManager.list = [];
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export declare const
|
|
7
|
-
export declare const
|
|
8
|
-
export declare const updateLocalMessage: (data: Record<string, any>) => IReturn;
|
|
9
|
-
export declare const updateUI: (data: Record<string, any>) => IReturn;
|
|
10
|
-
export declare const initSystemInfo: (data: Record<string, any>) => IReturn;
|
|
11
|
-
export declare const updateDp: (data: Record<string, any>) => IReturn;
|
|
1
|
+
import { RhythmAction, RhythmMessage, TRhythmData, LocalMessage, IState } from '../../types/rhythms';
|
|
2
|
+
export declare const updateRhythmsMessage: (data: RhythmMessage) => RhythmAction;
|
|
3
|
+
export declare const updateRhythms: (data: TRhythmData[]) => RhythmAction;
|
|
4
|
+
export declare const updateLocalMessage: (data: LocalMessage) => RhythmAction;
|
|
5
|
+
export declare const updateUI: (data: Partial<IState>) => RhythmAction;
|
|
6
|
+
export declare const initSystemInfo: (data: any) => RhythmAction;
|
|
7
|
+
export declare const updateDp: (data: Record<string, any>) => RhythmAction;
|
|
12
8
|
declare const _default: {
|
|
13
|
-
updateLocalMessage: (data:
|
|
14
|
-
updateRhythms: (data:
|
|
15
|
-
updateRhythmsMessage: (data:
|
|
16
|
-
updateUI: (data:
|
|
17
|
-
initSystemInfo: (data:
|
|
18
|
-
updateDp: (data: Record<string, any>) =>
|
|
9
|
+
updateLocalMessage: (data: LocalMessage) => RhythmAction;
|
|
10
|
+
updateRhythms: (data: TRhythmData[]) => RhythmAction;
|
|
11
|
+
updateRhythmsMessage: (data: RhythmMessage) => RhythmAction;
|
|
12
|
+
updateUI: (data: Partial<IState>) => RhythmAction;
|
|
13
|
+
initSystemInfo: (data: any) => RhythmAction;
|
|
14
|
+
updateDp: (data: Record<string, any>) => RhythmAction;
|
|
19
15
|
};
|
|
20
16
|
export default _default;
|
|
@@ -1,24 +1,3 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { IState } from '../../types/rhythms';
|
|
3
|
-
export declare const useRhythmReducer: (init: IState) => [IState
|
|
4
|
-
localMessage: Partial<IState> | undefined;
|
|
5
|
-
rhythms: [] | import("../../types/rhythms").TRhythmData[];
|
|
6
|
-
rhythmsMessage: import("../../types/rhythms").RhythmMessage;
|
|
7
|
-
systemInfo: any;
|
|
8
|
-
dpState: Record<string, any>;
|
|
9
|
-
} | {
|
|
10
|
-
rhythms: Partial<IState> | undefined;
|
|
11
|
-
localMessage: import("../../types/rhythms").LocalMessage;
|
|
12
|
-
rhythmsMessage: import("../../types/rhythms").RhythmMessage;
|
|
13
|
-
systemInfo: any;
|
|
14
|
-
dpState: Record<string, any>;
|
|
15
|
-
} | {
|
|
16
|
-
rhythmsMessage: Partial<IState> | undefined;
|
|
17
|
-
localMessage: import("../../types/rhythms").LocalMessage;
|
|
18
|
-
rhythms: [] | import("../../types/rhythms").TRhythmData[];
|
|
19
|
-
systemInfo: any;
|
|
20
|
-
dpState: Record<string, any>;
|
|
21
|
-
}, import("react").Dispatch<{
|
|
22
|
-
type: string;
|
|
23
|
-
payload?: Partial<IState> | undefined;
|
|
24
|
-
}>];
|
|
2
|
+
import { IState, RhythmAction } from '../../types/rhythms';
|
|
3
|
+
export declare const useRhythmReducer: (init: IState) => [IState, import("react").Dispatch<RhythmAction>];
|
|
@@ -6,28 +6,27 @@ import { ERhythm } from '../../types/rhythms';
|
|
|
6
6
|
// 默认值
|
|
7
7
|
export const useRhythmReducer = init => {
|
|
8
8
|
const rhythmReducer = useCallback((state, action) => {
|
|
9
|
-
const oldState = _objectSpread({}, state);
|
|
10
9
|
switch (action.type) {
|
|
11
10
|
case ERhythm.UPDATE_LOCAL_MESSAGE:
|
|
12
|
-
return _objectSpread(_objectSpread({},
|
|
11
|
+
return _objectSpread(_objectSpread({}, state), {}, {
|
|
13
12
|
localMessage: action.payload
|
|
14
13
|
});
|
|
15
14
|
case ERhythm.UPDATE_RHYTHMS:
|
|
16
|
-
return _objectSpread(_objectSpread({},
|
|
15
|
+
return _objectSpread(_objectSpread({}, state), {}, {
|
|
17
16
|
rhythms: action.payload
|
|
18
17
|
});
|
|
19
18
|
case ERhythm.UPDATE_DP:
|
|
20
|
-
return _objectSpread(_objectSpread({},
|
|
21
|
-
dpState: _objectSpread(_objectSpread({},
|
|
19
|
+
return _objectSpread(_objectSpread({}, state), {}, {
|
|
20
|
+
dpState: _objectSpread(_objectSpread({}, state.dpState), action.payload)
|
|
22
21
|
});
|
|
23
22
|
case ERhythm.UPDATE_RHYTHMS_MESSAGE:
|
|
24
|
-
return _objectSpread(_objectSpread({},
|
|
23
|
+
return _objectSpread(_objectSpread({}, state), {}, {
|
|
25
24
|
rhythmsMessage: action.payload
|
|
26
25
|
});
|
|
27
26
|
case ERhythm.UPDATE_UI:
|
|
28
|
-
return _objectSpread(_objectSpread({},
|
|
27
|
+
return _objectSpread(_objectSpread({}, state), action.payload);
|
|
29
28
|
case ERhythm.INIT_SYSTEM_INFO:
|
|
30
|
-
return _objectSpread(_objectSpread({},
|
|
29
|
+
return _objectSpread(_objectSpread({}, state), {}, {
|
|
31
30
|
systemInfo: action.payload
|
|
32
31
|
});
|
|
33
32
|
default:
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Dispatch } from 'react';
|
|
2
2
|
import { TTimerData } from '../../types';
|
|
3
|
+
type TTimerDataWithId = TTimerData & {
|
|
4
|
+
id?: string;
|
|
5
|
+
};
|
|
3
6
|
type TTimerReducer = {
|
|
4
|
-
commonFlatTimerList:
|
|
5
|
-
commonFoldTimerList:
|
|
7
|
+
commonFlatTimerList: TTimerDataWithId[];
|
|
8
|
+
commonFoldTimerList: TTimerDataWithId[];
|
|
6
9
|
randomList: any[];
|
|
7
10
|
rhythmList: any[];
|
|
8
11
|
sleepList: any[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const sleepParserStub = {
|
|
2
|
+
parser: jest.fn(),
|
|
3
|
+
formatter: jest.fn()
|
|
4
|
+
};
|
|
5
|
+
const wakeUpParserStub = {
|
|
6
|
+
parser: jest.fn(),
|
|
7
|
+
formatter: jest.fn()
|
|
8
|
+
};
|
|
9
|
+
jest.mock('../sleep', () => ({
|
|
10
|
+
getSleepParser: jest.fn(() => sleepParserStub)
|
|
11
|
+
}));
|
|
12
|
+
jest.mock('../wakeup', () => ({
|
|
13
|
+
getWakeUpParser: jest.fn(() => wakeUpParserStub)
|
|
14
|
+
}));
|
|
15
|
+
import { autoDispatchTransDpFun, randomParser, rhythmParser, rtcTimerParser, timerReportParser } from '../index';
|
|
16
|
+
import { scheduleDpCodes } from '../../config/dpCodes';
|
|
17
|
+
describe('autoDispatchTransDpFun', () => {
|
|
18
|
+
it('returns random parser for RANDOM_TIMING', () => {
|
|
19
|
+
expect(autoDispatchTransDpFun(scheduleDpCodes.RANDOM_TIMING)).toBe(randomParser);
|
|
20
|
+
});
|
|
21
|
+
it('returns rhythm parser for RHYTHM_MODE', () => {
|
|
22
|
+
expect(autoDispatchTransDpFun(scheduleDpCodes.RHYTHM_MODE)).toBe(rhythmParser);
|
|
23
|
+
});
|
|
24
|
+
it('returns sleep parser singleton for SLEEP_MODE', () => {
|
|
25
|
+
const first = autoDispatchTransDpFun(scheduleDpCodes.SLEEP_MODE);
|
|
26
|
+
const second = autoDispatchTransDpFun(scheduleDpCodes.SLEEP_MODE);
|
|
27
|
+
expect(first).toBe(sleepParserStub);
|
|
28
|
+
expect(second).toBe(sleepParserStub);
|
|
29
|
+
});
|
|
30
|
+
it('returns RTC parser for RTC_TIMER', () => {
|
|
31
|
+
expect(autoDispatchTransDpFun(scheduleDpCodes.RTC_TIMER)).toBe(rtcTimerParser);
|
|
32
|
+
});
|
|
33
|
+
it('returns timer report parser for TIMER_REPORT', () => {
|
|
34
|
+
expect(autoDispatchTransDpFun(scheduleDpCodes.TIMER_REPORT)).toBe(timerReportParser);
|
|
35
|
+
});
|
|
36
|
+
it('returns wake up parser singleton for WAKE_UP_MODE', () => {
|
|
37
|
+
const first = autoDispatchTransDpFun(scheduleDpCodes.WAKE_UP_MODE);
|
|
38
|
+
const second = autoDispatchTransDpFun(scheduleDpCodes.WAKE_UP_MODE);
|
|
39
|
+
expect(first).toBe(wakeUpParserStub);
|
|
40
|
+
expect(second).toBe(wakeUpParserStub);
|
|
41
|
+
});
|
|
42
|
+
it('returns null for unsupported dpCode', () => {
|
|
43
|
+
expect(autoDispatchTransDpFun('UNKNOWN_DP_CODE')).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -1,26 +1,43 @@
|
|
|
1
|
+
import "core-js/modules/esnext.iterator.constructor.js";
|
|
2
|
+
import "core-js/modules/esnext.iterator.for-each.js";
|
|
1
3
|
import { rhythmParser } from '../rhythms';
|
|
2
|
-
|
|
3
|
-
jest.mock('../../utils/ScheduleLogger', () =>
|
|
4
|
-
|
|
4
|
+
import { scheduleDpCodes } from '../../config/dpCodes';
|
|
5
|
+
jest.mock('../../utils/ScheduleLogger', () => {
|
|
6
|
+
const logger = {
|
|
5
7
|
debug: jest.fn(),
|
|
6
8
|
warn: jest.fn(),
|
|
7
9
|
error: jest.fn()
|
|
8
|
-
}
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
// Mock getSupportIns
|
|
12
|
-
const mockSupport = function () {
|
|
13
|
-
let isSigMesh = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
14
|
-
let isSupportTemp = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
10
|
+
};
|
|
15
11
|
return {
|
|
16
|
-
|
|
17
|
-
isSupportDp: () => isSupportTemp
|
|
12
|
+
scheduleLogger: logger
|
|
18
13
|
};
|
|
19
|
-
};
|
|
14
|
+
});
|
|
20
15
|
jest.mock('../../hooks/useCommonSupport', () => ({
|
|
21
|
-
getSupportIns: jest.fn(
|
|
16
|
+
getSupportIns: jest.fn()
|
|
22
17
|
}));
|
|
18
|
+
const {
|
|
19
|
+
scheduleLogger
|
|
20
|
+
} = require('../../utils/ScheduleLogger');
|
|
21
|
+
const {
|
|
22
|
+
getSupportIns
|
|
23
|
+
} = require('../../hooks/useCommonSupport');
|
|
23
24
|
describe('RhythmParser', () => {
|
|
25
|
+
const createSupport = function () {
|
|
26
|
+
let isSupportTemp = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
|
27
|
+
return {
|
|
28
|
+
isSigMeshDevice: jest.fn().mockReturnValue(false),
|
|
29
|
+
isSupportDp: jest.fn().mockImplementation(dp => {
|
|
30
|
+
if (dp === scheduleDpCodes.TEMPERATURE) {
|
|
31
|
+
return isSupportTemp;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
})
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
getSupportIns.mockReturnValue(createSupport(true));
|
|
39
|
+
Object.values(scheduleLogger).forEach(fn => fn.mockClear());
|
|
40
|
+
});
|
|
24
41
|
describe('parser', () => {
|
|
25
42
|
it('should return default data for empty DP string', () => {
|
|
26
43
|
const result = rhythmParser.parser('');
|
|
@@ -31,12 +48,71 @@ describe('RhythmParser', () => {
|
|
|
31
48
|
expect(result).toHaveProperty('number');
|
|
32
49
|
expect(result).toHaveProperty('rhythms');
|
|
33
50
|
});
|
|
51
|
+
it('should return default data when support instance is missing', () => {
|
|
52
|
+
getSupportIns.mockReturnValueOnce(null);
|
|
53
|
+
const result = rhythmParser.parser('001122334455');
|
|
54
|
+
expect(result).toHaveProperty('version', 0);
|
|
55
|
+
expect(result).toHaveProperty('rhythms');
|
|
56
|
+
});
|
|
57
|
+
it('should handle parser errors and log them', () => {
|
|
58
|
+
const panelUtils = require('@ray-js/panel-sdk/lib/utils');
|
|
59
|
+
const stepSpy = jest.spyOn(panelUtils, 'generateDpStrStep').mockImplementation(() => {
|
|
60
|
+
throw new Error('step error');
|
|
61
|
+
});
|
|
62
|
+
const result = rhythmParser.parser('0123456789abcdef0123456789ab');
|
|
63
|
+
expect(result).toEqual({});
|
|
64
|
+
expect(scheduleLogger.error).toHaveBeenCalledWith('rhythmParser parser error', expect.any(Error));
|
|
65
|
+
stepSpy.mockRestore();
|
|
66
|
+
});
|
|
34
67
|
});
|
|
35
68
|
describe('formatter', () => {
|
|
36
69
|
it('should return empty string if data is null', () => {
|
|
37
70
|
const result = rhythmParser.formatter(null);
|
|
38
71
|
expect(result).toBe('');
|
|
39
72
|
});
|
|
73
|
+
it('should return empty string when support instance is missing', () => {
|
|
74
|
+
getSupportIns.mockReturnValueOnce(null);
|
|
75
|
+
const result = rhythmParser.formatter({
|
|
76
|
+
version: 1,
|
|
77
|
+
power: true,
|
|
78
|
+
mode: 2,
|
|
79
|
+
weeks: [1, 1, 1, 1, 1, 1, 1, 1],
|
|
80
|
+
number: 1,
|
|
81
|
+
rhythms: [{
|
|
82
|
+
power: true,
|
|
83
|
+
hour: 8,
|
|
84
|
+
minute: 30,
|
|
85
|
+
hue: 100,
|
|
86
|
+
saturation: 90,
|
|
87
|
+
value: 80,
|
|
88
|
+
brightness: 70,
|
|
89
|
+
temperature: 60
|
|
90
|
+
}]
|
|
91
|
+
});
|
|
92
|
+
expect(result).toBe('');
|
|
93
|
+
expect(scheduleLogger.error).toHaveBeenCalledWith('协议解析 rhythms formatter =====', '数据为空');
|
|
94
|
+
});
|
|
95
|
+
it('should force temperature to 100 when device does not support temperature DP', () => {
|
|
96
|
+
getSupportIns.mockReturnValueOnce(createSupport(false));
|
|
97
|
+
const result = rhythmParser.formatter({
|
|
98
|
+
version: 0,
|
|
99
|
+
power: true,
|
|
100
|
+
mode: 1,
|
|
101
|
+
weeks: [1, 1, 1, 1, 1, 1, 1, 1],
|
|
102
|
+
number: 1,
|
|
103
|
+
rhythms: [{
|
|
104
|
+
power: true,
|
|
105
|
+
hour: 6,
|
|
106
|
+
minute: 15,
|
|
107
|
+
hue: 20,
|
|
108
|
+
saturation: 1,
|
|
109
|
+
value: 2,
|
|
110
|
+
brightness: 3,
|
|
111
|
+
temperature: 4
|
|
112
|
+
}]
|
|
113
|
+
});
|
|
114
|
+
expect(result.slice(-2)).toBe('64');
|
|
115
|
+
});
|
|
40
116
|
it('should format a valid TRhythmData object', () => {
|
|
41
117
|
const result = rhythmParser.formatter({
|
|
42
118
|
version: 0,
|