@homebound/form-state 2.18.0 → 2.19.0
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/fields/objectField.js +5 -1
- package/dist/fields/valueField.js +1 -0
- package/dist/formState.test.js +36 -0
- package/dist/formStateDomain.d.ts +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -11
- package/dist/useFormState.js +0 -4
- package/dist/useFormState.test.js +0 -29
- package/dist/useFormStates.js +0 -4
- package/dist/useFormStates.test.js +0 -26
- package/package.json +1 -1
- package/dist/AutoSaveStatus/AutoSaveStatusProvider.d.ts +0 -24
- package/dist/AutoSaveStatus/AutoSaveStatusProvider.js +0 -91
- package/dist/AutoSaveStatus/index.d.ts +0 -2
- package/dist/AutoSaveStatus/index.js +0 -9
- package/dist/AutoSaveStatus/useAutoSaveStatus.d.ts +0 -1
- package/dist/AutoSaveStatus/useAutoSaveStatus.js +0 -9
- package/dist/AutoSaveStatus/useAutoSaveStatus.test.d.ts +0 -1
- package/dist/AutoSaveStatus/useAutoSaveStatus.test.js +0 -152
|
@@ -34,13 +34,17 @@ function newObjectState(config, parentState, parentListState, instance, key, may
|
|
|
34
34
|
}
|
|
35
35
|
return proxy;
|
|
36
36
|
}
|
|
37
|
+
const objectConfig = config;
|
|
37
38
|
const fieldStates = Object.entries(config).map(([_key, _config]) => {
|
|
38
39
|
var _a, _b;
|
|
39
40
|
const key = _key;
|
|
40
41
|
const config = _config;
|
|
41
42
|
let field;
|
|
42
43
|
if (config.type === "value") {
|
|
43
|
-
field = valueField_1.newValueFieldState(instance, getObjectState, key, config.rules || [], config.isIdKey ||
|
|
44
|
+
field = valueField_1.newValueFieldState(instance, getObjectState, key, config.rules || [], config.isIdKey ||
|
|
45
|
+
// Default the id key to "id" unless some other field has isIdKey set
|
|
46
|
+
(key === "id" &&
|
|
47
|
+
!Object.entries(objectConfig).some(([other, c]) => other !== key && c.isIdKey)), config.isDeleteKey || false, config.isReadOnlyKey || false, config.computed || false, config.readOnly || false, (_a = config.strictOrder) !== null && _a !== void 0 ? _a : true, maybeAutoSave);
|
|
44
48
|
}
|
|
45
49
|
else if (config.type === "list") {
|
|
46
50
|
field = listField_1.newListFieldState(instance, getObjectState, key, config.rules || [], config, config.config, (_b = config.strictOrder) !== null && _b !== void 0 ? _b : true, maybeAutoSave);
|
|
@@ -25,6 +25,7 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
|
|
|
25
25
|
_isReadOnlyKey: isReadOnlyKey,
|
|
26
26
|
rules,
|
|
27
27
|
get value() {
|
|
28
|
+
// If we're wrapping a mobx store, then we'll get reactivity from parentInstance[key]
|
|
28
29
|
const value = _tick.value > 0 ? parentInstance[key] : utils_1.fail();
|
|
29
30
|
// Re-create the `keepNull` logic on sets but for our initial read where our
|
|
30
31
|
// originalValue is null (empty) but we want to expose it as undefined for
|
package/dist/formState.test.js
CHANGED
|
@@ -915,6 +915,33 @@ describe("formState", () => {
|
|
|
915
915
|
formState.revertChanges();
|
|
916
916
|
expect(formState.firstName.value).toEqual("first");
|
|
917
917
|
});
|
|
918
|
+
it("can observe observable objects being mutated directly", () => {
|
|
919
|
+
// Given a mobx class with a computed
|
|
920
|
+
const instance = new ObservableObject();
|
|
921
|
+
const formState = objectField_1.createObjectState({
|
|
922
|
+
firstName: { type: "value" },
|
|
923
|
+
lastName: { type: "value" },
|
|
924
|
+
fullName: { type: "value", computed: true },
|
|
925
|
+
}, instance);
|
|
926
|
+
expect(formState.firstName.value).toEqual("first");
|
|
927
|
+
expect(formState.fullName.value).toEqual("first last");
|
|
928
|
+
// And we hook up reactivity to the computed
|
|
929
|
+
let numCalcs = 0;
|
|
930
|
+
mobx_1.autorun(() => {
|
|
931
|
+
numCalcs++;
|
|
932
|
+
noop(formState.fullName.value);
|
|
933
|
+
});
|
|
934
|
+
// When the underlying mobx class is directly changed
|
|
935
|
+
instance.firstName = "change";
|
|
936
|
+
// Then our form-state computed re-run
|
|
937
|
+
expect(numCalcs).toBe(2);
|
|
938
|
+
expect(formState.fullName.value).toEqual("change last");
|
|
939
|
+
// And when the underlying mobx class has a field unset
|
|
940
|
+
instance.firstName = undefined;
|
|
941
|
+
// Then our fomr-state computed re-runs again
|
|
942
|
+
expect(numCalcs).toBe(3);
|
|
943
|
+
expect(formState.fullName.value).toEqual("undefined last");
|
|
944
|
+
});
|
|
918
945
|
it("can set computeds that have setters", () => {
|
|
919
946
|
const formState = objectField_1.createObjectState({
|
|
920
947
|
firstName: { type: "value" },
|
|
@@ -1390,6 +1417,15 @@ describe("formState", () => {
|
|
|
1390
1417
|
`);
|
|
1391
1418
|
});
|
|
1392
1419
|
});
|
|
1420
|
+
it("sets id key correctly", () => {
|
|
1421
|
+
// Given an author
|
|
1422
|
+
const a = objectField_1.createObjectState({
|
|
1423
|
+
id: { type: "value" },
|
|
1424
|
+
otherId: { type: "value", isIdKey: true },
|
|
1425
|
+
}, {});
|
|
1426
|
+
expect(a.id._isIdKey).toBe(false);
|
|
1427
|
+
expect(a.otherId._isIdKey).toBe(true);
|
|
1428
|
+
});
|
|
1393
1429
|
});
|
|
1394
1430
|
class ObservableObject {
|
|
1395
1431
|
constructor() {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
-
}) : (function(o, m, k, k2) {
|
|
6
|
-
if (k2 === undefined) k2 = k;
|
|
7
|
-
o[k2] = m[k];
|
|
8
|
-
}));
|
|
9
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
|
-
};
|
|
12
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
3
|
exports.useFormStates = exports.useFormState = exports.required = exports.createObjectState = void 0;
|
|
14
4
|
var objectField_1 = require("./fields/objectField");
|
|
@@ -19,4 +9,3 @@ var useFormState_1 = require("./useFormState");
|
|
|
19
9
|
Object.defineProperty(exports, "useFormState", { enumerable: true, get: function () { return useFormState_1.useFormState; } });
|
|
20
10
|
var useFormStates_1 = require("./useFormStates");
|
|
21
11
|
Object.defineProperty(exports, "useFormStates", { enumerable: true, get: function () { return useFormStates_1.useFormStates; } });
|
|
22
|
-
__exportStar(require("./AutoSaveStatus/index"), exports);
|
package/dist/useFormState.js
CHANGED
|
@@ -4,7 +4,6 @@ exports.useFormState = void 0;
|
|
|
4
4
|
const is_plain_object_1 = require("is-plain-object");
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const objectField_1 = require("./fields/objectField");
|
|
7
|
-
const AutoSaveStatus_1 = require("./AutoSaveStatus/index");
|
|
8
7
|
const utils_1 = require("./utils");
|
|
9
8
|
// If the user's autoSave hook makes some last-minute `.set` calls to sneak
|
|
10
9
|
// in some business logic right before their GraphQL mutation call, ignore it
|
|
@@ -18,7 +17,6 @@ let pendingAutoSave = false;
|
|
|
18
17
|
*/
|
|
19
18
|
function useFormState(opts) {
|
|
20
19
|
const { config, init, addRules, readOnly = false, loading, autoSave } = opts;
|
|
21
|
-
const autoSaveStatusContext = AutoSaveStatus_1.useAutoSaveStatus();
|
|
22
20
|
// Use a ref so our memo'ized `onBlur` always see the latest value
|
|
23
21
|
const autoSaveRef = react_1.useRef(autoSave);
|
|
24
22
|
autoSaveRef.current = autoSave;
|
|
@@ -47,7 +45,6 @@ function useFormState(opts) {
|
|
|
47
45
|
// user's autoSave function itself wants to call a .set.
|
|
48
46
|
const promise = autoSaveRef.current(form);
|
|
49
47
|
isAutoSaving = "in-flight";
|
|
50
|
-
autoSaveStatusContext.triggerAutoSave();
|
|
51
48
|
await promise;
|
|
52
49
|
}
|
|
53
50
|
catch (e) {
|
|
@@ -56,7 +53,6 @@ function useFormState(opts) {
|
|
|
56
53
|
}
|
|
57
54
|
finally {
|
|
58
55
|
isAutoSaving = false;
|
|
59
|
-
autoSaveStatusContext.resolveAutoSave(maybeError === null || maybeError === void 0 ? void 0 : maybeError.toString());
|
|
60
56
|
if (pendingAutoSave) {
|
|
61
57
|
pendingAutoSave = false;
|
|
62
58
|
// Push out the follow-up by 1 tick to allow refreshes to happen to potentially
|
|
@@ -3,14 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
4
|
const rtl_utils_1 = require("@homebound/rtl-utils");
|
|
5
5
|
const react_1 = require("@testing-library/react");
|
|
6
|
-
const react_hooks_1 = require("@testing-library/react-hooks");
|
|
7
6
|
const mobx_1 = require("mobx");
|
|
8
7
|
const mobx_react_1 = require("mobx-react");
|
|
9
8
|
const react_2 = require("react");
|
|
10
9
|
const FormStateApp_1 = require("./FormStateApp");
|
|
11
10
|
const rules_1 = require("./rules");
|
|
12
|
-
const AutoSaveStatus_1 = require("./AutoSaveStatus/index");
|
|
13
|
-
const AutoSaveStatusProvider_1 = require("./AutoSaveStatus/AutoSaveStatusProvider");
|
|
14
11
|
const useFormState_1 = require("./useFormState");
|
|
15
12
|
describe("useFormState", () => {
|
|
16
13
|
it("calls init.map if init.input is defined", async () => {
|
|
@@ -502,32 +499,6 @@ describe("useFormState", () => {
|
|
|
502
499
|
// Then the form is marked as not loading
|
|
503
500
|
expect(r.loading()).toHaveTextContent("false");
|
|
504
501
|
});
|
|
505
|
-
it("updates AutoSaveStatusProvider when autoSaving", async () => {
|
|
506
|
-
const config = { firstName: { type: "value" } };
|
|
507
|
-
const { result } = react_hooks_1.renderHook(() => ({
|
|
508
|
-
fs: useFormState_1.useFormState({ config, autoSave: async () => { } }),
|
|
509
|
-
autoSave: AutoSaveStatus_1.useAutoSaveStatus(),
|
|
510
|
-
}), {
|
|
511
|
-
wrapper: ({ children }) => (jsx_runtime_1.jsx(AutoSaveStatus_1.AutoSaveStatusProvider, { children: jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => jsx_runtime_1.jsx(jsx_runtime_1.Fragment, { children: children }, void 0) }, void 0) }, void 0)),
|
|
512
|
-
});
|
|
513
|
-
expect(result.current.autoSave.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.IDLE);
|
|
514
|
-
// When we invoke a change and autoSave fires
|
|
515
|
-
react_hooks_1.act(() => {
|
|
516
|
-
result.current.fs.set({ firstName: "test" });
|
|
517
|
-
});
|
|
518
|
-
react_hooks_1.act(() => {
|
|
519
|
-
// useFormState has a setTimeout(..., 0) that needs to run
|
|
520
|
-
jest.advanceTimersToNextTimer();
|
|
521
|
-
});
|
|
522
|
-
expect(result.current.autoSave.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.SAVING);
|
|
523
|
-
// Idk why this needs to be async but it doesn't work otherwise
|
|
524
|
-
await react_hooks_1.act(async () => {
|
|
525
|
-
jest.runAllTimers();
|
|
526
|
-
});
|
|
527
|
-
// Then we expect AutoSaveStatus to be notiied of Doneness
|
|
528
|
-
expect(result.current.autoSave.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.DONE);
|
|
529
|
-
jest.clearAllTimers();
|
|
530
|
-
});
|
|
531
502
|
it("treats the id changing as a whole new entity instead of a delete", async () => {
|
|
532
503
|
const config = { firstName: { type: "value" } };
|
|
533
504
|
function TestComponent() {
|
package/dist/useFormStates.js
CHANGED
|
@@ -4,7 +4,6 @@ exports.useFormStates = void 0;
|
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
const objectField_1 = require("./fields/objectField");
|
|
6
6
|
const utils_1 = require("./utils");
|
|
7
|
-
const AutoSaveStatus_1 = require("./AutoSaveStatus/index");
|
|
8
7
|
function useFormStates(opts) {
|
|
9
8
|
const { config, autoSave, getId, map, addRules, readOnly = false } = opts;
|
|
10
9
|
const objectStateCache = react_1.useMemo(() => ({}),
|
|
@@ -19,7 +18,6 @@ function useFormStates(opts) {
|
|
|
19
18
|
// Use a ref b/c we're memod
|
|
20
19
|
const readOnlyRef = react_1.useRef(readOnly);
|
|
21
20
|
readOnlyRef.current = readOnly;
|
|
22
|
-
const autoSaveStatusContext = AutoSaveStatus_1.useAutoSaveStatus();
|
|
23
21
|
const getFormState = react_1.useCallback((input, opts = {}) => {
|
|
24
22
|
const existing = objectStateCache[getId(input)];
|
|
25
23
|
let form = existing === null || existing === void 0 ? void 0 : existing[0];
|
|
@@ -34,7 +32,6 @@ function useFormStates(opts) {
|
|
|
34
32
|
let maybeError;
|
|
35
33
|
try {
|
|
36
34
|
isAutoSaving = true;
|
|
37
|
-
autoSaveStatusContext.triggerAutoSave();
|
|
38
35
|
// See if we have any reactions that want to run (i.e. added by addRules hooks)
|
|
39
36
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
40
37
|
// If a reaction re-queued our form during the ^ wait, remove it
|
|
@@ -47,7 +44,6 @@ function useFormStates(opts) {
|
|
|
47
44
|
}
|
|
48
45
|
finally {
|
|
49
46
|
isAutoSaving = false;
|
|
50
|
-
autoSaveStatusContext.resolveAutoSave(maybeError);
|
|
51
47
|
if (pending.size > 0) {
|
|
52
48
|
const first = pending.values().next().value;
|
|
53
49
|
pending.delete(first);
|
|
@@ -2,15 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
4
|
const rtl_utils_1 = require("@homebound/rtl-utils");
|
|
5
|
-
const react_hooks_1 = require("@testing-library/react-hooks");
|
|
6
5
|
const mobx_1 = require("mobx");
|
|
7
|
-
const mobx_react_1 = require("mobx-react");
|
|
8
6
|
const react_1 = require("react");
|
|
9
7
|
const FormStateApp_1 = require("./FormStateApp");
|
|
10
8
|
const rules_1 = require("./rules");
|
|
11
9
|
const useFormStates_1 = require("./useFormStates");
|
|
12
|
-
const AutoSaveStatus_1 = require("./AutoSaveStatus/index");
|
|
13
|
-
const AutoSaveStatusProvider_1 = require("./AutoSaveStatus/AutoSaveStatusProvider");
|
|
14
10
|
describe("useFormStates", () => {
|
|
15
11
|
it("can lazily create form states", async () => {
|
|
16
12
|
const autoSave = jest.fn();
|
|
@@ -223,28 +219,6 @@ describe("useFormStates", () => {
|
|
|
223
219
|
// Then it's not read only
|
|
224
220
|
expect(r.firstName()).toHaveAttribute("data-readonly", "false");
|
|
225
221
|
});
|
|
226
|
-
it("notifies AutoSaveStatusProvider of updates", async () => {
|
|
227
|
-
const { result } = react_hooks_1.renderHook(() => ({
|
|
228
|
-
fs: useFormStates_1.useFormStates({ config, autoSave: async () => { }, getId: (o) => o.id }),
|
|
229
|
-
autoSave: AutoSaveStatus_1.useAutoSaveStatus(),
|
|
230
|
-
}), {
|
|
231
|
-
wrapper: ({ children }) => (jsx_runtime_1.jsx(AutoSaveStatus_1.AutoSaveStatusProvider, { children: jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => jsx_runtime_1.jsx(jsx_runtime_1.Fragment, { children: children }, void 0) }, void 0) }, void 0)),
|
|
232
|
-
});
|
|
233
|
-
expect(result.current.autoSave.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.IDLE);
|
|
234
|
-
react_hooks_1.act(() => {
|
|
235
|
-
result.current.fs.getFormState({ id: "a:1" }).set({ firstName: "test" });
|
|
236
|
-
});
|
|
237
|
-
react_hooks_1.act(() => {
|
|
238
|
-
// useFormStates has a setTimeout(..., 0) that needs to trigger
|
|
239
|
-
jest.advanceTimersToNextTimer();
|
|
240
|
-
});
|
|
241
|
-
expect(result.current.autoSave.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.SAVING);
|
|
242
|
-
await react_hooks_1.act(async () => {
|
|
243
|
-
jest.runAllTimers();
|
|
244
|
-
});
|
|
245
|
-
expect(result.current.autoSave.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.DONE);
|
|
246
|
-
jest.clearAllTimers();
|
|
247
|
-
});
|
|
248
222
|
});
|
|
249
223
|
const config = {
|
|
250
224
|
id: { type: "value" },
|
package/package.json
CHANGED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import React, { PropsWithChildren } from "react";
|
|
2
|
-
export declare enum AutoSaveStatus {
|
|
3
|
-
IDLE = "idle",
|
|
4
|
-
SAVING = "saving",
|
|
5
|
-
DONE = "done",
|
|
6
|
-
ERROR = "error"
|
|
7
|
-
}
|
|
8
|
-
export interface AutoSaveStatusContextType {
|
|
9
|
-
status: AutoSaveStatus;
|
|
10
|
-
/** Resets status to IDLE, particularly useful if "Error" or "Done" is stale */
|
|
11
|
-
resetStatus: VoidFunction;
|
|
12
|
-
errors: unknown[];
|
|
13
|
-
/** Notifies AutoSaveContext that a request is in-flight */
|
|
14
|
-
triggerAutoSave: VoidFunction;
|
|
15
|
-
/** Notifies AutoSaveContext that a request has settled, optionally taking an error */
|
|
16
|
-
resolveAutoSave: (error?: unknown) => void;
|
|
17
|
-
}
|
|
18
|
-
export declare const AutoSaveStatusContext: React.Context<AutoSaveStatusContextType>;
|
|
19
|
-
declare type AutoSaveStatusProviderProps = PropsWithChildren<{
|
|
20
|
-
/** After a successful save, reset Status back to `Idle` after this many milliseconds */
|
|
21
|
-
resetToIdleTimeout?: number;
|
|
22
|
-
}>;
|
|
23
|
-
export declare function AutoSaveStatusProvider({ children, resetToIdleTimeout }: AutoSaveStatusProviderProps): JSX.Element;
|
|
24
|
-
export {};
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
-
}) : (function(o, m, k, k2) {
|
|
6
|
-
if (k2 === undefined) k2 = k;
|
|
7
|
-
o[k2] = m[k];
|
|
8
|
-
}));
|
|
9
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
-
}) : function(o, v) {
|
|
12
|
-
o["default"] = v;
|
|
13
|
-
});
|
|
14
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
-
if (mod && mod.__esModule) return mod;
|
|
16
|
-
var result = {};
|
|
17
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
-
__setModuleDefault(result, mod);
|
|
19
|
-
return result;
|
|
20
|
-
};
|
|
21
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.AutoSaveStatusProvider = exports.AutoSaveStatusContext = exports.AutoSaveStatus = void 0;
|
|
23
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
24
|
-
const react_1 = __importStar(require("react"));
|
|
25
|
-
var AutoSaveStatus;
|
|
26
|
-
(function (AutoSaveStatus) {
|
|
27
|
-
AutoSaveStatus["IDLE"] = "idle";
|
|
28
|
-
AutoSaveStatus["SAVING"] = "saving";
|
|
29
|
-
AutoSaveStatus["DONE"] = "done";
|
|
30
|
-
AutoSaveStatus["ERROR"] = "error";
|
|
31
|
-
})(AutoSaveStatus = exports.AutoSaveStatus || (exports.AutoSaveStatus = {}));
|
|
32
|
-
exports.AutoSaveStatusContext = react_1.default.createContext({
|
|
33
|
-
status: AutoSaveStatus.IDLE,
|
|
34
|
-
resetStatus() { },
|
|
35
|
-
errors: [],
|
|
36
|
-
triggerAutoSave() { },
|
|
37
|
-
resolveAutoSave() { },
|
|
38
|
-
});
|
|
39
|
-
function AutoSaveStatusProvider({ children, resetToIdleTimeout = 6000 }) {
|
|
40
|
-
const [status, setStatus] = react_1.useState(AutoSaveStatus.IDLE);
|
|
41
|
-
const [errors, setErrors] = react_1.useState([]);
|
|
42
|
-
const [inFlight, setInFlight] = react_1.useState(0);
|
|
43
|
-
const resetToIdleTimeoutRef = react_1.useRef(null);
|
|
44
|
-
/** Handles setting Status */
|
|
45
|
-
react_1.useEffect(() => {
|
|
46
|
-
if (inFlight > 0)
|
|
47
|
-
return setStatus(AutoSaveStatus.SAVING);
|
|
48
|
-
if (status === AutoSaveStatus.IDLE)
|
|
49
|
-
return;
|
|
50
|
-
if (errors.length)
|
|
51
|
-
return setStatus(AutoSaveStatus.ERROR);
|
|
52
|
-
return setStatus(AutoSaveStatus.DONE);
|
|
53
|
-
}, [errors.length, inFlight, status]);
|
|
54
|
-
const triggerAutoSave = react_1.useCallback(() => {
|
|
55
|
-
setInFlight((c) => c + 1);
|
|
56
|
-
setErrors([]);
|
|
57
|
-
}, []);
|
|
58
|
-
const resolveAutoSave = react_1.useCallback((error) => {
|
|
59
|
-
setInFlight((c) => Math.max(0, c - 1));
|
|
60
|
-
if (error)
|
|
61
|
-
setErrors((errs) => errs.concat(error));
|
|
62
|
-
}, []);
|
|
63
|
-
const resetStatus = react_1.useCallback(() => {
|
|
64
|
-
setStatus(AutoSaveStatus.IDLE);
|
|
65
|
-
setErrors([]);
|
|
66
|
-
}, []);
|
|
67
|
-
/** Resets AutoSaveStatus from "Done" to "Idle" after a timeout, if one is provided */
|
|
68
|
-
react_1.useEffect(() => {
|
|
69
|
-
if (resetToIdleTimeout === undefined)
|
|
70
|
-
return;
|
|
71
|
-
// Specifically avoid auto-reset if Errors are present
|
|
72
|
-
if (status !== AutoSaveStatus.DONE)
|
|
73
|
-
return;
|
|
74
|
-
// Only run the latest Timeout
|
|
75
|
-
if (resetToIdleTimeoutRef.current)
|
|
76
|
-
clearTimeout(resetToIdleTimeoutRef.current);
|
|
77
|
-
resetToIdleTimeoutRef.current = window.setTimeout(() => {
|
|
78
|
-
resetStatus();
|
|
79
|
-
resetToIdleTimeoutRef.current = null;
|
|
80
|
-
}, resetToIdleTimeout);
|
|
81
|
-
}, [resetStatus, resetToIdleTimeout, status]);
|
|
82
|
-
const value = react_1.useMemo(() => ({ status, resetStatus, errors, triggerAutoSave, resolveAutoSave }), [
|
|
83
|
-
errors,
|
|
84
|
-
resetStatus,
|
|
85
|
-
resolveAutoSave,
|
|
86
|
-
status,
|
|
87
|
-
triggerAutoSave,
|
|
88
|
-
]);
|
|
89
|
-
return jsx_runtime_1.jsx(exports.AutoSaveStatusContext.Provider, Object.assign({ value: value }, { children: children }), void 0);
|
|
90
|
-
}
|
|
91
|
-
exports.AutoSaveStatusProvider = AutoSaveStatusProvider;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useAutoSaveStatus = exports.AutoSaveStatusProvider = exports.AutoSaveStatusContext = exports.AutoSaveStatus = void 0;
|
|
4
|
-
var AutoSaveStatusProvider_1 = require("./AutoSaveStatusProvider");
|
|
5
|
-
Object.defineProperty(exports, "AutoSaveStatus", { enumerable: true, get: function () { return AutoSaveStatusProvider_1.AutoSaveStatus; } });
|
|
6
|
-
Object.defineProperty(exports, "AutoSaveStatusContext", { enumerable: true, get: function () { return AutoSaveStatusProvider_1.AutoSaveStatusContext; } });
|
|
7
|
-
Object.defineProperty(exports, "AutoSaveStatusProvider", { enumerable: true, get: function () { return AutoSaveStatusProvider_1.AutoSaveStatusProvider; } });
|
|
8
|
-
var useAutoSaveStatus_1 = require("./useAutoSaveStatus");
|
|
9
|
-
Object.defineProperty(exports, "useAutoSaveStatus", { enumerable: true, get: function () { return useAutoSaveStatus_1.useAutoSaveStatus; } });
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function useAutoSaveStatus(): import("./AutoSaveStatusProvider").AutoSaveStatusContextType;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useAutoSaveStatus = void 0;
|
|
4
|
-
const react_1 = require("react");
|
|
5
|
-
const AutoSaveStatusProvider_1 = require("./AutoSaveStatusProvider");
|
|
6
|
-
function useAutoSaveStatus() {
|
|
7
|
-
return react_1.useContext(AutoSaveStatusProvider_1.AutoSaveStatusContext);
|
|
8
|
-
}
|
|
9
|
-
exports.useAutoSaveStatus = useAutoSaveStatus;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
|
-
const react_hooks_1 = require("@testing-library/react-hooks");
|
|
5
|
-
const AutoSaveStatusProvider_1 = require("./AutoSaveStatusProvider");
|
|
6
|
-
const useAutoSaveStatus_1 = require("./useAutoSaveStatus");
|
|
7
|
-
describe(useAutoSaveStatus_1.useAutoSaveStatus, () => {
|
|
8
|
-
/** The internal setTimeout running after tests is spamming the console, so cancel them all here */
|
|
9
|
-
afterEach(() => {
|
|
10
|
-
jest.clearAllTimers();
|
|
11
|
-
});
|
|
12
|
-
it("renders without a provider", () => {
|
|
13
|
-
const { result } = react_hooks_1.renderHook(() => useAutoSaveStatus_1.useAutoSaveStatus());
|
|
14
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.IDLE);
|
|
15
|
-
});
|
|
16
|
-
it("renders with a provider", () => {
|
|
17
|
-
const { result } = react_hooks_1.renderHook(() => useAutoSaveStatus_1.useAutoSaveStatus(), {
|
|
18
|
-
wrapper: ({ children }) => jsx_runtime_1.jsx(AutoSaveStatusProvider_1.AutoSaveStatusProvider, { children: children }, void 0),
|
|
19
|
-
});
|
|
20
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.IDLE);
|
|
21
|
-
});
|
|
22
|
-
it("indicates when something is in-flight", () => {
|
|
23
|
-
const { result } = react_hooks_1.renderHook(() => useAutoSaveStatus_1.useAutoSaveStatus(), {
|
|
24
|
-
wrapper: ({ children }) => jsx_runtime_1.jsx(AutoSaveStatusProvider_1.AutoSaveStatusProvider, { children: children }, void 0),
|
|
25
|
-
});
|
|
26
|
-
react_hooks_1.act(() => result.current.triggerAutoSave());
|
|
27
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.SAVING);
|
|
28
|
-
});
|
|
29
|
-
it("indicates when a request has settled", () => {
|
|
30
|
-
const { result } = react_hooks_1.renderHook(() => useAutoSaveStatus_1.useAutoSaveStatus(), {
|
|
31
|
-
wrapper: ({ children }) => jsx_runtime_1.jsx(AutoSaveStatusProvider_1.AutoSaveStatusProvider, { children: children }, void 0),
|
|
32
|
-
});
|
|
33
|
-
react_hooks_1.act(() => result.current.triggerAutoSave());
|
|
34
|
-
react_hooks_1.act(() => result.current.resolveAutoSave());
|
|
35
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.DONE);
|
|
36
|
-
});
|
|
37
|
-
it("indicates when an error happened", () => {
|
|
38
|
-
const { result } = react_hooks_1.renderHook(() => useAutoSaveStatus_1.useAutoSaveStatus(), {
|
|
39
|
-
wrapper: ({ children }) => jsx_runtime_1.jsx(AutoSaveStatusProvider_1.AutoSaveStatusProvider, { children: children }, void 0),
|
|
40
|
-
});
|
|
41
|
-
react_hooks_1.act(() => result.current.triggerAutoSave());
|
|
42
|
-
react_hooks_1.act(() => result.current.resolveAutoSave(new Error("Some error")));
|
|
43
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.ERROR);
|
|
44
|
-
expect(result.current.errors.length).toBe(1);
|
|
45
|
-
});
|
|
46
|
-
it("resets status to Idle when told to", () => {
|
|
47
|
-
const { result } = react_hooks_1.renderHook(() => useAutoSaveStatus_1.useAutoSaveStatus(), {
|
|
48
|
-
wrapper: ({ children }) => jsx_runtime_1.jsx(AutoSaveStatusProvider_1.AutoSaveStatusProvider, { children: children }, void 0),
|
|
49
|
-
});
|
|
50
|
-
react_hooks_1.act(() => result.current.triggerAutoSave());
|
|
51
|
-
react_hooks_1.act(() => result.current.resolveAutoSave());
|
|
52
|
-
react_hooks_1.act(() => result.current.resetStatus());
|
|
53
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.IDLE);
|
|
54
|
-
});
|
|
55
|
-
it("status goes through the full lifecycle when passed a reset timeout", async () => {
|
|
56
|
-
// Given a timeout has been passed to `useAutoSave()`
|
|
57
|
-
const { result } = react_hooks_1.renderHook(() => useAutoSaveStatus_1.useAutoSaveStatus(), {
|
|
58
|
-
wrapper: ({ children }) => jsx_runtime_1.jsx(AutoSaveStatusProvider_1.AutoSaveStatusProvider, Object.assign({ resetToIdleTimeout: 1000 }, { children: children }), void 0),
|
|
59
|
-
});
|
|
60
|
-
// When we trigger a save
|
|
61
|
-
react_hooks_1.act(() => result.current.triggerAutoSave());
|
|
62
|
-
// Then status is Saving
|
|
63
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.SAVING);
|
|
64
|
-
// And when we trigger a resolution
|
|
65
|
-
react_hooks_1.act(() => result.current.resolveAutoSave());
|
|
66
|
-
// Then status is Done
|
|
67
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.DONE);
|
|
68
|
-
// But when the timer runs out
|
|
69
|
-
react_hooks_1.act(() => {
|
|
70
|
-
jest.runOnlyPendingTimers();
|
|
71
|
-
});
|
|
72
|
-
// Then the status is reset to Idle
|
|
73
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.IDLE);
|
|
74
|
-
});
|
|
75
|
-
it("clears errors on reset status", () => {
|
|
76
|
-
const { result } = react_hooks_1.renderHook(() => useAutoSaveStatus_1.useAutoSaveStatus(), {
|
|
77
|
-
wrapper: ({ children }) => jsx_runtime_1.jsx(AutoSaveStatusProvider_1.AutoSaveStatusProvider, { children: children }, void 0),
|
|
78
|
-
});
|
|
79
|
-
react_hooks_1.act(() => result.current.triggerAutoSave());
|
|
80
|
-
react_hooks_1.act(() => result.current.resolveAutoSave(new Error("some error")));
|
|
81
|
-
expect(result.current.errors.length).toBe(1);
|
|
82
|
-
react_hooks_1.act(() => result.current.resetStatus());
|
|
83
|
-
expect(result.current.errors.length).toBe(0);
|
|
84
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.IDLE);
|
|
85
|
-
});
|
|
86
|
-
it("does not automatically invoke reset timeout if there are errors", () => {
|
|
87
|
-
// Given a timeout has been passed to `useAutoSave()`
|
|
88
|
-
const { result } = react_hooks_1.renderHook(() => useAutoSaveStatus_1.useAutoSaveStatus(), {
|
|
89
|
-
wrapper: ({ children }) => jsx_runtime_1.jsx(AutoSaveStatusProvider_1.AutoSaveStatusProvider, { children: children }, void 0),
|
|
90
|
-
});
|
|
91
|
-
react_hooks_1.act(() => result.current.triggerAutoSave());
|
|
92
|
-
react_hooks_1.act(() => result.current.resolveAutoSave(new Error("Some error")));
|
|
93
|
-
react_hooks_1.act(() => {
|
|
94
|
-
jest.runOnlyPendingTimers();
|
|
95
|
-
});
|
|
96
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.ERROR);
|
|
97
|
-
expect(result.current.errors.length).toBe(1);
|
|
98
|
-
});
|
|
99
|
-
it("does allow manual resetting even if there are errors", () => {
|
|
100
|
-
// Given a timeout has been passed to `useAutoSave()`
|
|
101
|
-
const { result } = react_hooks_1.renderHook(() => useAutoSaveStatus_1.useAutoSaveStatus(), {
|
|
102
|
-
wrapper: ({ children }) => jsx_runtime_1.jsx(AutoSaveStatusProvider_1.AutoSaveStatusProvider, { children: children }, void 0),
|
|
103
|
-
});
|
|
104
|
-
react_hooks_1.act(() => result.current.triggerAutoSave());
|
|
105
|
-
react_hooks_1.act(() => result.current.resolveAutoSave(new Error("Some error")));
|
|
106
|
-
react_hooks_1.act(() => {
|
|
107
|
-
jest.runOnlyPendingTimers();
|
|
108
|
-
});
|
|
109
|
-
react_hooks_1.act(() => result.current.resetStatus());
|
|
110
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.IDLE);
|
|
111
|
-
expect(result.current.errors.length).toBe(0);
|
|
112
|
-
});
|
|
113
|
-
it("handles multiple in-flight requests", () => {
|
|
114
|
-
const { result } = react_hooks_1.renderHook(() => useAutoSaveStatus_1.useAutoSaveStatus(), {
|
|
115
|
-
wrapper: ({ children }) => jsx_runtime_1.jsx(AutoSaveStatusProvider_1.AutoSaveStatusProvider, { children: children }, void 0),
|
|
116
|
-
});
|
|
117
|
-
// When we trigger 2 AutoSaves and only resolve 1
|
|
118
|
-
react_hooks_1.act(() => result.current.triggerAutoSave());
|
|
119
|
-
react_hooks_1.act(() => result.current.triggerAutoSave());
|
|
120
|
-
react_hooks_1.act(() => result.current.resolveAutoSave());
|
|
121
|
-
// We expect something to still be in-flight
|
|
122
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.SAVING);
|
|
123
|
-
// And when we resolve the final one
|
|
124
|
-
react_hooks_1.act(() => result.current.resolveAutoSave());
|
|
125
|
-
// We expect it to finally settle
|
|
126
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.DONE);
|
|
127
|
-
});
|
|
128
|
-
it("clears errors when a new save is triggered", () => {
|
|
129
|
-
const { result } = react_hooks_1.renderHook(() => useAutoSaveStatus_1.useAutoSaveStatus(), {
|
|
130
|
-
wrapper: ({ children }) => jsx_runtime_1.jsx(AutoSaveStatusProvider_1.AutoSaveStatusProvider, { children: children }, void 0),
|
|
131
|
-
});
|
|
132
|
-
react_hooks_1.act(() => result.current.triggerAutoSave());
|
|
133
|
-
react_hooks_1.act(() => result.current.resolveAutoSave(new Error("some error")));
|
|
134
|
-
react_hooks_1.act(() => result.current.triggerAutoSave());
|
|
135
|
-
expect(result.current.errors.length).toBe(0);
|
|
136
|
-
});
|
|
137
|
-
it("handles calling resolve too much", () => {
|
|
138
|
-
const { result } = react_hooks_1.renderHook(() => useAutoSaveStatus_1.useAutoSaveStatus(), {
|
|
139
|
-
wrapper: ({ children }) => jsx_runtime_1.jsx(AutoSaveStatusProvider_1.AutoSaveStatusProvider, { children: children }, void 0),
|
|
140
|
-
});
|
|
141
|
-
// When save hasn't been invoked yet
|
|
142
|
-
react_hooks_1.act(() => result.current.resolveAutoSave());
|
|
143
|
-
// Then we effectively didn't run
|
|
144
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.IDLE);
|
|
145
|
-
// And when 1 save has triggered, and we resolve too much
|
|
146
|
-
react_hooks_1.act(() => result.current.triggerAutoSave());
|
|
147
|
-
react_hooks_1.act(() => result.current.resolveAutoSave());
|
|
148
|
-
react_hooks_1.act(() => result.current.resolveAutoSave());
|
|
149
|
-
// Then we expect it to be happily "Done"
|
|
150
|
-
expect(result.current.status).toBe(AutoSaveStatusProvider_1.AutoSaveStatus.DONE);
|
|
151
|
-
});
|
|
152
|
-
});
|