@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
- export declare function collectAllFields<TData extends Record<string, unknown>>(layout: FormLayout<TData>): FormField<TData>[];
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.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.14",
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/realtime": "0.2.3"
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
  }