@multiplatform.one/frappe 7.11.0 → 7.16.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 +1 -1
- package/dist/cjs/auth.native.js.map +1 -1
- package/dist/cjs/call.native.js.map +1 -1
- package/dist/cjs/collection.native.js.map +1 -1
- package/dist/cjs/common/types.native.js.map +1 -1
- package/dist/cjs/core/http.native.js.map +1 -1
- package/dist/cjs/core/types.native.js.map +1 -1
- package/dist/cjs/core/wire.native.js.map +1 -1
- package/dist/cjs/db.native.js.map +1 -1
- package/dist/cjs/devtools/FrappeDevtoolsPanel.native.js.map +1 -1
- package/dist/cjs/devtools/index.native.js.map +1 -1
- package/dist/cjs/devtools/plugin.native.js.map +1 -1
- package/dist/cjs/devtools/theme.native.js.map +1 -1
- package/dist/cjs/doctype.cjs +25 -43
- package/dist/cjs/doctype.native.js +27 -43
- package/dist/cjs/doctype.native.js.map +1 -1
- package/dist/cjs/file.native.js.map +1 -1
- package/dist/cjs/fixtureDb.native.js.map +1 -1
- package/dist/cjs/fixtures.cjs +3 -0
- package/dist/cjs/fixtures.native.js +3 -0
- package/dist/cjs/fixtures.native.js.map +1 -1
- package/dist/cjs/frappeApp.native.js.map +1 -1
- package/dist/cjs/frappeReact.cjs +13 -1
- package/dist/cjs/frappeReact.native.js +20 -2
- package/dist/cjs/frappeReact.native.js.map +1 -1
- package/dist/cjs/index.cjs +6 -0
- package/dist/cjs/index.native.js +8 -2
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/optimistic.native.js.map +1 -1
- package/dist/cjs/pacer.native.js.map +1 -1
- package/dist/cjs/plugin/index.native.js.map +1 -1
- package/dist/cjs/realtime.native.js.map +1 -1
- package/dist/cjs/serverLoaders.cjs +74 -0
- package/dist/cjs/serverLoaders.native.js +88 -0
- package/dist/cjs/serverLoaders.native.js.map +1 -0
- package/dist/cjs/sync/backfill.native.js.map +1 -1
- package/dist/cjs/sync/filters.native.js.map +1 -1
- package/dist/cjs/sync/index.cjs +8 -0
- package/dist/cjs/sync/index.native.js +10 -0
- package/dist/cjs/sync/index.native.js.map +1 -1
- package/dist/cjs/sync/merge.native.js.map +1 -1
- package/dist/cjs/sync/mutationEngine.native.js.map +1 -1
- package/dist/cjs/sync/mutationJournal.native.js.map +1 -1
- package/dist/cjs/sync/normalizedCache.native.js.map +1 -1
- package/dist/cjs/sync/persistence.native.js.map +1 -1
- package/dist/cjs/sync/realtime.native.js.map +1 -1
- package/dist/cjs/sync/rehydration.cjs +4 -0
- package/dist/cjs/sync/rehydration.native.js +6 -0
- package/dist/cjs/sync/rehydration.native.js.map +1 -1
- package/dist/cjs/sync/storage.native.js.map +1 -1
- package/dist/cjs/sync/subscriptionManager.native.js.map +1 -1
- package/dist/cjs/sync/types.native.js.map +1 -1
- package/dist/cjs/sync/virtualPageManager.native.js.map +1 -1
- package/dist/cjs/syncOptionsKey.native.js.map +1 -1
- package/dist/cjs/useLiveQuery.cjs +2 -20
- package/dist/cjs/useLiveQuery.native.js +1 -22
- package/dist/cjs/useLiveQuery.native.js.map +1 -1
- package/dist/cjs/util.native.js.map +1 -1
- package/dist/esm/doctype.mjs +23 -44
- package/dist/esm/doctype.mjs.map +1 -1
- package/dist/esm/doctype.native.js +25 -44
- package/dist/esm/doctype.native.js.map +1 -1
- package/dist/esm/fixtures.mjs +3 -0
- package/dist/esm/fixtures.mjs.map +1 -1
- package/dist/esm/fixtures.native.js +3 -0
- package/dist/esm/fixtures.native.js.map +1 -1
- package/dist/esm/frappeReact.mjs +13 -1
- package/dist/esm/frappeReact.mjs.map +1 -1
- package/dist/esm/frappeReact.native.js +19 -1
- package/dist/esm/frappeReact.native.js.map +1 -1
- package/dist/esm/index.mjs +3 -2
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +3 -2
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/serverLoaders.mjs +48 -0
- package/dist/esm/serverLoaders.mjs.map +1 -0
- package/dist/esm/serverLoaders.native.js +59 -0
- package/dist/esm/serverLoaders.native.js.map +1 -0
- package/dist/esm/sync/index.mjs +8 -0
- package/dist/esm/sync/index.mjs.map +1 -1
- package/dist/esm/sync/index.native.js +10 -0
- package/dist/esm/sync/index.native.js.map +1 -1
- package/dist/esm/sync/rehydration.mjs +4 -0
- package/dist/esm/sync/rehydration.mjs.map +1 -1
- package/dist/esm/sync/rehydration.native.js +6 -0
- package/dist/esm/sync/rehydration.native.js.map +1 -1
- package/dist/esm/useLiveQuery.mjs +1 -19
- package/dist/esm/useLiveQuery.mjs.map +1 -1
- package/dist/esm/useLiveQuery.native.js +1 -22
- package/dist/esm/useLiveQuery.native.js.map +1 -1
- package/dist/jsx/doctype.mjs +23 -44
- package/dist/jsx/doctype.mjs.map +1 -1
- package/dist/jsx/doctype.native.js +27 -43
- package/dist/jsx/doctype.native.js.map +1 -1
- package/dist/jsx/fixtures.mjs +3 -0
- package/dist/jsx/fixtures.mjs.map +1 -1
- package/dist/jsx/fixtures.native.js +3 -0
- package/dist/jsx/fixtures.native.js.map +1 -1
- package/dist/jsx/frappeReact.mjs +13 -1
- package/dist/jsx/frappeReact.mjs.map +1 -1
- package/dist/jsx/frappeReact.native.js +19 -1
- package/dist/jsx/frappeReact.native.js.map +1 -1
- package/dist/jsx/index.js +3 -2
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +3 -2
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +6 -0
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/serverLoaders.mjs +48 -0
- package/dist/jsx/serverLoaders.mjs.map +1 -0
- package/dist/jsx/serverLoaders.native.js +88 -0
- package/dist/jsx/serverLoaders.native.js.map +1 -0
- package/dist/jsx/sync/index.mjs +8 -0
- package/dist/jsx/sync/index.mjs.map +1 -1
- package/dist/jsx/sync/index.native.js +10 -0
- package/dist/jsx/sync/index.native.js.map +1 -1
- package/dist/jsx/sync/rehydration.mjs +4 -0
- package/dist/jsx/sync/rehydration.mjs.map +1 -1
- package/dist/jsx/sync/rehydration.native.js +6 -0
- package/dist/jsx/sync/rehydration.native.js.map +1 -1
- package/dist/jsx/useLiveQuery.mjs +1 -19
- package/dist/jsx/useLiveQuery.mjs.map +1 -1
- package/dist/jsx/useLiveQuery.native.js +1 -22
- package/dist/jsx/useLiveQuery.native.js.map +1 -1
- package/package.json +9 -9
- package/src/doctype.spec.ts +148 -5
- package/src/doctype.ts +202 -173
- package/src/fixtures.ts +1 -0
- package/src/frappeReact.hydration.spec.tsx +164 -0
- package/src/frappeReact.spec.ts +56 -0
- package/src/frappeReact.ts +31 -5
- package/src/index.ts +11 -0
- package/src/serverLoaders.ts +77 -0
- package/src/sync/index.ts +9 -0
- package/src/sync/rehydration.ts +5 -0
- package/src/sync/syncModule.spec.ts +20 -0
- package/src/useLiveQuery.hydration.spec.tsx +129 -0
- package/src/useLiveQuery.spec.ts +3 -11
- package/src/useLiveQuery.ts +3 -57
- package/types/doctype.d.ts +117 -33
- package/types/doctype.d.ts.map +1 -1
- package/types/fixtures.d.ts.map +1 -1
- package/types/frappeReact.d.ts +3 -2
- package/types/frappeReact.d.ts.map +1 -1
- package/types/index.d.ts +3 -2
- package/types/index.d.ts.map +1 -1
- package/types/serverLoaders.d.ts +40 -0
- package/types/serverLoaders.d.ts.map +1 -0
- package/types/sync/index.d.ts +6 -0
- package/types/sync/index.d.ts.map +1 -1
- package/types/sync/rehydration.d.ts +2 -0
- package/types/sync/rehydration.d.ts.map +1 -1
- package/types/useLiveQuery.d.ts +3 -13
- package/types/useLiveQuery.d.ts.map +1 -1
package/src/doctype.spec.ts
CHANGED
|
@@ -2,8 +2,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
2
2
|
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
3
3
|
import type { DocTypeField, FrappeFieldType } from "./doctype";
|
|
4
4
|
import {
|
|
5
|
+
customFieldTypes,
|
|
6
|
+
frappeFieldTypes,
|
|
5
7
|
parseDependsOn,
|
|
6
8
|
parseFormLayout,
|
|
9
|
+
stockFieldTypes,
|
|
7
10
|
useDocTypeMeta,
|
|
8
11
|
useLinkSearch,
|
|
9
12
|
useLinkTitle,
|
|
@@ -53,6 +56,18 @@ describe("FrappeFieldType", () => {
|
|
|
53
56
|
const action = field("make_payment_entry", "Button", { label: "Make Payment Entry" });
|
|
54
57
|
expect(action.fieldtype).toBe("Button");
|
|
55
58
|
});
|
|
59
|
+
|
|
60
|
+
it("is the one list of fieldtypes: every stock type plus the registry's custom ones", () => {
|
|
61
|
+
expect(frappeFieldTypes).toEqual([...stockFieldTypes, ...customFieldTypes]);
|
|
62
|
+
expect(new Set(frappeFieldTypes).size).toBe(frappeFieldTypes.length);
|
|
63
|
+
expect(stockFieldTypes).toContain("Fold");
|
|
64
|
+
expect(stockFieldTypes).toHaveLength(43);
|
|
65
|
+
const fold: FrappeFieldType = "Fold";
|
|
66
|
+
const toggle: FrappeFieldType = "Toggle Group";
|
|
67
|
+
expect([fold, toggle].every((t) => frappeFieldTypes.includes(t))).toBe(true);
|
|
68
|
+
// @ts-expect-error a fieldtype outside the union is not a DocTypeField
|
|
69
|
+
field("bogus", "Not A Fieldtype");
|
|
70
|
+
});
|
|
56
71
|
});
|
|
57
72
|
|
|
58
73
|
// ---------------------------------------------------------------------------
|
|
@@ -141,7 +156,7 @@ describe("parseFormLayout", () => {
|
|
|
141
156
|
});
|
|
142
157
|
|
|
143
158
|
it("skips hidden fields", () => {
|
|
144
|
-
const fields = [field("visible", "Data"), field("secret", "Data", { hidden:
|
|
159
|
+
const fields = [field("visible", "Data"), field("secret", "Data", { hidden: 1 })];
|
|
145
160
|
const sections = parseFormLayout(fields);
|
|
146
161
|
expect(sections[0].columns[0].fields).toHaveLength(1);
|
|
147
162
|
expect(sections[0].columns[0].fields[0].fieldname).toBe("visible");
|
|
@@ -150,7 +165,7 @@ describe("parseFormLayout", () => {
|
|
|
150
165
|
it("starts a new section on Section Break", () => {
|
|
151
166
|
const fields = [
|
|
152
167
|
field("field1", "Data"),
|
|
153
|
-
field("sec", "Section Break", { label: "Details", collapsible:
|
|
168
|
+
field("sec", "Section Break", { label: "Details", collapsible: 1 }),
|
|
154
169
|
field("field2", "Data"),
|
|
155
170
|
];
|
|
156
171
|
const sections = parseFormLayout(fields);
|
|
@@ -227,7 +242,7 @@ describe("parseFormLayout", () => {
|
|
|
227
242
|
|
|
228
243
|
it("drops a leading section whose fields are all hidden", () => {
|
|
229
244
|
const fields = [
|
|
230
|
-
field("secret", "Data", { hidden:
|
|
245
|
+
field("secret", "Data", { hidden: 1 }),
|
|
231
246
|
field("sec", "Section Break", { label: "Details" }),
|
|
232
247
|
field("field1", "Data"),
|
|
233
248
|
];
|
|
@@ -250,7 +265,7 @@ describe("parseFormLayout", () => {
|
|
|
250
265
|
});
|
|
251
266
|
|
|
252
267
|
it("keeps one section for a fieldless doctype", () => {
|
|
253
|
-
const sections = parseFormLayout([field("secret", "Data", { hidden:
|
|
268
|
+
const sections = parseFormLayout([field("secret", "Data", { hidden: 1 })]);
|
|
254
269
|
expect(sections).toHaveLength(1);
|
|
255
270
|
expect(sections[0].columns[0].fields).toHaveLength(0);
|
|
256
271
|
});
|
|
@@ -295,7 +310,58 @@ describe("useDocTypeMeta", () => {
|
|
|
295
310
|
expect(result.current.meta).toBeDefined();
|
|
296
311
|
expect(result.current.meta!.name).toBe(doctype);
|
|
297
312
|
expect(result.current.meta!.fields).toHaveLength(2);
|
|
298
|
-
expect(result.current.meta!.permissions[0]
|
|
313
|
+
expect(result.current.meta!.permissions?.[0]?.read).toBe(1);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// FrappeForm read this hook's meta, and the old parse copied seventeen keys
|
|
317
|
+
// and dropped the rest, so permlevel, fetch_from, set_only_once and the
|
|
318
|
+
// *_depends_on chains never reached a form. The meta is the server's own
|
|
319
|
+
// now, keys and 0|1 flags alike.
|
|
320
|
+
it("keeps every key the server sends, on the fields and on the meta", async () => {
|
|
321
|
+
const doctype = `DocLossless${testId}`;
|
|
322
|
+
const config = { baseURL: `https://meta-lossless-${testId}.example.com` };
|
|
323
|
+
const raw = {
|
|
324
|
+
...rawDocType(doctype),
|
|
325
|
+
is_submittable: 1,
|
|
326
|
+
links: [{ link_doctype: "Payment Entry", link_fieldname: "reference_name", group: null }],
|
|
327
|
+
fields: [
|
|
328
|
+
{
|
|
329
|
+
fieldname: "customer",
|
|
330
|
+
fieldtype: "Link",
|
|
331
|
+
options: "Customer",
|
|
332
|
+
reqd: 1,
|
|
333
|
+
set_only_once: 1,
|
|
334
|
+
permlevel: 1,
|
|
335
|
+
},
|
|
336
|
+
{ fieldname: "customer_name", fieldtype: "Data", fetch_from: "customer.customer_name" },
|
|
337
|
+
{
|
|
338
|
+
fieldname: "po_no",
|
|
339
|
+
fieldtype: "Data",
|
|
340
|
+
mandatory_depends_on: "eval:doc.is_export",
|
|
341
|
+
read_only_depends_on: "eval:doc.docstatus==1",
|
|
342
|
+
allow_on_submit: 1,
|
|
343
|
+
},
|
|
344
|
+
{ fieldname: "more", fieldtype: "Section Break", collapsible_depends_on: "po_no" },
|
|
345
|
+
],
|
|
346
|
+
};
|
|
347
|
+
mockGetFn.mockResolvedValueOnce({ payload: { docs: [raw] } });
|
|
348
|
+
|
|
349
|
+
const { result } = renderHook(() => useDocTypeMeta(config, doctype));
|
|
350
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
351
|
+
|
|
352
|
+
const meta = result.current.meta!;
|
|
353
|
+
expect(meta.is_submittable).toBe(1);
|
|
354
|
+
expect(meta.links?.[0]?.link_doctype).toBe("Payment Entry");
|
|
355
|
+
const byName = Object.fromEntries(meta.fields.map((f) => [f.fieldname, f]));
|
|
356
|
+
expect(byName.customer).toMatchObject({ reqd: 1, set_only_once: 1, permlevel: 1 });
|
|
357
|
+
expect(byName.customer_name.fetch_from).toBe("customer.customer_name");
|
|
358
|
+
expect(byName.po_no).toMatchObject({
|
|
359
|
+
mandatory_depends_on: "eval:doc.is_export",
|
|
360
|
+
read_only_depends_on: "eval:doc.docstatus==1",
|
|
361
|
+
allow_on_submit: 1,
|
|
362
|
+
});
|
|
363
|
+
expect(byName.more.collapsible_depends_on).toBe("po_no");
|
|
364
|
+
expect(meta.fields[0]).not.toBe(raw.fields[0]);
|
|
299
365
|
});
|
|
300
366
|
|
|
301
367
|
// The REST route reads the DocType *document* — System-Manager-only, so
|
|
@@ -391,6 +457,83 @@ describe("useDocTypeMeta", () => {
|
|
|
391
457
|
expect(result.current.error!.message).toBe("Not found");
|
|
392
458
|
});
|
|
393
459
|
|
|
460
|
+
// MPO-322: getdoctype and the DocType document are closed to Guest, so a
|
|
461
|
+
// signed-out visitor's meta read was a certain 403 in the browser log.
|
|
462
|
+
describe("signed out", () => {
|
|
463
|
+
const signedOut = { useToken: true, type: "Bearer", token: async () => "" };
|
|
464
|
+
|
|
465
|
+
afterEach(() => {
|
|
466
|
+
document.cookie = "user_id=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it("never asks for meta when the token source resolves empty", async () => {
|
|
470
|
+
const doctype = `DocGuest${testId}`;
|
|
471
|
+
const config = { baseURL: `https://meta-guest-${testId}.example.com`, auth: signedOut };
|
|
472
|
+
|
|
473
|
+
const { result } = renderHook(() => useDocTypeMeta(config, doctype));
|
|
474
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
475
|
+
|
|
476
|
+
expect(mockGetFn).not.toHaveBeenCalled();
|
|
477
|
+
expect(result.current.meta).toBeUndefined();
|
|
478
|
+
expect(result.current.error).toMatchObject({ status: 403 });
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("still reads fixture meta", async () => {
|
|
482
|
+
const doctype = `DocGuestFix${testId}`;
|
|
483
|
+
const fixtures = {
|
|
484
|
+
getDoc: vi.fn(async () => rawDocType(doctype)),
|
|
485
|
+
} as unknown as NonNullable<Parameters<typeof useDocTypeMeta>[0]>["fixtures"];
|
|
486
|
+
const config = {
|
|
487
|
+
baseURL: `https://meta-guest-fix-${testId}.example.com`,
|
|
488
|
+
auth: signedOut,
|
|
489
|
+
fixtures,
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const { result } = renderHook(() => useDocTypeMeta(config, doctype));
|
|
493
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
494
|
+
|
|
495
|
+
expect(result.current.meta!.name).toBe(doctype);
|
|
496
|
+
expect(mockGetFn).not.toHaveBeenCalled();
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it("asks when a token resolves", async () => {
|
|
500
|
+
const doctype = `DocSignedIn${testId}`;
|
|
501
|
+
const config = {
|
|
502
|
+
baseURL: `https://meta-signed-in-${testId}.example.com`,
|
|
503
|
+
auth: { ...signedOut, token: async () => "jwt" },
|
|
504
|
+
};
|
|
505
|
+
mockGetFn.mockResolvedValueOnce({ payload: { docs: [rawDocType(doctype)] } });
|
|
506
|
+
|
|
507
|
+
const { result } = renderHook(() => useDocTypeMeta(config, doctype));
|
|
508
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
509
|
+
|
|
510
|
+
expect(result.current.meta!.name).toBe(doctype);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it("asks when a Frappe desk session names a user", async () => {
|
|
514
|
+
document.cookie = "user_id=desk%40example.com; path=/";
|
|
515
|
+
const doctype = `DocDesk${testId}`;
|
|
516
|
+
const config = { baseURL: `https://meta-desk-${testId}.example.com`, auth: signedOut };
|
|
517
|
+
mockGetFn.mockResolvedValueOnce({ payload: { docs: [rawDocType(doctype)] } });
|
|
518
|
+
|
|
519
|
+
const { result } = renderHook(() => useDocTypeMeta(config, doctype));
|
|
520
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
521
|
+
|
|
522
|
+
expect(result.current.meta!.name).toBe(doctype);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("asks on a cookie connection, which names no token source", async () => {
|
|
526
|
+
const doctype = `DocCookie${testId}`;
|
|
527
|
+
const config = { baseURL: `https://meta-cookie-${testId}.example.com` };
|
|
528
|
+
mockGetFn.mockRejectedValueOnce(Object.assign(new Error("Not permitted"), { status: 403 }));
|
|
529
|
+
|
|
530
|
+
const { result } = renderHook(() => useDocTypeMeta(config, doctype));
|
|
531
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
532
|
+
|
|
533
|
+
expect(mockGetFn).toHaveBeenCalledTimes(1);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
394
537
|
it("returns undefined meta and isLoading=false when config is undefined", () => {
|
|
395
538
|
const { result } = renderHook(() => useDocTypeMeta(undefined, "AnyDoc"));
|
|
396
539
|
|