@isi-ui7/bos7-shared 0.4.4 → 0.4.5
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.
|
@@ -22,7 +22,28 @@ export declare function runFieldValidation<TData extends Record<string, unknown>
|
|
|
22
22
|
* mungkin jadi medan yatim seperti di atas.
|
|
23
23
|
*/
|
|
24
24
|
export declare function ringkasGalatMedan<TData extends Record<string, unknown>>(fieldErrors: Partial<Record<keyof TData, string>>, fields: FormField<TData>[], maks?: number): string | null;
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Seluruh field yang BOLEH divalidasi — section `hidden` DILEWATI, sama seperti
|
|
27
|
+
* `collectPageFields`.
|
|
28
|
+
*
|
|
29
|
+
* 🔴 Dulu ia nol menyaring apa pun, bahkan `hidden: true` literal. Akibatnya
|
|
30
|
+
* `validateAll` (Save) menuntut field yang aplikasinya sendiri sembunyikan,
|
|
31
|
+
* sementara `validatePage` (Next) melewatinya dengan benar — jadi operator
|
|
32
|
+
* lolos di setiap halaman lalu ditolak di langkah terakhir, untuk field yang
|
|
33
|
+
* nol ada di layar mana pun dan nol bisa diisi gestur apa pun.
|
|
34
|
+
*
|
|
35
|
+
* `mode`/`data` opsional supaya pemanggil lama tetap jalan; tanpa keduanya
|
|
36
|
+
* hanya `hidden: true` literal yang bisa dievaluasi — predikat fungsi
|
|
37
|
+
* diperlakukan TIDAK tersembunyi, arah aman untuk validasi.
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ Konsekuensi yang wajib dijaga di sisi SKEMA: kalau satu key punya dua
|
|
40
|
+
* salinan field (satu tampil di `create`, satu di `edit`), keduanya WAJIB
|
|
41
|
+
* membawa `validation`. Sebelum perbaikan ini, salinan tersembunyi ikut
|
|
42
|
+
* divalidasi sehingga satu salinan bervalidasi terasa cukup — ketergantungan
|
|
43
|
+
* terbalik yang kini nol berlaku lagi. Skema yang bersandar padanya akan
|
|
44
|
+
* kehilangan "wajib diisi" tanpa satu galat pun.
|
|
45
|
+
*/
|
|
46
|
+
export declare function collectAllFields<TData extends Record<string, unknown>>(layout: FormLayout<TData>, mode?: FormMode, data?: TData): FormField<TData>[];
|
|
26
47
|
/**
|
|
27
48
|
* Field yang BOLEH divalidasi pada satu halaman.
|
|
28
49
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isi-ui7/bos7-shared",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Shared auth7 and layout primitives for bos7 applications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"@carbon/icons-react": "^11.71.0",
|
|
72
72
|
"@carbon/react": "^1.97.0",
|
|
73
|
-
"@isi-ui7/corporate-themes": ">=0.2.
|
|
73
|
+
"@isi-ui7/corporate-themes": ">=0.2.15",
|
|
74
74
|
"@isi-ui7/data-table": ">=0.2.5",
|
|
75
75
|
"@isi-ui7/editable-table": ">=0.2.3",
|
|
76
76
|
"@isi-ui7/i18n": ">=0.2.3",
|
|
@@ -106,14 +106,14 @@
|
|
|
106
106
|
"vite": "^5.0.0",
|
|
107
107
|
"vite-plugin-dts": "^3.6.0",
|
|
108
108
|
"vitest": "^1.0.0",
|
|
109
|
+
"@isi-ui7/corporate-themes": "0.2.15",
|
|
109
110
|
"@isi-ui7/data-table": "0.2.5",
|
|
110
111
|
"@isi-ui7/editable-table": "0.2.3",
|
|
111
|
-
"@isi-ui7/i18n": "0.2.3",
|
|
112
|
-
"@isi-ui7/corporate-themes": "0.2.14",
|
|
113
112
|
"@isi-ui7/lookup-input": "0.2.4",
|
|
113
|
+
"@isi-ui7/realtime": "0.2.3",
|
|
114
114
|
"@isi-ui7/modal-manager": "0.2.2",
|
|
115
115
|
"@isi-ui7/ui-shell": "0.2.8",
|
|
116
|
-
"@isi-ui7/
|
|
116
|
+
"@isi-ui7/i18n": "0.2.3"
|
|
117
117
|
},
|
|
118
118
|
"dependencies": {
|
|
119
119
|
"@carbon/icons-react": "^11.71.0",
|
|
@@ -155,13 +155,36 @@ export function ringkasGalatMedan<TData extends Record<string, unknown>>(
|
|
|
155
155
|
|
|
156
156
|
// ── Layout helpers ────────────────────────────────────────────────────────────
|
|
157
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Seluruh field yang BOLEH divalidasi — section `hidden` DILEWATI, sama seperti
|
|
160
|
+
* `collectPageFields`.
|
|
161
|
+
*
|
|
162
|
+
* 🔴 Dulu ia nol menyaring apa pun, bahkan `hidden: true` literal. Akibatnya
|
|
163
|
+
* `validateAll` (Save) menuntut field yang aplikasinya sendiri sembunyikan,
|
|
164
|
+
* sementara `validatePage` (Next) melewatinya dengan benar — jadi operator
|
|
165
|
+
* lolos di setiap halaman lalu ditolak di langkah terakhir, untuk field yang
|
|
166
|
+
* nol ada di layar mana pun dan nol bisa diisi gestur apa pun.
|
|
167
|
+
*
|
|
168
|
+
* `mode`/`data` opsional supaya pemanggil lama tetap jalan; tanpa keduanya
|
|
169
|
+
* hanya `hidden: true` literal yang bisa dievaluasi — predikat fungsi
|
|
170
|
+
* diperlakukan TIDAK tersembunyi, arah aman untuk validasi.
|
|
171
|
+
*
|
|
172
|
+
* ⚠️ Konsekuensi yang wajib dijaga di sisi SKEMA: kalau satu key punya dua
|
|
173
|
+
* salinan field (satu tampil di `create`, satu di `edit`), keduanya WAJIB
|
|
174
|
+
* membawa `validation`. Sebelum perbaikan ini, salinan tersembunyi ikut
|
|
175
|
+
* divalidasi sehingga satu salinan bervalidasi terasa cukup — ketergantungan
|
|
176
|
+
* terbalik yang kini nol berlaku lagi. Skema yang bersandar padanya akan
|
|
177
|
+
* kehilangan "wajib diisi" tanpa satu galat pun.
|
|
178
|
+
*/
|
|
158
179
|
export function collectAllFields<TData extends Record<string, unknown>>(
|
|
159
180
|
layout: FormLayout<TData>,
|
|
181
|
+
mode?: FormMode,
|
|
182
|
+
data?: TData,
|
|
160
183
|
): FormField<TData>[] {
|
|
161
184
|
const sections = layout.type === "single-page"
|
|
162
185
|
? layout.sections
|
|
163
186
|
: layout.pages.flatMap((p) => p.sections);
|
|
164
|
-
return sections.flatMap((s) => s.fields);
|
|
187
|
+
return sections.filter((s) => !isSectionHidden(s, mode, data)).flatMap((s) => s.fields);
|
|
165
188
|
}
|
|
166
189
|
|
|
167
190
|
/**
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { act, renderHook } from "@testing-library/react";
|
|
3
|
+
import { useCrudForm } from "./use-crud-form";
|
|
4
|
+
import type { CrudForm } from "../form-types";
|
|
5
|
+
|
|
6
|
+
type D = Record<string, unknown>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `validateAll` (Save) dulu memakai `collectAllFields` TANPA mode, jadi ia
|
|
10
|
+
* menuntut field di section TERSEMBUNYI — sementara `validatePage` (Next)
|
|
11
|
+
* melewatinya dengan benar. Operator lolos di tiap halaman lalu ditolak di
|
|
12
|
+
* langkah terakhir, untuk field yang nol ada di layarnya.
|
|
13
|
+
*
|
|
14
|
+
* Diuji lewat PERILAKU handleSave, bukan lewat collectAllFields langsung:
|
|
15
|
+
* uji atas helper tetap hijau meski hook nol memakainya.
|
|
16
|
+
*/
|
|
17
|
+
function schema(sembunyi: boolean | ((m: string, d: D) => boolean)): CrudForm<D> {
|
|
18
|
+
return {
|
|
19
|
+
title: { create: "c", edit: "e", view: "v" },
|
|
20
|
+
emptyData: { tampak: "", tersembunyi: "" } as D,
|
|
21
|
+
createApiPath: "/api/x",
|
|
22
|
+
layout: {
|
|
23
|
+
type: "single-page",
|
|
24
|
+
sections: [
|
|
25
|
+
{ title: "tampak", fields: [
|
|
26
|
+
{ key: "tampak", label: "Tampak", type: "text", span: 4, validation: { required: true } }] },
|
|
27
|
+
{ title: "rahasia", hidden: sembunyi as never, fields: [
|
|
28
|
+
{ key: "tersembunyi", label: "Tersembunyi", type: "text", span: 4, validation: { required: true } }] },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
} as unknown as CrudForm<D>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function galat(s: CrudForm<D>, isi: D) {
|
|
35
|
+
const { result } = renderHook(() => useCrudForm<D>({ schema: s, mode: "create" }));
|
|
36
|
+
act(() => { result.current.setForm(isi); });
|
|
37
|
+
await act(async () => { await result.current.handleSave(); });
|
|
38
|
+
return { pesan: String(result.current.error ?? ""), medan: result.current.fieldErrors as Record<string,string> };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("Save nol menuntut field di section tersembunyi", () => {
|
|
42
|
+
it("hidden literal: field tersembunyi NOL memblokir Save", async () => {
|
|
43
|
+
const r = await galat(schema(true), { tampak: "x", tersembunyi: "" });
|
|
44
|
+
expect(r.medan.tersembunyi, "field tersembunyi nol boleh dituntut").toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("hidden predikat: sama", async () => {
|
|
48
|
+
const r = await galat(schema(() => true), { tampak: "x", tersembunyi: "" });
|
|
49
|
+
expect(r.medan.tersembunyi).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("KONTROL POSITIF: section TERLIHAT tetap dituntut", async () => {
|
|
53
|
+
const r = await galat(schema(true), { tampak: "", tersembunyi: "" });
|
|
54
|
+
expect(r.medan.tampak, "penjaga masih menggigit utk yg terlihat").toBeTruthy();
|
|
55
|
+
expect(r.pesan).toContain("Tampak");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("KONTROL POSITIF: section yang TIDAK tersembunyi tetap dituntut", async () => {
|
|
59
|
+
const r = await galat(schema(false), { tampak: "x", tersembunyi: "" });
|
|
60
|
+
expect(r.medan.tersembunyi, "hidden=false ⇒ tetap divalidasi").toBeTruthy();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -109,7 +109,7 @@ export function useCrudForm<TData extends Record<string, unknown>>({
|
|
|
109
109
|
};
|
|
110
110
|
|
|
111
111
|
const validateAll = () => {
|
|
112
|
-
const errs = runFieldValidation(collectAllFields(schema.layout), form, mode, labels);
|
|
112
|
+
const errs = runFieldValidation(collectAllFields(schema.layout, mode, form), form, mode, labels);
|
|
113
113
|
if (schema.validate) {
|
|
114
114
|
return { ...errs, ...schema.validate(form) };
|
|
115
115
|
}
|