@isi-ui7/bos7-shared 0.3.9 → 0.3.11

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.
@@ -0,0 +1,173 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { render, screen, waitFor, fireEvent } from "@testing-library/react";
3
+
4
+ // Dimock supaya mode `edit`/`view` tidak menggantung di InlineLoading: tanpa
5
+ // `loadApiPath` yang selesai, `loading` tidak pernah kembali false dan yang
6
+ // terender hanya spinner — uji akan "lulus" karena stepper-nya memang belum
7
+ // sempat dirender, bukan karena perbaikannya bekerja. Itu lulus palsu.
8
+ vi.mock("./workflow/crud-client", () => ({
9
+ fetchDetailData: vi.fn(async () => ({ f1: "satu", f2: "dua", f3: "tiga" })),
10
+ submitWorkflowForm: vi.fn(async () => "wf-1"),
11
+ }));
12
+
13
+ import { CrudSchemaPage } from "./crud-components";
14
+ import type { CrudForm } from "./form-types";
15
+
16
+ type D = { f1: string; f2: string; f3: string };
17
+
18
+ const schema: CrudForm<D> = {
19
+ title: { create: "Baru", edit: "Ubah", view: "Lihat" },
20
+ emptyData: { f1: "", f2: "", f3: "" },
21
+ createApiPath: "/api/x/create",
22
+ updateApiPath: () => "/api/x/1",
23
+ loadApiPath: () => "/api/x/1",
24
+ layout: {
25
+ type: "wizard",
26
+ pages: [
27
+ { label: "Langkah Satu", sublabel: "Sub Satu", sections: [{ title: "S1", fields: [{ key: "f1", label: "Field Satu", type: "text", span: 4 }] }] },
28
+ { label: "Langkah Dua", sublabel: "Sub Dua", sections: [{ title: "S2", fields: [{ key: "f2", label: "Field Dua", type: "text", span: 4 }] }] },
29
+ { label: "Langkah Tiga", sublabel: "Sub Tiga", sections: [{ title: "S3", fields: [{ key: "f3", label: "Field Tiga", type: "text", span: 4 }] }] },
30
+ ],
31
+ },
32
+ };
33
+
34
+ // Stepper dirender sebagai <nav aria-label=…> — satu-satunya landmark navigasi
35
+ // di halaman ini, jadi role "navigation" cukup untuk membedakannya.
36
+ const stepper = () => screen.queryByRole("navigation");
37
+
38
+ // Pada `view` field dirender sebagai nilai baca-saja, BUKAN input berlabel —
39
+ // jadi getByLabelText hanya bekerja di create/edit. Query teks label bekerja di
40
+ // ketiga mode, sehingga uji ini membandingkan hal yang sama di semua mode.
41
+ const fieldTampil = (nama: string) => screen.queryByText(nama) !== null;
42
+
43
+ /**
44
+ * ADA DI DOM ≠ TERLIHAT.
45
+ *
46
+ * Panel tab yang tidak aktif tetap dirender, hanya `display: none`. Jadi
47
+ * `queryByText` menemukannya, dan uji yang hanya bertanya "ada?" akan HIJAU
48
+ * untuk halaman yang tidak bisa dilihat siapa pun. Helper ini menaiki pohon ke
49
+ * `[role=tabpanel]` terdekat dan membaca `display`-nya, sehingga yang diukur
50
+ * adalah apa yang benar-benar tampil di layar.
51
+ *
52
+ * `null` = teksnya tidak ada sama sekali (beda dari "ada tapi tersembunyi").
53
+ */
54
+ const panelTampil = (nama: string): boolean | null => {
55
+ const el = screen.queryByText(nama);
56
+ if (!el) return null;
57
+ const panel = el.closest('[role="tabpanel"]') as HTMLElement | null;
58
+ if (!panel) return true; // bukan layout bertab — terender langsung
59
+ return panel.style.display !== "none";
60
+ };
61
+
62
+ const tabs = () => screen.queryAllByRole("tab");
63
+
64
+ beforeEach(() => {
65
+ vi.clearAllMocks();
66
+ });
67
+
68
+ describe("navigasi wizard hanya pada mode create", () => {
69
+ it("create — stepper TAMPIL dan hanya halaman aktif yang dirender", async () => {
70
+ render(<CrudSchemaPage schema={schema} mode="create" />);
71
+
72
+ expect(stepper(), "stepper harus ada saat membuat data baru").not.toBeNull();
73
+ expect(screen.getByText("Langkah Tiga")).toBeTruthy(); // ada di stepper…
74
+ expect(fieldTampil("Field Satu")).toBe(true);
75
+ // …tapi field-nya belum, karena halaman 3 belum dibuka.
76
+ expect(fieldTampil("Field Tiga")).toBe(false);
77
+ });
78
+
79
+ it("view — stepper TIDAK ada, diganti TAB, dan tiap halaman terjangkau", async () => {
80
+ render(<CrudSchemaPage schema={schema} mode="view" />);
81
+ await waitFor(() => expect(fieldTampil("Field Satu")).toBe(true));
82
+
83
+ expect(stepper(), "view tidak boleh punya navigasi langkah").toBeNull();
84
+ expect(tabs().length, "wizard di luar create harus jadi tab, bukan datar").toBe(3);
85
+
86
+ // Inti perbaikannya: nol stepper TIDAK boleh berarti isi yang tak
87
+ // terjangkau. Sebelum ini `view` merender satu halaman saja sementara
88
+ // footernya nol tombol Lanjut — langkah 2-5 tidak bisa dibaca sama sekali.
89
+ expect(panelTampil("Field Satu")).toBe(true);
90
+ expect(panelTampil("Field Tiga"), "halaman 3 belum dibuka").toBe(false);
91
+
92
+ fireEvent.click(screen.getByRole("tab", { name: "Langkah Tiga" }));
93
+ expect(panelTampil("Field Tiga"), "klik tab 3 harus menampilkannya").toBe(true);
94
+ expect(panelTampil("Field Satu"), "tab 1 harus ikut tertutup").toBe(false);
95
+ });
96
+
97
+ // Ditambahkan setelah verifikasi adversarial membantah klaim "nol isi yang
98
+ // hilang": versi pertama perbaikan ini me-`flatMap` seluruh section dan
99
+ // membuang `pg.label`/`pg.sublabel` — 10 string konkret lenyap dari layar
100
+ // perorangan, dan uji versi pertama nol memeriksanya karena ia hanya
101
+ // menanyakan field dan stepper.
102
+ it.each(["view", "edit"] as const)(
103
+ "%s — label & sublabel halaman TETAP terbaca sebagai judul kelompok",
104
+ async (mode) => {
105
+ render(<CrudSchemaPage schema={schema} mode={mode} />);
106
+ await waitFor(() => expect(fieldTampil("Field Satu")).toBe(true));
107
+
108
+ expect(stepper(), "tab bukan landmark navigasi").toBeNull();
109
+ // Label halaman kini jadi teks tombol tab.
110
+ for (const l of ["Langkah Satu", "Langkah Dua", "Langkah Tiga"]) {
111
+ expect(
112
+ tabs().some((t) => t.textContent === l),
113
+ `${l} hilang dari ${mode}`,
114
+ ).toBe(true);
115
+ }
116
+ // Sublabel TIDAK muat di tombol tab, jadi ia pindah ke dalam panel.
117
+ // Diperiksa terpisah justru karena itu: tempatnya berbeda dari labelnya,
118
+ // dan tempat yang berbeda adalah tempat yang bisa terlupa.
119
+ for (const [i, sub] of ["Sub Satu", "Sub Dua", "Sub Tiga"].entries()) {
120
+ fireEvent.click(tabs()[i]);
121
+ expect(panelTampil(sub), `sublabel ${sub} hilang dari ${mode}`).toBe(true);
122
+ }
123
+ },
124
+ );
125
+
126
+ it("edit — stepper TIDAK ada, seluruh halaman terbaca, nol tombol Lanjut", async () => {
127
+ render(<CrudSchemaPage schema={schema} mode="edit" />);
128
+ await waitFor(() => expect(fieldTampil("Field Satu")).toBe(true));
129
+
130
+ expect(stepper(), "edit tidak boleh punya navigasi langkah").toBeNull();
131
+ expect(tabs().length).toBe(3);
132
+ fireEvent.click(screen.getByRole("tab", { name: "Langkah Tiga" }));
133
+ expect(panelTampil("Field Tiga")).toBe(true);
134
+
135
+ const tombol = screen.getAllByRole("button").map((b) => b.textContent ?? "");
136
+ expect(tombol.some((t) => /lanjut|next/i.test(t)), `masih ada tombol Lanjut: ${tombol.join("|")}`).toBe(false);
137
+ expect(tombol.some((t) => /kembali|prev/i.test(t)), `masih ada tombol Kembali: ${tombol.join("|")}`).toBe(false);
138
+ });
139
+
140
+ // Kontrol negatif — kini SEMAKIN perlu, bukan semakin tidak: sejak kedua
141
+ // layout memakai perender yang SAMA, cacat di perender itu memukul keduanya
142
+ // sekaligus, dan uji wizard sendirian tidak akan menunjukkan bahwa 4 layar
143
+ // bos7-enterprise/bos7-financing ikut rusak. `tabs` di sini punya 2 halaman
144
+ // (wizard 3) supaya keduanya tidak bisa saling menyamar.
145
+ it("kontrol negatif — layout tabs ikut sehat memakai perender bersama", async () => {
146
+ const skemaTabs: CrudForm<D> = {
147
+ ...schema,
148
+ layout: {
149
+ type: "tabs",
150
+ pages: [
151
+ { label: "Tab Satu", sections: [{ title: "T1", fields: [{ key: "f1", label: "Field Satu", type: "text", span: 4 }] }] },
152
+ { label: "Tab Dua", sections: [{ title: "T2", fields: [{ key: "f2", label: "Field Dua", type: "text", span: 4 }] }] },
153
+ ],
154
+ },
155
+ };
156
+ render(<CrudSchemaPage schema={skemaTabs} mode="view" />);
157
+ await waitFor(() => expect(fieldTampil("Field Satu")).toBe(true));
158
+
159
+ // Tab masih tab: tombol role=tab ada, dan hanya tab aktif yang terlihat.
160
+ expect(tabs().length, "layout tabs kehilangan tabnya").toBe(2);
161
+ expect(stepper()).toBeNull();
162
+ });
163
+
164
+ it("kontrol negatif — layout single-page tidak ikut berubah", async () => {
165
+ const single: CrudForm<D> = {
166
+ ...schema,
167
+ layout: { type: "single-page", sections: [{ title: "S", fields: [{ key: "f1", label: "Field Satu", type: "text", span: 4 }] }] },
168
+ };
169
+ render(<CrudSchemaPage schema={single} mode="view" />);
170
+ await waitFor(() => expect(fieldTampil("Field Satu")).toBe(true));
171
+ expect(stepper()).toBeNull();
172
+ });
173
+ });