@angular-architects/ngrx-toolkit 20.0.0 → 20.0.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 (84) hide show
  1. package/eslint.config.cjs +43 -0
  2. package/jest.config.ts +22 -0
  3. package/ng-package.json +7 -0
  4. package/package.json +4 -21
  5. package/project.json +37 -0
  6. package/redux-connector/docs/README.md +131 -0
  7. package/redux-connector/index.ts +6 -0
  8. package/redux-connector/ng-package.json +5 -0
  9. package/redux-connector/src/lib/create-redux.ts +102 -0
  10. package/redux-connector/src/lib/model.ts +89 -0
  11. package/redux-connector/src/lib/rxjs-interop/redux-method.ts +66 -0
  12. package/redux-connector/src/lib/signal-redux-store.ts +59 -0
  13. package/redux-connector/src/lib/util.ts +22 -0
  14. package/src/index.ts +43 -0
  15. package/src/lib/assertions/assertions.ts +9 -0
  16. package/src/lib/devtools/features/with-disabled-name-indicies.ts +31 -0
  17. package/src/lib/devtools/features/with-glitch-tracking.ts +35 -0
  18. package/src/lib/devtools/features/with-mapper.ts +34 -0
  19. package/src/lib/devtools/internal/current-action-names.ts +1 -0
  20. package/src/lib/devtools/internal/default-tracker.ts +60 -0
  21. package/src/lib/devtools/internal/devtools-feature.ts +37 -0
  22. package/src/lib/devtools/internal/devtools-syncer.service.ts +202 -0
  23. package/src/lib/devtools/internal/glitch-tracker.service.ts +61 -0
  24. package/src/lib/devtools/internal/models.ts +29 -0
  25. package/src/lib/devtools/provide-devtools-config.ts +32 -0
  26. package/src/lib/devtools/rename-devtools-name.ts +21 -0
  27. package/src/lib/devtools/tests/action-name.spec.ts +48 -0
  28. package/src/lib/devtools/tests/basic.spec.ts +111 -0
  29. package/src/lib/devtools/tests/connecting.spec.ts +37 -0
  30. package/src/lib/devtools/tests/helpers.spec.ts +43 -0
  31. package/src/lib/devtools/tests/naming.spec.ts +216 -0
  32. package/src/lib/devtools/tests/provide-devtools-config.spec.ts +25 -0
  33. package/src/lib/devtools/tests/types.spec.ts +19 -0
  34. package/src/lib/devtools/tests/update-state.spec.ts +29 -0
  35. package/src/lib/devtools/tests/with-devtools.spec.ts +5 -0
  36. package/src/lib/devtools/tests/with-glitch-tracking.spec.ts +272 -0
  37. package/src/lib/devtools/tests/with-mapper.spec.ts +69 -0
  38. package/src/lib/devtools/update-state.ts +38 -0
  39. package/src/lib/devtools/with-dev-tools-stub.ts +6 -0
  40. package/src/lib/devtools/with-devtools.ts +81 -0
  41. package/src/lib/immutable-state/deep-freeze.ts +43 -0
  42. package/src/lib/immutable-state/is-dev-mode.ts +6 -0
  43. package/src/lib/immutable-state/tests/with-immutable-state.spec.ts +278 -0
  44. package/src/lib/immutable-state/with-immutable-state.ts +150 -0
  45. package/src/lib/shared/prettify.ts +3 -0
  46. package/src/lib/shared/signal-store-models.ts +30 -0
  47. package/src/lib/shared/throw-if-null.ts +7 -0
  48. package/src/lib/storage-sync/features/with-indexed-db.ts +81 -0
  49. package/src/lib/storage-sync/features/with-local-storage.ts +58 -0
  50. package/src/lib/storage-sync/internal/indexeddb.service.ts +124 -0
  51. package/src/lib/storage-sync/internal/local-storage.service.ts +19 -0
  52. package/src/lib/storage-sync/internal/models.ts +62 -0
  53. package/src/lib/storage-sync/internal/session-storage.service.ts +18 -0
  54. package/src/lib/storage-sync/tests/indexeddb.service.spec.ts +99 -0
  55. package/src/lib/storage-sync/tests/with-storage-async.spec.ts +305 -0
  56. package/src/lib/storage-sync/tests/with-storage-sync.spec.ts +273 -0
  57. package/src/lib/storage-sync/with-storage-sync.ts +236 -0
  58. package/src/lib/with-call-state.spec.ts +42 -0
  59. package/src/lib/with-call-state.ts +195 -0
  60. package/src/lib/with-conditional.spec.ts +125 -0
  61. package/src/lib/with-conditional.ts +74 -0
  62. package/src/lib/with-data-service.spec.ts +564 -0
  63. package/src/lib/with-data-service.ts +433 -0
  64. package/src/lib/with-feature-factory.spec.ts +69 -0
  65. package/src/lib/with-feature-factory.ts +56 -0
  66. package/src/lib/with-pagination.spec.ts +135 -0
  67. package/src/lib/with-pagination.ts +373 -0
  68. package/src/lib/with-redux.spec.ts +258 -0
  69. package/src/lib/with-redux.ts +387 -0
  70. package/src/lib/with-reset.spec.ts +112 -0
  71. package/src/lib/with-reset.ts +62 -0
  72. package/src/lib/with-undo-redo.spec.ts +274 -0
  73. package/src/lib/with-undo-redo.ts +200 -0
  74. package/src/test-setup.ts +6 -0
  75. package/tsconfig.json +29 -0
  76. package/tsconfig.lib.json +17 -0
  77. package/tsconfig.lib.prod.json +9 -0
  78. package/tsconfig.spec.json +17 -0
  79. package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs +0 -119
  80. package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs.map +0 -1
  81. package/fesm2022/angular-architects-ngrx-toolkit.mjs +0 -1780
  82. package/fesm2022/angular-architects-ngrx-toolkit.mjs.map +0 -1
  83. package/index.d.ts +0 -938
  84. package/redux-connector/index.d.ts +0 -59
@@ -0,0 +1,124 @@
1
+ import { Injectable } from '@angular/core';
2
+
3
+ export const keyPath = 'ngrxToolkitKeyPath';
4
+
5
+ export const dbName = 'ngrxToolkitDb';
6
+
7
+ export const storeName = 'ngrxToolkitStore';
8
+
9
+ export const VERSION: number = 1 as const;
10
+
11
+ @Injectable({ providedIn: 'root' })
12
+ export class IndexedDBService {
13
+ /**
14
+ * write to indexedDB
15
+ * @param key
16
+ * @param data
17
+ */
18
+ async setItem(key: string, data: string): Promise<void> {
19
+ const db = await this.openDB();
20
+
21
+ const tx = db.transaction(storeName, 'readwrite');
22
+
23
+ const store = tx.objectStore(storeName);
24
+
25
+ store.put({
26
+ [keyPath]: key,
27
+ value: data,
28
+ });
29
+
30
+ return new Promise((resolve, reject) => {
31
+ tx.oncomplete = (): void => {
32
+ db.close();
33
+ resolve();
34
+ };
35
+
36
+ tx.onerror = (): void => {
37
+ db.close();
38
+ reject();
39
+ };
40
+ });
41
+ }
42
+
43
+ /**
44
+ * read from indexedDB
45
+ * @param key
46
+ */
47
+ async getItem(key: string): Promise<string | null> {
48
+ const db = await this.openDB();
49
+
50
+ const tx = db.transaction(storeName, 'readonly');
51
+
52
+ const store = tx.objectStore(storeName);
53
+
54
+ const request = store.get(key);
55
+
56
+ return new Promise((resolve, reject) => {
57
+ request.onsuccess = (): void => {
58
+ db.close();
59
+ // localStorage(sessionStorage) returns null if the key does not exist
60
+ // Similarly, indexedDB should return null
61
+ if (request.result === undefined) {
62
+ resolve(null);
63
+ }
64
+ resolve(request.result?.['value']);
65
+ };
66
+
67
+ request.onerror = (): void => {
68
+ db.close();
69
+ reject();
70
+ };
71
+ });
72
+ }
73
+
74
+ /**
75
+ * delete indexedDB
76
+ * @param key
77
+ */
78
+ async clear(key: string): Promise<void> {
79
+ const db = await this.openDB();
80
+
81
+ const tx = db.transaction(storeName, 'readwrite');
82
+
83
+ const store = tx.objectStore(storeName);
84
+
85
+ const request = store.delete(key);
86
+
87
+ return new Promise((resolve, reject) => {
88
+ request.onsuccess = (): void => {
89
+ db.close();
90
+ resolve();
91
+ };
92
+
93
+ request.onerror = (): void => {
94
+ db.close();
95
+ reject();
96
+ };
97
+ });
98
+ }
99
+
100
+ /**
101
+ * open indexedDB
102
+ */
103
+ private async openDB(): Promise<IDBDatabase> {
104
+ return new Promise((resolve, reject) => {
105
+ const request = indexedDB.open(dbName, VERSION);
106
+
107
+ request.onupgradeneeded = () => {
108
+ const db = request.result;
109
+
110
+ if (!db.objectStoreNames.contains(storeName)) {
111
+ db.createObjectStore(storeName, { keyPath });
112
+ }
113
+ };
114
+
115
+ request.onsuccess = (): void => {
116
+ resolve(request.result);
117
+ };
118
+
119
+ request.onerror = (): void => {
120
+ reject(request.error);
121
+ };
122
+ });
123
+ }
124
+ }
@@ -0,0 +1,19 @@
1
+ import { Injectable } from '@angular/core';
2
+ import {} from './models';
3
+
4
+ @Injectable({
5
+ providedIn: 'root',
6
+ })
7
+ export class LocalStorageService {
8
+ getItem(key: string): string | null {
9
+ return localStorage.getItem(key);
10
+ }
11
+
12
+ setItem(key: string, data: string): void {
13
+ return localStorage.setItem(key, data);
14
+ }
15
+
16
+ clear(key: string): void {
17
+ return localStorage.removeItem(key);
18
+ }
19
+ }
@@ -0,0 +1,62 @@
1
+ import { Signal, WritableSignal } from '@angular/core';
2
+ import { EmptyFeatureResult, WritableStateSource } from '@ngrx/signals';
3
+ import { SyncConfig } from '../with-storage-sync';
4
+
5
+ export type SyncMethods = {
6
+ clearStorage(): void;
7
+ readFromStorage(): void;
8
+ writeToStorage(): void;
9
+ };
10
+
11
+ export type SyncFeatureResult = EmptyFeatureResult & {
12
+ methods: SyncMethods;
13
+ };
14
+
15
+ export type SyncStoreForFactory<State extends object> =
16
+ WritableStateSource<State>;
17
+
18
+ export type SyncStorageStrategy<State extends object> = ((
19
+ config: Required<SyncConfig<State>>,
20
+ store: SyncStoreForFactory<State>,
21
+ useStubs: boolean,
22
+ ) => SyncMethods) & { type: 'sync' };
23
+
24
+ export type AsyncMethods = {
25
+ clearStorage(): Promise<void>;
26
+ readFromStorage(): Promise<void>;
27
+ writeToStorage(): Promise<void>;
28
+ };
29
+
30
+ /**
31
+ * AsyncFeatureResult is used as the public interface that users interact with
32
+ * when calling `withIndexedDB`. It intentionally omits the internal SYNC_STATUS
33
+ * property to avoid TypeScript error TS4058 (return type of public method
34
+ * includes private type).
35
+ *
36
+ * For internal implementation, we use AsyncStoreForFactory which includes
37
+ * the SYNC_STATUS property needed for state management.
38
+ */
39
+ export const SYNC_STATUS = Symbol('SYNC_STATUS');
40
+ export type SyncStatus = 'idle' | 'syncing' | 'synced';
41
+
42
+ // Keeping it internal avoids TS4058 error
43
+ export type InternalAsyncProps = AsyncFeatureResult['props'] & {
44
+ [SYNC_STATUS]: WritableSignal<SyncStatus>;
45
+ };
46
+
47
+ export type AsyncFeatureResult = EmptyFeatureResult & {
48
+ methods: AsyncMethods;
49
+ props: {
50
+ isSynced: Signal<boolean>;
51
+ whenSynced: () => Promise<void>;
52
+ };
53
+ };
54
+
55
+ export type AsyncStoreForFactory<State extends object> =
56
+ WritableStateSource<State> & InternalAsyncProps;
57
+
58
+ export type AsyncStorageStrategy<State extends object> = ((
59
+ config: Required<SyncConfig<State>>,
60
+ store: AsyncStoreForFactory<State>,
61
+ useStubs: boolean,
62
+ ) => AsyncMethods) & { type: 'async' };
@@ -0,0 +1,18 @@
1
+ import { Injectable } from '@angular/core';
2
+
3
+ @Injectable({
4
+ providedIn: 'root',
5
+ })
6
+ export class SessionStorageService {
7
+ getItem(key: string): string | null {
8
+ return sessionStorage.getItem(key);
9
+ }
10
+
11
+ setItem(key: string, data: string): void {
12
+ return sessionStorage.setItem(key, data);
13
+ }
14
+
15
+ clear(key: string): void {
16
+ return sessionStorage.removeItem(key);
17
+ }
18
+ }
@@ -0,0 +1,99 @@
1
+ import 'fake-indexeddb/auto';
2
+ import { IndexedDBService } from '../internal/indexeddb.service';
3
+
4
+ describe('IndexedDBService', () => {
5
+ const sampleData = JSON.stringify({
6
+ foo: 'bar',
7
+ users: [
8
+ { name: 'John', age: 30, isAdmin: true },
9
+ { name: 'Jane', age: 25, isAdmin: false },
10
+ ],
11
+ });
12
+
13
+ let indexedDBService: IndexedDBService;
14
+
15
+ beforeEach(() => {
16
+ indexedDBService = new IndexedDBService();
17
+ });
18
+
19
+ it('It should be possible to write data using write() and then read the data using read()', async (): Promise<void> => {
20
+ const key = 'users';
21
+
22
+ const expectedData = sampleData;
23
+
24
+ await indexedDBService.setItem(key, sampleData);
25
+
26
+ const receivedData = await indexedDBService.getItem(key);
27
+
28
+ expect(receivedData).toEqual(expectedData);
29
+ });
30
+
31
+ it('It should be possible to delete data using clear()', async (): Promise<void> => {
32
+ const key = 'sample';
33
+
34
+ await indexedDBService.setItem(key, sampleData);
35
+
36
+ await indexedDBService.clear(key);
37
+
38
+ const receivedData = await indexedDBService.getItem(key);
39
+
40
+ expect(receivedData).toEqual(null);
41
+ });
42
+
43
+ it('When there is no data, read() should return null', async (): Promise<void> => {
44
+ const key = 'nullData';
45
+
46
+ const receivedData = await indexedDBService.getItem(key);
47
+
48
+ expect(receivedData).toEqual(null);
49
+ });
50
+
51
+ it('write() should handle null data', async (): Promise<void> => {
52
+ const key = 'nullData';
53
+
54
+ await indexedDBService.setItem(key, JSON.stringify(null));
55
+
56
+ const receivedData = await indexedDBService.getItem(key);
57
+
58
+ expect(receivedData).toEqual('null');
59
+ });
60
+
61
+ it('write() should handle empty object data', async (): Promise<void> => {
62
+ const key = 'emptyData';
63
+
64
+ const emptyData = JSON.stringify({});
65
+ const expectedData = emptyData;
66
+
67
+ await indexedDBService.setItem(key, emptyData);
68
+
69
+ const receivedData = await indexedDBService.getItem(key);
70
+
71
+ expect(receivedData).toEqual(expectedData);
72
+ });
73
+
74
+ it('write() should handle large data objects', async (): Promise<void> => {
75
+ const key = 'largeData';
76
+
77
+ const largeData = JSON.stringify({ foo: 'a'.repeat(100000) });
78
+ const expectedData = largeData;
79
+
80
+ await indexedDBService.setItem(key, largeData);
81
+
82
+ const receivedData = await indexedDBService.getItem(key);
83
+
84
+ expect(receivedData).toEqual(expectedData);
85
+ });
86
+
87
+ it('write() should handle special characters in data', async (): Promise<void> => {
88
+ const key = 'specialCharData';
89
+
90
+ const specialCharData = JSON.stringify({ foo: 'bar!@#$%^&*()_+{}:"<>?' });
91
+ const expectedData = specialCharData;
92
+
93
+ await indexedDBService.setItem(key, specialCharData);
94
+
95
+ const receivedData = await indexedDBService.getItem(key);
96
+
97
+ expect(receivedData).toEqual(expectedData);
98
+ });
99
+ });
@@ -0,0 +1,305 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { getState, patchState, signalStore, withState } from '@ngrx/signals';
3
+ import 'fake-indexeddb/auto';
4
+ import { withIndexedDB } from '../features/with-indexed-db';
5
+ import { IndexedDBService } from '../internal/indexeddb.service';
6
+ import { withStorageSync } from '../with-storage-sync';
7
+
8
+ interface StateObject {
9
+ foo: string;
10
+ age: number;
11
+ }
12
+
13
+ const initialState: StateObject = {
14
+ foo: 'bar',
15
+ age: 18,
16
+ };
17
+ const key = 'FooBar';
18
+
19
+ const waitForSyncStable = async (store: {
20
+ whenSynced?: () => Promise<void>;
21
+ }) => {
22
+ if (store.whenSynced) {
23
+ await store.whenSynced();
24
+ }
25
+ };
26
+
27
+ describe('withStorageSync (async storage)', () => {
28
+ beforeEach(() => {
29
+ // make sure to start with a clean storage
30
+ globalThis.indexedDB = new IDBFactory();
31
+ });
32
+
33
+ it('adds methods for storage access to the store', () => {
34
+ TestBed.runInInjectionContext(() => {
35
+ const Store = signalStore(withStorageSync({ key }, withIndexedDB()));
36
+ const store = new Store();
37
+
38
+ expect(Object.keys(store)).toEqual([
39
+ 'isSynced',
40
+ 'whenSynced',
41
+ 'clearStorage',
42
+ 'readFromStorage',
43
+ 'writeToStorage',
44
+ ]);
45
+ });
46
+ });
47
+
48
+ it('offers manual sync using provided methods', async () => {
49
+ TestBed.runInInjectionContext(async () => {
50
+ // prefill storage
51
+ const indexedDBService = TestBed.inject(IndexedDBService);
52
+ await indexedDBService.setItem(
53
+ key,
54
+ JSON.stringify({
55
+ foo: 'baz',
56
+ age: 99,
57
+ }),
58
+ );
59
+
60
+ const Store = signalStore(
61
+ { protectedState: false },
62
+ withState(initialState),
63
+ withStorageSync({ key, autoSync: false }, withIndexedDB()),
64
+ );
65
+ const store = TestBed.inject(Store);
66
+ await waitForSyncStable(store);
67
+
68
+ expect(getState(store)).toEqual({});
69
+
70
+ await store.readFromStorage();
71
+
72
+ expect(getState(store)).toEqual({
73
+ foo: 'baz',
74
+ age: 99,
75
+ });
76
+
77
+ patchState(store, { ...initialState });
78
+ await waitForSyncStable(store);
79
+
80
+ expect(await indexedDBService.getItem(key)).toEqual({
81
+ foo: 'baz',
82
+ age: 99,
83
+ });
84
+
85
+ await store.writeToStorage();
86
+ expect(await indexedDBService.getItem(key)).toEqual({
87
+ ...initialState,
88
+ });
89
+
90
+ await store.clearStorage();
91
+ expect(await indexedDBService.getItem(key)).toEqual(null);
92
+ });
93
+ });
94
+
95
+ describe('autoSync', () => {
96
+ it('inits from storage and write to storage on changes when set to `true`', async () => {
97
+ const indexedDBService = TestBed.inject(IndexedDBService);
98
+ // prefill storage
99
+ await indexedDBService.setItem(
100
+ key,
101
+ JSON.stringify({
102
+ foo: 'baz',
103
+ age: 99,
104
+ } as StateObject),
105
+ );
106
+
107
+ const Store = signalStore(
108
+ { providedIn: 'root', protectedState: false },
109
+ withState(initialState),
110
+ withStorageSync(key, withIndexedDB()),
111
+ );
112
+
113
+ const store = TestBed.inject(Store);
114
+ await waitForSyncStable(store);
115
+ expect(getState(store)).toEqual({
116
+ foo: 'baz',
117
+ age: 99,
118
+ });
119
+
120
+ patchState(store, { ...initialState });
121
+ await waitForSyncStable(store);
122
+
123
+ expect(getState(store)).toEqual({
124
+ ...initialState,
125
+ });
126
+
127
+ expect(await indexedDBService.getItem(key)).toEqual(
128
+ JSON.stringify(initialState),
129
+ );
130
+ });
131
+
132
+ it('does not init from storage and does write to storage on changes when set to `false`', async () => {
133
+ const indexedDBService = TestBed.inject(IndexedDBService);
134
+ await indexedDBService.setItem(
135
+ key,
136
+ JSON.stringify({
137
+ foo: 'baz',
138
+ age: 99,
139
+ }),
140
+ );
141
+
142
+ const Store = signalStore(
143
+ { providedIn: 'root', protectedState: false },
144
+ withStorageSync({ key, autoSync: false }, withIndexedDB()),
145
+ );
146
+ const store = TestBed.inject(Store);
147
+ expect(store.isSynced()).toBe(false);
148
+ expect(getState(store)).toEqual({});
149
+
150
+ patchState(store, { ...initialState });
151
+ expect(store.isSynced()).toBe(false);
152
+
153
+ const storeItem = JSON.parse(
154
+ (await indexedDBService.getItem(key)) || '{}',
155
+ );
156
+ expect(storeItem).toEqual({
157
+ foo: 'baz',
158
+ age: 99,
159
+ });
160
+ });
161
+ });
162
+
163
+ describe('select', () => {
164
+ it('syncs the whole state by default', async () => {
165
+ const indexedDBService = TestBed.inject(IndexedDBService);
166
+ const Store = signalStore(
167
+ { providedIn: 'root', protectedState: false },
168
+ withState(initialState),
169
+ withStorageSync(key, withIndexedDB()),
170
+ );
171
+ const store = TestBed.inject(Store);
172
+ await waitForSyncStable(store);
173
+
174
+ patchState(store, { foo: 'baz', age: 25 });
175
+ await waitForSyncStable(store);
176
+
177
+ expect(await indexedDBService.getItem(key)).toEqual(
178
+ JSON.stringify({ foo: 'baz', age: 25 }),
179
+ );
180
+ });
181
+
182
+ it('syncs selected slices when specified', async () => {
183
+ const indexedDBService = TestBed.inject(IndexedDBService);
184
+ const Store = signalStore(
185
+ { providedIn: 'root', protectedState: false },
186
+ withState(initialState),
187
+ withStorageSync(
188
+ { key, select: ({ foo }) => ({ foo }) },
189
+ withIndexedDB(),
190
+ ),
191
+ );
192
+ const store = TestBed.inject(Store);
193
+ await waitForSyncStable(store);
194
+
195
+ patchState(store, { foo: 'baz' });
196
+ await waitForSyncStable(store);
197
+
198
+ const storeItem = JSON.parse(
199
+ (await indexedDBService.getItem(key)) || '{}',
200
+ );
201
+ expect(storeItem).toEqual({
202
+ foo: 'baz',
203
+ });
204
+ });
205
+ });
206
+
207
+ describe('parse/stringify', () => {
208
+ it('uses custom parsing/stringification when specified', async () => {
209
+ const indexedDBService = TestBed.inject(IndexedDBService);
210
+ const parse = (stateString: string) => {
211
+ const [foo, age] = stateString.split('_');
212
+ return {
213
+ foo,
214
+ age: +age,
215
+ };
216
+ };
217
+
218
+ const Store = signalStore(
219
+ { providedIn: 'root', protectedState: false },
220
+ withState(initialState),
221
+ withStorageSync(
222
+ {
223
+ key,
224
+ parse,
225
+ stringify: (state) => `${state.foo}_${state.age}`,
226
+ },
227
+ withIndexedDB(),
228
+ ),
229
+ );
230
+
231
+ const store = TestBed.inject(Store);
232
+ await waitForSyncStable(store);
233
+ patchState(store, { foo: 'baz' });
234
+ await waitForSyncStable(store);
235
+
236
+ const storeItem = parse((await indexedDBService.getItem(key)) || '');
237
+ expect(storeItem).toEqual({
238
+ ...initialState,
239
+ foo: 'baz',
240
+ });
241
+ });
242
+ });
243
+
244
+ describe('withStorageSync', () => {
245
+ let warnings = [] as string[];
246
+
247
+ jest.spyOn(console, 'warn').mockImplementation((...messages: string[]) => {
248
+ warnings.push(...messages);
249
+ });
250
+
251
+ beforeEach(() => {
252
+ warnings = [];
253
+ });
254
+
255
+ it('logs when writing happens before state is synchronized', async () => {
256
+ const Store = signalStore(
257
+ { providedIn: 'root', protectedState: false },
258
+ withState({ name: 'Delta', age: 52 }),
259
+ withStorageSync('flights', withIndexedDB()),
260
+ );
261
+ const store = TestBed.inject(Store);
262
+ await waitForSyncStable(store);
263
+
264
+ expect(warnings).toEqual([]);
265
+ });
266
+
267
+ it('warns when reading happens during a write', async () => {
268
+ const Store = signalStore(
269
+ { providedIn: 'root', protectedState: false },
270
+ withState({ name: 'Delta', age: 52 }),
271
+ withStorageSync('flights', withIndexedDB()),
272
+ );
273
+
274
+ const store = TestBed.inject(Store);
275
+ await waitForSyncStable(store);
276
+ patchState(store, { name: 'Lufthansa', age: 27 });
277
+ store.readFromStorage();
278
+
279
+ expect(warnings).toEqual([
280
+ 'Reading to Store (flights) happened during an ongoing synchronization process.',
281
+ 'Please ensure that the store is not in syncing state via `store.whenSynced()`.',
282
+ 'Alternatively, you can disable the autoSync by passing `autoSync: false` in the config.',
283
+ ]);
284
+ });
285
+
286
+ it('warns when writing happens during a read', async () => {
287
+ const Store = signalStore(
288
+ { providedIn: 'root', protectedState: false },
289
+ withState({ name: 'Delta', age: 52 }),
290
+ withStorageSync('flights', withIndexedDB()),
291
+ );
292
+
293
+ const store = TestBed.inject(Store);
294
+ await waitForSyncStable(store);
295
+
296
+ store.readFromStorage();
297
+ patchState(store, { name: 'Lufthansa', age: 27 });
298
+ expect(warnings).toEqual([
299
+ 'Writing to Store (flights) happened during an ongoing synchronization process.',
300
+ 'Please ensure that the store is not in syncing state via `store.whenSynced()`.',
301
+ 'Alternatively, you can disable the autoSync by passing `autoSync: false` in the config.',
302
+ ]);
303
+ });
304
+ });
305
+ });