@fogpipe/forma-react 0.12.0 → 0.13.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.
@@ -7,7 +7,7 @@
7
7
  * - Field type rendering
8
8
  */
9
9
 
10
- import { describe, it, expect } from "vitest";
10
+ import { describe, it, expect, vi } from "vitest";
11
11
  import { render, screen, waitFor } from "@testing-library/react";
12
12
  import { userEvent } from "@testing-library/user-event";
13
13
  import { FormRenderer } from "../FormRenderer.js";
@@ -19,6 +19,7 @@ import type {
19
19
  JSONSchemaInteger,
20
20
  } from "@fogpipe/forma-core";
21
21
  import type {
22
+ ArrayComponentProps,
22
23
  ComponentMap,
23
24
  LayoutProps,
24
25
  NumberComponentProps,
@@ -380,6 +381,132 @@ describe("FieldRenderer", () => {
380
381
  });
381
382
  });
382
383
 
384
+ // ============================================================================
385
+ // Array Item Default Values
386
+ // ============================================================================
387
+
388
+ describe("array item default values", () => {
389
+ /**
390
+ * Create a component map where the array add button calls push()
391
+ * without arguments, triggering createDefaultItem() in FieldRenderer.
392
+ */
393
+ function createDefaultItemComponentMap(): ComponentMap {
394
+ const base = createTestComponentMap();
395
+ return {
396
+ ...base,
397
+ array: ({ field: props }: { field: ArrayComponentProps["field"] }) => {
398
+ const { name, field, value, helpers } = props;
399
+ const items = (value || []) as unknown[];
400
+ return (
401
+ <div data-testid={`field-${name}`}>
402
+ <label>{field.label}</label>
403
+ <div>
404
+ {items.map((_, index) => (
405
+ <div key={index} data-testid={`array-item-${name}-${index}`}>
406
+ Item {index}
407
+ </div>
408
+ ))}
409
+ </div>
410
+ <button
411
+ type="button"
412
+ onClick={() => helpers.push()}
413
+ data-testid={`add-${name}`}
414
+ >
415
+ Add
416
+ </button>
417
+ </div>
418
+ );
419
+ },
420
+ };
421
+ }
422
+
423
+ it("should use defaultValue from itemFields when adding array items", async () => {
424
+ const user = userEvent.setup();
425
+ const onChange = vi.fn();
426
+ const spec = createTestSpec({
427
+ fields: {
428
+ items: {
429
+ type: "array",
430
+ label: "Items",
431
+ itemFields: {
432
+ name: { type: "text", defaultValue: "New Item" },
433
+ quantity: { type: "integer", defaultValue: 1 },
434
+ active: { type: "boolean", defaultValue: true },
435
+ },
436
+ },
437
+ },
438
+ });
439
+
440
+ render(
441
+ <FormRenderer
442
+ spec={spec}
443
+ initialData={{ items: [] }}
444
+ components={createDefaultItemComponentMap()}
445
+ onChange={onChange}
446
+ />,
447
+ );
448
+
449
+ const addButton = screen.getByTestId("add-items");
450
+ await user.click(addButton);
451
+
452
+ await waitFor(() => {
453
+ expect(screen.getByTestId("array-item-items-0")).toBeInTheDocument();
454
+ });
455
+
456
+ // Verify onChange was called with default values from itemFields
457
+ const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1];
458
+ const data = lastCall[0];
459
+ expect(data.items).toHaveLength(1);
460
+ expect(data.items[0]).toEqual({
461
+ name: "New Item",
462
+ quantity: 1,
463
+ active: true,
464
+ });
465
+ });
466
+
467
+ it("should fall back to type defaults when itemFields lack defaultValue", async () => {
468
+ const user = userEvent.setup();
469
+ const onChange = vi.fn();
470
+ const spec = createTestSpec({
471
+ fields: {
472
+ items: {
473
+ type: "array",
474
+ label: "Items",
475
+ itemFields: {
476
+ name: { type: "text" },
477
+ count: { type: "number" },
478
+ enabled: { type: "boolean" },
479
+ },
480
+ },
481
+ },
482
+ });
483
+
484
+ render(
485
+ <FormRenderer
486
+ spec={spec}
487
+ initialData={{ items: [] }}
488
+ components={createDefaultItemComponentMap()}
489
+ onChange={onChange}
490
+ />,
491
+ );
492
+
493
+ const addButton = screen.getByTestId("add-items");
494
+ await user.click(addButton);
495
+
496
+ await waitFor(() => {
497
+ expect(screen.getByTestId("array-item-items-0")).toBeInTheDocument();
498
+ });
499
+
500
+ const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1];
501
+ const data = lastCall[0];
502
+ expect(data.items[0]).toEqual({
503
+ name: "",
504
+ count: null,
505
+ enabled: false,
506
+ });
507
+ });
508
+ });
509
+
383
510
  // ============================================================================
384
511
  // FieldRenderer Visibility Wrapper Stability
385
512
  // ============================================================================
@@ -75,6 +75,60 @@ describe("FormRenderer", () => {
75
75
  expect(input).toHaveValue("John Doe");
76
76
  });
77
77
 
78
+ it("should render with defaultValue pre-populated", () => {
79
+ const spec = createTestSpec({
80
+ fields: {
81
+ country: {
82
+ type: "select",
83
+ label: "Country",
84
+ defaultValue: "us",
85
+ options: [
86
+ { value: "us", label: "United States" },
87
+ { value: "ca", label: "Canada" },
88
+ ],
89
+ },
90
+ quantity: {
91
+ type: "number",
92
+ label: "Quantity",
93
+ defaultValue: 5,
94
+ },
95
+ },
96
+ });
97
+
98
+ render(
99
+ <FormRenderer spec={spec} components={createTestComponentMap()} />,
100
+ );
101
+
102
+ const select = screen.getByRole("combobox");
103
+ expect(select).toHaveValue("us");
104
+
105
+ const numberInput = screen.getByRole("spinbutton");
106
+ expect(numberInput).toHaveValue(5);
107
+ });
108
+
109
+ it("should let initialData override defaultValue in rendered output", () => {
110
+ const spec = createTestSpec({
111
+ fields: {
112
+ name: {
113
+ type: "text",
114
+ label: "Name",
115
+ defaultValue: "Default Name",
116
+ },
117
+ },
118
+ });
119
+
120
+ render(
121
+ <FormRenderer
122
+ spec={spec}
123
+ initialData={{ name: "Override" }}
124
+ components={createTestComponentMap()}
125
+ />,
126
+ );
127
+
128
+ const input = screen.getByRole("textbox");
129
+ expect(input).toHaveValue("Override");
130
+ });
131
+
78
132
  it("should use custom layout component", () => {
79
133
  const spec = createTestSpec({
80
134
  fields: { name: { type: "text" } },