@multiplatform.one/frappe-ui 7.10.0 → 7.15.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/README.md +2 -2
- package/dist/cjs/FrappeDashboard.cjs +8 -10
- package/dist/cjs/FrappeDashboard.native.js +8 -14
- package/dist/cjs/FrappeDashboard.native.js.map +1 -1
- package/dist/cjs/FrappeForm.cjs +465 -175
- package/dist/cjs/FrappeForm.native.js +611 -207
- package/dist/cjs/FrappeForm.native.js.map +1 -1
- package/dist/cjs/FrappeFormConnections.cjs +125 -0
- package/dist/cjs/FrappeFormConnections.native.js +149 -0
- package/dist/cjs/FrappeFormConnections.native.js.map +1 -0
- package/dist/cjs/FrappeImageView.cjs +7 -9
- package/dist/cjs/FrappeImageView.native.js +7 -13
- package/dist/cjs/FrappeImageView.native.js.map +1 -1
- package/dist/cjs/FrappeList.cjs +29 -4
- package/dist/cjs/FrappeList.native.js +37 -5
- package/dist/cjs/FrappeList.native.js.map +1 -1
- package/dist/cjs/FrappeListView.cjs +102 -29
- package/dist/cjs/FrappeListView.native.js +109 -29
- package/dist/cjs/FrappeListView.native.js.map +1 -1
- package/dist/cjs/FrappeProvider.cjs +7 -2
- package/dist/cjs/FrappeProvider.native.js +7 -2
- package/dist/cjs/FrappeProvider.native.js.map +1 -1
- package/dist/cjs/FrappeReportBuilder.cjs +10 -11
- package/dist/cjs/FrappeReportBuilder.native.js +10 -16
- package/dist/cjs/FrappeReportBuilder.native.js.map +1 -1
- package/dist/cjs/FrappeRowsReader.cjs +49 -0
- package/dist/cjs/FrappeRowsReader.native.js +53 -0
- package/dist/cjs/FrappeRowsReader.native.js.map +1 -0
- package/dist/cjs/FrappeTable.cjs +18 -6
- package/dist/cjs/FrappeTable.native.js +24 -6
- package/dist/cjs/FrappeTable.native.js.map +1 -1
- package/dist/cjs/fieldRegistry.cjs +36 -11
- package/dist/cjs/fieldRegistry.native.js +59 -11
- package/dist/cjs/fieldRegistry.native.js.map +1 -1
- package/dist/cjs/fields/Date/index.native.js +5 -2
- package/dist/cjs/fields/Date/index.native.js.map +1 -1
- package/dist/cjs/fields/Date/sheetKeyboard.cjs +2 -2
- package/dist/cjs/fields/Date/sheetKeyboard.native.js +2 -2
- package/dist/cjs/fields/Date/sheetKeyboard.native.js.map +1 -1
- package/dist/cjs/fields/DateRange/index.cjs +2 -1
- package/dist/cjs/fields/DateRange/index.native.js +4 -1
- package/dist/cjs/fields/DateRange/index.native.js.map +1 -1
- package/dist/cjs/formEngine/index.cjs +4 -2
- package/dist/cjs/formEngine/index.native.js +3 -1
- package/dist/cjs/formEngine/index.native.js.map +1 -1
- package/dist/cjs/formEngine/useSessionRoles.cjs +77 -0
- package/dist/cjs/formEngine/useSessionRoles.native.js +84 -0
- package/dist/cjs/formEngine/useSessionRoles.native.js.map +1 -0
- package/dist/cjs/frappeConnections.cjs +41 -0
- package/dist/cjs/frappeConnections.native.js +50 -0
- package/dist/cjs/frappeConnections.native.js.map +1 -0
- package/dist/cjs/frappeNavigation.cjs +107 -0
- package/dist/cjs/frappeNavigation.native.js +112 -0
- package/dist/cjs/frappeNavigation.native.js.map +1 -0
- package/dist/cjs/index.cjs +18 -1
- package/dist/cjs/index.native.js +18 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/schemaForm/index.cjs +4 -1
- package/dist/cjs/schemaForm/index.native.js +4 -1
- package/dist/cjs/schemaForm/index.native.js.map +1 -1
- package/dist/cjs/useConnectionFilters.cjs +146 -0
- package/dist/cjs/useConnectionFilters.native.js +213 -0
- package/dist/cjs/useConnectionFilters.native.js.map +1 -0
- package/dist/cjs/useFrappePagination.cjs +37 -18
- package/dist/cjs/useFrappePagination.native.js +59 -23
- package/dist/cjs/useFrappePagination.native.js.map +1 -1
- package/dist/cjs/useFrappeRows.native.js.map +1 -1
- package/dist/esm/FrappeDashboard.mjs +9 -11
- package/dist/esm/FrappeDashboard.mjs.map +1 -1
- package/dist/esm/FrappeDashboard.native.js +9 -15
- package/dist/esm/FrappeDashboard.native.js.map +1 -1
- package/dist/esm/FrappeForm.mjs +471 -181
- package/dist/esm/FrappeForm.mjs.map +1 -1
- package/dist/esm/FrappeForm.native.js +599 -195
- package/dist/esm/FrappeForm.native.js.map +1 -1
- package/dist/esm/FrappeFormConnections.mjs +99 -0
- package/dist/esm/FrappeFormConnections.mjs.map +1 -0
- package/dist/esm/FrappeFormConnections.native.js +120 -0
- package/dist/esm/FrappeFormConnections.native.js.map +1 -0
- package/dist/esm/FrappeImageView.mjs +8 -10
- package/dist/esm/FrappeImageView.mjs.map +1 -1
- package/dist/esm/FrappeImageView.native.js +8 -14
- package/dist/esm/FrappeImageView.native.js.map +1 -1
- package/dist/esm/FrappeList.mjs +31 -6
- package/dist/esm/FrappeList.mjs.map +1 -1
- package/dist/esm/FrappeList.native.js +39 -7
- package/dist/esm/FrappeList.native.js.map +1 -1
- package/dist/esm/FrappeListView.mjs +103 -30
- package/dist/esm/FrappeListView.mjs.map +1 -1
- package/dist/esm/FrappeListView.native.js +110 -30
- package/dist/esm/FrappeListView.native.js.map +1 -1
- package/dist/esm/FrappeProvider.mjs +7 -2
- package/dist/esm/FrappeProvider.mjs.map +1 -1
- package/dist/esm/FrappeProvider.native.js +7 -2
- package/dist/esm/FrappeProvider.native.js.map +1 -1
- package/dist/esm/FrappeReportBuilder.mjs +11 -12
- package/dist/esm/FrappeReportBuilder.mjs.map +1 -1
- package/dist/esm/FrappeReportBuilder.native.js +11 -17
- package/dist/esm/FrappeReportBuilder.native.js.map +1 -1
- package/dist/esm/FrappeRowsReader.mjs +24 -0
- package/dist/esm/FrappeRowsReader.mjs.map +1 -0
- package/dist/esm/FrappeRowsReader.native.js +25 -0
- package/dist/esm/FrappeRowsReader.native.js.map +1 -0
- package/dist/esm/FrappeTable.mjs +18 -6
- package/dist/esm/FrappeTable.mjs.map +1 -1
- package/dist/esm/FrappeTable.native.js +24 -6
- package/dist/esm/FrappeTable.native.js.map +1 -1
- package/dist/esm/fieldRegistry.mjs +35 -12
- package/dist/esm/fieldRegistry.mjs.map +1 -1
- package/dist/esm/fieldRegistry.native.js +58 -12
- package/dist/esm/fieldRegistry.native.js.map +1 -1
- package/dist/esm/fields/Date/index.native.js +6 -3
- package/dist/esm/fields/Date/index.native.js.map +1 -1
- package/dist/esm/fields/Date/sheetKeyboard.mjs +2 -2
- package/dist/esm/fields/Date/sheetKeyboard.mjs.map +1 -1
- package/dist/esm/fields/Date/sheetKeyboard.native.js +2 -2
- package/dist/esm/fields/Date/sheetKeyboard.native.js.map +1 -1
- package/dist/esm/fields/DateRange/index.mjs +2 -1
- package/dist/esm/fields/DateRange/index.mjs.map +1 -1
- package/dist/esm/fields/DateRange/index.native.js +4 -1
- package/dist/esm/fields/DateRange/index.native.js.map +1 -1
- package/dist/esm/formEngine/index.mjs +2 -1
- package/dist/esm/formEngine/index.mjs.map +1 -1
- package/dist/esm/formEngine/index.native.js +2 -1
- package/dist/esm/formEngine/index.native.js.map +1 -1
- package/dist/esm/formEngine/useSessionRoles.mjs +52 -0
- package/dist/esm/formEngine/useSessionRoles.mjs.map +1 -0
- package/dist/esm/formEngine/useSessionRoles.native.js +56 -0
- package/dist/esm/formEngine/useSessionRoles.native.js.map +1 -0
- package/dist/esm/frappeConnections.mjs +15 -0
- package/dist/esm/frappeConnections.mjs.map +1 -0
- package/dist/esm/frappeConnections.native.js +21 -0
- package/dist/esm/frappeConnections.native.js.map +1 -0
- package/dist/esm/frappeNavigation.mjs +78 -0
- package/dist/esm/frappeNavigation.mjs.map +1 -0
- package/dist/esm/frappeNavigation.native.js +80 -0
- package/dist/esm/frappeNavigation.native.js.map +1 -0
- package/dist/esm/index.mjs +7 -3
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +7 -3
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/schemaForm/index.mjs +4 -1
- package/dist/esm/schemaForm/index.mjs.map +1 -1
- package/dist/esm/schemaForm/index.native.js +4 -1
- package/dist/esm/schemaForm/index.native.js.map +1 -1
- package/dist/esm/useConnectionFilters.mjs +121 -0
- package/dist/esm/useConnectionFilters.mjs.map +1 -0
- package/dist/esm/useConnectionFilters.native.js +185 -0
- package/dist/esm/useConnectionFilters.native.js.map +1 -0
- package/dist/esm/useFrappePagination.mjs +38 -19
- package/dist/esm/useFrappePagination.mjs.map +1 -1
- package/dist/esm/useFrappePagination.native.js +60 -24
- package/dist/esm/useFrappePagination.native.js.map +1 -1
- package/dist/esm/useFrappeRows.mjs.map +1 -1
- package/dist/esm/useFrappeRows.native.js.map +1 -1
- package/dist/jsx/FrappeDashboard.mjs +9 -11
- package/dist/jsx/FrappeDashboard.mjs.map +1 -1
- package/dist/jsx/FrappeDashboard.native.js +8 -14
- package/dist/jsx/FrappeDashboard.native.js.map +1 -1
- package/dist/jsx/FrappeForm.mjs +471 -181
- package/dist/jsx/FrappeForm.mjs.map +1 -1
- package/dist/jsx/FrappeForm.native.js +611 -207
- package/dist/jsx/FrappeForm.native.js.map +1 -1
- package/dist/jsx/FrappeFormConnections.mjs +99 -0
- package/dist/jsx/FrappeFormConnections.mjs.map +1 -0
- package/dist/jsx/FrappeFormConnections.native.js +149 -0
- package/dist/jsx/FrappeFormConnections.native.js.map +1 -0
- package/dist/jsx/FrappeImageView.mjs +8 -10
- package/dist/jsx/FrappeImageView.mjs.map +1 -1
- package/dist/jsx/FrappeImageView.native.js +7 -13
- package/dist/jsx/FrappeImageView.native.js.map +1 -1
- package/dist/jsx/FrappeList.mjs +31 -6
- package/dist/jsx/FrappeList.mjs.map +1 -1
- package/dist/jsx/FrappeList.native.js +37 -5
- package/dist/jsx/FrappeList.native.js.map +1 -1
- package/dist/jsx/FrappeListView.mjs +103 -30
- package/dist/jsx/FrappeListView.mjs.map +1 -1
- package/dist/jsx/FrappeListView.native.js +109 -29
- package/dist/jsx/FrappeListView.native.js.map +1 -1
- package/dist/jsx/FrappeProvider.mjs +7 -2
- package/dist/jsx/FrappeProvider.mjs.map +1 -1
- package/dist/jsx/FrappeProvider.native.js +7 -2
- package/dist/jsx/FrappeProvider.native.js.map +1 -1
- package/dist/jsx/FrappeReportBuilder.mjs +11 -12
- package/dist/jsx/FrappeReportBuilder.mjs.map +1 -1
- package/dist/jsx/FrappeReportBuilder.native.js +10 -16
- package/dist/jsx/FrappeReportBuilder.native.js.map +1 -1
- package/dist/jsx/FrappeRowsReader.mjs +24 -0
- package/dist/jsx/FrappeRowsReader.mjs.map +1 -0
- package/dist/jsx/FrappeRowsReader.native.js +53 -0
- package/dist/jsx/FrappeRowsReader.native.js.map +1 -0
- package/dist/jsx/FrappeTable.mjs +18 -6
- package/dist/jsx/FrappeTable.mjs.map +1 -1
- package/dist/jsx/FrappeTable.native.js +24 -6
- package/dist/jsx/FrappeTable.native.js.map +1 -1
- package/dist/jsx/fieldRegistry.mjs +35 -12
- package/dist/jsx/fieldRegistry.mjs.map +1 -1
- package/dist/jsx/fieldRegistry.native.js +59 -11
- package/dist/jsx/fieldRegistry.native.js.map +1 -1
- package/dist/jsx/fields/Date/index.native.js +5 -2
- package/dist/jsx/fields/Date/index.native.js.map +1 -1
- package/dist/jsx/fields/Date/sheetKeyboard.mjs +2 -2
- package/dist/jsx/fields/Date/sheetKeyboard.mjs.map +1 -1
- package/dist/jsx/fields/Date/sheetKeyboard.native.js +2 -2
- package/dist/jsx/fields/Date/sheetKeyboard.native.js.map +1 -1
- package/dist/jsx/fields/DateRange/index.mjs +2 -1
- package/dist/jsx/fields/DateRange/index.mjs.map +1 -1
- package/dist/jsx/fields/DateRange/index.native.js +4 -1
- package/dist/jsx/fields/DateRange/index.native.js.map +1 -1
- package/dist/jsx/formEngine/index.mjs +2 -1
- package/dist/jsx/formEngine/index.mjs.map +1 -1
- package/dist/jsx/formEngine/index.native.js +3 -1
- package/dist/jsx/formEngine/index.native.js.map +1 -1
- package/dist/jsx/formEngine/useSessionRoles.mjs +52 -0
- package/dist/jsx/formEngine/useSessionRoles.mjs.map +1 -0
- package/dist/jsx/formEngine/useSessionRoles.native.js +84 -0
- package/dist/jsx/formEngine/useSessionRoles.native.js.map +1 -0
- package/dist/jsx/frappeConnections.mjs +15 -0
- package/dist/jsx/frappeConnections.mjs.map +1 -0
- package/dist/jsx/frappeConnections.native.js +50 -0
- package/dist/jsx/frappeConnections.native.js.map +1 -0
- package/dist/jsx/frappeNavigation.mjs +78 -0
- package/dist/jsx/frappeNavigation.mjs.map +1 -0
- package/dist/jsx/frappeNavigation.native.js +112 -0
- package/dist/jsx/frappeNavigation.native.js.map +1 -0
- package/dist/jsx/index.js +7 -3
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +7 -3
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +18 -1
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/schemaForm/index.mjs +4 -1
- package/dist/jsx/schemaForm/index.mjs.map +1 -1
- package/dist/jsx/schemaForm/index.native.js +4 -1
- package/dist/jsx/schemaForm/index.native.js.map +1 -1
- package/dist/jsx/useConnectionFilters.mjs +121 -0
- package/dist/jsx/useConnectionFilters.mjs.map +1 -0
- package/dist/jsx/useConnectionFilters.native.js +213 -0
- package/dist/jsx/useConnectionFilters.native.js.map +1 -0
- package/dist/jsx/useFrappePagination.mjs +38 -19
- package/dist/jsx/useFrappePagination.mjs.map +1 -1
- package/dist/jsx/useFrappePagination.native.js +59 -23
- package/dist/jsx/useFrappePagination.native.js.map +1 -1
- package/dist/jsx/useFrappeRows.mjs.map +1 -1
- package/dist/jsx/useFrappeRows.native.js.map +1 -1
- package/package.json +26 -22
- package/src/FileBrowser/AttachLibrary.stories.tsx +23 -0
- package/src/FrappeDashboard.tsx +11 -16
- package/src/FrappeForm.connections.spec.tsx +280 -0
- package/src/FrappeForm.connections.stories.tsx +93 -0
- package/src/FrappeForm.deskSemantics.spec.tsx +284 -0
- package/src/FrappeForm.tsx +560 -145
- package/src/FrappeFormConnections.tsx +148 -0
- package/src/FrappeGuestMeta.spec.tsx +143 -0
- package/src/FrappeImageView.tsx +8 -12
- package/src/FrappeList.tsx +71 -15
- package/src/FrappeListView.cards.spec.tsx +175 -0
- package/src/FrappeListView.cards.stories.tsx +131 -0
- package/src/FrappeListView.connections.spec.tsx +192 -0
- package/src/FrappeListView.connections.stories.tsx +99 -0
- package/src/FrappeListView.seed.spec.tsx +228 -0
- package/src/FrappeListView.tsx +177 -31
- package/src/FrappeProvider.catchUp.spec.tsx +502 -0
- package/src/FrappeProvider.tsx +15 -3
- package/src/FrappeReportBuilder.tsx +11 -19
- package/src/FrappeRowsReader.tsx +38 -0
- package/src/FrappeTable.optionalProps.spec.tsx +249 -0
- package/src/FrappeTable.spec.tsx +32 -42
- package/src/FrappeTable.stories.spec.tsx +55 -0
- package/src/FrappeTable.stories.tsx +28 -37
- package/src/FrappeTable.tsx +108 -45
- package/src/childTableDependsOn.spec.tsx +85 -0
- package/src/desk/DeskFormSidebar.spec.tsx +20 -0
- package/src/desk/DeskFormSidebar.stories.tsx +42 -0
- package/src/fieldRegistry.spec.tsx +27 -64
- package/src/fieldRegistry.ts +71 -14
- package/src/fields/Date/index.native.tsx +4 -2
- package/src/fields/Date/sheetKeyboard.spec.ts +14 -5
- package/src/fields/Date/sheetKeyboard.ts +12 -4
- package/src/fields/DateRange/DateRange.spec.tsx +67 -0
- package/src/fields/DateRange/index.tsx +5 -1
- package/src/fields/LinkTree.spec.tsx +8 -0
- package/src/fields/LinkTree.stories.tsx +28 -5
- package/src/fields/Select/Select.hydration.spec.tsx +177 -0
- package/src/formEngine/doctypeMeta.ts +15 -114
- package/src/formEngine/index.ts +2 -0
- package/src/formEngine/seam.spec.ts +23 -4
- package/src/formEngine/useSessionRoles.ts +73 -0
- package/src/frappeConnections.ts +50 -0
- package/src/frappeNavigation.tsx +112 -0
- package/src/frappeRoutes.spec.tsx +335 -0
- package/src/index.ts +28 -1
- package/src/schemaForm/index.tsx +1 -0
- package/src/useConnectionFilters.tsx +157 -0
- package/src/useFrappePagination.ts +70 -26
- package/src/useFrappeRows.sharedPath.spec.tsx +206 -0
- package/src/useFrappeRows.ts +5 -4
- package/types/FrappeDashboard.d.ts +3 -4
- package/types/FrappeDashboard.d.ts.map +1 -1
- package/types/FrappeForm.d.ts +57 -5
- package/types/FrappeForm.d.ts.map +1 -1
- package/types/FrappeFormConnections.d.ts +18 -0
- package/types/FrappeFormConnections.d.ts.map +1 -0
- package/types/FrappeImageView.d.ts +2 -3
- package/types/FrappeImageView.d.ts.map +1 -1
- package/types/FrappeList.d.ts +20 -2
- package/types/FrappeList.d.ts.map +1 -1
- package/types/FrappeListView.d.ts +53 -3
- package/types/FrappeListView.d.ts.map +1 -1
- package/types/FrappeProvider.d.ts +10 -3
- package/types/FrappeProvider.d.ts.map +1 -1
- package/types/FrappeReportBuilder.d.ts.map +1 -1
- package/types/FrappeRowsReader.d.ts +26 -0
- package/types/FrappeRowsReader.d.ts.map +1 -0
- package/types/FrappeTable.d.ts +39 -8
- package/types/FrappeTable.d.ts.map +1 -1
- package/types/fieldRegistry.d.ts +17 -3
- package/types/fieldRegistry.d.ts.map +1 -1
- package/types/fields/Date/index.native.d.ts.map +1 -1
- package/types/fields/Date/sheetKeyboard.d.ts +4 -3
- package/types/fields/Date/sheetKeyboard.d.ts.map +1 -1
- package/types/fields/DateRange/index.d.ts.map +1 -1
- package/types/formEngine/doctypeMeta.d.ts +10 -114
- package/types/formEngine/doctypeMeta.d.ts.map +1 -1
- package/types/formEngine/index.d.ts +1 -0
- package/types/formEngine/index.d.ts.map +1 -1
- package/types/formEngine/useSessionRoles.d.ts +19 -0
- package/types/formEngine/useSessionRoles.d.ts.map +1 -0
- package/types/frappeConnections.d.ts +33 -0
- package/types/frappeConnections.d.ts.map +1 -0
- package/types/frappeNavigation.d.ts +49 -0
- package/types/frappeNavigation.d.ts.map +1 -0
- package/types/index.d.ts +7 -3
- package/types/index.d.ts.map +1 -1
- package/types/schemaForm/index.d.ts.map +1 -1
- package/types/useConnectionFilters.d.ts +34 -0
- package/types/useConnectionFilters.d.ts.map +1 -0
- package/types/useFrappePagination.d.ts +18 -5
- package/types/useFrappePagination.d.ts.map +1 -1
- package/types/useFrappeRows.d.ts +5 -4
- package/types/useFrappeRows.d.ts.map +1 -1
- package/src/FileBrowser/AttachLibrary.spec.tsx +0 -74
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { DocTypeMeta } from "@multiplatform.one/frappe";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A doctype whose rows point at one record of another doctype through a Link
|
|
5
|
+
* field: the desk's Connections, one DocType Link row (`link_doctype` +
|
|
6
|
+
* `link_fieldname`). Pokemon's types, abilities and moves are three of them,
|
|
7
|
+
* each a standalone doctype with a `pokemon` Link, not a child table.
|
|
8
|
+
*/
|
|
9
|
+
export interface FrappeConnection {
|
|
10
|
+
/** The linked doctype (DocType Link `link_doctype`). */
|
|
11
|
+
doctype: string;
|
|
12
|
+
/** Its Link field naming the record (DocType Link `link_fieldname`). */
|
|
13
|
+
fieldname: string;
|
|
14
|
+
/** Columns to read from the linked rows; the link field is always read. */
|
|
15
|
+
fields?: readonly string[];
|
|
16
|
+
/** Section heading; defaults to the linked doctype. */
|
|
17
|
+
label?: string;
|
|
18
|
+
/** DocType Link `group`, for desk parity. */
|
|
19
|
+
group?: string;
|
|
20
|
+
/** Row ceiling per record; 0 (the default) reads every linked row. */
|
|
21
|
+
limit?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* `meta.links` as connections. Hidden rows and links that resolve through a
|
|
26
|
+
* child table (`is_child_table`) are left out: neither names the record by a
|
|
27
|
+
* Link field on the linked doctype itself.
|
|
28
|
+
*/
|
|
29
|
+
export function connectionsFromMeta(
|
|
30
|
+
meta: Pick<DocTypeMeta, "links"> | null | undefined,
|
|
31
|
+
): FrappeConnection[] {
|
|
32
|
+
return (meta?.links ?? [])
|
|
33
|
+
.filter(
|
|
34
|
+
(link) => !link.hidden && !link.is_child_table && link.link_doctype && link.link_fieldname,
|
|
35
|
+
)
|
|
36
|
+
.map((link) => ({
|
|
37
|
+
doctype: link.link_doctype,
|
|
38
|
+
fieldname: link.link_fieldname,
|
|
39
|
+
...(link.group ? { group: link.group } : {}),
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The fields a connection read asks for: `name`, the link field, then its
|
|
45
|
+
* own; every field (`*`) when it names none, as a meta.links row does.
|
|
46
|
+
*/
|
|
47
|
+
export function connectionFields(connection: FrappeConnection): string[] {
|
|
48
|
+
if (!connection.fields?.length) return ["*"];
|
|
49
|
+
return Array.from(new Set(["name", connection.fieldname, ...connection.fields]));
|
|
50
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The host router as frappe-ui sees it. frappe-ui imports no router: the
|
|
5
|
+
* host app hands this to `FrappeProvider` once (`@multiplatform.one/core`
|
|
6
|
+
* does it from One's router), and a routed `FrappeListView` or `FrappeForm`
|
|
7
|
+
* (`route` prop) reads its record from the path and moves with it.
|
|
8
|
+
*
|
|
9
|
+
* Both members are hooks, called where the routed view renders, so they see
|
|
10
|
+
* that page's route. A host keeps the same navigation for the life of the
|
|
11
|
+
* tree.
|
|
12
|
+
*/
|
|
13
|
+
export interface FrappeNavigation {
|
|
14
|
+
/** The pathname of the route the caller renders in. */
|
|
15
|
+
usePathname(): string;
|
|
16
|
+
/** A function that pushes an href onto the history. */
|
|
17
|
+
useNavigate(): (href: string) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A routed view's handle on the host router. */
|
|
21
|
+
export interface FrappeRouter {
|
|
22
|
+
pathname: string;
|
|
23
|
+
navigate(href: string): void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const FrappeNavigationContext = createContext<FrappeNavigation | undefined>(undefined);
|
|
27
|
+
|
|
28
|
+
export function FrappeNavigationProvider({
|
|
29
|
+
navigation,
|
|
30
|
+
children,
|
|
31
|
+
}: {
|
|
32
|
+
navigation: FrappeNavigation | undefined;
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
}) {
|
|
35
|
+
const inherited = useContext(FrappeNavigationContext);
|
|
36
|
+
return (
|
|
37
|
+
<FrappeNavigationContext.Provider value={navigation ?? inherited}>
|
|
38
|
+
{children}
|
|
39
|
+
</FrappeNavigationContext.Provider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useFrappeNavigation(): FrappeNavigation | undefined {
|
|
44
|
+
return useContext(FrappeNavigationContext);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The host router for a routed view, or undefined when the host gave none. */
|
|
48
|
+
export function useFrappeRouter(): FrappeRouter | undefined {
|
|
49
|
+
const navigation = useFrappeNavigation();
|
|
50
|
+
if (!navigation) return undefined;
|
|
51
|
+
// oxlint-disable-next-line rules-of-hooks -- one navigation per tree
|
|
52
|
+
const pathname = navigation.usePathname();
|
|
53
|
+
// oxlint-disable-next-line rules-of-hooks -- one navigation per tree
|
|
54
|
+
const navigate = navigation.useNavigate();
|
|
55
|
+
return { pathname, navigate };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* What a path under a doctype's route names, desk style: the list
|
|
60
|
+
* (`/route`), a new record (`/route/new`), a record (`/route/<name>`) or its
|
|
61
|
+
* edit form (`/route/<name>/edit`).
|
|
62
|
+
*/
|
|
63
|
+
export type FrappeRouteTarget =
|
|
64
|
+
| { mode: "list" }
|
|
65
|
+
| { mode: "create" }
|
|
66
|
+
| { mode: "view"; name: string }
|
|
67
|
+
| { mode: "edit"; name: string };
|
|
68
|
+
|
|
69
|
+
function trimPath(path: string): string {
|
|
70
|
+
const bare = path.split(/[?#]/)[0] ?? "";
|
|
71
|
+
return bare.length > 1 ? bare.replace(/\/+$/, "") : bare;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function decodeSegment(segment: string): string {
|
|
75
|
+
try {
|
|
76
|
+
return decodeURIComponent(segment);
|
|
77
|
+
} catch {
|
|
78
|
+
return segment;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function frappeRouteTarget(route: string, pathname: string): FrappeRouteTarget | undefined {
|
|
83
|
+
const base = trimPath(route);
|
|
84
|
+
const path = trimPath(pathname);
|
|
85
|
+
if (path === base) return { mode: "list" };
|
|
86
|
+
if (!path.startsWith(`${base}/`)) return undefined;
|
|
87
|
+
const segments = path.slice(base.length + 1).split("/");
|
|
88
|
+
const [first, second] = segments;
|
|
89
|
+
if (!first) return undefined;
|
|
90
|
+
if (segments.length === 1) {
|
|
91
|
+
return first === "new" ? { mode: "create" } : { mode: "view", name: decodeSegment(first) };
|
|
92
|
+
}
|
|
93
|
+
if (segments.length === 2 && second === "edit") {
|
|
94
|
+
return { mode: "edit", name: decodeSegment(first) };
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The href of a doctype route's list, new record, record or record edit form. */
|
|
100
|
+
export function frappeRouteHref(route: string, target: FrappeRouteTarget): string {
|
|
101
|
+
const base = trimPath(route);
|
|
102
|
+
switch (target.mode) {
|
|
103
|
+
case "list":
|
|
104
|
+
return base;
|
|
105
|
+
case "create":
|
|
106
|
+
return `${base}/new`;
|
|
107
|
+
case "view":
|
|
108
|
+
return `${base}/${encodeURIComponent(target.name)}`;
|
|
109
|
+
case "edit":
|
|
110
|
+
return `${base}/${encodeURIComponent(target.name)}/edit`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
// A doctype's list, record, edit and new pages from a declaration alone
|
|
2
|
+
// (MPO-333): `<FrappeListView doctype route>` and `<FrappeForm doctype
|
|
3
|
+
// route>` read the record from the host router's path, draw the page, and
|
|
4
|
+
// move between the pages on row press, Add, Edit, Save, Cancel and Delete.
|
|
5
|
+
// The bench below is a stock one: getdoctype for meta, /api/resource for
|
|
6
|
+
// reads, the live app's create_doc / update_doc / delete_doc for writes, and
|
|
7
|
+
// a hash name the server picks on create. Poke Move is the doctype.
|
|
8
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
9
|
+
import { cleanup, fireEvent, screen, waitFor } from "@testing-library/react";
|
|
10
|
+
import i18next from "i18next";
|
|
11
|
+
import { initReactI18next } from "react-i18next";
|
|
12
|
+
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
13
|
+
import { FrappeForm } from "./FrappeForm";
|
|
14
|
+
import { FrappeListView } from "./FrappeListView";
|
|
15
|
+
import { frappeRouteHref, frappeRouteTarget, type FrappeNavigation } from "./frappeNavigation";
|
|
16
|
+
import { FrappeProvider } from "./FrappeProvider";
|
|
17
|
+
|
|
18
|
+
const doctype = "Poke Move";
|
|
19
|
+
const route = "/moves";
|
|
20
|
+
|
|
21
|
+
const pokeMoveMeta = {
|
|
22
|
+
name: doctype,
|
|
23
|
+
doctype: "DocType",
|
|
24
|
+
title_field: "move_name",
|
|
25
|
+
fields: [
|
|
26
|
+
{ fieldname: "move_name", fieldtype: "Data", label: "Move Name", reqd: 1 },
|
|
27
|
+
{ fieldname: "power", fieldtype: "Int", label: "Power" },
|
|
28
|
+
{ fieldname: "pp", fieldtype: "Int", label: "PP" },
|
|
29
|
+
{
|
|
30
|
+
fieldname: "damage_class",
|
|
31
|
+
fieldtype: "Select",
|
|
32
|
+
label: "Damage Class",
|
|
33
|
+
options: "\nphysical\nspecial\nstatus",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
permissions: [
|
|
37
|
+
{ role: "System Manager", read: 1, write: 1, create: 1, delete: 1 },
|
|
38
|
+
{ role: "Guest", read: 1 },
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
interface Move {
|
|
43
|
+
name: string;
|
|
44
|
+
doctype: string;
|
|
45
|
+
move_name?: string;
|
|
46
|
+
power?: number;
|
|
47
|
+
pp?: number;
|
|
48
|
+
damage_class?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface Write {
|
|
52
|
+
method: string;
|
|
53
|
+
body: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let run = 0;
|
|
57
|
+
let bench = "";
|
|
58
|
+
let moves = new Map<string, Move>();
|
|
59
|
+
let writes: Write[] = [];
|
|
60
|
+
let guest = false;
|
|
61
|
+
let confirmDelete = vi.fn(() => true);
|
|
62
|
+
|
|
63
|
+
function json(body: unknown, status = 200) {
|
|
64
|
+
return new Response(JSON.stringify(body), {
|
|
65
|
+
status,
|
|
66
|
+
headers: { "content-type": "application/json" },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function matchesNameFilter(row: Move, filters: string | null): boolean {
|
|
71
|
+
if (!filters) return true;
|
|
72
|
+
const parsed = JSON.parse(filters) as unknown;
|
|
73
|
+
const clauses = Array.isArray(parsed)
|
|
74
|
+
? (parsed as Array<[string, string, unknown]>)
|
|
75
|
+
: Object.entries(parsed as Record<string, unknown>).map(([key, value]) => [key, "=", value]);
|
|
76
|
+
return clauses.every(([field, operator, value]) =>
|
|
77
|
+
field === "name" && operator === "=" ? row.name === value : true,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function stubBench() {
|
|
82
|
+
vi.stubGlobal("fetch", async (input: string | URL | Request, init?: RequestInit) => {
|
|
83
|
+
const url = new URL(String(input instanceof Request ? input.url : input));
|
|
84
|
+
const method = url.pathname.replace(/^\/api\/method\//, "");
|
|
85
|
+
if (method === "frappe.desk.form.load.getdoctype") {
|
|
86
|
+
if (guest) return json({ exc_type: "PermissionError" }, 403);
|
|
87
|
+
return json({ docs: [pokeMoveMeta] });
|
|
88
|
+
}
|
|
89
|
+
const resource = `/api/resource/${encodeURIComponent(doctype)}`;
|
|
90
|
+
if (url.pathname === resource) {
|
|
91
|
+
const rows = [...moves.values()].filter((row) =>
|
|
92
|
+
matchesNameFilter(row, url.searchParams.get("filters")),
|
|
93
|
+
);
|
|
94
|
+
return json({ data: rows });
|
|
95
|
+
}
|
|
96
|
+
if (url.pathname.startsWith(`${resource}/`)) {
|
|
97
|
+
const row = moves.get(decodeURIComponent(url.pathname.slice(resource.length + 1)));
|
|
98
|
+
return row ? json({ data: row }) : json({ exc_type: "DoesNotExistError" }, 404);
|
|
99
|
+
}
|
|
100
|
+
if (/^live\.live\.api\.(create|update|delete)_doc$/.test(method)) {
|
|
101
|
+
const body = JSON.parse(String(init?.body ?? "{}")) as Record<string, unknown>;
|
|
102
|
+
writes.push({ method, body });
|
|
103
|
+
if (method === "live.live.api.create_doc") {
|
|
104
|
+
const created = {
|
|
105
|
+
...(body as unknown as Move),
|
|
106
|
+
name: `move-${moves.size + 1}-hash`,
|
|
107
|
+
doctype,
|
|
108
|
+
};
|
|
109
|
+
moves.set(created.name, created);
|
|
110
|
+
return json({ message: created });
|
|
111
|
+
}
|
|
112
|
+
if (method === "live.live.api.update_doc") {
|
|
113
|
+
const name = String(body.name);
|
|
114
|
+
const updated = { ...moves.get(name), ...(body.data as object) } as Move;
|
|
115
|
+
moves.set(name, updated);
|
|
116
|
+
return json({ message: updated });
|
|
117
|
+
}
|
|
118
|
+
if (method === "live.live.api.delete_doc") {
|
|
119
|
+
moves.delete(String(body.name));
|
|
120
|
+
return json({ message: "ok" });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return json({ data: [], message: [] });
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function stubNavigation(pathname: string) {
|
|
128
|
+
const pushed: string[] = [];
|
|
129
|
+
const navigation: FrappeNavigation = {
|
|
130
|
+
usePathname: () => pathname,
|
|
131
|
+
useNavigate: () => (href) => pushed.push(href),
|
|
132
|
+
};
|
|
133
|
+
return { navigation, pushed };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function renderAt(pathname: string, page: "list" | "form") {
|
|
137
|
+
const { navigation, pushed } = stubNavigation(pathname);
|
|
138
|
+
const view = renderWithProviders(
|
|
139
|
+
<FrappeProvider baseURL={bench} realtime={false} navigation={navigation}>
|
|
140
|
+
{page === "list" ? (
|
|
141
|
+
<FrappeListView<Move> doctype={doctype} route={route} />
|
|
142
|
+
) : (
|
|
143
|
+
<FrappeForm<Move> doctype={doctype} route={route} />
|
|
144
|
+
)}
|
|
145
|
+
</FrappeProvider>,
|
|
146
|
+
);
|
|
147
|
+
return { ...view, pushed };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function button(container: HTMLElement, label: string): HTMLElement {
|
|
151
|
+
const found = Array.from(container.querySelectorAll('[role="button"], button')).find(
|
|
152
|
+
(node) => node.textContent?.trim() === label || node.getAttribute("aria-label") === label,
|
|
153
|
+
);
|
|
154
|
+
if (!found) throw new Error(`no ${label} button`);
|
|
155
|
+
return found as HTMLElement;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function input(container: HTMLElement, label: string): HTMLInputElement {
|
|
159
|
+
const field = screen.getByLabelText(label, { exact: false });
|
|
160
|
+
if (field instanceof HTMLInputElement) return field;
|
|
161
|
+
const nested = field.querySelector("input") ?? container.querySelector(`input[name="${label}"]`);
|
|
162
|
+
if (!nested) throw new Error(`no ${label} input`);
|
|
163
|
+
return nested as HTMLInputElement;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
beforeAll(async () => {
|
|
167
|
+
await i18next.use(initReactI18next).init({
|
|
168
|
+
lng: "en",
|
|
169
|
+
interpolation: { escapeValue: false },
|
|
170
|
+
resources: { en: { translation: {} } },
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
beforeEach(() => {
|
|
175
|
+
run += 1;
|
|
176
|
+
bench = `http://routes-${run}.spec.test`;
|
|
177
|
+
guest = false;
|
|
178
|
+
writes = [];
|
|
179
|
+
moves = new Map([
|
|
180
|
+
["a1b2c3", { name: "a1b2c3", doctype, move_name: "tackle", power: 40, pp: 35 }],
|
|
181
|
+
["d4e5f6", { name: "d4e5f6", doctype, move_name: "vine-whip", power: 45, pp: 25 }],
|
|
182
|
+
]);
|
|
183
|
+
stubBench();
|
|
184
|
+
confirmDelete = vi.fn(() => true);
|
|
185
|
+
vi.stubGlobal("confirm", confirmDelete);
|
|
186
|
+
window.confirm = confirmDelete;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
afterEach(() => {
|
|
190
|
+
cleanup();
|
|
191
|
+
vi.restoreAllMocks();
|
|
192
|
+
vi.unstubAllGlobals();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("frappeRouteTarget", () => {
|
|
196
|
+
it("reads the list, a new record, a record and its edit form off the path", () => {
|
|
197
|
+
expect(frappeRouteTarget(route, "/moves")).toEqual({ mode: "list" });
|
|
198
|
+
expect(frappeRouteTarget(route, "/moves/")).toEqual({ mode: "list" });
|
|
199
|
+
expect(frappeRouteTarget(route, "/moves/new")).toEqual({ mode: "create" });
|
|
200
|
+
expect(frappeRouteTarget(route, "/moves/a1b2c3")).toEqual({ mode: "view", name: "a1b2c3" });
|
|
201
|
+
expect(frappeRouteTarget(route, "/moves/Hyper%20Beam/edit")).toEqual({
|
|
202
|
+
mode: "edit",
|
|
203
|
+
name: "Hyper Beam",
|
|
204
|
+
});
|
|
205
|
+
expect(frappeRouteTarget(route, "/pokemon/bulbasaur")).toBeUndefined();
|
|
206
|
+
expect(frappeRouteTarget(route, "/moves/a/b/c")).toBeUndefined();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("writes the href for each page, the record name escaped", () => {
|
|
210
|
+
expect(frappeRouteHref(route, { mode: "list" })).toBe("/moves");
|
|
211
|
+
expect(frappeRouteHref(route, { mode: "create" })).toBe("/moves/new");
|
|
212
|
+
expect(frappeRouteHref(route, { mode: "view", name: "Hyper Beam" })).toBe(
|
|
213
|
+
"/moves/Hyper%20Beam",
|
|
214
|
+
);
|
|
215
|
+
expect(frappeRouteHref(`${route}/`, { mode: "edit", name: "a1b2c3" })).toBe(
|
|
216
|
+
"/moves/a1b2c3/edit",
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe("FrappeListView with a route", () => {
|
|
222
|
+
it("is the doctype's list page: its rows, a row opens the record, Add opens a new one", async () => {
|
|
223
|
+
const { container, pushed } = renderAt("/moves", "list");
|
|
224
|
+
await waitFor(() => expect(screen.getByText("tackle")).toBeTruthy());
|
|
225
|
+
expect(screen.getByText("vine-whip")).toBeTruthy();
|
|
226
|
+
expect(screen.getByRole("heading", { name: doctype })).toBeTruthy();
|
|
227
|
+
|
|
228
|
+
fireEvent.click(screen.getByText("tackle"));
|
|
229
|
+
expect(pushed).toEqual(["/moves/a1b2c3"]);
|
|
230
|
+
|
|
231
|
+
await waitFor(() => expect(button(container, `Add ${doctype}`)).toBeTruthy());
|
|
232
|
+
fireEvent.click(button(container, `Add ${doctype}`));
|
|
233
|
+
expect(pushed).toEqual(["/moves/a1b2c3", "/moves/new"]);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("offers no Add to a reader who cannot load the doctype's meta", async () => {
|
|
237
|
+
guest = true;
|
|
238
|
+
const { container } = renderAt("/moves", "list");
|
|
239
|
+
await waitFor(() => expect(screen.getByText("tackle")).toBeTruthy());
|
|
240
|
+
expect(() => button(container, `Add ${doctype}`)).toThrow();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("FrappeForm with a route", () => {
|
|
245
|
+
it("shows the record read-only on its page, with Edit and Delete", async () => {
|
|
246
|
+
const { container, pushed } = renderAt("/moves/a1b2c3", "form");
|
|
247
|
+
await waitFor(() => expect(input(container, "Move Name").value).toBe("tackle"));
|
|
248
|
+
expect(screen.getByRole("heading", { name: "tackle" })).toBeTruthy();
|
|
249
|
+
expect(input(container, "Move Name").disabled || input(container, "Move Name").readOnly).toBe(
|
|
250
|
+
true,
|
|
251
|
+
);
|
|
252
|
+
expect(() => button(container, "Save")).toThrow();
|
|
253
|
+
|
|
254
|
+
fireEvent.click(button(container, "Edit"));
|
|
255
|
+
expect(pushed).toEqual(["/moves/a1b2c3/edit"]);
|
|
256
|
+
|
|
257
|
+
fireEvent.click(button(container, `Back to ${doctype} list`));
|
|
258
|
+
expect(pushed).toEqual(["/moves/a1b2c3/edit", "/moves"]);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("edits the record through update_doc and returns to it", async () => {
|
|
262
|
+
const { container, pushed } = renderAt("/moves/a1b2c3/edit", "form");
|
|
263
|
+
await waitFor(() => expect(input(container, "Move Name").value).toBe("tackle"));
|
|
264
|
+
fireEvent.change(input(container, "Power"), { target: { value: "50" } });
|
|
265
|
+
fireEvent.click(button(container, "Save"));
|
|
266
|
+
|
|
267
|
+
await waitFor(() => expect(pushed).toEqual(["/moves/a1b2c3"]));
|
|
268
|
+
expect(writes).toEqual([
|
|
269
|
+
{
|
|
270
|
+
method: "live.live.api.update_doc",
|
|
271
|
+
body: { doctype, name: "a1b2c3", data: { power: 50 } },
|
|
272
|
+
},
|
|
273
|
+
]);
|
|
274
|
+
expect(moves.get("a1b2c3")?.power).toBe(50);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("cancels an edit back to the record", async () => {
|
|
278
|
+
const { container, pushed } = renderAt("/moves/a1b2c3/edit", "form");
|
|
279
|
+
await waitFor(() => expect(input(container, "Move Name").value).toBe("tackle"));
|
|
280
|
+
fireEvent.click(button(container, "Cancel"));
|
|
281
|
+
expect(pushed).toEqual(["/moves/a1b2c3"]);
|
|
282
|
+
expect(writes).toEqual([]);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("creates through create_doc and opens the record under the name the server gave it", async () => {
|
|
286
|
+
const { container, pushed } = renderAt("/moves/new", "form");
|
|
287
|
+
await waitFor(() => expect(input(container, "Move Name")).toBeTruthy());
|
|
288
|
+
expect(screen.getByRole("heading", { name: `New ${doctype}` })).toBeTruthy();
|
|
289
|
+
fireEvent.change(input(container, "Move Name"), { target: { value: "hyper-beam" } });
|
|
290
|
+
fireEvent.change(input(container, "Power"), { target: { value: "150" } });
|
|
291
|
+
fireEvent.click(button(container, "Save"));
|
|
292
|
+
|
|
293
|
+
await waitFor(() => expect(pushed).toEqual(["/moves/move-3-hash"]));
|
|
294
|
+
expect(writes).toHaveLength(1);
|
|
295
|
+
expect(writes[0]?.method).toBe("live.live.api.create_doc");
|
|
296
|
+
expect(writes[0]?.body).toMatchObject({ doctype, move_name: "hyper-beam", power: 150 });
|
|
297
|
+
expect(moves.get("move-3-hash")?.move_name).toBe("hyper-beam");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("deletes through delete_doc and returns to the list", async () => {
|
|
301
|
+
const { container, pushed } = renderAt("/moves/d4e5f6", "form");
|
|
302
|
+
await waitFor(() => expect(input(container, "Move Name").value).toBe("vine-whip"));
|
|
303
|
+
fireEvent.click(button(container, "Delete"));
|
|
304
|
+
|
|
305
|
+
await waitFor(() => expect(pushed).toEqual(["/moves"]));
|
|
306
|
+
expect(confirmDelete).toHaveBeenCalledTimes(1);
|
|
307
|
+
expect(writes).toEqual([
|
|
308
|
+
{ method: "live.live.api.delete_doc", body: { doctype, name: "d4e5f6" } },
|
|
309
|
+
]);
|
|
310
|
+
expect(moves.has("d4e5f6")).toBe(false);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("shows a reader without meta the record's own fields, read-only and with no actions", async () => {
|
|
314
|
+
guest = true;
|
|
315
|
+
const { container } = renderAt("/moves/a1b2c3", "form");
|
|
316
|
+
await waitFor(() => expect(input(container, "Move Name").value).toBe("tackle"));
|
|
317
|
+
expect(input(container, "Power").value).toBe("40");
|
|
318
|
+
expect(screen.queryByText(`Failed to load ${doctype} form`)).toBeNull();
|
|
319
|
+
expect(() => button(container, "Edit")).toThrow();
|
|
320
|
+
expect(() => button(container, "Delete")).toThrow();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("draws no field for the collection's own $ keys on a reader's record", async () => {
|
|
324
|
+
guest = true;
|
|
325
|
+
const { container } = renderAt("/moves/a1b2c3", "form");
|
|
326
|
+
await waitFor(() => expect(input(container, "Move Name").value).toBe("tackle"));
|
|
327
|
+
const labels = Array.from(container.querySelectorAll("label"), (node) =>
|
|
328
|
+
node.textContent?.trim(),
|
|
329
|
+
);
|
|
330
|
+
expect(labels.filter((label) => label?.startsWith("$"))).toEqual([]);
|
|
331
|
+
for (const key of ["$origin", "$key", "$collectionId"]) {
|
|
332
|
+
expect(screen.queryByText(key)).toBeNull();
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -24,6 +24,16 @@ export {
|
|
|
24
24
|
type FrappeConfig,
|
|
25
25
|
type FrappeProviderProps,
|
|
26
26
|
} from "./FrappeProvider";
|
|
27
|
+
export {
|
|
28
|
+
FrappeNavigationProvider,
|
|
29
|
+
frappeRouteHref,
|
|
30
|
+
frappeRouteTarget,
|
|
31
|
+
useFrappeNavigation,
|
|
32
|
+
useFrappeRouter,
|
|
33
|
+
type FrappeNavigation,
|
|
34
|
+
type FrappeRouter,
|
|
35
|
+
type FrappeRouteTarget,
|
|
36
|
+
} from "./frappeNavigation";
|
|
27
37
|
export {
|
|
28
38
|
FrappeReconnectIndicator,
|
|
29
39
|
type FrappeReconnectIndicatorProps,
|
|
@@ -43,7 +53,13 @@ export {
|
|
|
43
53
|
|
|
44
54
|
export { FrappeTable, filterValueToFrappeTuple, type FrappeTableProps } from "./FrappeTable";
|
|
45
55
|
export { docFieldsToFilterFields } from "./filterFields";
|
|
46
|
-
export { FrappeForm, type FrappeFormProps } from "./FrappeForm";
|
|
56
|
+
export { FrappeForm, type FrappeConnectionRows, type FrappeFormProps } from "./FrappeForm";
|
|
57
|
+
export {
|
|
58
|
+
FrappeFormConnectionSection,
|
|
59
|
+
FrappeFormConnections,
|
|
60
|
+
type FrappeFormConnectionSectionProps,
|
|
61
|
+
type FrappeFormConnectionsProps,
|
|
62
|
+
} from "./FrappeFormConnections";
|
|
47
63
|
export {
|
|
48
64
|
FormSidebar,
|
|
49
65
|
type FormSidebarProps,
|
|
@@ -53,6 +69,13 @@ export {
|
|
|
53
69
|
} from "./FormSidebar";
|
|
54
70
|
export { FrappeList, type FrappeListProps } from "./FrappeList";
|
|
55
71
|
export { FrappeListView, type FrappeListViewProps, type ListViewMode } from "./FrappeListView";
|
|
72
|
+
export {
|
|
73
|
+
useConnectionFilters,
|
|
74
|
+
type ConnectionFiltersResult,
|
|
75
|
+
type FrappeDoctypeFilter,
|
|
76
|
+
type FrappeFieldFilter,
|
|
77
|
+
type FrappeFilters,
|
|
78
|
+
} from "./useConnectionFilters";
|
|
56
79
|
|
|
57
80
|
export {
|
|
58
81
|
FrappeKanban,
|
|
@@ -206,6 +229,8 @@ export {
|
|
|
206
229
|
getFieldComponent,
|
|
207
230
|
initDefaultRegistry,
|
|
208
231
|
childFieldsToColumns,
|
|
232
|
+
childFieldsToDetailColumns,
|
|
233
|
+
childCellHidden,
|
|
209
234
|
fieldtypeToTableField,
|
|
210
235
|
} from "./fieldRegistry";
|
|
211
236
|
export { FieldOverridesProvider, useFieldResolver } from "./fieldOverrides";
|
|
@@ -437,6 +462,7 @@ export {
|
|
|
437
462
|
setCreatePrefill,
|
|
438
463
|
takeCreatePrefill,
|
|
439
464
|
useFormEngine,
|
|
465
|
+
useSessionRoles,
|
|
440
466
|
} from "./formEngine";
|
|
441
467
|
export type {
|
|
442
468
|
BuildTabsOptions,
|
|
@@ -451,3 +477,4 @@ export type {
|
|
|
451
477
|
LevelPerm,
|
|
452
478
|
PermLevelMatrix,
|
|
453
479
|
} from "./formEngine";
|
|
480
|
+
export { connectionFields, connectionsFromMeta, type FrappeConnection } from "./frappeConnections";
|