@ray-js/lamp-schedule-core 1.0.1-beta-10 → 1.0.2-beta-2

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.
Files changed (37) hide show
  1. package/lib/conflict/ConflictResolver.js +48 -21
  2. package/lib/conflict/__test__/ConflictResolver.test.js +49 -0
  3. package/lib/conflict/__test__/scheduleDataManager.test.js +66 -0
  4. package/lib/conflict/__test__/transform.test.js +5 -1
  5. package/lib/conflict/index.js +27 -7
  6. package/lib/conflict/scheduleDataManager.js +28 -33
  7. package/lib/context/rhythms/actions.d.ts +13 -17
  8. package/lib/context/rhythms/reducer.d.ts +2 -23
  9. package/lib/context/rhythms/reducer.js +7 -8
  10. package/lib/context/schedule/reducer.d.ts +5 -2
  11. package/lib/dpParser/__test__/autoDispatch.test.d.ts +1 -0
  12. package/lib/dpParser/__test__/autoDispatch.test.js +45 -0
  13. package/lib/dpParser/__test__/rhythms.test.js +90 -14
  14. package/lib/dpParser/__test__/rtcTimer.test.js +35 -0
  15. package/lib/dpParser/__test__/sleep.test.js +17 -0
  16. package/lib/dpParser/__test__/wakeup.test.js +17 -4
  17. package/lib/dpParser/__test__/wakeupSigmesh.test.d.ts +1 -0
  18. package/lib/dpParser/__test__/wakeupSigmesh.test.js +126 -0
  19. package/lib/hooks/__test__/useBaseLightDp.test.d.ts +1 -0
  20. package/lib/hooks/__test__/useBaseLightDp.test.js +251 -0
  21. package/lib/hooks/__test__/useCommonSupport.test.d.ts +1 -0
  22. package/lib/hooks/__test__/useCommonSupport.test.js +204 -0
  23. package/lib/hooks/__test__/useTimerFlushList.test.d.ts +1 -0
  24. package/lib/hooks/__test__/useTimerFlushList.test.js +117 -0
  25. package/lib/hooks/__test__/useTimerOperate.test.js +239 -5
  26. package/lib/hooks/__test__/useTimerOperateLocal.test.js +161 -53
  27. package/lib/hooks/useCommonSupport.js +16 -19
  28. package/lib/types/rhythms.d.ts +25 -13
  29. package/lib/types/ty.d.ts +3 -0
  30. package/lib/utils/ScheduleDataSync.js +4 -1
  31. package/lib/utils/ScheduleLogger.js +1 -1
  32. package/lib/utils/__test__/ScheduleDataSync.test.d.ts +11 -0
  33. package/lib/utils/__test__/ScheduleDataSync.test.js +137 -0
  34. package/lib/utils/__test__/ScheduleEmit.test.d.ts +1 -0
  35. package/lib/utils/__test__/ScheduleEmit.test.js +35 -0
  36. package/lib/utils/__test__/ScheduleSupport.test.js +288 -154
  37. package/package.json +1 -1
@@ -0,0 +1,117 @@
1
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
+ import { renderHook } from '@testing-library/react-hooks';
3
+ import { useCloudTimerListFlush, useRtcTimerListFlush, useTimerListFlush } from '../useTimerFlushList';
4
+ import { useTimerContext } from '../../context/timer/context';
5
+ import { useSupportLocalTimer } from '../useTimerSupport';
6
+ jest.mock('../../context/timer/context', () => ({
7
+ useTimerContext: jest.fn()
8
+ }));
9
+ jest.mock('../useTimerSupport', () => ({
10
+ useSupportLocalTimer: jest.fn()
11
+ }));
12
+ describe('useTimerFlushList hooks', () => {
13
+ const mockDispatch = jest.fn();
14
+ const mockFlushCloud = jest.fn();
15
+ const mockFlushRtc = jest.fn();
16
+ const setContextMock = overrides => {
17
+ useTimerContext.mockReturnValue(_objectSpread({
18
+ actions: {
19
+ flushCloudTimerList: mockFlushCloud,
20
+ flushRtcTimerList: mockFlushRtc
21
+ },
22
+ dispatch: mockDispatch,
23
+ state: {
24
+ cloudTimerList: [],
25
+ rtcTimerList: []
26
+ }
27
+ }, overrides));
28
+ };
29
+ beforeEach(() => {
30
+ jest.clearAllMocks();
31
+ setContextMock({});
32
+ });
33
+ describe('useCloudTimerListFlush', () => {
34
+ it('dispatches flushCloudTimerList with provided payload', () => {
35
+ const mockActionResult = {
36
+ type: 'cloud'
37
+ };
38
+ mockFlushCloud.mockReturnValue(mockActionResult);
39
+ const payload = [{
40
+ timerId: '1'
41
+ }];
42
+ const {
43
+ result
44
+ } = renderHook(() => useCloudTimerListFlush());
45
+ result.current.flushCloudTimerList(payload);
46
+ expect(mockFlushCloud).toHaveBeenCalledWith(payload);
47
+ expect(mockDispatch).toHaveBeenCalledWith(mockActionResult);
48
+ });
49
+ });
50
+ describe('useRtcTimerListFlush', () => {
51
+ it('dispatches flushRtcTimerList with provided payload', () => {
52
+ const mockActionResult = {
53
+ type: 'rtc'
54
+ };
55
+ mockFlushRtc.mockReturnValue(mockActionResult);
56
+ const payload = [{
57
+ id: 'rtc_1'
58
+ }];
59
+ const {
60
+ result
61
+ } = renderHook(() => useRtcTimerListFlush());
62
+ result.current.flushRtcTimerList(payload);
63
+ expect(mockFlushRtc).toHaveBeenCalledWith(payload);
64
+ expect(mockDispatch).toHaveBeenCalledWith(mockActionResult);
65
+ });
66
+ });
67
+ describe('useTimerListFlush', () => {
68
+ const setSupportMock = value => {
69
+ useSupportLocalTimer.mockReturnValue(_objectSpread({
70
+ isReady: true,
71
+ isSupport: false
72
+ }, value));
73
+ };
74
+ it('uses rtc flush when local timer is supported', () => {
75
+ const mockRtcAction = {
76
+ type: 'rtc'
77
+ };
78
+ mockFlushRtc.mockReturnValue(mockRtcAction);
79
+ setSupportMock({
80
+ isSupport: true,
81
+ isReady: true
82
+ });
83
+ const {
84
+ result
85
+ } = renderHook(() => useTimerListFlush());
86
+ const payload = [{
87
+ id: 'rtc_1'
88
+ }];
89
+ result.current.flushTimerList(payload);
90
+ expect(mockFlushRtc).toHaveBeenCalledWith(payload);
91
+ expect(mockFlushCloud).not.toHaveBeenCalled();
92
+ expect(mockDispatch).toHaveBeenCalledWith(mockRtcAction);
93
+ expect(result.current.isReady).toBe(true);
94
+ });
95
+ it('falls back to cloud flush when local timer is not supported', () => {
96
+ const mockCloudAction = {
97
+ type: 'cloud'
98
+ };
99
+ mockFlushCloud.mockReturnValue(mockCloudAction);
100
+ setSupportMock({
101
+ isSupport: false,
102
+ isReady: false
103
+ });
104
+ const {
105
+ result
106
+ } = renderHook(() => useTimerListFlush());
107
+ const payload = [{
108
+ id: 'cloud_1'
109
+ }];
110
+ result.current.flushTimerList(payload);
111
+ expect(mockFlushCloud).toHaveBeenCalledWith(payload);
112
+ expect(mockFlushRtc).not.toHaveBeenCalled();
113
+ expect(mockDispatch).toHaveBeenCalledWith(mockCloudAction);
114
+ expect(result.current.isReady).toBe(false);
115
+ });
116
+ });
117
+ });
@@ -1,6 +1,6 @@
1
1
  import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
2
  import { renderHook, act } from '@testing-library/react-hooks';
3
- import { useTimerAdd, useTimerAddWithFlush, useTimerUpdate, useTimerRemove, useTimerUpdateStatus } from '../useTimerOperate';
3
+ import { useTimerAdd, useTimerAddWithFlush, useTimerUpdate, useTimerRemove, useTimerUpdateStatus, useTimerUpdateWithFlush, useTimerRemoveWithFlush, useTimerUpdateStatusWithFlush } from '../useTimerOperate';
4
4
  import { useSupportLocalTimer } from '../useTimerSupport';
5
5
  import { useSupport } from '../useCommonSupport';
6
6
  import { useTimerDp } from '../useTimerDp';
@@ -29,6 +29,25 @@ const mockTimer = {
29
29
  aliasName: 'Test Timer',
30
30
  isAppPush: false
31
31
  };
32
+ const mockTimerAddPayload = {
33
+ time: '12:00',
34
+ loops: '1111111',
35
+ dps: {
36
+ power: true
37
+ },
38
+ aliasName: 'Test Timer',
39
+ isAppPush: true
40
+ };
41
+ const mockTimerUpdatePayload = {
42
+ timerId: '1',
43
+ time: '12:30',
44
+ loops: '1111111',
45
+ dps: {
46
+ power: false
47
+ },
48
+ aliasName: 'Updated Timer',
49
+ isAppPush: false
50
+ };
32
51
  describe('useTimerOperate Hooks', () => {
33
52
  beforeEach(() => {
34
53
  jest.clearAllMocks();
@@ -152,7 +171,7 @@ describe('useTimerOperate Hooks', () => {
152
171
  const {
153
172
  result
154
173
  } = renderHook(() => useTimerAdd());
155
- const response = await result.current.addTimer(mockTimer, CLOUD_TIMER_CATEGORY);
174
+ const response = await result.current.addTimer(mockTimerAddPayload, CLOUD_TIMER_CATEGORY);
156
175
  expect(response).toBeTruthy();
157
176
  });
158
177
  it('should add cloud timer when device is in group', async () => {
@@ -165,13 +184,50 @@ describe('useTimerOperate Hooks', () => {
165
184
  await result.current.addTimer(mockTimer, CLOUD_TIMER_CATEGORY);
166
185
  expect(useTimerOperateCloud.useCloudTimerAdd).toHaveBeenCalled();
167
186
  });
187
+ it('should generate missing timerId for local timer addition', async () => {
188
+ var _createdTimer;
189
+ const mockUpdateDp = jest.fn();
190
+ useTimerDp.mockReturnValue({
191
+ updateDp: mockUpdateDp
192
+ });
193
+ getTimerState.mockReturnValue({
194
+ rtcTimerList: [{
195
+ timerId: '2',
196
+ time: '10:00',
197
+ loops: '1111111',
198
+ status: true,
199
+ dps: {},
200
+ aliasName: '',
201
+ isAppPush: false
202
+ }]
203
+ });
204
+ const {
205
+ result
206
+ } = renderHook(() => useTimerAdd());
207
+ const timerInput = {
208
+ time: '09:00',
209
+ loops: '0000000',
210
+ dps: {
211
+ power: false
212
+ },
213
+ aliasName: 'Auto Id',
214
+ isAppPush: false
215
+ };
216
+ let createdTimer = null;
217
+ await act(async () => {
218
+ createdTimer = await result.current.addTimer(timerInput, CLOUD_TIMER_CATEGORY);
219
+ });
220
+ expect(mockUpdateDp).toHaveBeenCalledWith(expect.objectContaining({
221
+ timerId: '1',
222
+ status: true,
223
+ time: '09:00'
224
+ }));
225
+ expect((_createdTimer = createdTimer) === null || _createdTimer === void 0 ? void 0 : _createdTimer.timerId).toBe('1');
226
+ });
168
227
  });
169
228
  describe('useTimerAddWithFlush', () => {
170
229
  it('should add timer and flush list', async () => {
171
230
  const mockFlushTimerList = jest.fn();
172
- jest.fn().mockResolvedValue({
173
- timers: []
174
- });
175
231
  useTimerListFlush.mockReturnValue({
176
232
  flushTimerList: mockFlushTimerList,
177
233
  isReady: true
@@ -193,6 +249,33 @@ describe('useTimerOperate Hooks', () => {
193
249
  });
194
250
  expect(mockFlushTimerList).toHaveBeenCalled();
195
251
  });
252
+ it('should reset loading when refresh list fails', async () => {
253
+ const refreshError = new Error('refresh failed');
254
+ const mockFlushTimerList = jest.fn();
255
+ useTimerListFlush.mockReturnValue({
256
+ flushTimerList: mockFlushTimerList,
257
+ isReady: true
258
+ });
259
+ useTimerOperateLocal.useLocalTimerList.mockReturnValueOnce({
260
+ isLoading: false,
261
+ isSupportLocalTimer: true,
262
+ getTimerList: jest.fn().mockRejectedValue(refreshError)
263
+ });
264
+ const {
265
+ result
266
+ } = renderHook(() => useTimerAddWithFlush());
267
+ let caughtError = null;
268
+ await act(async () => {
269
+ try {
270
+ await result.current.addTimerWithFlush(mockTimerAddPayload);
271
+ } catch (err) {
272
+ caughtError = err;
273
+ }
274
+ });
275
+ expect(caughtError).toBe(refreshError);
276
+ expect(mockFlushTimerList).not.toHaveBeenCalled();
277
+ expect(result.current.isLoading).toBe(false);
278
+ });
196
279
  });
197
280
  describe('useTimerUpdate', () => {
198
281
  it('should handle local timer update when supported', async () => {
@@ -232,6 +315,21 @@ describe('useTimerOperate Hooks', () => {
232
315
  }));
233
316
  expect(useTimerOperateCloud.useCloudTimerUpdate).toHaveBeenCalled();
234
317
  });
318
+ it('should propagate errors from local timer update', async () => {
319
+ const updateError = new Error('update failed');
320
+ const mockUpdate = jest.fn().mockRejectedValue(updateError);
321
+ useTimerOperateLocal.useLocalTimerUpdate.mockReturnValue({
322
+ isLoading: false,
323
+ isSupportLocalTimer: true,
324
+ updateTimer: mockUpdate
325
+ });
326
+ const {
327
+ result
328
+ } = renderHook(() => useTimerUpdate());
329
+ await expect(act(async () => {
330
+ await result.current.updateTimer(mockTimerUpdatePayload, mockTimerUpdatePayload);
331
+ })).rejects.toThrow(updateError);
332
+ });
235
333
  });
236
334
  describe('useTimerRemove', () => {
237
335
  it('should handle local timer removal when supported', async () => {
@@ -283,4 +381,140 @@ describe('useTimerOperate Hooks', () => {
283
381
  expect(useTimerOperateCloud.useCloudTimerUpdateStatus).toHaveBeenCalled();
284
382
  });
285
383
  });
384
+ describe('useTimerUpdateWithFlush', () => {
385
+ it('should flush list after successful update', async () => {
386
+ const timersResponse = {
387
+ timers: [mockTimer]
388
+ };
389
+ const mockFlushTimerList = jest.fn();
390
+ useTimerListFlush.mockReturnValue({
391
+ flushTimerList: mockFlushTimerList,
392
+ isReady: true
393
+ });
394
+ useTimerOperateLocal.useLocalTimerList.mockReturnValueOnce({
395
+ isLoading: false,
396
+ isSupportLocalTimer: true,
397
+ getTimerList: jest.fn().mockResolvedValue(timersResponse)
398
+ });
399
+ const {
400
+ result
401
+ } = renderHook(() => useTimerUpdateWithFlush());
402
+ await act(async () => {
403
+ const success = await result.current.updateTimerWithFlush(mockTimerUpdatePayload, mockTimerUpdatePayload, CLOUD_TIMER_CATEGORY);
404
+ expect(success).toBe(true);
405
+ });
406
+ expect(mockFlushTimerList).toHaveBeenCalledWith(timersResponse.timers);
407
+ });
408
+ it('should reset loading when update fails', async () => {
409
+ const updateError = new Error('update with flush failed');
410
+ useTimerOperateLocal.useLocalTimerUpdate.mockReturnValueOnce({
411
+ isLoading: false,
412
+ isSupportLocalTimer: true,
413
+ updateTimer: jest.fn().mockRejectedValue(updateError)
414
+ });
415
+ const {
416
+ result
417
+ } = renderHook(() => useTimerUpdateWithFlush());
418
+ let caught = null;
419
+ await act(async () => {
420
+ try {
421
+ await result.current.updateTimerWithFlush(mockTimerUpdatePayload, mockTimerUpdatePayload, CLOUD_TIMER_CATEGORY);
422
+ } catch (err) {
423
+ caught = err;
424
+ }
425
+ });
426
+ expect(caught).toBe(updateError);
427
+ expect(result.current.isLoading).toBe(false);
428
+ });
429
+ });
430
+ describe('useTimerRemoveWithFlush', () => {
431
+ it('should flush list after removing timer', async () => {
432
+ const mockFlushTimerList = jest.fn();
433
+ useTimerListFlush.mockReturnValue({
434
+ flushTimerList: mockFlushTimerList,
435
+ isReady: true
436
+ });
437
+ useTimerOperateLocal.useLocalTimerList.mockReturnValueOnce({
438
+ isLoading: false,
439
+ isSupportLocalTimer: true,
440
+ getTimerList: jest.fn().mockResolvedValue({
441
+ timers: [mockTimer]
442
+ })
443
+ });
444
+ const {
445
+ result
446
+ } = renderHook(() => useTimerRemoveWithFlush());
447
+ await act(async () => {
448
+ const res = await result.current.removeTimerWithFlush('1', CLOUD_TIMER_CATEGORY);
449
+ expect(res).toBe(true);
450
+ });
451
+ expect(mockFlushTimerList).toHaveBeenCalledWith([mockTimer]);
452
+ });
453
+ it('should stop loading when remove fails', async () => {
454
+ const removeError = new Error('remove failed');
455
+ useTimerOperateLocal.useLocalTimerRemove.mockReturnValueOnce({
456
+ isLoading: false,
457
+ isSupportLocalTimer: true,
458
+ removeTimer: jest.fn().mockRejectedValue(removeError)
459
+ });
460
+ const {
461
+ result
462
+ } = renderHook(() => useTimerRemoveWithFlush());
463
+ let caught = null;
464
+ await act(async () => {
465
+ try {
466
+ await result.current.removeTimerWithFlush('1', CLOUD_TIMER_CATEGORY);
467
+ } catch (err) {
468
+ caught = err;
469
+ }
470
+ });
471
+ expect(caught).toBe(removeError);
472
+ expect(result.current.isLoading).toBe(false);
473
+ });
474
+ });
475
+ describe('useTimerUpdateStatusWithFlush', () => {
476
+ it('should flush list after updating status', async () => {
477
+ const mockFlushTimerList = jest.fn();
478
+ useTimerListFlush.mockReturnValue({
479
+ flushTimerList: mockFlushTimerList,
480
+ isReady: true
481
+ });
482
+ useTimerOperateLocal.useLocalTimerList.mockReturnValueOnce({
483
+ isLoading: false,
484
+ isSupportLocalTimer: true,
485
+ getTimerList: jest.fn().mockResolvedValue({
486
+ timers: [mockTimer]
487
+ })
488
+ });
489
+ const {
490
+ result
491
+ } = renderHook(() => useTimerUpdateStatusWithFlush());
492
+ await act(async () => {
493
+ const success = await result.current.updateStatusTimerWithFlush('1', false, CLOUD_TIMER_CATEGORY);
494
+ expect(success).toBe(true);
495
+ });
496
+ expect(mockFlushTimerList).toHaveBeenCalledWith([mockTimer]);
497
+ });
498
+ it('should reset loading when status update fails', async () => {
499
+ const statusError = new Error('status update failed');
500
+ useTimerOperateLocal.useLocalTimerUpdateStatus.mockReturnValueOnce({
501
+ isLoading: false,
502
+ isSupportLocalTimer: true,
503
+ updateTimerStatus: jest.fn().mockRejectedValue(statusError)
504
+ });
505
+ const {
506
+ result
507
+ } = renderHook(() => useTimerUpdateStatusWithFlush());
508
+ let caught = null;
509
+ await act(async () => {
510
+ try {
511
+ await result.current.updateStatusTimerWithFlush('1', false, CLOUD_TIMER_CATEGORY);
512
+ } catch (err) {
513
+ caught = err;
514
+ }
515
+ });
516
+ expect(caught).toBe(statusError);
517
+ expect(result.current.isLoading).toBe(false);
518
+ });
519
+ });
286
520
  });