@webkrafters/react-observable-context 4.7.7 → 5.0.0-rc.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.
- package/dist/constants.d.ts +2 -9
- package/dist/constants.js +64 -19
- package/dist/index.d.ts +90 -0
- package/dist/index.js +98 -0
- package/dist/main/hooks/use-prehooks-ref/index.d.ts +3 -3
- package/dist/main/hooks/use-prehooks-ref/index.js +8 -8
- package/dist/main/hooks/use-prehooks-ref/index.test.d.ts +1 -0
- package/dist/main/hooks/use-prehooks-ref/index.test.js +21 -0
- package/dist/main/hooks/use-render-key-provider/index.d.ts +4 -5
- package/dist/main/hooks/use-render-key-provider/index.js +23 -11
- package/dist/main/hooks/use-render-key-provider/index.test.d.ts +1 -0
- package/dist/main/hooks/use-render-key-provider/index.test.js +72 -0
- package/dist/main/hooks/use-store/index.d.ts +8 -21
- package/dist/main/hooks/use-store/index.js +113 -93
- package/dist/main/hooks/use-store/index.test.d.ts +1 -0
- package/dist/main/hooks/use-store/index.test.js +456 -0
- package/dist/main/index.d.ts +11 -140
- package/dist/main/index.js +301 -159
- package/dist/main/index.test.d.ts +11 -0
- package/dist/main/index.test.js +1298 -0
- package/dist/main/test-apps/normal.d.ts +36 -0
- package/dist/main/test-apps/normal.js +243 -0
- package/dist/main/test-apps/with-connected-children.d.ts +25 -0
- package/dist/main/test-apps/with-connected-children.js +229 -0
- package/dist/main/test-apps/with-pure-children.d.ts +15 -0
- package/dist/main/test-apps/with-pure-children.js +127 -0
- package/dist/model/storage/index.d.ts +7 -7
- package/dist/model/storage/index.js +55 -59
- package/dist/model/storage/index.test.d.ts +1 -0
- package/dist/model/storage/index.test.js +139 -0
- package/dist/test-artifacts/data/create-state-obj.d.ts +58 -0
- package/dist/test-artifacts/data/create-state-obj.js +95 -0
- package/dist/test-artifacts/suppress-render-compat.d.ts +1 -0
- package/dist/test-artifacts/suppress-render-compat.js +7 -0
- package/logo.png +0 -0
- package/package.json +31 -68
- package/dist/main/hooks/use-state-manager/index.d.ts +0 -12
- package/dist/main/hooks/use-state-manager/index.js +0 -42
- package/dist/main/set-state/index.d.ts +0 -31
- package/dist/main/set-state/index.js +0 -211
- package/dist/main/set-state/tag-functions/index.d.ts +0 -38
- package/dist/main/set-state/tag-functions/index.js +0 -360
- package/dist/model/accessor/index.d.ts +0 -23
- package/dist/model/accessor/index.js +0 -180
- package/dist/model/accessor-cache/index.d.ts +0 -12
- package/dist/model/accessor-cache/index.js +0 -170
- package/dist/model/atom/index.d.ts +0 -10
- package/dist/model/atom/index.js +0 -79
- package/dist/types.d.ts +0 -91
- package/dist/utils/clonedeep-eligibility-check.d.ts +0 -7
- package/dist/utils/clonedeep-eligibility-check.js +0 -52
- package/dist/utils/index.d.ts +0 -12
- package/dist/utils/index.js +0 -173
- package/index.js +0 -1
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
const auto_immutable_1 = __importDefault(require("@webkrafters/auto-immutable"));
|
|
30
|
+
const react_1 = require("@testing-library/react");
|
|
31
|
+
require("../../../test-artifacts/suppress-render-compat");
|
|
32
|
+
const constants_1 = require("../../../constants");
|
|
33
|
+
const _1 = __importStar(require("."));
|
|
34
|
+
const noop = () => { };
|
|
35
|
+
beforeAll(() => { jest.spyOn(_1.deps, 'createStorageKey').mockReturnValue(expect.any(String)); });
|
|
36
|
+
afterAll(jest.restoreAllMocks);
|
|
37
|
+
describe('useStore', () => {
|
|
38
|
+
let initialState;
|
|
39
|
+
beforeAll(() => { initialState = { a: 1 }; });
|
|
40
|
+
describe('fundamentals', () => {
|
|
41
|
+
test('creates a store', () => {
|
|
42
|
+
const { result } = (0, react_1.renderHook)(({ prehooks: p, value: v }) => (0, _1.default)(p, v), {
|
|
43
|
+
initialProps: {
|
|
44
|
+
prehooks: {},
|
|
45
|
+
value: initialState
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
expect(result.current).toEqual(expect.objectContaining({
|
|
49
|
+
cache: expect.any(auto_immutable_1.default),
|
|
50
|
+
resetState: expect.any(Function),
|
|
51
|
+
setState: expect.any(Function),
|
|
52
|
+
subscribe: expect.any(Function)
|
|
53
|
+
}));
|
|
54
|
+
});
|
|
55
|
+
test('retains a clone of the initial state in storage', () => {
|
|
56
|
+
const clone = jest.fn().mockReturnValue(initialState);
|
|
57
|
+
const removeItem = noop;
|
|
58
|
+
const setItem = jest.fn();
|
|
59
|
+
(0, react_1.renderHook)(({ prehooks: p, value: v, storage: s }) => (0, _1.default)(p, v, s), {
|
|
60
|
+
initialProps: {
|
|
61
|
+
prehooks: {},
|
|
62
|
+
value: initialState,
|
|
63
|
+
storage: { clone, getItem: () => { }, removeItem, setItem }
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
expect(clone).toHaveBeenCalledTimes(1);
|
|
67
|
+
expect(clone).toHaveBeenCalledWith(initialState);
|
|
68
|
+
expect(setItem).toHaveBeenCalledTimes(1);
|
|
69
|
+
expect(setItem.mock.calls[0][1]).toStrictEqual(initialState);
|
|
70
|
+
});
|
|
71
|
+
test('cleans up retained state from storage on store unmount if storage supported', () => {
|
|
72
|
+
const clone = v => v;
|
|
73
|
+
const setItem = noop;
|
|
74
|
+
const removeItem = jest.fn();
|
|
75
|
+
const { unmount } = (0, react_1.renderHook)(({ prehooks: p, value: v, storage: s }) => (0, _1.default)(p, v, s), {
|
|
76
|
+
initialProps: {
|
|
77
|
+
prehooks: {},
|
|
78
|
+
value: initialState,
|
|
79
|
+
storage: { clone, getItem: () => { }, removeItem, setItem }
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
expect(removeItem).not.toHaveBeenCalled();
|
|
83
|
+
unmount();
|
|
84
|
+
expect(removeItem).toHaveBeenCalledTimes(1);
|
|
85
|
+
});
|
|
86
|
+
test('merges copies of subsequent value prop updates to state', async () => {
|
|
87
|
+
const setStateSpy = jest.fn();
|
|
88
|
+
const ConnectionMock = {
|
|
89
|
+
disconnect: noop,
|
|
90
|
+
set: setStateSpy
|
|
91
|
+
};
|
|
92
|
+
const ImmutableConnectSpy = jest
|
|
93
|
+
.spyOn(auto_immutable_1.default.prototype, 'connect')
|
|
94
|
+
.mockReturnValue(ConnectionMock);
|
|
95
|
+
const { rerender } = (0, react_1.renderHook)(({ prehooks: p, value: v }) => (0, _1.default)(p, v), {
|
|
96
|
+
initialProps: {
|
|
97
|
+
prehooks: {},
|
|
98
|
+
value: initialState
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
expect(setStateSpy).not.toHaveBeenCalled();
|
|
102
|
+
const updateState = { v: 3 };
|
|
103
|
+
rerender({ prehooks: {}, value: updateState });
|
|
104
|
+
expect(setStateSpy).toHaveBeenCalledTimes(1);
|
|
105
|
+
expect(setStateSpy.mock.calls[0][0]).toBe(updateState);
|
|
106
|
+
ImmutableConnectSpy.mockRestore();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe('store', () => {
|
|
110
|
+
let storage;
|
|
111
|
+
beforeAll(() => {
|
|
112
|
+
storage = {
|
|
113
|
+
clone: jest.fn().mockReturnValue(initialState),
|
|
114
|
+
getItem: jest.fn().mockReturnValue(initialState),
|
|
115
|
+
removeItem: jest.fn(),
|
|
116
|
+
setItem: jest.fn()
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
describe('normal flow', () => {
|
|
120
|
+
let ConnectionMock, ImmutableConnectSpy, initialProps, prehooks, setAddSpy, setDeleteSpy, setStateSpy, store;
|
|
121
|
+
beforeAll(() => {
|
|
122
|
+
jest.clearAllMocks();
|
|
123
|
+
setStateSpy = jest.fn();
|
|
124
|
+
ConnectionMock = {
|
|
125
|
+
disconnect: noop,
|
|
126
|
+
get: expect.anything,
|
|
127
|
+
set: setStateSpy
|
|
128
|
+
};
|
|
129
|
+
ImmutableConnectSpy = jest
|
|
130
|
+
.spyOn(auto_immutable_1.default.prototype, 'connect')
|
|
131
|
+
.mockReturnValue(ConnectionMock);
|
|
132
|
+
setAddSpy = jest.spyOn(Set.prototype, 'add');
|
|
133
|
+
setDeleteSpy = jest.spyOn(Set.prototype, 'delete');
|
|
134
|
+
prehooks = {
|
|
135
|
+
resetState: jest.fn().mockReturnValue(true),
|
|
136
|
+
setState: jest.fn().mockReturnValue(true)
|
|
137
|
+
};
|
|
138
|
+
initialProps = { prehooks, value: initialState, storage };
|
|
139
|
+
const { result } = (0, react_1.renderHook)(({ prehooks: p, storage: s, value: v }) => (0, _1.default)(p, v, s), { initialProps });
|
|
140
|
+
store = result.current;
|
|
141
|
+
});
|
|
142
|
+
afterAll(() => {
|
|
143
|
+
setAddSpy.mockRestore();
|
|
144
|
+
setDeleteSpy.mockRestore();
|
|
145
|
+
ImmutableConnectSpy.mockRestore();
|
|
146
|
+
});
|
|
147
|
+
describe('resetState', () => {
|
|
148
|
+
beforeAll(() => {
|
|
149
|
+
prehooks.resetState.mockClear();
|
|
150
|
+
setStateSpy.mockClear();
|
|
151
|
+
storage.getItem.mockClear();
|
|
152
|
+
store.resetState(ConnectionMock);
|
|
153
|
+
});
|
|
154
|
+
test('obtains initial state from storage', () => {
|
|
155
|
+
expect(storage.getItem).toHaveBeenCalled();
|
|
156
|
+
});
|
|
157
|
+
test('runs the avaiable prehook', () => {
|
|
158
|
+
expect(prehooks.resetState).toHaveBeenCalled();
|
|
159
|
+
});
|
|
160
|
+
test('resets the state if prehook evaluates to true', () => {
|
|
161
|
+
// prehook.resetState had been mocked to return true
|
|
162
|
+
// please see 'prehooks effects' describe block for alternate scenario
|
|
163
|
+
expect(setStateSpy).toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
describe('with no arguments', () => {
|
|
166
|
+
test('runs the available prehook with an empty update data', () => {
|
|
167
|
+
expect(prehooks.resetState.mock.calls[0][0]).toEqual({});
|
|
168
|
+
});
|
|
169
|
+
test('attempts to update current state with an empty update data', () => {
|
|
170
|
+
expect(setStateSpy.mock.calls[0][0]).toEqual({});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe('with arguments', () => {
|
|
174
|
+
let stateKey0, resetData;
|
|
175
|
+
beforeAll(() => {
|
|
176
|
+
prehooks.resetState.mockClear();
|
|
177
|
+
setStateSpy.mockClear();
|
|
178
|
+
stateKey0 = Object.keys(initialState)[0];
|
|
179
|
+
resetData = { [stateKey0]: { [constants_1.REPLACE_TAG]: initialState[stateKey0] } };
|
|
180
|
+
store.resetState(ConnectionMock, [stateKey0]);
|
|
181
|
+
});
|
|
182
|
+
test('runs the available prehook with update data corresponding to resetState argument', () => {
|
|
183
|
+
expect(prehooks.resetState.mock.calls[0][0]).toEqual(resetData);
|
|
184
|
+
});
|
|
185
|
+
test('merges the update data into current state', () => {
|
|
186
|
+
expect(setStateSpy.mock.calls[0][0]).toEqual(resetData);
|
|
187
|
+
});
|
|
188
|
+
describe('containing the `' + constants_1.FULL_STATE_SELECTOR + '` path', () => {
|
|
189
|
+
let initialState, storageCloneMockImpl, storageGetItemMockImpl;
|
|
190
|
+
beforeAll(() => {
|
|
191
|
+
storageCloneMockImpl = storage.clone.getMockImplementation();
|
|
192
|
+
storageGetItemMockImpl = storage.getItem.getMockImplementation();
|
|
193
|
+
prehooks.resetState.mockClear();
|
|
194
|
+
setStateSpy.mockClear();
|
|
195
|
+
initialState = { ...initialState, b: { z: expect.anything() } };
|
|
196
|
+
storage.clone.mockReset().mockReturnValue(initialState);
|
|
197
|
+
storage.getItem.mockReset().mockReturnValue(initialState);
|
|
198
|
+
const { result } = (0, react_1.renderHook)(({ prehooks: p, storage: s, value: v }) => (0, _1.default)(p, v, s), { initialProps: { prehooks, storage, value: initialState } });
|
|
199
|
+
const store = result.current;
|
|
200
|
+
store.resetState(ConnectionMock, ['a', constants_1.FULL_STATE_SELECTOR, 'b.z']);
|
|
201
|
+
});
|
|
202
|
+
afterAll(() => {
|
|
203
|
+
storage.clone.mockReset().mockImplementation(storageCloneMockImpl);
|
|
204
|
+
storage.getItem.mockReset().mockImplementation(storageGetItemMockImpl);
|
|
205
|
+
});
|
|
206
|
+
test('runs the available prehook with update data equaling the initial state', () => {
|
|
207
|
+
expect(prehooks.resetState.mock.calls[0][0])
|
|
208
|
+
.toEqual({ [constants_1.REPLACE_TAG]: initialState });
|
|
209
|
+
});
|
|
210
|
+
test('merges the initial state into current state', () => {
|
|
211
|
+
expect(setStateSpy.mock.calls[0][0])
|
|
212
|
+
.toEqual({ [constants_1.REPLACE_TAG]: initialState });
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
describe('path arguments not occurring in intial state', () => {
|
|
217
|
+
let nonInitStatePaths, resetData;
|
|
218
|
+
beforeAll(() => {
|
|
219
|
+
prehooks.resetState.mockClear();
|
|
220
|
+
setStateSpy.mockClear();
|
|
221
|
+
nonInitStatePaths = [
|
|
222
|
+
'a',
|
|
223
|
+
'dsdfd.adfsdff',
|
|
224
|
+
'dsdfd.sfgrwfg'
|
|
225
|
+
];
|
|
226
|
+
resetData = {
|
|
227
|
+
a: { [constants_1.REPLACE_TAG]: initialState.a },
|
|
228
|
+
dsdfd: { [constants_1.DELETE_TAG]: ['adfsdff', 'sfgrwfg'] }
|
|
229
|
+
};
|
|
230
|
+
store.resetState(ConnectionMock, nonInitStatePaths);
|
|
231
|
+
});
|
|
232
|
+
test('are deleted from current state', () => {
|
|
233
|
+
expect(setStateSpy.mock.calls[0][0]).toEqual(resetData);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
describe('with paths containing the `' + constants_1.FULL_STATE_SELECTOR + '` path where initial state is empty', () => {
|
|
237
|
+
let storageCloneMockImpl, storageGetItemMockImpl;
|
|
238
|
+
beforeAll(() => {
|
|
239
|
+
storageCloneMockImpl = storage.clone.getMockImplementation();
|
|
240
|
+
storageGetItemMockImpl = storage.getItem.getMockImplementation();
|
|
241
|
+
prehooks.resetState.mockClear();
|
|
242
|
+
setStateSpy.mockClear();
|
|
243
|
+
storage.clone.mockReset().mockReturnValue();
|
|
244
|
+
storage.getItem.mockReset().mockReturnValue();
|
|
245
|
+
const { result } = (0, react_1.renderHook)(({ prehooks: p, storage: s }) => (0, _1.default)(p, undefined, s), { initialProps: { prehooks, storage } });
|
|
246
|
+
const store = result.current;
|
|
247
|
+
store.resetState(ConnectionMock, ['a', constants_1.FULL_STATE_SELECTOR, 'b.z']);
|
|
248
|
+
});
|
|
249
|
+
afterAll(() => {
|
|
250
|
+
storage.clone.mockReset().mockImplementation(storageCloneMockImpl);
|
|
251
|
+
storage.getItem.mockReset().mockImplementation(storageGetItemMockImpl);
|
|
252
|
+
});
|
|
253
|
+
test('empties the current state', () => {
|
|
254
|
+
expect(setStateSpy.mock.calls[0][0]).toEqual(constants_1.CLEAR_TAG);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
describe('setState', () => {
|
|
259
|
+
describe('normal operations', () => {
|
|
260
|
+
beforeAll(() => {
|
|
261
|
+
prehooks.setState.mockClear();
|
|
262
|
+
setStateSpy.mockClear();
|
|
263
|
+
store.setState(ConnectionMock);
|
|
264
|
+
});
|
|
265
|
+
test('runs the avaiable prehook', () => {
|
|
266
|
+
expect(prehooks.setState).toHaveBeenCalled();
|
|
267
|
+
});
|
|
268
|
+
test('sets the state if prehook evaluates to true', () => {
|
|
269
|
+
// prehook.setState had been mocked to return true
|
|
270
|
+
// please see 'prehooks effects' describe block for alternate scenario
|
|
271
|
+
expect(setStateSpy).toHaveBeenCalled();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
describe('payload', () => {
|
|
275
|
+
beforeEach(() => { setStateSpy.mockClear(); });
|
|
276
|
+
test('can be a single change object', () => {
|
|
277
|
+
const payload = { a: expect.anything() };
|
|
278
|
+
store.setState(ConnectionMock, payload);
|
|
279
|
+
expect(setStateSpy.mock.calls[0][0]).toEqual(payload);
|
|
280
|
+
});
|
|
281
|
+
test('can be an array of change objects', () => {
|
|
282
|
+
const payload = [
|
|
283
|
+
{ a: expect.anything() },
|
|
284
|
+
{ x: expect.anything() }
|
|
285
|
+
];
|
|
286
|
+
store.setState(ConnectionMock, payload);
|
|
287
|
+
expect(setStateSpy.mock.calls[0][0]).toEqual(payload);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
describe('payload translation for compatibility with the cache', () => {
|
|
291
|
+
beforeEach(() => { setStateSpy.mockClear(); });
|
|
292
|
+
test('returns empty payload as-is', () => {
|
|
293
|
+
store.setState(ConnectionMock);
|
|
294
|
+
expect(setStateSpy.mock.calls[0][0]).toEqual(undefined);
|
|
295
|
+
setStateSpy.mockClear();
|
|
296
|
+
store.setState(ConnectionMock, null);
|
|
297
|
+
expect(setStateSpy.mock.calls[0][0]).toEqual(null);
|
|
298
|
+
});
|
|
299
|
+
test('returns non top-level ' + constants_1.FULL_STATE_SELECTOR + ' key bearing payload as-is', () => {
|
|
300
|
+
const payload = {
|
|
301
|
+
a: expect.anything(),
|
|
302
|
+
b: {
|
|
303
|
+
[constants_1.FULL_STATE_SELECTOR]: expect.anything()
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
store.setState(ConnectionMock, payload);
|
|
307
|
+
expect(setStateSpy.mock.calls[0][0]).toEqual(payload);
|
|
308
|
+
});
|
|
309
|
+
test('converts all top-level' + constants_1.FULL_STATE_SELECTOR + ' payload keys only', () => {
|
|
310
|
+
const asIsPayload = {
|
|
311
|
+
a: expect.anything(),
|
|
312
|
+
q: {
|
|
313
|
+
[constants_1.FULL_STATE_SELECTOR]: expect.anything()
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
store.setState(ConnectionMock, {
|
|
317
|
+
[constants_1.FULL_STATE_SELECTOR]: expect.anything(),
|
|
318
|
+
...asIsPayload
|
|
319
|
+
});
|
|
320
|
+
expect(setStateSpy.mock.calls[0][0]).toEqual({
|
|
321
|
+
[constants_1.GLOBAL_SELECTOR]: expect.anything(),
|
|
322
|
+
...asIsPayload
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
test('converts any payload bearing top-level ' + constants_1.FULL_STATE_SELECTOR + ' keys amongst a payload list', () => {
|
|
326
|
+
store.setState(ConnectionMock, [{
|
|
327
|
+
[constants_1.FULL_STATE_SELECTOR]: expect.anything(),
|
|
328
|
+
a: expect.anything()
|
|
329
|
+
}, {
|
|
330
|
+
a: expect.anything()
|
|
331
|
+
}, {
|
|
332
|
+
z: expect.anything(),
|
|
333
|
+
k: expect.anything(),
|
|
334
|
+
[constants_1.FULL_STATE_SELECTOR]: expect.anything(),
|
|
335
|
+
a: expect.anything()
|
|
336
|
+
}, {
|
|
337
|
+
s: expect.anything(),
|
|
338
|
+
t: {
|
|
339
|
+
[constants_1.FULL_STATE_SELECTOR]: expect.anything()
|
|
340
|
+
}
|
|
341
|
+
}, {
|
|
342
|
+
l: [[constants_1.FULL_STATE_SELECTOR]],
|
|
343
|
+
p: expect.anything()
|
|
344
|
+
}]);
|
|
345
|
+
expect(setStateSpy.mock.calls[0][0]).toEqual([{
|
|
346
|
+
[constants_1.GLOBAL_SELECTOR]: expect.anything(),
|
|
347
|
+
a: expect.anything()
|
|
348
|
+
}, {
|
|
349
|
+
a: expect.anything()
|
|
350
|
+
}, {
|
|
351
|
+
z: expect.anything(),
|
|
352
|
+
k: expect.anything(),
|
|
353
|
+
[constants_1.GLOBAL_SELECTOR]: expect.anything(),
|
|
354
|
+
a: expect.anything()
|
|
355
|
+
}, {
|
|
356
|
+
s: expect.anything(),
|
|
357
|
+
t: {
|
|
358
|
+
[constants_1.FULL_STATE_SELECTOR]: expect.anything()
|
|
359
|
+
}
|
|
360
|
+
}, {
|
|
361
|
+
l: [[constants_1.FULL_STATE_SELECTOR]],
|
|
362
|
+
p: expect.anything()
|
|
363
|
+
}]);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
describe('subscribe', () => {
|
|
368
|
+
const LISTENER = 'LISTENER STUB';
|
|
369
|
+
let result;
|
|
370
|
+
beforeAll(() => {
|
|
371
|
+
setAddSpy.mockClear();
|
|
372
|
+
setDeleteSpy.mockClear();
|
|
373
|
+
result = store.subscribe(LISTENER);
|
|
374
|
+
});
|
|
375
|
+
test('adds a new subscriber', () => {
|
|
376
|
+
expect(setAddSpy).toHaveBeenCalled();
|
|
377
|
+
expect(setAddSpy).toHaveBeenCalledWith(LISTENER);
|
|
378
|
+
});
|
|
379
|
+
test('returns a function to unsub the new subscriber', () => {
|
|
380
|
+
expect(result).toBeInstanceOf(Function);
|
|
381
|
+
expect(setDeleteSpy).not.toHaveBeenCalled();
|
|
382
|
+
result();
|
|
383
|
+
expect(setDeleteSpy).toHaveBeenCalledWith(LISTENER);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
describe('prehooks effects', () => {
|
|
388
|
+
let ConnectionMock, ImmutableConnectSpy, setStateSpy, store;
|
|
389
|
+
beforeAll(() => {
|
|
390
|
+
jest.clearAllMocks();
|
|
391
|
+
setStateSpy = jest.fn();
|
|
392
|
+
ConnectionMock = {
|
|
393
|
+
disconnect: noop,
|
|
394
|
+
get: expect.anything,
|
|
395
|
+
set: setStateSpy
|
|
396
|
+
};
|
|
397
|
+
ImmutableConnectSpy = jest
|
|
398
|
+
.spyOn(auto_immutable_1.default.prototype, 'connect')
|
|
399
|
+
.mockReturnValue(ConnectionMock);
|
|
400
|
+
store = (0, react_1.renderHook)(({ prehooks: p, storage: s, value: v }) => (0, _1.default)(p, v, s), {
|
|
401
|
+
initialProps: {
|
|
402
|
+
prehooks: {
|
|
403
|
+
resetState: jest.fn().mockReturnValue(false),
|
|
404
|
+
setState: jest.fn().mockReturnValue(false)
|
|
405
|
+
},
|
|
406
|
+
storage,
|
|
407
|
+
value: initialState
|
|
408
|
+
}
|
|
409
|
+
}).result.current;
|
|
410
|
+
});
|
|
411
|
+
afterAll(() => { ImmutableConnectSpy.mockRestore(); });
|
|
412
|
+
describe('resetState #2', () => {
|
|
413
|
+
test('will not reset the state if prehook evaluates to false', () => {
|
|
414
|
+
// prehooks.resetState had been mocked to return false
|
|
415
|
+
store.resetState(ConnectionMock);
|
|
416
|
+
expect(setStateSpy).not.toHaveBeenCalled();
|
|
417
|
+
});
|
|
418
|
+
test('throws if return type is not boolean', () => {
|
|
419
|
+
const { result } = (0, react_1.renderHook)(({ prehooks: p, storage: s, value: v }) => (0, _1.default)(p, v, s), {
|
|
420
|
+
initialProps: {
|
|
421
|
+
prehooks: {
|
|
422
|
+
resetState: noop,
|
|
423
|
+
setState: () => true
|
|
424
|
+
},
|
|
425
|
+
storage,
|
|
426
|
+
value: initialState
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
expect(() => result.current.resetState(ConnectionMock, expect.anything()))
|
|
430
|
+
.toThrow('`resetState` prehook must return a boolean value.');
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
describe('setState #2', () => {
|
|
434
|
+
test('will not set the state if prehook evaluates to false', () => {
|
|
435
|
+
// prehooks.setState had been mocked to return false
|
|
436
|
+
store.setState();
|
|
437
|
+
expect(setStateSpy).not.toHaveBeenCalled();
|
|
438
|
+
});
|
|
439
|
+
test('throws if return type is not boolean', () => {
|
|
440
|
+
const { result } = (0, react_1.renderHook)(({ prehooks: p, storage: s, value: v }) => (0, _1.default)(p, v, s), {
|
|
441
|
+
initialProps: {
|
|
442
|
+
prehooks: {
|
|
443
|
+
resetState: () => true,
|
|
444
|
+
setState: noop
|
|
445
|
+
},
|
|
446
|
+
storage,
|
|
447
|
+
value: initialState
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
expect(() => result.current.setState(ConnectionMock, expect.anything()))
|
|
451
|
+
.toThrow('`setState` prehook must return a boolean value.');
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
});
|
package/dist/main/index.d.ts
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* @see {ObservableContext<T>}
|
|
5
|
-
*/
|
|
6
|
-
export function createContext<T extends import("../types").State>(): ObservableContext<T, BaseSelectorMap<T>>;
|
|
7
|
-
|
|
1
|
+
import type { ComponentType, NamedExoticComponent } from 'react';
|
|
2
|
+
import type { ConnectedComponent, ConnectProps, InjectedProps, ObservableContext, PropsExtract, SelectorMap, State, Store } from '..';
|
|
3
|
+
export declare function createContext<T extends State = State>(): ObservableContext<T>;
|
|
8
4
|
/**
|
|
9
5
|
* Actively monitors the store and triggers component re-render if any of the watched keys in the state objects changes
|
|
10
6
|
*
|
|
11
|
-
* @param
|
|
12
|
-
* @param
|
|
13
|
-
* @returns {Store<STATE, SELECTOR_MAP>}
|
|
14
|
-
* @template {State} STATE
|
|
15
|
-
* @template {SelectorMap<STATE>} [SELECTOR_MAP=SelectorMap<STATE>]
|
|
7
|
+
* @param context - Refers to the PublicObservableContext<T> type of the ObservableContext<T>
|
|
8
|
+
* @param [selectorMap = {}] - Key:value pairs where `key` => arbitrary key given to a Store.data property holding a state slice and `value` => property path to a state slice used by this component: see examples below. May add a mapping for a certain arbitrary key='state' and value='@@STATE' to indicate a desire to obtain the entire state object and assign to a `state` property of Store.data. A change in any of the referenced properties results in this component render. When using '@@STATE', note that any change within the state object will result in this component render.
|
|
16
9
|
* @see {ObservableContext<STATE,SELECTOR_MAP>}
|
|
10
|
+
*
|
|
17
11
|
* @example
|
|
18
12
|
* a valid property path follows the `lodash` object property path convention.
|
|
19
13
|
* for a state = { a: 1, b: 2, c: 3, d: { e: 5, f: [6, { x: 7, y: 8, z: 9 } ] } }
|
|
@@ -35,139 +29,16 @@ export function createContext<T extends import("../types").State>(): ObservableC
|
|
|
35
29
|
* {myX: 'd.e.f[1].x'} or {myX: 'd.e.f.1.x'} => {myX: 7} // same applies to {myY: 'd.e.f[1].y'} = {myY: 8}; {myZ: 'd.e.f[1].z'} = {myZ: 9}
|
|
36
30
|
* {myData: '@@STATE'} => {myData: state}
|
|
37
31
|
*/
|
|
38
|
-
export function useContext<STATE extends
|
|
39
|
-
|
|
32
|
+
export declare function useContext<STATE extends State, SELECTOR_MAP extends SelectorMap>(context: ObservableContext<STATE>, selectorMap?: SELECTOR_MAP): Store<STATE, SELECTOR_MAP>;
|
|
40
33
|
/**
|
|
41
34
|
* Provides an HOC function for connecting its WrappedComponent argument to the context store.
|
|
42
35
|
*
|
|
43
36
|
* The HOC function automatically memoizes any un-memoized WrappedComponent argument.
|
|
44
37
|
*
|
|
45
|
-
* @param
|
|
46
|
-
* @param
|
|
47
|
-
* @returns {(WrappedComponent: C) => ConnectedComponent<OWNPROPS, Store<STATE, SELECTOR_MAP>>} - Connector HOC function
|
|
48
|
-
* @template {State} STATE
|
|
49
|
-
* @template {OwnProps<State>} [OWNPROPS=OwnProps]
|
|
50
|
-
* @template {SelectorMap<STATE>} [SELECTOR_MAP=SelectorMap<STATE>]
|
|
51
|
-
* @template {ComponentType<ConnectedComponentProps<OWNPROPS, PartialStore<STATE, SELECTOR_MAP>>>|ExoticComponent<ConnectedComponentProps<OWNPROPS, PartialStore<STATE, SELECTOR_MAP>>>} [C = ComponentType<ConnectedComponentProps<OWNPROPS, PartialStore<STATE, SELECTOR_MAP>>>]
|
|
52
|
-
* @see {ObservableContext<STATE,SELECTOR_MAP>}
|
|
38
|
+
* @param context - Refers to the PublicObservableContext<T> type of the ObservableContext<T>
|
|
39
|
+
* @param [selectorMap] - Key:value pairs where `key` => arbitrary key given to a Store.data property holding a state slice and `value` => property path to a state slice used by this component: see examples below. May add a mapping for a certain arbitrary key='state' and value='@@STATE' to indicate a desire to obtain the entire state object and assign to a `state` property of Store.data. A change in any of the referenced properties results in this component render. When using '@@STATE', note that any change within the state object will result in this component render.
|
|
53
40
|
* @see {useContext} for selectorMap sample
|
|
54
41
|
*/
|
|
55
|
-
export function connect<STATE extends
|
|
56
|
-
|
|
57
|
-
} = {}, SELECTOR_MAP extends BaseSelectorMap<STATE> = BaseSelectorMap<STATE>, C extends ComponentType<ConnectedComponentProps<OWNPROPS, PartialStore<STATE, SELECTOR_MAP>>> | import("react").ExoticComponent<ComponentType<ConnectedComponentProps<OWNPROPS, PartialStore<STATE, SELECTOR_MAP>>>> = ComponentType<ConnectedComponentProps<OWNPROPS, PartialStore<STATE, SELECTOR_MAP>>>>(context: ObservableContext<STATE, SELECTOR_MAP>, selectorMap?: SELECTOR_MAP): (WrappedComponent: C) => ConnectedComponent<OWNPROPS, Store<STATE, SELECTOR_MAP>>;
|
|
58
|
-
export type CLEAR_TAG = typeof CLEAR_TAG;
|
|
59
|
-
|
|
60
|
-
/** @example changes = { property: CLEAR_TAG } */
|
|
61
|
-
export const CLEAR_TAG: "@@CLEAR";
|
|
62
|
-
export type DELETE_TAG = typeof DELETE_TAG;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* @example
|
|
66
|
-
* changes = {
|
|
67
|
-
* property: {
|
|
68
|
-
* [DELETE_TAG]: [keys/indexes to remove from property]
|
|
69
|
-
* }
|
|
70
|
-
* }
|
|
71
|
-
*/
|
|
72
|
-
export const DELETE_TAG: "@@DELETE";
|
|
73
|
-
export type FULL_STATE_SELECTOR = import("../types").FULL_STATE_SELECTOR;
|
|
74
|
-
export const FULL_STATE_SELECTOR: "@@STATE";
|
|
75
|
-
export type MOVE_TAG = typeof MOVE_TAG;
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* @example
|
|
79
|
-
* changes = {
|
|
80
|
-
* arrayProperty: {
|
|
81
|
-
* [MOVE_TAG]: [-/+fromIndex, -/+toIndex, +numItems?] // numItems = 1 by default
|
|
82
|
-
* }
|
|
83
|
-
* }
|
|
84
|
-
*/
|
|
85
|
-
export const MOVE_TAG: "@@MOVE";
|
|
86
|
-
export type PUSH_TAG = typeof PUSH_TAG;
|
|
87
|
-
|
|
88
|
-
/** @example changes = { arrayProperty: { [PUSH_TAG]: [new items to append to array] } } */
|
|
89
|
-
export const PUSH_TAG: "@@PUSH";
|
|
90
|
-
export type REPLACE_TAG = typeof REPLACE_TAG;
|
|
91
|
-
|
|
92
|
-
/** @example changes = { property: { [REPLACE_TAG]: replacement } } */
|
|
93
|
-
export const REPLACE_TAG: "@@REPLACE";
|
|
94
|
-
export type SET_TAG = typeof SET_TAG;
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* @example
|
|
98
|
-
* changes = {
|
|
99
|
-
* property: {
|
|
100
|
-
* [SET_TAG]: replacement // or a compute replacement function (i.e. currentProperty => replacement)
|
|
101
|
-
* }
|
|
102
|
-
* }
|
|
103
|
-
*/
|
|
104
|
-
export const SET_TAG: "@@SET";
|
|
105
|
-
export type SPLICE_TAG = typeof SPLICE_TAG;
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* @example
|
|
109
|
-
* changes = {
|
|
110
|
-
* arrayProperty: {
|
|
111
|
-
* [SPLICE_TAG]: [-/+fromIndex, +deleteCount, ...newItems?] // newItems = ...[] by default
|
|
112
|
-
* }
|
|
113
|
-
* }
|
|
114
|
-
*/
|
|
115
|
-
export const SPLICE_TAG: "@@SPLICE";
|
|
116
|
-
export class UsageError extends Error {
|
|
42
|
+
export declare function connect<STATE extends State = State, SELECTOR_MAP extends SelectorMap = SelectorMap>(context: ObservableContext<STATE>, selectorMap?: SELECTOR_MAP): <C extends ComponentType<ConnectProps<P, STATE, SELECTOR_MAP>> | NamedExoticComponent<ConnectProps<P, STATE, SELECTOR_MAP>>, P extends InjectedProps<PropsExtract<C, STATE, SELECTOR_MAP>> = InjectedProps<PropsExtract<C, STATE, SELECTOR_MAP>>>(WrappedComponent: C) => ConnectedComponent<P>;
|
|
43
|
+
export declare class UsageError extends Error {
|
|
117
44
|
}
|
|
118
|
-
export type ObservableContext<T extends import("../types").State, SELECTOR_MAP extends BaseSelectorMap<T> = BaseSelectorMap<T>> = IObservableContext<T> | PublicObservableContext<T, SELECTOR_MAP>;
|
|
119
|
-
export type PublicObservableContext<T extends import("../types").State, SELECTOR_MAP extends BaseSelectorMap<T> = BaseSelectorMap<T>> = WithObservableProvider<Context<Store<T, SELECTOR_MAP>>, T>;
|
|
120
|
-
export type IObservableContext<T extends import("../types").State> = WithObservableProvider<Context<IStore>, T>;
|
|
121
|
-
export type WithObservableProvider<T, S extends import("../types").State> = T & {
|
|
122
|
-
Provider: ObservableProvider<S>;
|
|
123
|
-
};
|
|
124
|
-
export type ObservableProvider<T extends import("../types").State> = ForwardRefExoticComponent<ProviderProps<T>, StoreRef<T>>;
|
|
125
|
-
export type ProviderProps<T extends import("../types").State> = {
|
|
126
|
-
children?: ReactNode;
|
|
127
|
-
prehooks?: Prehooks<T>;
|
|
128
|
-
storage?: IStorage<T>;
|
|
129
|
-
value: PartialState<T>;
|
|
130
|
-
};
|
|
131
|
-
export type StoreRef<T extends import("../types").State> = {
|
|
132
|
-
[x: string]: any;
|
|
133
|
-
getState: () => T;
|
|
134
|
-
} & {
|
|
135
|
-
resetState: (propertyPaths?: string[]) => void;
|
|
136
|
-
setState: (changes: import("../types").Changes<T>) => void;
|
|
137
|
-
subscribe: (listener: import("../types").Listener<T>) => VoidFunction;
|
|
138
|
-
};
|
|
139
|
-
export type State = import("../types").State;
|
|
140
|
-
export type PartialState<T extends import("../types").State> = import("../types").PartialState<T>;
|
|
141
|
-
export type Prehooks<T extends import("../types").State> = import("../types").Prehooks<T>;
|
|
142
|
-
export type BaseSelectorMap<T extends import("../types").State> = import("../types").BaseSelectorMap<T>;
|
|
143
|
-
export type SelectorMap<T extends import("../types").State = import("../types").State, MAP extends BaseSelectorMap<T> = BaseSelectorMap<T>> = import("../types").SelectorMap<T, MAP>;
|
|
144
|
-
export type StoreInternal<T extends import("../types").State> = import("../types").StoreInternal<T>;
|
|
145
|
-
export type PartialStore<T extends import("../types").State, SELECTOR_MAP extends BaseSelectorMap<T> = BaseSelectorMap<T>> = {
|
|
146
|
-
data?: import("../types").Data<SELECTOR_MAP>;
|
|
147
|
-
resetState?: (propertyPaths?: string[]) => void;
|
|
148
|
-
setState?: (changes: import("../types").Changes<T>) => void;
|
|
149
|
-
};
|
|
150
|
-
export type Store<T extends import("../types").State, SELECTOR_MAP extends BaseSelectorMap<T> = BaseSelectorMap<T>> = import("../types").Store<T, SELECTOR_MAP>;
|
|
151
|
-
export type IStore = import("../types").IStore;
|
|
152
|
-
export type IStorage<T extends import("../types").State> = import("../types").IStorage<T>;
|
|
153
|
-
export type ConnectedComponent<OWNPROPS extends Pick<import("../types").State, keyof import("../types").State> & {
|
|
154
|
-
ref?: unknown;
|
|
155
|
-
} = {}, STORE extends Store<import("../types").State, BaseSelectorMap<import("../types").State>> = Store<import("../types").State, BaseSelectorMap<import("../types").State>>> = MemoExoticComponent<ConnectedComponentProps<OWNPROPS, STORE>>;
|
|
156
|
-
export type ConnectedComponentProps<OWNPROPS extends Pick<import("../types").State, keyof import("../types").State> & {
|
|
157
|
-
ref?: unknown;
|
|
158
|
-
} = {}, STORE extends Store<import("../types").State, BaseSelectorMap<import("../types").State>> = Store<import("../types").State, BaseSelectorMap<import("../types").State>>> = STORE & OWNPROPS;
|
|
159
|
-
export type OwnProps<P extends import("../types").State = {}> = import("react").PropsWithRef<P>;
|
|
160
|
-
export type NonReactUsageReport = import("../types").NonReactUsageReport;
|
|
161
|
-
export type Data<SELECTOR_MAP extends BaseSelectorMap<import("../types").State> = BaseSelectorMap<import("../types").State>> = import("../types").Data<SELECTOR_MAP>;
|
|
162
|
-
export type ReactNode = import("react").ReactNode;
|
|
163
|
-
export type ForwardRefExoticComponent<P, T> = import('react').ForwardRefExoticComponent<import('react').PropsWithRef<P> & import('react').RefAttributes<T>>;
|
|
164
|
-
export type MemoExoticComponent<P extends {
|
|
165
|
-
[x: string]: any;
|
|
166
|
-
} = {}> = import('react').MemoExoticComponent<ComponentType<P>>;
|
|
167
|
-
export type ExoticComponent<P extends {
|
|
168
|
-
[x: string]: any;
|
|
169
|
-
} = {}> = import("react").ExoticComponent<ComponentType<P>>;
|
|
170
|
-
export type ComponentType<P = any> = import("react").ComponentType<P>;
|
|
171
|
-
export type FC<P = {}> = import("react").FC<P>;
|
|
172
|
-
export type Provider<T> = import("react").Provider<T>;
|
|
173
|
-
export type Context<T> = import("react").Context<T>;
|