@ray-js/lamp-schedule-core 1.0.1-beta-9 → 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.
Files changed (36) 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/types/rhythms.d.ts +25 -13
  28. package/lib/types/ty.d.ts +3 -0
  29. package/lib/utils/ScheduleDataSync.js +4 -1
  30. package/lib/utils/ScheduleLogger.js +1 -1
  31. package/lib/utils/__test__/ScheduleDataSync.test.d.ts +11 -0
  32. package/lib/utils/__test__/ScheduleDataSync.test.js +137 -0
  33. package/lib/utils/__test__/ScheduleEmit.test.d.ts +1 -0
  34. package/lib/utils/__test__/ScheduleEmit.test.js +35 -0
  35. package/lib/utils/__test__/ScheduleSupport.test.js +288 -154
  36. package/package.json +1 -1
@@ -1,10 +1,23 @@
1
+ import "core-js/modules/esnext.iterator.constructor.js";
2
+ import "core-js/modules/esnext.iterator.for-each.js";
1
3
  import { renderHook, act } from '@testing-library/react-hooks';
2
- import { useLocalTimerAdd, useLocalTimerUpdate, useLocalTimerRemove, useLocalTimerList, useLocalTimerUpdateStatus } from '../useTimerOperateLocal';
3
- import { useLocalTimerCloudProperty } from '../../utils/ScheduleCloudProperty';
4
+ import { useLocalTimerAdd, useLocalTimerUpdate, useLocalTimerRemove, useLocalTimerList, useLocalTimerUpdateStatus, getTimerListWithFlush } from '../useTimerOperateLocal';
4
5
  import { LOCAL_TIMER_CATEGORY } from '../../constant';
5
- import { getTimerState } from '../../context/timer/';
6
-
7
- // Mock dependencies
6
+ const mockSet = jest.fn();
7
+ const mockUpdate = jest.fn();
8
+ const mockRemove = jest.fn();
9
+ const mockUpdateStatus = jest.fn();
10
+ const mockGetAllMethod = jest.fn();
11
+ const mockStandaloneGetAll = jest.fn();
12
+ const mockUseLocalTimerCloudProperty = jest.fn(() => ({
13
+ set: mockSet,
14
+ update: mockUpdate,
15
+ remove: mockRemove,
16
+ updateStatus: mockUpdateStatus,
17
+ getAll: mockGetAllMethod
18
+ }));
19
+ const mockGetTimerState = jest.fn();
20
+ const mockGetTimerReducer = jest.fn();
8
21
  jest.mock('../../utils/ScheduleCache', () => ({
9
22
  devIdOrGroupIdCache: {
10
23
  get: jest.fn().mockReturnValue({
@@ -16,35 +29,58 @@ jest.mock('../../utils/ScheduleCache', () => ({
16
29
  getScheduleCache: jest.fn()
17
30
  }));
18
31
  jest.mock('../../context/timer/', () => ({
19
- getTimerState: jest.fn() // 直接模拟返回值
32
+ getTimerState: function () {
33
+ return mockGetTimerState(...arguments);
34
+ },
35
+ getTimerReducer: function () {
36
+ return mockGetTimerReducer(...arguments);
37
+ }
20
38
  }));
21
-
22
- // Mock ray functions
23
39
  jest.mock('@ray-js/ray', () => ({
24
40
  offDpDataChange: jest.fn(),
25
41
  onDpDataChange: jest.fn()
26
42
  }));
27
43
  jest.mock('../../utils/ScheduleCloudProperty', () => ({
28
- useLocalTimerCloudProperty: jest.fn().mockReturnValue({
29
- set: jest.fn().mockResolvedValue(true),
30
- update: jest.fn().mockResolvedValue(true),
31
- remove: jest.fn().mockResolvedValue(true),
32
- updateStatus: jest.fn().mockResolvedValue(true),
33
- getAll: jest.fn().mockResolvedValue({
34
- properties: {}
35
- })
36
- })
44
+ useLocalTimerCloudProperty: () => mockUseLocalTimerCloudProperty(),
45
+ getAll: () => mockStandaloneGetAll()
46
+ }));
47
+ jest.mock('../../utils/ScheduleLogger', () => ({
48
+ scheduleLogger: {
49
+ debug: jest.fn(),
50
+ error: jest.fn(),
51
+ warn: jest.fn(),
52
+ info: jest.fn()
53
+ }
37
54
  }));
55
+ jest.mock('../../dpParser', () => ({
56
+ timerReportParser: {
57
+ parser: jest.fn()
58
+ }
59
+ }));
60
+ const mockedRay = jest.requireMock('@ray-js/ray');
61
+ const mockScheduleLogger = jest.requireMock('../../utils/ScheduleLogger').scheduleLogger;
62
+ const mockTimerReportParser = jest.requireMock('../../dpParser').timerReportParser.parser;
38
63
  describe('useTimerOperateLocal Hooks', () => {
39
64
  beforeEach(() => {
40
65
  jest.clearAllMocks();
66
+ mockSet.mockReset().mockResolvedValue(true);
67
+ mockUpdate.mockReset().mockResolvedValue(true);
68
+ mockRemove.mockReset().mockResolvedValue(true);
69
+ mockUpdateStatus.mockReset().mockResolvedValue(true);
70
+ mockGetAllMethod.mockReset().mockResolvedValue({
71
+ properties: {}
72
+ });
73
+ mockStandaloneGetAll.mockReset().mockResolvedValue({
74
+ properties: {}
75
+ });
76
+ mockUseLocalTimerCloudProperty.mockClear();
77
+ mockGetTimerState.mockReset();
78
+ mockGetTimerReducer.mockReset();
79
+ mockTimerReportParser.mockReset();
80
+ Object.values(mockScheduleLogger).forEach(fn => fn.mockReset());
41
81
  });
42
82
  describe('useLocalTimerAdd', () => {
43
83
  it('should add local timer successfully', async () => {
44
- const mockSet = jest.fn().mockResolvedValue(true);
45
- useLocalTimerCloudProperty.mockReturnValue({
46
- set: mockSet
47
- });
48
84
  const {
49
85
  result
50
86
  } = renderHook(() => useLocalTimerAdd(true));
@@ -64,10 +100,8 @@ describe('useTimerOperateLocal Hooks', () => {
64
100
  expect(result.current.isLoading).toBe(false);
65
101
  });
66
102
  it('should handle add timer error', async () => {
67
- const mockSet = jest.fn().mockRejectedValue(new Error('Add failed'));
68
- useLocalTimerCloudProperty.mockReturnValue({
69
- set: mockSet
70
- });
103
+ const error = new Error('Add failed');
104
+ mockSet.mockRejectedValueOnce(error);
71
105
  const {
72
106
  result
73
107
  } = renderHook(() => useLocalTimerAdd(true));
@@ -80,17 +114,20 @@ describe('useTimerOperateLocal Hooks', () => {
80
114
  aliasName: 'Test Timer',
81
115
  isAppPush: true
82
116
  };
83
- await expect(act(async () => {
84
- await result.current.addTimer(mockTimer);
85
- })).rejects.toThrow('Add failed');
117
+ let caught = null;
118
+ await act(async () => {
119
+ try {
120
+ await result.current.addTimer(mockTimer);
121
+ } catch (err) {
122
+ caught = err;
123
+ }
124
+ });
125
+ expect(caught).toBe(error);
126
+ expect(result.current.isLoading).toBe(false);
86
127
  });
87
128
  });
88
129
  describe('useLocalTimerUpdate', () => {
89
130
  it('should update local timer successfully', async () => {
90
- const mockUpdate = jest.fn().mockResolvedValue(true);
91
- useLocalTimerCloudProperty.mockReturnValue({
92
- update: mockUpdate
93
- });
94
131
  const {
95
132
  result
96
133
  } = renderHook(() => useLocalTimerUpdate(true));
@@ -112,10 +149,6 @@ describe('useTimerOperateLocal Hooks', () => {
112
149
  });
113
150
  describe('useLocalTimerRemove', () => {
114
151
  it('should remove local timer successfully', async () => {
115
- const mockRemove = jest.fn().mockResolvedValue(true);
116
- useLocalTimerCloudProperty.mockReturnValue({
117
- remove: mockRemove
118
- });
119
152
  const {
120
153
  result
121
154
  } = renderHook(() => useLocalTimerRemove(true));
@@ -127,7 +160,7 @@ describe('useTimerOperateLocal Hooks', () => {
127
160
  });
128
161
  describe('useLocalTimerList', () => {
129
162
  it('should get local timer list successfully', async () => {
130
- const mockGetAll = jest.fn().mockResolvedValue({
163
+ mockGetAllMethod.mockResolvedValueOnce({
131
164
  properties: {
132
165
  [`${LOCAL_TIMER_CATEGORY}_1`]: JSON.stringify({
133
166
  time: '12:00',
@@ -139,9 +172,6 @@ describe('useTimerOperateLocal Hooks', () => {
139
172
  })
140
173
  }
141
174
  });
142
- useLocalTimerCloudProperty.mockReturnValue({
143
- getAll: mockGetAll
144
- });
145
175
  const {
146
176
  result
147
177
  } = renderHook(() => useLocalTimerList(true));
@@ -153,16 +183,13 @@ describe('useTimerOperateLocal Hooks', () => {
153
183
  });
154
184
  });
155
185
  it('should handle invalid timer data', async () => {
156
- const mockGetAll = jest.fn().mockResolvedValue({
186
+ mockGetAllMethod.mockResolvedValueOnce({
157
187
  properties: {
158
188
  [`${LOCAL_TIMER_CATEGORY}_1`]: 'invalid_json',
159
189
  [`${LOCAL_TIMER_CATEGORY}_2`]: '1',
160
190
  [`${LOCAL_TIMER_CATEGORY}_3`]: 'null'
161
191
  }
162
192
  });
163
- useLocalTimerCloudProperty.mockReturnValue({
164
- getAll: mockGetAll
165
- });
166
193
  const {
167
194
  result
168
195
  } = renderHook(() => useLocalTimerList(true));
@@ -172,12 +199,9 @@ describe('useTimerOperateLocal Hooks', () => {
172
199
  });
173
200
  });
174
201
  it('should get empty timer list when no timers exist', async () => {
175
- const mockGetAll = jest.fn().mockResolvedValue({
202
+ mockGetAllMethod.mockResolvedValueOnce({
176
203
  properties: {}
177
204
  });
178
- useLocalTimerCloudProperty.mockReturnValue({
179
- getAll: mockGetAll
180
- });
181
205
  const {
182
206
  result
183
207
  } = renderHook(() => useLocalTimerList(false));
@@ -189,11 +213,7 @@ describe('useTimerOperateLocal Hooks', () => {
189
213
  });
190
214
  describe('useLocalTimerUpdateStatus', () => {
191
215
  it('should update timer status successfully', async () => {
192
- const mockUpdateStatus = jest.fn().mockResolvedValue(true);
193
- useLocalTimerCloudProperty.mockReturnValue({
194
- updateStatus: mockUpdateStatus
195
- });
196
- getTimerState.mockReturnValue({
216
+ mockGetTimerState.mockReturnValue({
197
217
  rtcTimerList: [{
198
218
  timerId: '1',
199
219
  time: '12:00',
@@ -213,4 +233,92 @@ describe('useTimerOperateLocal Hooks', () => {
213
233
  expect(result.current.isLoading).toBe(false);
214
234
  });
215
235
  });
236
+ describe('event handling and utilities', () => {
237
+ it('should flush rtc list when timer_report dp changes', () => {
238
+ const flushRtcTimerListMock = jest.fn().mockReturnValue('flush-action');
239
+ const dispatchMock = jest.fn();
240
+ mockGetTimerState.mockReturnValue({
241
+ rtcTimerList: [{
242
+ timerId: '1',
243
+ status: false,
244
+ time: '08:00',
245
+ loops: '0000000',
246
+ dps: {}
247
+ }]
248
+ });
249
+ mockGetTimerReducer.mockReturnValue({
250
+ dispatch: dispatchMock,
251
+ actions: {
252
+ flushRtcTimerList: flushRtcTimerListMock
253
+ }
254
+ });
255
+ mockTimerReportParser.mockReturnValue([{
256
+ id: '1',
257
+ status: true
258
+ }]);
259
+ const {
260
+ unmount
261
+ } = renderHook(() => useLocalTimerList(true));
262
+ const dpCallback = mockedRay.onDpDataChange.mock.calls[0][0];
263
+ dpCallback({
264
+ dps: {
265
+ 38: '[{"id":"1","status":true}]'
266
+ }
267
+ });
268
+ expect(mockTimerReportParser).toHaveBeenCalledWith('[{"id":"1","status":true}]');
269
+ expect(flushRtcTimerListMock).toHaveBeenCalledWith([{
270
+ timerId: '1',
271
+ status: true,
272
+ time: '08:00',
273
+ loops: '0000000',
274
+ dps: {}
275
+ }]);
276
+ expect(dispatchMock).toHaveBeenCalledWith('flush-action');
277
+ unmount();
278
+ });
279
+ it('should warn when updating status without matching timer', async () => {
280
+ mockGetTimerState.mockReturnValue({
281
+ rtcTimerList: []
282
+ });
283
+ const {
284
+ result
285
+ } = renderHook(() => useLocalTimerUpdateStatus(true));
286
+ await act(async () => {
287
+ result.current.updateTimerStatus('not-exist', true);
288
+ });
289
+ expect(mockScheduleLogger.warn).toHaveBeenCalledWith('useLocalTimerUpdateStatus: currentTimer with timerId not-exist is undefined');
290
+ expect(mockUpdateStatus).not.toHaveBeenCalled();
291
+ });
292
+ it('should fetch timers and dispatch via getTimerListWithFlush', async () => {
293
+ mockStandaloneGetAll.mockResolvedValueOnce({
294
+ properties: {
295
+ [`${LOCAL_TIMER_CATEGORY}_5`]: JSON.stringify({
296
+ time: '10:10',
297
+ loops: '0101010',
298
+ status: false,
299
+ dps: {
300
+ brightness: 10
301
+ }
302
+ })
303
+ }
304
+ });
305
+ const dispatchMock = jest.fn();
306
+ const flushActionMock = jest.fn().mockReturnValue('action');
307
+ await getTimerListWithFlush(dispatchMock, {
308
+ flushRtcTimerList: flushActionMock
309
+ });
310
+ expect(flushActionMock).toHaveBeenCalledWith([{
311
+ timerId: '5',
312
+ time: '10:10',
313
+ loops: '0101010',
314
+ status: false,
315
+ dps: {
316
+ brightness: 10
317
+ },
318
+ aliasName: '',
319
+ isAppPush: false
320
+ }]);
321
+ expect(dispatchMock).toHaveBeenCalledWith('action');
322
+ });
323
+ });
216
324
  });
@@ -45,24 +45,36 @@ interface IProps {
45
45
  onSave: (data: Record<string, any>[]) => void;
46
46
  onCancel: (data: Record<string, any>[]) => void;
47
47
  }
48
- interface IReturn {
49
- type: ERhythm;
48
+ export type RhythmAction = {
49
+ type: ERhythm.UPDATE_RHYTHMS_MESSAGE;
50
+ payload: RhythmMessage;
51
+ } | {
52
+ type: ERhythm.UPDATE_RHYTHMS;
53
+ payload: TRhythmData[];
54
+ } | {
55
+ type: ERhythm.UPDATE_LOCAL_MESSAGE;
56
+ payload: LocalMessage;
57
+ } | {
58
+ type: ERhythm.UPDATE_UI;
59
+ payload: Partial<IState>;
60
+ } | {
61
+ type: ERhythm.INIT_SYSTEM_INFO;
50
62
  payload: any;
51
- }
63
+ } | {
64
+ type: ERhythm.UPDATE_DP;
65
+ payload: Record<string, any>;
66
+ };
52
67
  export interface IContext {
53
68
  state: IState;
54
- dispatch: Dispatch<{
55
- type: string;
56
- payload?: Partial<IState>;
57
- }>;
69
+ dispatch: Dispatch<RhythmAction>;
58
70
  props: IProps;
59
71
  actions?: {
60
- updateRhythmsMessage: (data: RhythmMessage) => IReturn;
61
- updateRhythms: (data: TRhythmData[]) => IReturn;
62
- updateLocalMessage: (data: LocalMessage) => IReturn;
63
- updateUI: (data: any[]) => IReturn;
64
- initSystemInfo: (data: any) => IReturn;
65
- updateDp: (data: any) => IReturn;
72
+ updateRhythmsMessage: (data: RhythmMessage) => RhythmAction;
73
+ updateRhythms: (data: TRhythmData[]) => RhythmAction;
74
+ updateLocalMessage: (data: LocalMessage) => RhythmAction;
75
+ updateUI: (data: Partial<IState>) => RhythmAction;
76
+ initSystemInfo: (data: any) => RhythmAction;
77
+ updateDp: (data: Record<string, any>) => RhythmAction;
66
78
  };
67
79
  }
68
80
  export interface IState {
@@ -0,0 +1,3 @@
1
+ declare const ty: Record<string, any> & {
2
+ presetFunctionalData?: (options: { url: string; data: any }) => void;
3
+ };
@@ -49,7 +49,10 @@ export const navigateToSchedule = (params, data, eventsMap) => {
49
49
  groupId
50
50
  } = params;
51
51
  if (!deviceId && !groupId) {
52
- throw new Error('devId or groupId is required');
52
+ throw new Error(JSON.stringify({
53
+ params,
54
+ message: 'devId or groupId is required'
55
+ }));
53
56
  }
54
57
  let scheduleUrl = `${SCHEDULE_FUNCTIONAL_URL}?deviceId=${deviceId}`;
55
58
  if (groupId) {
@@ -68,4 +68,4 @@ export class ScheduleLogger {
68
68
  }
69
69
  }
70
70
  export const scheduleLogger = new ScheduleLogger();
71
- scheduleLogger.setLogLevel(LogLevel.WARN);
71
+ scheduleLogger.setLogLevel(LogLevel.DEBUG);
@@ -0,0 +1,11 @@
1
+ /// <reference types="jest" />
2
+ declare const mockNavigateTo: jest.Mock<any, any, any>;
3
+ declare const mockUsePageInstance: jest.Mock<any, any, any>;
4
+ declare const mockLogger: {
5
+ debug: jest.Mock<any, any, any>;
6
+ error: jest.Mock<any, any, any>;
7
+ };
8
+ declare let navigateToSchedule: typeof import('../ScheduleDataSync').navigateToSchedule;
9
+ declare let navigateToMainApp: typeof import('../ScheduleDataSync').navigateToMainApp;
10
+ declare let functionalTransDataEventKey: typeof import('../ScheduleDataSync').functionalTransDataEventKey;
11
+ declare const loadModule: () => void;
@@ -0,0 +1,137 @@
1
+ const mockNavigateTo = jest.fn();
2
+ const mockUsePageInstance = jest.fn();
3
+ const mockLogger = {
4
+ debug: jest.fn(),
5
+ error: jest.fn()
6
+ };
7
+ let navigateToSchedule;
8
+ let navigateToMainApp;
9
+ let functionalTransDataEventKey;
10
+ const loadModule = () => {
11
+ const moduleExports = require('../ScheduleDataSync');
12
+ ({
13
+ navigateToSchedule,
14
+ navigateToMainApp,
15
+ functionalTransDataEventKey
16
+ } = moduleExports);
17
+ };
18
+ describe('ScheduleDataSync utilities', () => {
19
+ beforeEach(() => {
20
+ jest.resetModules();
21
+ mockNavigateTo.mockReset();
22
+ mockUsePageInstance.mockReset();
23
+ mockLogger.debug.mockReset();
24
+ mockLogger.error.mockReset();
25
+ global.ty = {
26
+ presetFunctionalData: jest.fn()
27
+ };
28
+ jest.doMock('@ray-js/ray', () => ({
29
+ usePageInstance: mockUsePageInstance,
30
+ navigateTo: mockNavigateTo
31
+ }));
32
+ jest.doMock('../ScheduleLogger', () => ({
33
+ scheduleLogger: mockLogger
34
+ }));
35
+ loadModule();
36
+ });
37
+ afterEach(() => {
38
+ delete global.ty;
39
+ });
40
+ describe('navigateToSchedule', () => {
41
+ it('throws when both deviceId and groupId are missing', () => {
42
+ expect(() => navigateToSchedule({
43
+ deviceId: '',
44
+ groupId: ''
45
+ }, {})).toThrow('devId or groupId is required');
46
+ });
47
+ it('presets data with deviceId and forwards events', () => {
48
+ var _navigateArgs$success, _navigateArgs$fail;
49
+ const data = {
50
+ forceSupport: {
51
+ countdown: true
52
+ },
53
+ i18nKeyMap: {
54
+ key: 'value'
55
+ }
56
+ };
57
+ const eventsMap = {
58
+ timerDataChange: jest.fn()
59
+ };
60
+ navigateToSchedule({
61
+ deviceId: 'dev123',
62
+ groupId: ''
63
+ }, data, eventsMap);
64
+ expect(global.ty.presetFunctionalData).toHaveBeenCalledWith({
65
+ url: 'functional://LampScheduleSetFunction/home?deviceId=dev123',
66
+ data
67
+ });
68
+ expect(mockNavigateTo).toHaveBeenCalledTimes(1);
69
+ const navigateArgs = mockNavigateTo.mock.calls[0][0];
70
+ expect(navigateArgs.url).toBe('functional://LampScheduleSetFunction/home?deviceId=dev123');
71
+ expect(navigateArgs.events).toBe(eventsMap);
72
+ (_navigateArgs$success = navigateArgs.success) === null || _navigateArgs$success === void 0 || _navigateArgs$success.call(navigateArgs, 'ok');
73
+ expect(mockLogger.debug).toHaveBeenCalledWith(' success:', 'ok');
74
+ (_navigateArgs$fail = navigateArgs.fail) === null || _navigateArgs$fail === void 0 || _navigateArgs$fail.call(navigateArgs, 'err');
75
+ expect(mockLogger.error).toHaveBeenCalledWith('navigateToSchedule fail:', 'err');
76
+ });
77
+ it('uses groupId when provided', () => {
78
+ navigateToSchedule({
79
+ deviceId: 'dev123',
80
+ groupId: 'group456'
81
+ }, {});
82
+ expect(global.ty.presetFunctionalData).toHaveBeenCalledWith({
83
+ url: 'functional://LampScheduleSetFunction/home?groupId=group456',
84
+ data: {}
85
+ });
86
+ expect(mockNavigateTo).toHaveBeenCalledWith(expect.objectContaining({
87
+ url: 'functional://LampScheduleSetFunction/home?groupId=group456'
88
+ }));
89
+ });
90
+ });
91
+ describe('navigateToMainApp', () => {
92
+ it('throws when route does not start with slash', () => {
93
+ expect(() => navigateToMainApp('pages/index')).toThrow('route must start with /');
94
+ });
95
+ it('navigates with query params and triggers callback', () => {
96
+ const callback = jest.fn();
97
+ mockNavigateTo.mockImplementation(_ref => {
98
+ var _events$functionalTra;
99
+ let {
100
+ events,
101
+ success
102
+ } = _ref;
103
+ (_events$functionalTra = events[functionalTransDataEventKey]) === null || _events$functionalTra === void 0 || _events$functionalTra.call(events, {
104
+ dps: {
105
+ switch_led: true
106
+ },
107
+ foo: 'bar'
108
+ });
109
+ success === null || success === void 0 || success('success-result');
110
+ });
111
+ navigateToMainApp('/pages/index', {
112
+ foo: '1',
113
+ bar: '2'
114
+ }, callback);
115
+ expect(mockNavigateTo).toHaveBeenCalledTimes(1);
116
+ const navigateArgs = mockNavigateTo.mock.calls[0][0];
117
+ expect(navigateArgs.url).toBe('/pages/index?foo=1&bar=2&');
118
+ expect(callback).toHaveBeenCalledWith({
119
+ dps: {
120
+ switch_led: true
121
+ },
122
+ foo: 'bar'
123
+ });
124
+ expect(mockLogger.debug).toHaveBeenCalledWith('navigateToMainApp success: ', 'success-result');
125
+ });
126
+ it('logs error when navigation fails', () => {
127
+ mockNavigateTo.mockImplementation(_ref2 => {
128
+ let {
129
+ fail
130
+ } = _ref2;
131
+ return fail === null || fail === void 0 ? void 0 : fail('oops');
132
+ });
133
+ navigateToMainApp('/pages/index');
134
+ expect(mockLogger.error).toHaveBeenCalledWith('navigateToMainApp fail:', 'oops');
135
+ });
136
+ });
137
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ import emitt, { emitter } from '../ScheduleEmit';
2
+ describe('ScheduleEmit', () => {
3
+ it('registers and emits events to handlers', () => {
4
+ const localEmitter = emitt();
5
+ const handler = jest.fn();
6
+ localEmitter.on('event', handler);
7
+ localEmitter.emit('event', 1, 2);
8
+ expect(handler).toHaveBeenCalledWith(1, 2);
9
+ });
10
+ it('supports removing handlers', () => {
11
+ const localEmitter = emitt();
12
+ const handler = jest.fn();
13
+ localEmitter.on('event', handler);
14
+ localEmitter.off('event', handler);
15
+ localEmitter.emit('event', 'data');
16
+ expect(handler).not.toHaveBeenCalled();
17
+ });
18
+ it('invokes wildcard handlers after specific ones', () => {
19
+ const localEmitter = emitt();
20
+ const eventHandler = jest.fn();
21
+ const wildcardHandler = jest.fn();
22
+ localEmitter.on('event', eventHandler);
23
+ localEmitter.on('*', wildcardHandler);
24
+ localEmitter.emit('event', 'payload');
25
+ expect(eventHandler).toHaveBeenCalledWith('payload');
26
+ expect(wildcardHandler).toHaveBeenCalledWith('event', 'payload');
27
+ });
28
+ it('global emitter works as singleton', () => {
29
+ const handler = jest.fn();
30
+ emitter.on('global', handler);
31
+ emitter.emit('global', 123);
32
+ expect(handler).toHaveBeenCalledWith(123);
33
+ emitter.off('global', handler);
34
+ });
35
+ });