@multiplatform.one/frappe-ui 7.78.0 → 7.80.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/cjs/FrappeForm.cjs +20 -26
- package/dist/cjs/FrappeForm.native.js +26 -26
- package/dist/cjs/FrappeForm.native.js.map +1 -1
- package/dist/cjs/FrappeListView.cjs +26 -12
- package/dist/cjs/FrappeListView.native.js +41 -12
- package/dist/cjs/FrappeListView.native.js.map +1 -1
- package/dist/cjs/fields/Password/index.cjs +125 -36
- package/dist/cjs/fields/Password/index.native.js +101 -13
- package/dist/cjs/fields/Password/index.native.js.map +1 -1
- package/dist/cjs/list/ListControlsSheet.cjs +5 -11
- package/dist/cjs/list/ListControlsSheet.native.js +5 -11
- package/dist/cjs/list/ListControlsSheet.native.js.map +1 -1
- package/dist/cjs/list/ListFiltersButton.cjs +56 -0
- package/dist/cjs/list/ListFiltersButton.native.js +60 -0
- package/dist/cjs/list/ListFiltersButton.native.js.map +1 -0
- package/dist/cjs/list/ListSidebarToolbar.cjs +5 -17
- package/dist/cjs/list/ListSidebarToolbar.native.js +7 -19
- package/dist/cjs/list/ListSidebarToolbar.native.js.map +1 -1
- package/dist/cjs/list/ListToolsSheet.cjs +5 -18
- package/dist/cjs/list/ListToolsSheet.native.js +7 -20
- package/dist/cjs/list/ListToolsSheet.native.js.map +1 -1
- package/dist/cjs/list/defaultProjection.cjs +56 -0
- package/dist/cjs/list/defaultProjection.native.js +107 -0
- package/dist/cjs/list/defaultProjection.native.js.map +1 -0
- package/dist/cjs/shared/devWarn.native.js.map +1 -1
- package/dist/esm/FrappeForm.mjs +21 -27
- package/dist/esm/FrappeForm.mjs.map +1 -1
- package/dist/esm/FrappeForm.native.js +27 -27
- package/dist/esm/FrappeForm.native.js.map +1 -1
- package/dist/esm/FrappeListView.mjs +26 -12
- package/dist/esm/FrappeListView.mjs.map +1 -1
- package/dist/esm/FrappeListView.native.js +41 -12
- package/dist/esm/FrappeListView.native.js.map +1 -1
- package/dist/esm/fields/Password/index.mjs +125 -36
- package/dist/esm/fields/Password/index.mjs.map +1 -1
- package/dist/esm/fields/Password/index.native.js +101 -13
- package/dist/esm/fields/Password/index.native.js.map +1 -1
- package/dist/esm/list/ListControlsSheet.mjs +7 -13
- package/dist/esm/list/ListControlsSheet.mjs.map +1 -1
- package/dist/esm/list/ListControlsSheet.native.js +7 -13
- package/dist/esm/list/ListControlsSheet.native.js.map +1 -1
- package/dist/esm/list/ListFiltersButton.mjs +31 -0
- package/dist/esm/list/ListFiltersButton.mjs.map +1 -0
- package/dist/esm/list/ListFiltersButton.native.js +32 -0
- package/dist/esm/list/ListFiltersButton.native.js.map +1 -0
- package/dist/esm/list/ListSidebarToolbar.mjs +7 -19
- package/dist/esm/list/ListSidebarToolbar.mjs.map +1 -1
- package/dist/esm/list/ListSidebarToolbar.native.js +9 -21
- package/dist/esm/list/ListSidebarToolbar.native.js.map +1 -1
- package/dist/esm/list/ListToolsSheet.mjs +7 -20
- package/dist/esm/list/ListToolsSheet.mjs.map +1 -1
- package/dist/esm/list/ListToolsSheet.native.js +9 -22
- package/dist/esm/list/ListToolsSheet.native.js.map +1 -1
- package/dist/esm/list/defaultProjection.mjs +29 -0
- package/dist/esm/list/defaultProjection.mjs.map +1 -0
- package/dist/esm/list/defaultProjection.native.js +77 -0
- package/dist/esm/list/defaultProjection.native.js.map +1 -0
- package/dist/esm/shared/devWarn.mjs.map +1 -1
- package/dist/esm/shared/devWarn.native.js.map +1 -1
- package/dist/jsx/FrappeForm.mjs +21 -27
- package/dist/jsx/FrappeForm.mjs.map +1 -1
- package/dist/jsx/FrappeForm.native.js +26 -26
- package/dist/jsx/FrappeForm.native.js.map +1 -1
- package/dist/jsx/FrappeListView.mjs +26 -12
- package/dist/jsx/FrappeListView.mjs.map +1 -1
- package/dist/jsx/FrappeListView.native.js +41 -12
- package/dist/jsx/FrappeListView.native.js.map +1 -1
- package/dist/jsx/fields/Password/index.mjs +125 -36
- package/dist/jsx/fields/Password/index.mjs.map +1 -1
- package/dist/jsx/fields/Password/index.native.js +101 -13
- package/dist/jsx/fields/Password/index.native.js.map +1 -1
- package/dist/jsx/list/ListControlsSheet.mjs +7 -13
- package/dist/jsx/list/ListControlsSheet.mjs.map +1 -1
- package/dist/jsx/list/ListControlsSheet.native.js +5 -11
- package/dist/jsx/list/ListControlsSheet.native.js.map +1 -1
- package/dist/jsx/list/ListFiltersButton.mjs +31 -0
- package/dist/jsx/list/ListFiltersButton.mjs.map +1 -0
- package/dist/jsx/list/ListFiltersButton.native.js +60 -0
- package/dist/jsx/list/ListFiltersButton.native.js.map +1 -0
- package/dist/jsx/list/ListSidebarToolbar.mjs +7 -19
- package/dist/jsx/list/ListSidebarToolbar.mjs.map +1 -1
- package/dist/jsx/list/ListSidebarToolbar.native.js +7 -19
- package/dist/jsx/list/ListSidebarToolbar.native.js.map +1 -1
- package/dist/jsx/list/ListToolsSheet.mjs +7 -20
- package/dist/jsx/list/ListToolsSheet.mjs.map +1 -1
- package/dist/jsx/list/ListToolsSheet.native.js +7 -20
- package/dist/jsx/list/ListToolsSheet.native.js.map +1 -1
- package/dist/jsx/list/defaultProjection.mjs +29 -0
- package/dist/jsx/list/defaultProjection.mjs.map +1 -0
- package/dist/jsx/list/defaultProjection.native.js +107 -0
- package/dist/jsx/list/defaultProjection.native.js.map +1 -0
- package/dist/jsx/shared/devWarn.mjs.map +1 -1
- package/dist/jsx/shared/devWarn.native.js.map +1 -1
- package/package.json +16 -16
- package/src/FrappeForm.recordChanged.spec.tsx +179 -0
- package/src/FrappeForm.recordChanged.stories.tsx +83 -0
- package/src/FrappeForm.successIsCallback.spec.tsx +154 -0
- package/src/FrappeForm.tsx +34 -33
- package/src/FrappeListView.projection.spec.tsx +193 -0
- package/src/FrappeListView.sidebar.spec.tsx +20 -2
- package/src/FrappeListView.successIsCallback.spec.tsx +143 -0
- package/src/FrappeListView.tsx +63 -25
- package/src/fields/Password/Password.secret.spec.tsx +194 -0
- package/src/fields/Password/Password.spec.tsx +15 -8
- package/src/fields/Password/index.tsx +134 -35
- package/src/list/ListControls.spec.tsx +10 -1
- package/src/list/ListControlsSheet.spec.tsx +4 -2
- package/src/list/ListControlsSheet.tsx +5 -9
- package/src/list/ListFiltersButton.tsx +33 -0
- package/src/list/ListSidebarToolbar.tsx +13 -20
- package/src/list/ListToolsSheet.tsx +10 -18
- package/src/list/defaultProjection.ts +66 -0
- package/src/shared/devWarn.ts +2 -1
- package/types/FrappeForm.d.ts.map +1 -1
- package/types/FrappeListView.d.ts.map +1 -1
- package/types/fields/Password/index.d.ts +6 -0
- package/types/fields/Password/index.d.ts.map +1 -1
- package/types/list/ListControlsSheet.d.ts.map +1 -1
- package/types/list/ListFiltersButton.d.ts +11 -0
- package/types/list/ListFiltersButton.d.ts.map +1 -0
- package/types/list/ListSidebarToolbar.d.ts +5 -3
- package/types/list/ListSidebarToolbar.d.ts.map +1 -1
- package/types/list/ListToolsSheet.d.ts.map +1 -1
- package/types/list/defaultProjection.d.ts +26 -0
- package/types/list/defaultProjection.d.ts.map +1 -0
- package/types/shared/devWarn.d.ts +1 -1
- package/types/shared/devWarn.d.ts.map +1 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all) __defProp(target, name, {
|
|
9
|
+
get: all[name],
|
|
10
|
+
enumerable: true
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
16
|
+
get: () => from[key],
|
|
17
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
|
|
23
|
+
value: true
|
|
24
|
+
}), mod);
|
|
25
|
+
var defaultProjection_exports = {};
|
|
26
|
+
__export(defaultProjection_exports, {
|
|
27
|
+
defaultProjection: () => defaultProjection,
|
|
28
|
+
defaultProjectionCap: () => defaultProjectionCap,
|
|
29
|
+
scopePinnedFields: () => scopePinnedFields
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(defaultProjection_exports);
|
|
32
|
+
function _type_of(obj) {
|
|
33
|
+
"@swc/helpers - typeof";
|
|
34
|
+
|
|
35
|
+
return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
|
|
36
|
+
}
|
|
37
|
+
var defaultProjectionCap = 5;
|
|
38
|
+
function isScalar(value) {
|
|
39
|
+
return value === null || ["string", "number", "boolean"].includes(typeof value === "undefined" ? "undefined" : _type_of(value));
|
|
40
|
+
}
|
|
41
|
+
function scopePinnedFields(filters, doctype) {
|
|
42
|
+
var pinned = /* @__PURE__ */new Set();
|
|
43
|
+
if (!filters) return pinned;
|
|
44
|
+
if (!Array.isArray(filters)) {
|
|
45
|
+
var _iteratorNormalCompletion = true,
|
|
46
|
+
_didIteratorError = false,
|
|
47
|
+
_iteratorError = void 0;
|
|
48
|
+
try {
|
|
49
|
+
for (var _iterator = Object.entries(filters)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
|
50
|
+
var [field, value] = _step.value;
|
|
51
|
+
if (isScalar(value)) pinned.add(field);
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
_didIteratorError = true;
|
|
55
|
+
_iteratorError = err;
|
|
56
|
+
} finally {
|
|
57
|
+
try {
|
|
58
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
59
|
+
_iterator.return();
|
|
60
|
+
}
|
|
61
|
+
} finally {
|
|
62
|
+
if (_didIteratorError) {
|
|
63
|
+
throw _iteratorError;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return pinned;
|
|
68
|
+
}
|
|
69
|
+
var _iteratorNormalCompletion1 = true,
|
|
70
|
+
_didIteratorError1 = false,
|
|
71
|
+
_iteratorError1 = void 0;
|
|
72
|
+
try {
|
|
73
|
+
for (var _iterator1 = filters[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true) {
|
|
74
|
+
var condition = _step1.value;
|
|
75
|
+
var [field1, operator, value1] = condition.length === 4 ? condition[0] === doctype || doctype === void 0 ? [condition[1], condition[2], condition[3]] : [void 0, void 0, void 0] : condition;
|
|
76
|
+
if (field1 && operator === "=" && isScalar(value1)) pinned.add(field1);
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
_didIteratorError1 = true;
|
|
80
|
+
_iteratorError1 = err;
|
|
81
|
+
} finally {
|
|
82
|
+
try {
|
|
83
|
+
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
84
|
+
_iterator1.return();
|
|
85
|
+
}
|
|
86
|
+
} finally {
|
|
87
|
+
if (_didIteratorError1) {
|
|
88
|
+
throw _iteratorError1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return pinned;
|
|
93
|
+
}
|
|
94
|
+
function defaultProjection(fields, pinned) {
|
|
95
|
+
var cap = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : defaultProjectionCap;
|
|
96
|
+
var open = fields.filter(function (field) {
|
|
97
|
+
return !pinned.has(field.fieldname);
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
shown: open.slice(0, cap),
|
|
101
|
+
optIn: open.slice(cap),
|
|
102
|
+
pinned: fields.filter(function (field) {
|
|
103
|
+
return pinned.has(field.fieldname);
|
|
104
|
+
})
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=defaultProjection.native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call","__toCommonJS","mod","value","defaultProjection_exports","defaultProjection","defaultProjectionCap","scopePinnedFields","module","exports","_type_of","obj"],"sources":["../../../src/list/defaultProjection.ts"],"sourcesContent":[null],"mappings":"AAIO,YAAM;;AAOb,IAAAA,SAAS,GAAAC,MAAS,CAAAC,cAAyB;AACzC,IAAAC,gBAAiB,GAAAF,MAAS,CAAAG,wBAAoB;AAChD,IAAAC,iBAAA,GAAAJ,MAAA,CAAAK,mBAAA;AAOO,IAAAC,YAAS,GAAAN,MAAA,CAAAO,SAAkB,CAAkBC,cAA+B;AACjF,IAAAC,QAAM,GAAAA,CAAAC,MAAS,EAAAC,GAAA;EACf,KAAK,IAAAC,IAAS,IAAAD,GAAA,EACdZ,SAAW,CAAAW,MAAA,EAAQE,IAAA,EAAO;IAAAC,GAAG,EAAAF,GAAA,CAAAC,IAAA;IAAAE,UAAA;EAAA;AAC3B;AACE,IAAAC,WAAI,GAAAA,CAASC,EAAA,EAAAC,IAAQ,EAAAC,MAAO,EAAAC,IAAI,KAAK;EAAA,IACvCF,IAAA,WAAAA,IAAA,wBAAAA,IAAA;IACA,SAAOG,GAAA,IAAAhB,iBAAA,CAAAa,IAAA,GACT,KAAAX,YAAA,CAAAe,IAAA,CAAAL,EAAA,EAAAI,GAAA,KAAAA,GAAA,KAAAF,MAAA,EACAnB,SAAW,CAAAiB,EAAA,EAAAI,GAAA,EAAa;MAAAP,GAAA,EAAAA,CAAA,KAASI,IAAA,CAAAG,GAAA;MAAAN,UAAA,IAAAK,IAAA,GAAAjB,gBAAA,CAAAe,IAAA,EAAAG,GAAA,MAAAD,IAAA,CAAAL;IAAA;EAC/B;EAMA,OAAIE,EAAA;AAA8D;AAEpE,IAAAM,YAAO,GAAAC,GAAA,IAAAR,WAAA,CAAAhB,SAAA;EAAAyB,KAAA;AAAA,IAAAD,GAAA;AACT,IAAAE,yBAAA;AAeOhB,QAAA,CAASgB,yBACd,EACA;EAGAC,iBAAa,EAAAA,CAAA,KAAOA,iBAAmB;EACvCC,oBAAO,EAAAA,CAAA,KAAAA,oBAAA;EAAAC,iBACO,EAAAA,CAAA,KAASA;AAAG;AACHC,MACrB,CAAAC,OAAQ,GAAAR,YAAe,CAAAG,yBAA2B;AAAU,SAC9DM,SAAAC,GAAA;EACF","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["seen","Set","frappeDevWarn","code","detail","process","env","NODE_ENV","key","component","id","doctype","join","has","add","bits","suggest","filter","Boolean","console","warn","length","__resetFrappeDevWarnSeen","clear"],"sources":["../../../src/shared/devWarn.ts"],"sourcesContent":[null],"mappings":"
|
|
1
|
+
{"version":3,"names":["seen","Set","frappeDevWarn","code","detail","process","env","NODE_ENV","key","component","id","doctype","join","has","add","bits","suggest","filter","Boolean","console","warn","length","__resetFrappeDevWarnSeen","clear"],"sources":["../../../src/shared/devWarn.ts"],"sourcesContent":[null],"mappings":"AAyBA,MAAMA,IAAA,GAAO,mBAAIC,GAAA,CAAY;AAEtB,SAASC,cAAcC,IAAA,EAAyBC,MAAA,GAA8B,CAAC,GAAS;EAC7F,IAAIC,OAAA,CAAQC,GAAA,CAAIC,QAAA,KAAa,cAAc;EAC3C,MAAMC,GAAA,GAAM,CAACL,IAAA,EAAMC,MAAA,CAAOK,SAAA,IAAa,IAAIL,MAAA,CAAOM,EAAA,IAAM,IAAIN,MAAA,CAAOO,OAAA,IAAW,EAAE,EAAEC,IAAA,CAAK,GAAG;EAC1F,IAAIZ,IAAA,CAAKa,GAAA,CAAIL,GAAG,GAAG;EACnBR,IAAA,CAAKc,GAAA,CAAIN,GAAG;EACZ,MAAMO,IAAA,GAAO,CACXX,MAAA,CAAOK,SAAA,GAAY,aAAaL,MAAA,CAAOK,SAAS,KAAK,QACrDL,MAAA,CAAOM,EAAA,GAAK,MAAMN,MAAA,CAAOM,EAAE,KAAK,QAChCN,MAAA,CAAOO,OAAA,GAAU,WAAWP,MAAA,CAAOO,OAAO,KAAK,QAC/CP,MAAA,CAAOY,OAAA,GAAU,WAAWZ,MAAA,CAAOY,OAAO,KAAK,OACjD,CAAEC,MAAA,CAAOC,OAAO;EAChBC,OAAA,CAAQC,IAAA,CAAK,eAAejB,IAAI,GAAGY,IAAA,CAAKM,MAAA,GAAS,KAAKN,IAAA,CAAKH,IAAA,CAAK,IAAI,CAAC,MAAM,EAAE,EAAE;AACjF;AAGO,SAASU,yBAAA,EAAiC;EAC/CtB,IAAA,CAAKuB,KAAA,CAAM;AACb","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call"],"sources":["../../../src/shared/devWarn.ts"],"sourcesContent":[null],"mappings":"
|
|
1
|
+
{"version":3,"names":["__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call"],"sources":["../../../src/shared/devWarn.ts"],"sourcesContent":[null],"mappings":"AAyBA,YAAM;;AAEC,IAAAA,SAAS,GAAAC,MAAA,CAAcC,cAAyB;AACrD,IAAAC,gBAAgB,GAAAF,MAAA,CAAAG,wBAA2B;AAC3C,IAAAC,iBAAmB,GAAAJ,MAAO,CAAAK,mBAAiB;AAC3C,IAAAC,YAAa,GAAGN,MAAG,CAAAO,SAAA,CAAAC,cAAA;AACnB,IAAAC,QAAS,GAAGA,CAAAC,MAAA,EAAAC,GAAA;EACZ,SAAMC,IAAO,IAAAD,GAAA,EACXZ,SAAO,CAAAW,MAAA,EAAAE,IAAY;IAAAC,GAAA,EAAAF,GAAa,CAAAC,IAAO;IAAAE,UAAS,EAAK;EAAA;AAAA;AACrB,IAChCC,WAAO,GAAAA,CAAAC,EAAU,EAAAC,IAAA,EAAAC,MAAW,EAAAC,IAAO,KAAO;EAAK,IAC/CF,IAAA,IAAO,OAAUA,IAAA,aAAW,IAAO,OAAOA,IAAK;IAC/C,SAAOG,GAAA,IAAOhB,iBAAA,CAAAa,IAAA,GAChB,IAAQ,CAAAX,YAAK,CAAAe,IAAA,CAAAL,EAAe,EAAAI,GAAI,CAAG,IAAAA,GAAK,KAAAF,MAAS,EACnDnB,SAAA,CAAAiB,EAAA,EAAAI,GAAA;MAAAP,GAAA,EAAAA,CAAA,KAAAI,IAAA,CAAAG,GAAA;MAAAN,UAAA,IAAAK,IAAA,GAAAjB,gBAAA,CAAAe,IAAA,EAAAG,GAAA,MAAAD,IAAA,CAAAL;IAAA;EAGO;EACL,OAAKE,EAAA;AACP","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/frappe-ui",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.80.0",
|
|
4
4
|
"description": "Pre-wired Tamagui components for Frappe doctypes with live data",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cross-platform",
|
|
@@ -72,18 +72,18 @@
|
|
|
72
72
|
"access": "public"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@multiplatform.one/components": "^7.
|
|
76
|
-
"@multiplatform.one/desktop": "^7.
|
|
77
|
-
"@multiplatform.one/forms": "^7.
|
|
78
|
-
"@multiplatform.one/frappe": "^7.
|
|
79
|
-
"@multiplatform.one/i18n": "^7.
|
|
80
|
-
"@multiplatform.one/markdown": "^7.
|
|
81
|
-
"@multiplatform.one/platform": "^7.
|
|
82
|
-
"@multiplatform.one/rich-text": "^7.
|
|
83
|
-
"@multiplatform.one/router": "^7.
|
|
84
|
-
"@multiplatform.one/store": "^7.
|
|
85
|
-
"@multiplatform.one/table": "^7.
|
|
86
|
-
"@multiplatform.one/theme": "^7.
|
|
75
|
+
"@multiplatform.one/components": "^7.80.0",
|
|
76
|
+
"@multiplatform.one/desktop": "^7.80.0",
|
|
77
|
+
"@multiplatform.one/forms": "^7.80.0",
|
|
78
|
+
"@multiplatform.one/frappe": "^7.80.0",
|
|
79
|
+
"@multiplatform.one/i18n": "^7.80.0",
|
|
80
|
+
"@multiplatform.one/markdown": "^7.80.0",
|
|
81
|
+
"@multiplatform.one/platform": "^7.80.0",
|
|
82
|
+
"@multiplatform.one/rich-text": "^7.80.0",
|
|
83
|
+
"@multiplatform.one/router": "^7.80.0",
|
|
84
|
+
"@multiplatform.one/store": "^7.80.0",
|
|
85
|
+
"@multiplatform.one/table": "^7.80.0",
|
|
86
|
+
"@multiplatform.one/theme": "^7.80.0",
|
|
87
87
|
"@phosphor-icons/react": "^2.1.10",
|
|
88
88
|
"@tamagui/colors": "2.7.6",
|
|
89
89
|
"@tanstack/db": "0.8.0",
|
|
@@ -100,9 +100,9 @@
|
|
|
100
100
|
"tamagui": "2.7.6"
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
|
-
"@multiplatform.one/config": "7.
|
|
104
|
-
"@multiplatform.one/storybook": "7.
|
|
105
|
-
"@multiplatform.one/test-utils": "7.
|
|
103
|
+
"@multiplatform.one/config": "7.80.0",
|
|
104
|
+
"@multiplatform.one/storybook": "7.80.0",
|
|
105
|
+
"@multiplatform.one/test-utils": "7.80.0",
|
|
106
106
|
"@tamagui/build": "2.7.6",
|
|
107
107
|
"@testing-library/jest-dom": "^7.0.1",
|
|
108
108
|
"@testing-library/react": "^16.3.3",
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LC-191 RECORD-CHANGED-UNDER-YOU: an open record that changes on the server
|
|
3
|
+
* says so and keeps the work. A newer server version pushed mid-edit keeps
|
|
4
|
+
* the edit, names the field by its label and offers Reload or Overwrite; a
|
|
5
|
+
* record deleted mid-edit stays mounted, turns read-only and says so, which
|
|
6
|
+
* is not the not-found state.
|
|
7
|
+
*
|
|
8
|
+
* The server is the fixture corpus; a snapshot refresh of the shared
|
|
9
|
+
* SyncModule plays the change the live socket would deliver.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { getFrappeSyncModule, type InMemoryFixtureProvider } from "@multiplatform.one/frappe";
|
|
13
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
14
|
+
import { act, cleanup, fireEvent, screen, waitFor } from "@testing-library/react";
|
|
15
|
+
import i18next from "i18next";
|
|
16
|
+
import { initReactI18next } from "react-i18next";
|
|
17
|
+
import { Text } from "tamagui";
|
|
18
|
+
import { afterEach, beforeAll, describe, expect, it } from "vitest";
|
|
19
|
+
import { assetLoanLifecycleFixtures } from "../tests/fixtures/assetLoanLifecycle";
|
|
20
|
+
import { FrappeForm } from "./FrappeForm";
|
|
21
|
+
import { FrappeProvider } from "./FrappeProvider";
|
|
22
|
+
import type { FieldOverride } from "./schemaForm";
|
|
23
|
+
|
|
24
|
+
beforeAll(async () => {
|
|
25
|
+
await i18next.use(initReactI18next).init({
|
|
26
|
+
lng: "en",
|
|
27
|
+
interpolation: { escapeValue: false },
|
|
28
|
+
resources: { en: { translation: {} } },
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(cleanup);
|
|
33
|
+
|
|
34
|
+
const probe: FieldOverride = (props) => {
|
|
35
|
+
const name = props.fieldMeta?.name ?? "";
|
|
36
|
+
const value = props.value === undefined || props.value === null ? "" : String(props.value);
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<Text
|
|
40
|
+
testID={`probe-${name}`}
|
|
41
|
+
>{`ro=${!!props.readOnly || !!props.disabled} val=${value}`}</Text>
|
|
42
|
+
<button type="button" data-testid={`set-${name}`} onClick={() => props.onChange("mine")}>
|
|
43
|
+
set
|
|
44
|
+
</button>
|
|
45
|
+
</>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const overrides = { employee_name: probe, serial_no: probe, remarks: probe };
|
|
50
|
+
|
|
51
|
+
function renderDraft(fixtures: InMemoryFixtureProvider) {
|
|
52
|
+
return renderWithProviders(
|
|
53
|
+
<FrappeProvider baseURL="http://test" fixtures={fixtures}>
|
|
54
|
+
<FrappeForm
|
|
55
|
+
doctype="Asset Loan"
|
|
56
|
+
name="LOAN-DRAFT"
|
|
57
|
+
roles={["Loan Manager"]}
|
|
58
|
+
overrides={overrides}
|
|
59
|
+
showSidebar={false}
|
|
60
|
+
showTimeline={false}
|
|
61
|
+
onDelete={() => {}}
|
|
62
|
+
/>
|
|
63
|
+
</FrappeProvider>,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function serverWrites(fixtures: InMemoryFixtureProvider, write: () => Promise<unknown>) {
|
|
68
|
+
await act(async () => {
|
|
69
|
+
await write();
|
|
70
|
+
await getFrappeSyncModule({ fixtures })!.refreshSubscriptions({ doctypes: ["Asset Loan"] });
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function expectProbe(field: string, text: string) {
|
|
75
|
+
await waitFor(() => expect(screen.getByTestId(`probe-${field}`).textContent).toContain(text));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe("a newer server version lands mid-edit (LC-191)", () => {
|
|
79
|
+
it("keeps the edit, names the field by its label and offers Reload and Overwrite", async () => {
|
|
80
|
+
const fixtures = assetLoanLifecycleFixtures();
|
|
81
|
+
renderDraft(fixtures);
|
|
82
|
+
await expectProbe("employee_name", "val=Ada Lovelace");
|
|
83
|
+
fireEvent.click(screen.getByTestId("set-employee_name"));
|
|
84
|
+
await expectProbe("employee_name", "val=mine");
|
|
85
|
+
|
|
86
|
+
await serverWrites(fixtures, () =>
|
|
87
|
+
fixtures.updateDoc("Asset Loan", "LOAN-DRAFT", { employee_name: "Grace Hopper" }),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const banner = await screen.findByTestId("frappe-form-conflict-banner");
|
|
91
|
+
expect(banner.textContent).toContain("Employee Name");
|
|
92
|
+
expect(banner.textContent).not.toContain("employee_name");
|
|
93
|
+
expect(screen.getByTestId("probe-employee_name").textContent).toContain("val=mine");
|
|
94
|
+
expect(banner.querySelector("[role='button'], button")).toBeTruthy();
|
|
95
|
+
expect(screen.getByText("Reload")).toBeTruthy();
|
|
96
|
+
expect(screen.getByText("Overwrite")).toBeTruthy();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("a server change to a field the user left alone lands without a banner", async () => {
|
|
100
|
+
const fixtures = assetLoanLifecycleFixtures();
|
|
101
|
+
renderDraft(fixtures);
|
|
102
|
+
await expectProbe("remarks", "val=");
|
|
103
|
+
fireEvent.click(screen.getByTestId("set-employee_name"));
|
|
104
|
+
|
|
105
|
+
await serverWrites(fixtures, () =>
|
|
106
|
+
fixtures.updateDoc("Asset Loan", "LOAN-DRAFT", { remarks: "from the server" }),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
await expectProbe("remarks", "val=from the server");
|
|
110
|
+
expect(screen.getByTestId("probe-employee_name").textContent).toContain("val=mine");
|
|
111
|
+
expect(screen.queryByTestId("frappe-form-conflict-banner")).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("Reload drops the edit for the server copy", async () => {
|
|
115
|
+
const fixtures = assetLoanLifecycleFixtures();
|
|
116
|
+
renderDraft(fixtures);
|
|
117
|
+
await expectProbe("employee_name", "val=Ada Lovelace");
|
|
118
|
+
fireEvent.click(screen.getByTestId("set-employee_name"));
|
|
119
|
+
await serverWrites(fixtures, () =>
|
|
120
|
+
fixtures.updateDoc("Asset Loan", "LOAN-DRAFT", { employee_name: "Grace Hopper" }),
|
|
121
|
+
);
|
|
122
|
+
await screen.findByTestId("frappe-form-conflict-banner");
|
|
123
|
+
|
|
124
|
+
fireEvent.click(screen.getByText("Reload"));
|
|
125
|
+
|
|
126
|
+
await expectProbe("employee_name", "val=Grace Hopper");
|
|
127
|
+
expect(screen.queryByTestId("frappe-form-conflict-banner")).toBeNull();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("Overwrite writes the edit over the newer server value", async () => {
|
|
131
|
+
const fixtures = assetLoanLifecycleFixtures();
|
|
132
|
+
renderDraft(fixtures);
|
|
133
|
+
await expectProbe("employee_name", "val=Ada Lovelace");
|
|
134
|
+
fireEvent.click(screen.getByTestId("set-employee_name"));
|
|
135
|
+
await serverWrites(fixtures, () =>
|
|
136
|
+
fixtures.updateDoc("Asset Loan", "LOAN-DRAFT", { employee_name: "Grace Hopper" }),
|
|
137
|
+
);
|
|
138
|
+
await screen.findByTestId("frappe-form-conflict-banner");
|
|
139
|
+
|
|
140
|
+
fireEvent.click(screen.getByText("Overwrite"));
|
|
141
|
+
|
|
142
|
+
await waitFor(async () =>
|
|
143
|
+
expect((await fixtures.getDoc("Asset Loan", "LOAN-DRAFT"))?.employee_name).toBe("mine"),
|
|
144
|
+
);
|
|
145
|
+
await waitFor(() => expect(screen.queryByTestId("frappe-form-conflict-banner")).toBeNull());
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe("the record is deleted mid-edit (LC-191)", () => {
|
|
150
|
+
it("stays mounted, turns read-only and says the edits can no longer be saved", async () => {
|
|
151
|
+
const fixtures = assetLoanLifecycleFixtures();
|
|
152
|
+
renderDraft(fixtures);
|
|
153
|
+
await expectProbe("employee_name", "ro=false val=Ada Lovelace");
|
|
154
|
+
fireEvent.click(screen.getByTestId("set-employee_name"));
|
|
155
|
+
|
|
156
|
+
await serverWrites(fixtures, () => fixtures.deleteDoc("Asset Loan", "LOAN-DRAFT"));
|
|
157
|
+
|
|
158
|
+
const banner = await screen.findByTestId("frappe-form-deleted-banner");
|
|
159
|
+
expect(banner.textContent).toContain("deleted on the server");
|
|
160
|
+
expect(banner.textContent).toContain("unsaved edits can no longer be saved");
|
|
161
|
+
await expectProbe("employee_name", "ro=true val=mine");
|
|
162
|
+
expect(screen.queryByTestId("frappe-form-status-notFound")).toBeNull();
|
|
163
|
+
expect(screen.queryByText("Asset Loan not found")).toBeNull();
|
|
164
|
+
expect(screen.queryByTestId("frappe-form-conflict-banner")).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("a clean record says only that it is read-only now", async () => {
|
|
168
|
+
const fixtures = assetLoanLifecycleFixtures();
|
|
169
|
+
renderDraft(fixtures);
|
|
170
|
+
await expectProbe("employee_name", "ro=false val=Ada Lovelace");
|
|
171
|
+
|
|
172
|
+
await serverWrites(fixtures, () => fixtures.deleteDoc("Asset Loan", "LOAN-DRAFT"));
|
|
173
|
+
|
|
174
|
+
const banner = await screen.findByTestId("frappe-form-deleted-banner");
|
|
175
|
+
expect(banner.textContent).toContain("Fields are read-only.");
|
|
176
|
+
expect(banner.textContent).not.toContain("unsaved edits");
|
|
177
|
+
await expectProbe("employee_name", "ro=true val=Ada Lovelace");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { getFrappeSyncModule, type InMemoryFixtureProvider } from "@multiplatform.one/frappe";
|
|
2
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
3
|
+
import { assetLoanLifecycleFixtures } from "../tests/fixtures/assetLoanLifecycle";
|
|
4
|
+
import { FrappeForm } from "./FrappeForm";
|
|
5
|
+
import { FrappeProvider } from "./FrappeProvider";
|
|
6
|
+
import { useFrappeForm } from "./useFrappeForm";
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
title: "FrappeUI/FrappeForm Record Changed",
|
|
10
|
+
component: FrappeForm,
|
|
11
|
+
tags: ["!test"],
|
|
12
|
+
parameters: {
|
|
13
|
+
status: { type: "beta" },
|
|
14
|
+
docs: {
|
|
15
|
+
description: {
|
|
16
|
+
component:
|
|
17
|
+
"LC-191: the open record changes on the server. A newer version that touches a field the reader edited keeps the edit, names the field by its label and offers Reload or Overwrite. A record deleted mid-edit stays on screen, read-only, and says the unsaved edits can no longer be saved. Each story edits Employee Name, then plays the server change on the fixture corpus.",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type Loan = { name: string; doctype: string; employee_name?: string };
|
|
24
|
+
|
|
25
|
+
function Changed({
|
|
26
|
+
fixtures,
|
|
27
|
+
serverChange,
|
|
28
|
+
}: {
|
|
29
|
+
fixtures: InMemoryFixtureProvider;
|
|
30
|
+
serverChange: (fixtures: InMemoryFixtureProvider) => Promise<unknown>;
|
|
31
|
+
}) {
|
|
32
|
+
const form = useFrappeForm<Loan>({
|
|
33
|
+
doctype: "Asset Loan",
|
|
34
|
+
name: "LOAN-DRAFT",
|
|
35
|
+
roles: ["Loan Manager"],
|
|
36
|
+
});
|
|
37
|
+
const played = useRef(false);
|
|
38
|
+
const loaded = form.status === "ready" && form.baseline?.employee_name === "Ada Lovelace";
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!loaded || played.current) return;
|
|
41
|
+
played.current = true;
|
|
42
|
+
form.setValue("employee_name", "Ada King");
|
|
43
|
+
void serverChange(fixtures).then(() =>
|
|
44
|
+
getFrappeSyncModule({ fixtures })?.refreshSubscriptions({ doctypes: ["Asset Loan"] }),
|
|
45
|
+
);
|
|
46
|
+
}, [loaded, form, fixtures, serverChange]);
|
|
47
|
+
return (
|
|
48
|
+
<FrappeForm<Loan>
|
|
49
|
+
form={form}
|
|
50
|
+
doctype="Asset Loan"
|
|
51
|
+
name="LOAN-DRAFT"
|
|
52
|
+
showSidebar={false}
|
|
53
|
+
showTimeline={false}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function Story({
|
|
59
|
+
serverChange,
|
|
60
|
+
}: {
|
|
61
|
+
serverChange: (fixtures: InMemoryFixtureProvider) => Promise<unknown>;
|
|
62
|
+
}) {
|
|
63
|
+
const fixtures = useMemo(() => assetLoanLifecycleFixtures(), []);
|
|
64
|
+
return (
|
|
65
|
+
<FrappeProvider baseURL="https://form-record-changed.mock.test" fixtures={fixtures}>
|
|
66
|
+
<Changed fixtures={fixtures} serverChange={serverChange} />
|
|
67
|
+
</FrappeProvider>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Someone else saved Employee Name while it was being edited. */
|
|
72
|
+
export const ChangedOnServer = () => (
|
|
73
|
+
<Story
|
|
74
|
+
serverChange={(fixtures) =>
|
|
75
|
+
fixtures.updateDoc("Asset Loan", "LOAN-DRAFT", { employee_name: "Grace Hopper" })
|
|
76
|
+
}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
/** The record was deleted while it was open with an unsaved edit. */
|
|
81
|
+
export const DeletedOnServer = () => (
|
|
82
|
+
<Story serverChange={(fixtures) => fixtures.deleteDoc("Asset Loan", "LOAN-DRAFT")} />
|
|
83
|
+
);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LC-194 SUCCESS-IS-A-CALLBACK: a data component reports, the page reacts.
|
|
3
|
+
* FrappeForm completes a create, a save and a delete and hands each result to
|
|
4
|
+
* its callback, with no toast or notify of its own and no navigation, even
|
|
5
|
+
* when the host has a router it could push.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { InMemoryFixtureProvider } from "@multiplatform.one/frappe";
|
|
9
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
10
|
+
import { cleanup, fireEvent, screen, waitFor } from "@testing-library/react";
|
|
11
|
+
import i18next from "i18next";
|
|
12
|
+
import { initReactI18next } from "react-i18next";
|
|
13
|
+
import { Text } from "tamagui";
|
|
14
|
+
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
15
|
+
import { assetLoanLifecycleFixtures } from "../tests/fixtures/assetLoanLifecycle";
|
|
16
|
+
import { FrappeForm } from "./FrappeForm";
|
|
17
|
+
import type { FrappeNavigation } from "./frappeNavigation";
|
|
18
|
+
import { FrappeProvider } from "./FrappeProvider";
|
|
19
|
+
import type { FieldOverride } from "./schemaForm";
|
|
20
|
+
|
|
21
|
+
const feedback = vi.hoisted(() => ({ notify: vi.fn(), showToast: vi.fn() }));
|
|
22
|
+
|
|
23
|
+
vi.mock("@multiplatform.one/components", async (importOriginal) => {
|
|
24
|
+
const actual = await importOriginal<typeof import("@multiplatform.one/components")>();
|
|
25
|
+
return {
|
|
26
|
+
...actual,
|
|
27
|
+
notify: (...args: Parameters<typeof actual.notify>) => {
|
|
28
|
+
feedback.notify(...args);
|
|
29
|
+
return actual.notify(...args);
|
|
30
|
+
},
|
|
31
|
+
showToast: (...args: Parameters<typeof actual.showToast>) => {
|
|
32
|
+
feedback.showToast(...args);
|
|
33
|
+
return actual.showToast(...args);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
beforeAll(async () => {
|
|
39
|
+
await i18next.use(initReactI18next).init({
|
|
40
|
+
lng: "en",
|
|
41
|
+
interpolation: { escapeValue: false },
|
|
42
|
+
resources: { en: { translation: {} } },
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
vi.stubGlobal(
|
|
48
|
+
"confirm",
|
|
49
|
+
vi.fn(() => true),
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
cleanup();
|
|
55
|
+
vi.unstubAllGlobals();
|
|
56
|
+
feedback.notify.mockClear();
|
|
57
|
+
feedback.showToast.mockClear();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const probe: FieldOverride = (props) => {
|
|
61
|
+
const name = props.fieldMeta?.name ?? "";
|
|
62
|
+
const value = props.value === undefined || props.value === null ? "" : String(props.value);
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
<Text testID={`probe-${name}`}>{`val=${value}`}</Text>
|
|
66
|
+
<button type="button" data-testid={`set-${name}`} onClick={() => props.onChange("Edited")}>
|
|
67
|
+
set
|
|
68
|
+
</button>
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const overrides = { employee_name: probe, serial_no: probe, remarks: probe };
|
|
74
|
+
|
|
75
|
+
function renderForm(fixtures: InMemoryFixtureProvider, name?: string) {
|
|
76
|
+
const pushed: string[] = [];
|
|
77
|
+
const navigation: FrappeNavigation = {
|
|
78
|
+
usePathname: () => "/loans",
|
|
79
|
+
useNavigate: () => (href) => pushed.push(href),
|
|
80
|
+
};
|
|
81
|
+
const onSave = vi.fn();
|
|
82
|
+
const onDelete = vi.fn();
|
|
83
|
+
renderWithProviders(
|
|
84
|
+
<FrappeProvider baseURL="http://test" fixtures={fixtures} navigation={navigation}>
|
|
85
|
+
<FrappeForm
|
|
86
|
+
doctype="Asset Loan"
|
|
87
|
+
name={name}
|
|
88
|
+
roles={["Loan Manager"]}
|
|
89
|
+
overrides={overrides}
|
|
90
|
+
showSidebar={false}
|
|
91
|
+
showTimeline={false}
|
|
92
|
+
onSave={onSave}
|
|
93
|
+
onDelete={onDelete}
|
|
94
|
+
/>
|
|
95
|
+
</FrappeProvider>,
|
|
96
|
+
);
|
|
97
|
+
return { pushed, onSave, onDelete };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function press(name: string) {
|
|
101
|
+
const byLabel = document.querySelector(`[aria-label='${name}']`);
|
|
102
|
+
fireEvent.click((byLabel as HTMLElement | null) ?? screen.getByText(name));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function expectNoOwnFeedback(pushed: string[]) {
|
|
106
|
+
expect(feedback.notify).not.toHaveBeenCalled();
|
|
107
|
+
expect(feedback.showToast).not.toHaveBeenCalled();
|
|
108
|
+
expect(pushed).toEqual([]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
describe("FrappeForm reports a mutation through its callback only (LC-194)", () => {
|
|
112
|
+
it("a create hands the new record to onSave", async () => {
|
|
113
|
+
const fixtures = assetLoanLifecycleFixtures();
|
|
114
|
+
const { pushed, onSave } = renderForm(fixtures);
|
|
115
|
+
await screen.findByTestId("probe-employee_name");
|
|
116
|
+
fireEvent.click(screen.getByTestId("set-employee_name"));
|
|
117
|
+
|
|
118
|
+
press("Save");
|
|
119
|
+
|
|
120
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
121
|
+
expect(onSave.mock.calls[0]?.[0]).toMatchObject({ employee_name: "Edited" });
|
|
122
|
+
expect(typeof onSave.mock.calls[0]?.[0].name).toBe("string");
|
|
123
|
+
expectNoOwnFeedback(pushed);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("a save hands the saved record to onSave", async () => {
|
|
127
|
+
const fixtures = assetLoanLifecycleFixtures();
|
|
128
|
+
const { pushed, onSave } = renderForm(fixtures, "LOAN-DRAFT");
|
|
129
|
+
await waitFor(() => expect(screen.getByTestId("probe-remarks").textContent).toBe("val="));
|
|
130
|
+
fireEvent.click(screen.getByTestId("set-remarks"));
|
|
131
|
+
|
|
132
|
+
press("Save");
|
|
133
|
+
|
|
134
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
135
|
+
expect(onSave.mock.calls[0]?.[0]).toMatchObject({ name: "LOAN-DRAFT", remarks: "Edited" });
|
|
136
|
+
expect((await fixtures.getDoc("Asset Loan", "LOAN-DRAFT"))?.remarks).toBe("Edited");
|
|
137
|
+
expectNoOwnFeedback(pushed);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("a delete hands the removed record to onDelete", async () => {
|
|
141
|
+
const fixtures = assetLoanLifecycleFixtures();
|
|
142
|
+
const { pushed, onDelete } = renderForm(fixtures, "LOAN-DRAFT");
|
|
143
|
+
await waitFor(() =>
|
|
144
|
+
expect(screen.getByTestId("probe-employee_name").textContent).toBe("val=Ada Lovelace"),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
press("Delete");
|
|
148
|
+
|
|
149
|
+
await waitFor(() => expect(onDelete).toHaveBeenCalledTimes(1));
|
|
150
|
+
expect(onDelete.mock.calls[0]?.[0]).toMatchObject({ name: "LOAN-DRAFT" });
|
|
151
|
+
expect(await fixtures.getDoc("Asset Loan", "LOAN-DRAFT")).toBeFalsy();
|
|
152
|
+
expectNoOwnFeedback(pushed);
|
|
153
|
+
});
|
|
154
|
+
});
|