• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ringcentral / ringcentral-js-widgets / 4878562030

pending completion
4878562030

push

github

GitHub
sync features and bugfixs from e3d1ac7 (#1714)

1724 of 6102 branches covered (28.25%)

Branch coverage included in aggregate %.

751 of 2684 new or added lines in 97 files covered. (27.98%)

457 existing lines in 34 files now uncovered.

3248 of 8911 relevant lines covered (36.45%)

18.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.11
/packages/core/lib/RcModule/RcModule.ts
1
import { combineReducers, Reducer, ReducersMapObject } from 'redux';
2
import {
3
  action,
4
  Action,
5
  applyPatches,
6
  computed,
7
  createStore,
8
  enableES5,
9
  enablePatches,
10
  getStagedState,
11
  identifierKey,
12
  Service,
13
  setAutoFreeze,
14
  setPatchesToggle,
15
  state,
16
  stateKey,
17
  Store,
18
  storeKey,
19
  subscribe,
20
  Subscription,
21
  subscriptionsKey,
22
  usm as usmAction,
23
  watch,
24
} from '../usm-redux';
25

26
setAutoFreeze(false);
51✔
27

28
setPatchesToggle(!process.env.DISABLE_PATCHES);
51✔
29
if (!process.env.DISABLE_PATCHES) {
51!
30
  enablePatches();
51✔
31
}
32

33
export const enum ModuleStatus {
34
  Pending = 'PENDING',
35
  Initializing = 'INITIALIZING',
36
  Ready = 'READY',
37
  Resetting = 'RESETTING',
38
}
39

40
export const onceKey: unique symbol = Symbol('once');
51✔
41
export const onInitOnceKey: unique symbol = Symbol('onInitOnce');
51✔
42
export const notReadyModulesKey: unique symbol = Symbol('notReadyModules');
51✔
43
export const checkStatusChangeKey: unique symbol = Symbol('checkStatusChange');
51✔
44
export const enableCacheKey: unique symbol = Symbol('enableCache');
51✔
45
export const enableGlobalCacheKey: unique symbol = Symbol('enableGlobalCache');
51✔
46
export const storageKey: unique symbol = Symbol('storage');
51✔
47
export const storageStateKey: unique symbol = Symbol('storageState');
51✔
48
export const globalStorageStateKey: unique symbol =
49
  Symbol('globalStorageState');
51✔
50
export const spawnReducersKey: unique symbol = Symbol('spawnReducers');
51✔
51
export const spawnStorageReducersKey: unique symbol = Symbol(
51✔
52
  'spawnStorageReducers',
53
);
54
export const ignoreReadyModulesKey: unique symbol =
55
  Symbol('ignoreReadyModules');
51✔
56

57
export interface RcModuleOptions<T> {
58
  deps?: T & {
59
    storage?: any;
60
    globalStorage?: any;
61
  };
62
  enableCache?: boolean;
63
  enableGlobalCache?: boolean;
64
  storageKey?: string;
65
}
66

67
interface RcModuleV2 {
68
  parentModule: RcModuleV2;
69
  [storageStateKey]: string[];
70
  [globalStorageStateKey]: string[];
71
  [stateKey]: Record<string, any>;
72
  [identifierKey]: string;
73
  [subscriptionsKey]: Subscription[];
74
  [storeKey]: Store;
75
}
76

77
interface IStorage extends RcModuleV2 {
78
  registerReducer(options: {
79
    key: string;
80
    reducer: Reducer<any, Action>;
81
  }): void;
82
  data: Record<string, any>;
83
}
84

85
/**
86
 * Module system based on Dependency Injection and Redux
87
 *
88
 * life cycle:
89
 * - `constructor`
90
 * - `onInitOnce`: when deps are ready, only run once in whole life cycle
91
 * - `onInit`: when module init, module status will be set to `ready` after that event.
92
 * - `onInitSuccess`: when onInit be passed successfully, this event will be triggered.
93
 * - `onReset`: when one of deps be reset, this event will be triggered.
94
 */
95
abstract class RcModuleV2<
96
  T extends Record<string, any> & {
97
    storage?: IStorage;
98
    globalStorage?: IStorage;
99
  } = {},
100
> {
101
  private [onceKey] = false;
241✔
102

103
  private [storageKey]?: string;
104

105
  private [enableCacheKey]: boolean;
106

107
  private [enableGlobalCacheKey]: boolean;
108

109
  /**
110
   * background/client transport for browser extension
111
   */
112
  protected _transport?: any; // TODO: add transport type
113
  private [ignoreReadyModulesKey] = new Set<RcModuleV2>();
241✔
114

115
  protected initializeProxy?(): Promise<void> | void;
116
  /**
117
   * `onInit` life cycle for current initialization before all deps modules are all ready.
118
   */
119
  protected onInit?(): Promise<void> | void;
120
  /**
121
   * `onInitOnce` once life cycle for current initialization before all deps modules are all ready.
122
   */
123
  protected onInitOnce?(): Promise<void> | void;
124
  /**
125
   * `onInitSuccess` life cycle for current initialization after this module is ready.
126
   */
127
  protected onInitSuccess?(): Promise<void> | void;
128
  /**
129
   * `onReset` life cycle for current reset before one of deps modules is not ready.
130
   */
131
  protected onReset?(): Promise<void> | void;
132
  /**
133
   * Each Redux dispatch action will trigger `onStateChange` once.
134
   */
135
  protected onStateChange?(): Promise<void> | void;
136

137
  protected _deps: T;
138

139
  constructor({
16✔
140
    deps,
141
    enableCache = false,
122✔
142
    enableGlobalCache = false,
226✔
143
    ...options
144
  }: RcModuleOptions<T> = {}) {
145
    this._deps = deps! ?? {};
241✔
146
    this[storageKey] = options.storageKey;
241✔
147
    this[enableCacheKey] = enableCache;
241✔
148
    this[enableGlobalCacheKey] = enableGlobalCache;
241✔
149
    subscribe(this, () => {
241✔
150
      if (typeof this.onStateChange === 'function') {
103✔
151
        this.onStateChange();
17✔
152
      }
153
      this[checkStatusChangeKey]();
103✔
154
    });
155
    if (
241✔
156
      !this[storageStateKey] ||
479✔
157
      !this[enableCacheKey] ||
158
      !this._deps.storage
159
    ) {
160
      this[storageStateKey] = [];
217✔
161
    }
162
    this[storageStateKey].forEach((key) => delete this[stateKey][key]);
241✔
163
    if (
241✔
164
      !this[globalStorageStateKey] ||
266✔
165
      !this[enableGlobalCacheKey] ||
166
      !this._deps.globalStorage
167
    ) {
168
      this[globalStorageStateKey] = [];
240✔
169
    }
170
    this[globalStorageStateKey].forEach((key) => delete this[stateKey][key]);
241✔
171
  }
172

173
  @state
174
  status = ModuleStatus.Pending;
241✔
175

176
  @action
177
  private _setStatus(status: ModuleStatus) {
178
    this.status = status;
34✔
179
  }
180

181
  private async [onInitOnceKey]() {
182
    if (!this[onceKey]) {
18!
183
      this[onceKey] = true;
18✔
184
      if (typeof this.onInitOnce === 'function') {
18✔
185
        await this.onInitOnce();
1✔
186
      }
187
    }
188
  }
189

190
  private async [checkStatusChangeKey]() {
191
    if (this._shouldInit()) {
127✔
192
      this._setStatus(ModuleStatus.Initializing);
18✔
193
      await this[onInitOnceKey]();
18✔
194
      if (typeof this.onInit === 'function') {
18✔
195
        await this.onInit();
6✔
196
      }
197
      this._setStatus(ModuleStatus.Ready);
16✔
198
      if (typeof this.onInitSuccess === 'function') {
16!
UNCOV
199
        await this.onInitSuccess();
×
200
      }
201
    } else if (this._shouldReset()) {
109!
UNCOV
202
      this._setStatus(ModuleStatus.Resetting);
×
UNCOV
203
      if (typeof this.onReset === 'function') {
×
UNCOV
204
        await this.onReset();
×
205
      }
UNCOV
206
      this._setStatus(ModuleStatus.Pending);
×
207
    }
208
  }
209

210
  protected _ignoreModuleReadiness(dep: RcModuleV2) {
211
    this[ignoreReadyModulesKey].add(dep);
2✔
212
  }
213

214
  private get [notReadyModulesKey](): RcModuleV2[] {
215
    const modules = Object.values(this._deps || {}).filter(
236!
216
      // In order to be compatible with RcModuleV1
217
      (module: RcModuleV2) => module && typeof module.ready !== 'undefined',
125✔
218
    );
219
    return modules.filter(
236✔
220
      (module: RcModuleV2) =>
221
        !module.ready && !this[ignoreReadyModulesKey].has(module),
114✔
222
    );
223
  }
224

225
  private _getLastState() {
226
    const lastState: Record<string, any> = {
73✔
227
      // for combineReducers check state with reducers
228
      __state: this._getStateV2(this[storeKey].getState(), this[identifierKey]),
229
      __identifier: this[identifierKey],
230
    };
231
    if (this._deps.storage?.data) {
67!
NEW
232
      this[storageStateKey].forEach((key: string) => {
×
NEW
233
        const storageReducerKey = `${this[storageKey]}-${key}`;
×
NEW
234
        lastState[key] = this._deps.storage!.data[storageReducerKey];
×
235
      });
236
    }
237
    if (this._deps.globalStorage?.data) {
67!
NEW
238
      this[globalStorageStateKey].forEach((key: string) => {
×
NEW
239
        const storageReducerKey = `${this[storageKey]}-${key}`;
×
NEW
240
        lastState[key] = this._deps.globalStorage!.data[storageReducerKey];
×
241
      });
242
    }
243
    return lastState;
67✔
244
  }
245

246
  private _handleState(state: {
247
    __state?: Record<string, any>;
248
    __identifier?: string;
249
    [key: string]: any;
250
  }) {
251
    Object.assign(state, state.__state);
49✔
252
    delete state.__state;
49✔
253
    delete state.__identifier;
49✔
254
  }
255

256
  _shouldInit() {
257
    const areAllReady = this[notReadyModulesKey].length === 0;
127✔
258
    return areAllReady && this.pending;
127✔
259
  }
260

261
  _shouldReset() {
262
    const areNotReady = this[notReadyModulesKey].length > 0;
109✔
263
    return areNotReady && this.ready;
109✔
264
  }
265

266
  _depsCheck(
267
    checkedModules: RcModuleV2[] = [this],
4✔
268
    pickedModules: RcModuleV2[] = [],
4✔
269
  ) {
270
    Object.values(this._deps ?? {}).forEach((module) => {
5!
271
      if (module instanceof RcModuleV2 && !module.ready) {
5✔
272
        const notReadyModules = Object.values(module._deps ?? {}).filter(
3!
273
          (item) => item instanceof RcModuleV2 && !item.ready,
2✔
274
        );
275
        if (notReadyModules.length > 0 && !checkedModules.includes(module)) {
3✔
276
          checkedModules.push(module);
1✔
277
          module._depsCheck(checkedModules, pickedModules);
1✔
278
        } else if (
2!
279
          !pickedModules.includes(module) &&
4✔
280
          notReadyModules.length === 0
281
        ) {
282
          pickedModules.push(module);
2✔
283
        }
284
      }
285
    });
286
    // please check `_shouldInit()` or `onInit()`.
287
    return pickedModules;
5✔
288
  }
289

290
  @action
291
  _changeState(callback: () => void) {
292
    callback();
1✔
293
  }
294

295
  get pending() {
296
    return this.status === ModuleStatus.Pending;
101✔
297
  }
298

299
  get ready() {
300
    return this.status === ModuleStatus.Ready;
274✔
301
  }
302

303
  get resetting() {
304
    return this.status === ModuleStatus.Resetting;
×
305
  }
306

307
  get initializing() {
308
    return this.status === ModuleStatus.Initializing;
×
309
  }
310

311
  get store() {
312
    return this[storeKey];
×
313
  }
314

315
  private [spawnStorageReducersKey]() {
316
    const descriptors: PropertyDescriptorMap = {};
20✔
317
    /**
318
     * make storage reducer and state
319
     */
320
    this[storageStateKey].forEach((key) => {
20✔
321
      const descriptor = Object.getOwnPropertyDescriptor(this, key);
1✔
322
      if (typeof descriptor === 'undefined') return;
1!
323
      const initialState = descriptor.value;
1✔
324
      const storageReducerKey = `${this[storageKey]}-${key}`;
1✔
325
      this._deps.storage!.registerReducer({
1✔
326
        key: storageReducerKey,
327
        reducer: (state = initialState, action: Action) =>
2✔
328
          action._usm === usmAction &&
2!
329
          action.type === this[identifierKey] &&
330
          Object.hasOwnProperty.call(action._state, key)
331
            ? action._state[key]
332
            : state,
333
      });
334
      Object.assign(descriptors, {
1✔
335
        [key]: {
336
          enumerable: true,
337
          configurable: false,
338
          get(this: Service) {
UNCOV
339
            const stagedState: any = getStagedState();
×
NEW
340
            return stagedState?.__identifier === this[identifierKey]
×
341
              ? stagedState[key]
342
              : this._deps.storage.data![storageReducerKey];
343
          },
344
          set(this: Service, value: unknown) {
UNCOV
345
            const stagedState = getStagedState();
×
NEW
346
            if (
×
347
              stagedState &&
×
348
              stagedState.__identifier !== this[identifierKey]
349
            ) {
NEW
350
              throw new Error(
×
351
                `RcModule does not support cross-module execution of the methods decorated by action`,
352
              );
353
            }
UNCOV
354
            if (stagedState) {
×
NEW
355
              stagedState[key] = value;
×
UNCOV
356
              return;
×
357
            }
358
            this._deps.storage.data![storageReducerKey] = value;
×
359
          },
360
        },
361
      });
362
    });
363

364
    /**
365
     * make global storage reducer and state
366
     */
367
    this[globalStorageStateKey].forEach((key) => {
20✔
368
      const descriptor = Object.getOwnPropertyDescriptor(this, key);
1✔
369
      if (typeof descriptor === 'undefined') return;
1!
370
      const initialState = descriptor.value;
1✔
371
      const storageReducerKey = `${this[storageKey]}-${key}`;
1✔
372
      this._deps.globalStorage!.registerReducer({
1✔
373
        key: storageReducerKey,
374
        reducer: (state = initialState, action: Action) =>
2✔
375
          action._usm === usmAction &&
2!
376
          action.type === this[identifierKey] &&
377
          Object.hasOwnProperty.call(action._state, key)
378
            ? action._state[key]
379
            : state,
380
      });
381
      Object.assign(descriptors, {
1✔
382
        [key]: {
383
          enumerable: true,
384
          configurable: false,
385
          get(this: Service) {
UNCOV
386
            const stagedState = getStagedState();
×
NEW
387
            return stagedState?.__identifier === this[identifierKey]
×
388
              ? stagedState[key]
389
              : this._deps.globalStorage.data![storageReducerKey];
390
          },
391
          set(this: Service, value: unknown) {
UNCOV
392
            const stagedState = getStagedState();
×
NEW
393
            if (
×
394
              stagedState &&
×
395
              stagedState.__identifier !== this[identifierKey]
396
            ) {
NEW
397
              throw new Error(
×
398
                `RcModule does not support cross-module execution of the methods decorated by action`,
399
              );
400
            }
UNCOV
401
            if (stagedState) {
×
NEW
402
              stagedState[key] = value;
×
UNCOV
403
              return;
×
404
            }
405
            this._deps.globalStorage.data![storageReducerKey] = value;
×
406
          },
407
        },
408
      });
409
    });
410

411
    Object.defineProperties(this, descriptors);
20✔
412
  }
413

414
  // TODO: Refactor without RcModuleV1
415
  // harmony with RcModule V1 for modules initialization
416

417
  private _modulePath = 'root';
241✔
418

419
  private _initialized = false;
241✔
420

421
  private _suppressInit?: boolean;
422

423
  protected _reducers?: ReducersMapObject;
424

425
  protected _getStateV2 = (state: Record<string, any>, key: string) =>
241✔
426
    state[key];
75✔
427

428
  private _setStore() {
429
    return this._initModule();
×
430
  }
431

432
  get _store() {
433
    return this.store;
×
434
  }
435

436
  private [spawnReducersKey]() {
437
    const descriptors: PropertyDescriptorMap = {
10✔
438
      [stateKey]: {
439
        enumerable: false,
440
        configurable: false,
441
        get(this: Service) {
442
          const stagedState = getStagedState();
32✔
443
          return stagedState?.__identifier === this[identifierKey]
32✔
444
            ? stagedState.__state
445
            : this._getStateV2(this[storeKey]?.getState(), this[identifierKey]);
446
        },
447
      },
448
    };
449
    this._reducers = Object.keys(this[stateKey] ?? {}).reduce(
10!
450
      (serviceReducersMapObject: ReducersMapObject, key) => {
451
        const descriptor = Object.getOwnPropertyDescriptor(this, key);
36✔
452
        if (typeof descriptor === 'undefined') return serviceReducersMapObject;
36!
453
        const initialState = descriptor.value;
36✔
454
        Object.assign(descriptors, {
36✔
455
          [key]: {
456
            enumerable: true,
457
            configurable: false,
458
            get(this: Service) {
459
              return this[stateKey][key];
27✔
460
            },
461
            set(this: Service, value: unknown) {
462
              this[stateKey][key] = value;
5✔
463
            },
464
          },
465
        });
466
        return Object.assign(serviceReducersMapObject, {
36✔
467
          [key]: (state = initialState, action: Action) =>
222✔
468
            action._usm === usmAction &&
258✔
469
            this[identifierKey] === action.type &&
470
            Object.hasOwnProperty.call(action._state, key)
471
              ? action._state[key]
472
              : state,
473
        });
474
      },
475
      {},
476
    );
477
    const stateDescriptor = Object.getOwnPropertyDescriptor(this, stateKey);
10✔
478
    if (stateDescriptor && typeof stateDescriptor.get === 'function') return;
10!
479
    Object.defineProperties(this, descriptors);
10✔
480
  }
481

482
  get reducer(): Reducer<any, Action<any>> {
483
    if (this._reducers) return combineReducers(this._reducers);
20✔
484
    this[spawnStorageReducersKey]();
10✔
485
    this[spawnReducersKey]();
10✔
486
    if (!this._reducers) {
10!
NEW
487
      throw new Error(`Combine reducers error`);
×
488
    }
489
    return combineReducers(this._reducers!);
10✔
490
  }
491

492
  async _initModule() {
493
    this._initialized = true;
24✔
494
    if (
24✔
495
      (this.parentModule && !(this.parentModule instanceof RcModuleV2)) ||
70✔
496
      (!this.parentModule && !(this instanceof RcModuleV2))
497
    ) {
498
      for (const subscribe of this[subscriptionsKey] ?? []) {
2!
499
        subscribe();
2✔
500
      }
501
      this[subscriptionsKey] = [];
2✔
502
    }
503
    await this[checkStatusChangeKey]();
24✔
504
    // eslint-disable-next-line guard-for-in
505
    for (const subModule in this) {
22✔
506
      const subRcModule = this[subModule] as any;
215✔
507
      if (
215✔
508
        subRcModule &&
385✔
509
        typeof subRcModule._initModule === 'function' &&
510
        !subRcModule._initialized &&
511
        !subRcModule._suppressInit
512
      ) {
513
        subRcModule._initModule();
2✔
514
      }
515
    }
516
  }
517

518
  private get proxyReady() {
519
    return this.ready;
×
520
  }
521

522
  private get modulePath() {
523
    return this._modulePath;
6✔
524
  }
525

526
  private addModule(name: string, module: unknown) {
527
    if (Object.prototype.hasOwnProperty.call(this, name)) {
6✔
528
      throw new Error(`Property '${name}' already exists...`);
1✔
529
    }
530
    Object.defineProperty(this, name, {
5✔
531
      get() {
532
        return module;
20✔
533
      },
534
      enumerable: true,
535
    });
536
    if ((this as any)[name]._modulePath === 'root') {
5✔
537
      (this as any)[name]._modulePath = `${this.modulePath}.${name}`;
4✔
538
    }
539
  }
540

541
  private get state() {
UNCOV
542
    return this[stateKey];
×
543
  }
544

545
  private actionTypes() {
546
    return {};
×
547
  }
548
}
549

550
export {
551
  RcModuleV2,
552
  state,
553
  action,
554
  subscribe,
555
  computed,
556
  createStore,
557
  watch,
558
  stateKey,
559
  storeKey,
560
  identifierKey,
561
  applyPatches,
562
  usmAction,
563
  enableES5,
564
  setAutoFreeze,
565
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc