@ray-js/lamp-schedule-core 1.0.5-beta.7 → 1.0.5-beta.9
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/index.d.ts +13 -3
- package/lib/conflict/index.js +27 -1
- package/lib/conflict/scheduleDataManager.js +7 -3
- package/lib/conflict/type.d.ts +4 -0
- package/lib/dpParser/__test__/sleepSigmesh.test.js +4 -2
- package/lib/dpParser/__test__/wakeupSigmesh.test.js +4 -2
- package/lib/hooks/__test__/useBaseLightDp.test.js +10 -2
- package/lib/hooks/__test__/useTimerDp.test.js +5 -5
- package/lib/hooks/useBaseLightDp.d.ts +6 -1
- package/lib/hooks/useBaseLightDp.js +33 -16
- package/lib/utils/ScheduleSupport.js +5 -5
- package/lib/utils/__test__/ScheduleDataSync.test.d.ts +1 -0
- package/lib/utils/__test__/ScheduleDataSync.test.js +2 -0
- package/lib/utils/__test__/objectToId.test.d.ts +1 -0
- package/lib/utils/__test__/objectToId.test.js +98 -0
- package/package.json +1 -1
package/lib/conflict/index.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { TConflictRes, ConflictRule, TFoldScheduleList, TSchedule } from './type';
|
|
1
|
+
import { TConflictRes, ConflictRule, TFoldScheduleList, TSchedule, PartialSchedule } from './type';
|
|
2
|
+
import { EScheduleFunctionType } from '../types';
|
|
2
3
|
/**
|
|
3
4
|
* 冲突检测
|
|
4
5
|
*/
|
|
5
6
|
export declare class Conflict {
|
|
6
7
|
static rule: ConflictRule;
|
|
7
8
|
static isInit: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* 获取冲突检测数据
|
|
11
|
+
*/
|
|
12
|
+
static getData(): Record<string, any>[];
|
|
8
13
|
/**
|
|
9
14
|
* 添加冲突规则
|
|
10
15
|
*/
|
|
@@ -18,13 +23,18 @@ export declare class Conflict {
|
|
|
18
23
|
* conflictList - 具体的冲突检测结果数组
|
|
19
24
|
*/
|
|
20
25
|
static add(current: TSchedule): TConflictRes;
|
|
26
|
+
/**
|
|
27
|
+
* 删除指定类型的日程
|
|
28
|
+
* @param type - 日程类型
|
|
29
|
+
*/
|
|
30
|
+
static removeByType(type: EScheduleFunctionType): void;
|
|
21
31
|
/**
|
|
22
32
|
* 删除日程
|
|
23
33
|
*
|
|
24
|
-
* @param current - 待添加的日程对象,类型为 Schedule
|
|
34
|
+
* @param current - 待添加的日程对象,类型为 Schedule, 如果没有 detail 则认为是删除指定类型的日程
|
|
25
35
|
* @returns void
|
|
26
36
|
*/
|
|
27
|
-
static remove(current: TSchedule): void;
|
|
37
|
+
static remove(current: TSchedule | PartialSchedule): void;
|
|
28
38
|
/**
|
|
29
39
|
* 更新新日程时检测冲突
|
|
30
40
|
*
|
package/lib/conflict/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
2
2
|
import "core-js/modules/es.json.stringify.js";
|
|
3
3
|
import "core-js/modules/esnext.iterator.constructor.js";
|
|
4
|
+
import "core-js/modules/esnext.iterator.filter.js";
|
|
4
5
|
import "core-js/modules/esnext.iterator.find.js";
|
|
5
6
|
import { scheduleLogger } from '../utils/ScheduleLogger';
|
|
6
7
|
import { checkConflicts } from './ConflictResolver';
|
|
@@ -12,6 +13,13 @@ import { EScheduleFunctionType } from '../types';
|
|
|
12
13
|
* 冲突检测
|
|
13
14
|
*/
|
|
14
15
|
export class Conflict {
|
|
16
|
+
/**
|
|
17
|
+
* 获取冲突检测数据
|
|
18
|
+
*/
|
|
19
|
+
static getData() {
|
|
20
|
+
return ScheduleDataManager.getInstance().getData();
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
/**
|
|
16
24
|
* 添加冲突规则
|
|
17
25
|
*/
|
|
@@ -50,13 +58,31 @@ export class Conflict {
|
|
|
50
58
|
};
|
|
51
59
|
}
|
|
52
60
|
|
|
61
|
+
/**
|
|
62
|
+
* 删除指定类型的日程
|
|
63
|
+
* @param type - 日程类型
|
|
64
|
+
*/
|
|
65
|
+
static removeByType(type) {
|
|
66
|
+
const instance = ScheduleDataManager.getInstance();
|
|
67
|
+
const list = instance.getData() || [];
|
|
68
|
+
const filteredList = list.filter(item => item.type !== type);
|
|
69
|
+
ScheduleDataManager.list = filteredList;
|
|
70
|
+
}
|
|
71
|
+
|
|
53
72
|
/**
|
|
54
73
|
* 删除日程
|
|
55
74
|
*
|
|
56
|
-
* @param current - 待添加的日程对象,类型为 Schedule
|
|
75
|
+
* @param current - 待添加的日程对象,类型为 Schedule, 如果没有 detail 则认为是删除指定类型的日程
|
|
57
76
|
* @returns void
|
|
58
77
|
*/
|
|
59
78
|
static remove(current) {
|
|
79
|
+
if (!current) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (current.type && !current.detail) {
|
|
83
|
+
this.removeByType(current.type);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
60
86
|
const [cur] = transScheduleListToConflictList([current]);
|
|
61
87
|
scheduleLogger.debug('Conflict.remove cur, current:', cur, current);
|
|
62
88
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "core-js/modules/es.json.stringify.js";
|
|
1
2
|
import "core-js/modules/esnext.iterator.constructor.js";
|
|
2
3
|
import "core-js/modules/esnext.iterator.filter.js";
|
|
3
4
|
import "core-js/modules/esnext.iterator.for-each.js";
|
|
@@ -63,10 +64,11 @@ export class ScheduleDataManager {
|
|
|
63
64
|
}
|
|
64
65
|
const idsToAdd = new Set(itemsMap.keys());
|
|
65
66
|
const filteredList = ScheduleDataManager.list.filter(item => !idsToAdd.has(item.id));
|
|
66
|
-
|
|
67
|
+
const newList = [...filteredList, ...itemsMap.values()].filter(item => {
|
|
67
68
|
var _item$data;
|
|
68
69
|
return (item === null || item === void 0 || (_item$data = item.data) === null || _item$data === void 0 ? void 0 : _item$data.status) === true;
|
|
69
70
|
});
|
|
71
|
+
ScheduleDataManager.list = newList;
|
|
70
72
|
}
|
|
71
73
|
clearData() {
|
|
72
74
|
scheduleLogger.debug('ScheduleDataManager.clearData pre', ScheduleDataManager.list);
|
|
@@ -76,12 +78,14 @@ export class ScheduleDataManager {
|
|
|
76
78
|
if (!current) {
|
|
77
79
|
return;
|
|
78
80
|
}
|
|
79
|
-
scheduleLogger.debug('ScheduleDataManager.deleteData pre', current);
|
|
81
|
+
scheduleLogger.debug('ScheduleDataManager.deleteData pre', current, JSON.stringify(ScheduleDataManager.list));
|
|
80
82
|
const index = ScheduleDataManager.list.findIndex(item => item.id === current.id);
|
|
81
83
|
if (index !== -1) {
|
|
82
84
|
ScheduleDataManager.list.splice(index, 1);
|
|
85
|
+
scheduleLogger.debug('ScheduleDataManager.deleteData post', JSON.stringify(ScheduleDataManager.list));
|
|
86
|
+
} else {
|
|
87
|
+
scheduleLogger.debug('ScheduleDataManager.deleteData post not found', JSON.stringify(ScheduleDataManager.list));
|
|
83
88
|
}
|
|
84
|
-
scheduleLogger.debug('ScheduleDataManager.deleteData post', ScheduleDataManager.list);
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
// 添加remove方法作为deleteData的别名,用于测试
|
package/lib/conflict/type.d.ts
CHANGED
|
@@ -52,5 +52,9 @@ export type TSchedule = {
|
|
|
52
52
|
type: EScheduleFunctionType;
|
|
53
53
|
detail: ScheduleNodeType[];
|
|
54
54
|
};
|
|
55
|
+
export type PartialSchedule = {
|
|
56
|
+
type: EScheduleFunctionType;
|
|
57
|
+
detail?: ScheduleNodeType[];
|
|
58
|
+
};
|
|
55
59
|
export type TFoldScheduleList = TSchedule[];
|
|
56
60
|
export {};
|
|
@@ -121,6 +121,7 @@ describe('SleepSigmesh parser & formatter', () => {
|
|
|
121
121
|
index: 0
|
|
122
122
|
}]
|
|
123
123
|
});
|
|
124
|
+
// formatter 固定输出 version=1, dataMode=1
|
|
124
125
|
expect(result).toBe(buildDpStr([{
|
|
125
126
|
onOff: 1,
|
|
126
127
|
loops: loopsToHex('1000000'),
|
|
@@ -129,7 +130,7 @@ describe('SleepSigmesh parser & formatter', () => {
|
|
|
129
130
|
minute: 15,
|
|
130
131
|
brightness: 90,
|
|
131
132
|
temperature: 50
|
|
132
|
-
}], 1,
|
|
133
|
+
}], 1, 1));
|
|
133
134
|
});
|
|
134
135
|
it('round-trips dp string through parser and formatter', () => {
|
|
135
136
|
const nodes = [{
|
|
@@ -144,7 +145,8 @@ describe('SleepSigmesh parser & formatter', () => {
|
|
|
144
145
|
const dpStr = buildDpStr(nodes, 2, 3);
|
|
145
146
|
const parsed = sleepParserSigmesh.parser(dpStr);
|
|
146
147
|
const formatted = sleepParserSigmesh.formatter(parsed);
|
|
147
|
-
|
|
148
|
+
// formatter 固定输出 version=1, dataMode=1,故 round-trip 后与原始 dpStr 不同
|
|
149
|
+
expect(formatted).toBe(buildDpStr(nodes, 1, 1));
|
|
148
150
|
});
|
|
149
151
|
it('handles multiple nodes correctly', () => {
|
|
150
152
|
const nodes = [{
|
|
@@ -97,6 +97,7 @@ describe('WakeUpSigmesh parser & formatter', () => {
|
|
|
97
97
|
index: 0
|
|
98
98
|
}]
|
|
99
99
|
});
|
|
100
|
+
// formatter 固定输出 version=1, dataMode=1(与 parser 可解析的格式一致)
|
|
100
101
|
expect(result).toBe(buildDpStr([{
|
|
101
102
|
onOff: 1,
|
|
102
103
|
loops: loopsToHex('1000000'),
|
|
@@ -106,7 +107,7 @@ describe('WakeUpSigmesh parser & formatter', () => {
|
|
|
106
107
|
brightness: 90,
|
|
107
108
|
temperature: 50,
|
|
108
109
|
duration: 20
|
|
109
|
-
}], 1,
|
|
110
|
+
}], 1, 1));
|
|
110
111
|
});
|
|
111
112
|
it('round-trips dp string through parser and formatter', () => {
|
|
112
113
|
const nodes = [{
|
|
@@ -122,6 +123,7 @@ describe('WakeUpSigmesh parser & formatter', () => {
|
|
|
122
123
|
const dpStr = buildDpStr(nodes, 2, 3);
|
|
123
124
|
const parsed = wakeupParserSigmesh.parser(dpStr);
|
|
124
125
|
const formatted = wakeupParserSigmesh.formatter(parsed);
|
|
125
|
-
|
|
126
|
+
// formatter 固定输出 version=1, dataMode=1,故 round-trip 后与原始 dpStr 不同
|
|
127
|
+
expect(formatted).toBe(buildDpStr(nodes, 1, 1));
|
|
126
128
|
});
|
|
127
129
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { act, renderHook } from '@testing-library/react-hooks';
|
|
2
|
-
import { useBaseLightDp, getDpDataByMesh, fetchDpData, useFetchDpDataByMesh, useFetchDpData } from '../useBaseLightDp';
|
|
2
|
+
import { useBaseLightDp, getDpDataByMesh, fetchDpData, useFetchDpDataByMesh, useFetchDpData, __clearFetchDpDataThrottleCacheForTest } from '../useBaseLightDp';
|
|
3
3
|
import { DP_CHANGE_EVENT_KEY } from '../../constant';
|
|
4
4
|
import { devIdOrGroupIdCache } from '../../utils/ScheduleCache';
|
|
5
5
|
import { getDpIdByDpCode } from '../../utils/ScheduleUtils';
|
|
@@ -184,6 +184,7 @@ describe('getDpDataByMesh', () => {
|
|
|
184
184
|
});
|
|
185
185
|
describe('fetchDpData', () => {
|
|
186
186
|
beforeEach(() => {
|
|
187
|
+
__clearFetchDpDataThrottleCacheForTest();
|
|
187
188
|
jest.clearAllMocks();
|
|
188
189
|
devIdOrGroupIdCache.get.mockReturnValue({
|
|
189
190
|
devId: 'device-1',
|
|
@@ -210,9 +211,15 @@ describe('fetchDpData', () => {
|
|
|
210
211
|
dpIds: ['2']
|
|
211
212
|
}));
|
|
212
213
|
});
|
|
214
|
+
it('skips duplicate fetch for same dpCodes within throttle window', () => {
|
|
215
|
+
fetchDpData(['bright_value']);
|
|
216
|
+
fetchDpData(['bright_value']);
|
|
217
|
+
expect(queryDps).toHaveBeenCalledTimes(1);
|
|
218
|
+
});
|
|
213
219
|
});
|
|
214
220
|
describe('useFetch hooks', () => {
|
|
215
221
|
beforeEach(() => {
|
|
222
|
+
__clearFetchDpDataThrottleCacheForTest();
|
|
216
223
|
jest.clearAllMocks();
|
|
217
224
|
getDpIdByDpCode.mockReturnValue(3);
|
|
218
225
|
devIdOrGroupIdCache.get.mockReturnValue({
|
|
@@ -232,7 +239,8 @@ describe('useFetch hooks', () => {
|
|
|
232
239
|
act(() => {
|
|
233
240
|
result.current.refresh();
|
|
234
241
|
});
|
|
235
|
-
|
|
242
|
+
// 同一组 dpCodes 在 3s 内被节流,refresh 不会触发第二次请求
|
|
243
|
+
expect(queryDps).toHaveBeenCalledTimes(1);
|
|
236
244
|
});
|
|
237
245
|
it('useFetchDpDataByMesh skips when not sigmesh', () => {
|
|
238
246
|
useSupport.mockReturnValue({
|
|
@@ -2,10 +2,12 @@ import "core-js/modules/es.json.stringify.js";
|
|
|
2
2
|
import { renderHook, act } from '@testing-library/react-hooks';
|
|
3
3
|
import { useTimerDp } from '../useTimerDp';
|
|
4
4
|
import { useBaseLightDp } from '../useBaseLightDp';
|
|
5
|
+
import { useSupport } from '../useCommonSupport';
|
|
5
6
|
import { scheduleDpCodes } from '../../config/dpCodes';
|
|
6
7
|
import { rtcTimerParser } from '../../dpParser/rtcTimer';
|
|
7
8
|
// Mock dependencies
|
|
8
9
|
jest.mock('../useBaseLightDp');
|
|
10
|
+
jest.mock('../useCommonSupport');
|
|
9
11
|
jest.mock('../../dpParser/rtcTimer');
|
|
10
12
|
describe('useTimerDp', () => {
|
|
11
13
|
const mockTimerData = {
|
|
@@ -27,6 +29,9 @@ describe('useTimerDp', () => {
|
|
|
27
29
|
};
|
|
28
30
|
beforeEach(() => {
|
|
29
31
|
jest.clearAllMocks();
|
|
32
|
+
useSupport.mockReturnValue({
|
|
33
|
+
isSupportDp: jest.fn(code => code === scheduleDpCodes.RTC_TIMER)
|
|
34
|
+
});
|
|
30
35
|
useBaseLightDp.mockReturnValue({
|
|
31
36
|
dpValue: mockTimerData,
|
|
32
37
|
updateDp: jest.fn()
|
|
@@ -72,9 +77,4 @@ describe('useTimerDp', () => {
|
|
|
72
77
|
renderHook(() => useTimerDp());
|
|
73
78
|
expect(useBaseLightDp).toHaveBeenCalledWith(scheduleDpCodes.RTC_TIMER);
|
|
74
79
|
});
|
|
75
|
-
it('should handle custom dpCode', () => {
|
|
76
|
-
const customDpCode = scheduleDpCodes.RTC_TIMER;
|
|
77
|
-
renderHook(() => useTimerDp(customDpCode));
|
|
78
|
-
expect(useBaseLightDp).toHaveBeenCalledWith(customDpCode);
|
|
79
|
-
});
|
|
80
80
|
});
|
|
@@ -10,6 +10,8 @@ type TDpRes<T> = {
|
|
|
10
10
|
};
|
|
11
11
|
export declare function useBaseLightDp<T>(dpCode: string, defaultDpValue?: T): TDpRes<T>;
|
|
12
12
|
export declare const getDpDataByMesh: (dpCodes?: string[]) => void;
|
|
13
|
+
/** 仅用于测试:清空 fetchDpData 节流缓存,避免用例间相互影响 */
|
|
14
|
+
export declare const __clearFetchDpDataThrottleCacheForTest: () => void;
|
|
13
15
|
export declare const fetchDpData: (dpCodes?: string[]) => Promise<void>;
|
|
14
16
|
/**
|
|
15
17
|
* 主动拉取 dp 数据, 仅支持 sigmesh 设备
|
|
@@ -21,7 +23,10 @@ export declare const useFetchDpDataByMesh: (dpCodes: string[]) => {
|
|
|
21
23
|
};
|
|
22
24
|
/**
|
|
23
25
|
* 主动拉取 dp 数据, 不限制协议
|
|
24
|
-
*
|
|
26
|
+
* 同一组 dpCodes 在 3s 内只会实际拉取一次(模块级节流)。
|
|
27
|
+
* @param dpCodes dpCode 数组,建议使用 useMemo 稳定引用,避免因引用变化导致 effect 重复执行,例如:
|
|
28
|
+
* const dpCodes = useMemo(() => [scheduleDpCodes.SLEEP_MODE, scheduleDpCodes.WAKE_UP_MODE], []);
|
|
29
|
+
* useFetchDpData(dpCodes, { initFetch: true, offsetTime: 0 });
|
|
25
30
|
* @param options 拉取配置
|
|
26
31
|
* @param options.initFetch 是否初始化拉取
|
|
27
32
|
* @param options.offsetTime 延迟时间 ms
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "core-js/modules/esnext.iterator.constructor.js";
|
|
2
2
|
import "core-js/modules/esnext.iterator.filter.js";
|
|
3
|
+
import "core-js/modules/esnext.iterator.for-each.js";
|
|
3
4
|
import "core-js/modules/esnext.iterator.map.js";
|
|
4
5
|
/* eslint-disable no-console */
|
|
5
6
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
@@ -140,8 +141,20 @@ export const getDpDataByMesh = function () {
|
|
|
140
141
|
}
|
|
141
142
|
};
|
|
142
143
|
|
|
144
|
+
/** 模块级缓存:同一组 dpCodes 最近一次拉取时间,用于 3s 内去重 */
|
|
145
|
+
const lastFetchTimeByKey = {};
|
|
146
|
+
|
|
147
|
+
/** 相同 dpCodesKey 节流时间(毫秒) */
|
|
148
|
+
const FETCH_DP_THROTTLE_MS = 3000;
|
|
149
|
+
|
|
150
|
+
/** 仅用于测试:清空 fetchDpData 节流缓存,避免用例间相互影响 */
|
|
151
|
+
export const __clearFetchDpDataThrottleCacheForTest = () => {
|
|
152
|
+
Object.keys(lastFetchTimeByKey).forEach(k => delete lastFetchTimeByKey[k]);
|
|
153
|
+
};
|
|
154
|
+
|
|
143
155
|
// 查询设备的 DP
|
|
144
156
|
export const fetchDpData = function () {
|
|
157
|
+
var _lastFetchTimeByKey$k;
|
|
145
158
|
let dpCodes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
146
159
|
if (!Array.isArray(dpCodes)) {
|
|
147
160
|
scheduleLogger.error('fetchDpData: 参数dpCodes必须是数组');
|
|
@@ -162,6 +175,16 @@ export const fetchDpData = function () {
|
|
|
162
175
|
scheduleLogger.warn('fetchDpData', dpCodes, '未匹配到 dp');
|
|
163
176
|
return;
|
|
164
177
|
}
|
|
178
|
+
|
|
179
|
+
// 模块级节流:同一组 dpCodes 在 3s 内只拉取一次
|
|
180
|
+
const key = dpCodes.slice().sort().join(',');
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
const lastFetch = (_lastFetchTimeByKey$k = lastFetchTimeByKey[key]) !== null && _lastFetchTimeByKey$k !== void 0 ? _lastFetchTimeByKey$k : 0;
|
|
183
|
+
if (now - lastFetch < FETCH_DP_THROTTLE_MS) {
|
|
184
|
+
scheduleLogger.debug('fetchDpData: 相同 dpCodes 在 3s 内已拉取过,已跳过', dpCodes);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
lastFetchTimeByKey[key] = now;
|
|
165
188
|
scheduleLogger.debug('fetchDpData 查询DP转换后参数 dpIds:', dpIds);
|
|
166
189
|
try {
|
|
167
190
|
queryDps({
|
|
@@ -210,7 +233,10 @@ export const useFetchDpDataByMesh = dpCodes => {
|
|
|
210
233
|
|
|
211
234
|
/**
|
|
212
235
|
* 主动拉取 dp 数据, 不限制协议
|
|
213
|
-
*
|
|
236
|
+
* 同一组 dpCodes 在 3s 内只会实际拉取一次(模块级节流)。
|
|
237
|
+
* @param dpCodes dpCode 数组,建议使用 useMemo 稳定引用,避免因引用变化导致 effect 重复执行,例如:
|
|
238
|
+
* const dpCodes = useMemo(() => [scheduleDpCodes.SLEEP_MODE, scheduleDpCodes.WAKE_UP_MODE], []);
|
|
239
|
+
* useFetchDpData(dpCodes, { initFetch: true, offsetTime: 0 });
|
|
214
240
|
* @param options 拉取配置
|
|
215
241
|
* @param options.initFetch 是否初始化拉取
|
|
216
242
|
* @param options.offsetTime 延迟时间 ms
|
|
@@ -221,34 +247,25 @@ export const useFetchDpData = function (dpCodes) {
|
|
|
221
247
|
initFetch: true,
|
|
222
248
|
offsetTime: 0
|
|
223
249
|
};
|
|
224
|
-
// 使用 useMemo 稳定 dpCodes 的字符串表示,避免因数组引用变化导致重复执行
|
|
225
|
-
// 注意:这里依赖 dpCodes 数组本身,但由于调用方已使用 useMemo 稳定引用,所以是安全的
|
|
226
250
|
const dpCodesKey = useMemo(() => dpCodes.join(','), [dpCodes]);
|
|
227
251
|
const getDpDataFn = useCallback(() => {
|
|
228
|
-
scheduleLogger.debug('useFetchDpData getDpDataFn
|
|
252
|
+
scheduleLogger.debug('useFetchDpData getDpDataFn get:', dpCodes);
|
|
229
253
|
fetchDpData(dpCodes);
|
|
230
254
|
}, [dpCodes]);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const prevDpCodesKeyRef = useRef(null);
|
|
255
|
+
const getDpDataFnRef = useRef(getDpDataFn);
|
|
256
|
+
getDpDataFnRef.current = getDpDataFn;
|
|
234
257
|
useEffect(() => {
|
|
235
258
|
if (!options.initFetch) {
|
|
236
259
|
return () => null;
|
|
237
260
|
}
|
|
238
|
-
// 只有当 dpCodesKey 真正变化时才执行
|
|
239
|
-
if (prevDpCodesKeyRef.current === dpCodesKey) {
|
|
240
|
-
scheduleLogger.debug('useFetchDpData useEffect: dpCodesKey unchanged', dpCodesKey);
|
|
241
|
-
return () => null;
|
|
242
|
-
}
|
|
243
|
-
prevDpCodesKeyRef.current = dpCodesKey;
|
|
244
261
|
const timer = setTimeout(() => {
|
|
245
|
-
scheduleLogger.debug('useFetchDpData useEffect get:',
|
|
246
|
-
|
|
262
|
+
scheduleLogger.debug('useFetchDpData useEffect get:', dpCodesKey);
|
|
263
|
+
getDpDataFnRef.current();
|
|
247
264
|
}, options.offsetTime || 0);
|
|
248
265
|
return () => {
|
|
249
266
|
clearTimeout(timer);
|
|
250
267
|
};
|
|
251
|
-
}, [dpCodesKey,
|
|
268
|
+
}, [dpCodesKey, options.initFetch, options.offsetTime]);
|
|
252
269
|
return {
|
|
253
270
|
refresh: getDpDataFn
|
|
254
271
|
};
|
|
@@ -129,11 +129,11 @@ export class Support {
|
|
|
129
129
|
// 群组中为空
|
|
130
130
|
groupId && (devIdOrGroupIdCache === null || devIdOrGroupIdCache === void 0 ? void 0 : devIdOrGroupIdCache.set('', groupId));
|
|
131
131
|
const dpMap = {};
|
|
132
|
-
scheduleLogger.debug(
|
|
133
|
-
scheduleLogger.debug(
|
|
134
|
-
scheduleLogger.debug(
|
|
135
|
-
scheduleLogger.debug(
|
|
136
|
-
scheduleLogger.debug(
|
|
132
|
+
scheduleLogger.debug('group and devInfo merge: firstDevDps ==== ', firstDevDps, hasDps);
|
|
133
|
+
scheduleLogger.debug('group and devInfo merge: resresres ==== ', res);
|
|
134
|
+
scheduleLogger.debug('resresres groupRes ==== ', groupRes);
|
|
135
|
+
scheduleLogger.debug('resresres deviceInfoRes ==== ', deviceInfoRes);
|
|
136
|
+
scheduleLogger.debug('resresres filteredDeviceInfo ==== ', filteredDeviceInfo);
|
|
137
137
|
Object.keys(res.dps).forEach(dpId => {
|
|
138
138
|
const dpCode = getDpCodeByDpId(dpId);
|
|
139
139
|
dpCode && setDpState(dpCode, res.dps[dpId]);
|
|
@@ -3,6 +3,7 @@ declare const mockNavigateTo: jest.Mock<any, any, any>;
|
|
|
3
3
|
declare const mockUsePageInstance: jest.Mock<any, any, any>;
|
|
4
4
|
declare const mockLogger: {
|
|
5
5
|
debug: jest.Mock<any, any, any>;
|
|
6
|
+
info: jest.Mock<any, any, any>;
|
|
6
7
|
error: jest.Mock<any, any, any>;
|
|
7
8
|
};
|
|
8
9
|
declare let navigateToSchedule: typeof import('../ScheduleDataSync').navigateToSchedule;
|
|
@@ -2,6 +2,7 @@ const mockNavigateTo = jest.fn();
|
|
|
2
2
|
const mockUsePageInstance = jest.fn();
|
|
3
3
|
const mockLogger = {
|
|
4
4
|
debug: jest.fn(),
|
|
5
|
+
info: jest.fn(),
|
|
5
6
|
error: jest.fn()
|
|
6
7
|
};
|
|
7
8
|
let navigateToSchedule;
|
|
@@ -21,6 +22,7 @@ describe('ScheduleDataSync utilities', () => {
|
|
|
21
22
|
mockNavigateTo.mockReset();
|
|
22
23
|
mockUsePageInstance.mockReset();
|
|
23
24
|
mockLogger.debug.mockReset();
|
|
25
|
+
mockLogger.info.mockReset();
|
|
24
26
|
mockLogger.error.mockReset();
|
|
25
27
|
global.ty = {
|
|
26
28
|
presetFunctionalData: jest.fn()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { objectToId } from '../objectToId';
|
|
2
|
+
describe('objectToId', () => {
|
|
3
|
+
it('相同内容、不同 key 顺序的对象应得到相同 id', () => {
|
|
4
|
+
expect(objectToId({
|
|
5
|
+
z: [1, 2, 3],
|
|
6
|
+
a: 2,
|
|
7
|
+
m: 3
|
|
8
|
+
})).toBe(objectToId({
|
|
9
|
+
m: 3,
|
|
10
|
+
z: [1, 2, 3],
|
|
11
|
+
a: 2
|
|
12
|
+
}));
|
|
13
|
+
});
|
|
14
|
+
it('不同内容的对象应得到不同 id', () => {
|
|
15
|
+
expect(objectToId({
|
|
16
|
+
a: 1
|
|
17
|
+
})).not.toBe(objectToId({
|
|
18
|
+
a: 2
|
|
19
|
+
}));
|
|
20
|
+
expect(objectToId({
|
|
21
|
+
a: 1
|
|
22
|
+
})).not.toBe(objectToId({
|
|
23
|
+
b: 1
|
|
24
|
+
}));
|
|
25
|
+
expect(objectToId({})).not.toBe(objectToId({
|
|
26
|
+
a: 1
|
|
27
|
+
}));
|
|
28
|
+
});
|
|
29
|
+
it('空对象应返回稳定的 id', () => {
|
|
30
|
+
const id = objectToId({});
|
|
31
|
+
expect(typeof id).toBe('string');
|
|
32
|
+
expect(id).toMatch(/^[a-z0-9]+$/);
|
|
33
|
+
expect(objectToId({})).toBe(objectToId({}));
|
|
34
|
+
});
|
|
35
|
+
it('带 prefix 时应在 id 前拼接 prefix', () => {
|
|
36
|
+
const id = objectToId({
|
|
37
|
+
x: 1
|
|
38
|
+
});
|
|
39
|
+
const withPrefix = objectToId({
|
|
40
|
+
x: 1
|
|
41
|
+
}, 'prefix_');
|
|
42
|
+
expect(withPrefix).toBe('prefix_' + id);
|
|
43
|
+
expect(objectToId({}, 'id-')).toMatch(/^id-[a-z0-9]+$/);
|
|
44
|
+
});
|
|
45
|
+
it('嵌套对象按 key 排序后一致则 id 相同', () => {
|
|
46
|
+
expect(objectToId({
|
|
47
|
+
outer: {
|
|
48
|
+
z: 1,
|
|
49
|
+
a: 2
|
|
50
|
+
}
|
|
51
|
+
})).toBe(objectToId({
|
|
52
|
+
outer: {
|
|
53
|
+
a: 2,
|
|
54
|
+
z: 1
|
|
55
|
+
}
|
|
56
|
+
}));
|
|
57
|
+
});
|
|
58
|
+
it('数组内容与顺序会影响 id', () => {
|
|
59
|
+
expect(objectToId([1, 2])).not.toBe(objectToId([2, 1]));
|
|
60
|
+
expect(objectToId([1, 2])).toBe(objectToId([1, 2]));
|
|
61
|
+
});
|
|
62
|
+
it('返回值应为非空字符串且为 36 进制形式', () => {
|
|
63
|
+
const id = objectToId({
|
|
64
|
+
foo: 'bar'
|
|
65
|
+
});
|
|
66
|
+
expect(id.length).toBeGreaterThan(0);
|
|
67
|
+
expect(id).toMatch(/^[a-z0-9]+$/);
|
|
68
|
+
});
|
|
69
|
+
it('对 null 输入应返回稳定 id', () => {
|
|
70
|
+
const id = objectToId(null);
|
|
71
|
+
expect(typeof id).toBe('string');
|
|
72
|
+
expect(objectToId(null)).toBe(id);
|
|
73
|
+
});
|
|
74
|
+
it('对原始类型输入应返回稳定 id', () => {
|
|
75
|
+
const numId = objectToId(42);
|
|
76
|
+
const strId = objectToId('hello');
|
|
77
|
+
expect(typeof numId).toBe('string');
|
|
78
|
+
expect(typeof strId).toBe('string');
|
|
79
|
+
expect(numId).not.toBe(strId);
|
|
80
|
+
});
|
|
81
|
+
it('深层嵌套对象 key 顺序不影响 id', () => {
|
|
82
|
+
expect(objectToId({
|
|
83
|
+
a: {
|
|
84
|
+
b: {
|
|
85
|
+
c: 1,
|
|
86
|
+
d: 2
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
})).toBe(objectToId({
|
|
90
|
+
a: {
|
|
91
|
+
b: {
|
|
92
|
+
d: 2,
|
|
93
|
+
c: 1
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}));
|
|
97
|
+
});
|
|
98
|
+
});
|