@truedat/df 8.13.5 → 8.13.7

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.
Files changed (39) hide show
  1. package/package.json +4 -4
  2. package/src/components/DynamicForm.js +3 -0
  3. package/src/components/DynamicFormWithTranslations.js +3 -0
  4. package/src/components/FieldGroupSegment.js +3 -0
  5. package/src/components/FieldGroupWithTranslations.js +13 -0
  6. package/src/components/SelectDynamicFormWithTranslations.js +7 -1
  7. package/src/components/SelectableDynamicForm.js +7 -1
  8. package/src/components/__tests__/__snapshots__/SelectableDynamicForm.spec.js.snap +18 -1
  9. package/src/components/__tests__/originalContentFlow.spec.js +131 -0
  10. package/src/components/widgets/DateField.js +18 -9
  11. package/src/components/widgets/DateTimeField.js +26 -9
  12. package/src/components/widgets/DynamicField.js +54 -1
  13. package/src/components/widgets/DynamicTableField.js +41 -7
  14. package/src/components/widgets/FieldByWidget.js +36 -6
  15. package/src/components/widgets/__tests__/dateBoundsByWidget.spec.js +133 -0
  16. package/src/components/widgets/__tests__/dateBoundsEndToEnd.spec.js +83 -0
  17. package/src/components/widgets/__tests__/datePickerBoundaries.spec.js +124 -0
  18. package/src/components/widgets/__tests__/dateValueValidation.spec.js +215 -0
  19. package/src/components/widgets/__tests__/existingDateRestrictions.spec.js +354 -0
  20. package/src/templates/components/templateForm/DateRestrictionsForm.js +217 -0
  21. package/src/templates/components/templateForm/DefaultValue.js +79 -11
  22. package/src/templates/components/templateForm/FieldDefinition.js +15 -1
  23. package/src/templates/components/templateForm/TemplateForm.js +5 -0
  24. package/src/templates/components/templateForm/ValuesField.js +7 -0
  25. package/src/templates/components/templateForm/__tests__/FieldDefinition.spec.js +48 -0
  26. package/src/templates/components/templateForm/__tests__/TemplateForm.spec.js +107 -0
  27. package/src/templates/components/templateForm/__tests__/dateRestrictionsForm.spec.js +429 -0
  28. package/src/templates/components/templateForm/__tests__/dateRestrictionsValidation.spec.js +166 -0
  29. package/src/templates/components/templateForm/__tests__/defaultValueBounds.spec.js +191 -0
  30. package/src/templates/components/templateForm/__tests__/defaultValueRestrictions.spec.js +175 -0
  31. package/src/templates/components/templateForm/contentValidation.js +184 -0
  32. package/src/templates/utils/__tests__/dateRestrictions.spec.js +578 -0
  33. package/src/templates/utils/__tests__/originalTableRows.spec.js +129 -0
  34. package/src/templates/utils/__tests__/validateContentDateRestrictions.spec.js +519 -0
  35. package/src/templates/utils/applyTemplate.js +27 -20
  36. package/src/templates/utils/dateRestrictions.js +497 -0
  37. package/src/templates/utils/dateValueUnchanged.js +12 -0
  38. package/src/templates/utils/originalTableRows.js +41 -0
  39. package/src/templates/utils/validateContent.js +75 -4
@@ -1,4 +1,9 @@
1
1
  import PropTypes from "prop-types";
2
+ import { dateValueUnchanged } from "../../templates/utils/dateValueUnchanged";
3
+ import {
4
+ publishedBounds,
5
+ resolveRestrictions,
6
+ } from "../../templates/utils/dateRestrictions";
2
7
  import DateTimeField from "./DateTimeField";
3
8
  import CheckboxField from "./CheckboxField";
4
9
  import ColorPickerField from "./ColorPickerField";
@@ -16,7 +21,16 @@ import TextField from "./TextField";
16
21
  import NumberField from "./NumberField";
17
22
  import DateField from "./DateField";
18
23
 
19
- const FieldByWidget = ({ scope, widget, onChange, field }) => {
24
+ const boundsFor = (field, originalField) => {
25
+ const unchanged = dateValueUnchanged(field.value, originalField);
26
+ const bounds = unchanged
27
+ ? {}
28
+ : publishedBounds(field?.restrictions) ||
29
+ resolveRestrictions(field?.restrictions, field?.type);
30
+ return { min: bounds.min, max: bounds.max };
31
+ };
32
+
33
+ const FieldByWidget = ({ scope, widget, onChange, field, originalField }) => {
20
34
  switch (widget) {
21
35
  case "identifier":
22
36
  return <IdentifierField field={field} />;
@@ -37,17 +51,32 @@ const FieldByWidget = ({ scope, widget, onChange, field }) => {
37
51
  case "table":
38
52
  return <TableField field={field} onChange={onChange} />;
39
53
  case "dynamic_table":
40
- return <DynamicTableField field={field} onChange={onChange} />;
54
+ return (
55
+ <DynamicTableField
56
+ field={field}
57
+ originalField={originalField}
58
+ onChange={onChange}
59
+ scope={scope}
60
+ />
61
+ );
41
62
  case "password":
42
63
  return <PasswordField field={field} onChange={onChange} />;
43
64
  case "image":
44
65
  return <ImageField field={field} onChange={onChange} />;
45
66
  case "number":
46
67
  return <NumberField field={field} onChange={onChange} />;
47
- case "date":
48
- return <DateField field={field} onChange={onChange} />;
49
- case "datetime":
50
- return <DateTimeField field={field} onChange={onChange} />;
68
+ case "date": {
69
+ const { min, max } = boundsFor(field, originalField);
70
+ return (
71
+ <DateField field={field} onChange={onChange} min={min} max={max} />
72
+ );
73
+ }
74
+ case "datetime": {
75
+ const { min, max } = boundsFor(field, originalField);
76
+ return (
77
+ <DateTimeField field={field} onChange={onChange} min={min} max={max} />
78
+ );
79
+ }
51
80
  default:
52
81
  return <StringField field={field} onChange={onChange} />;
53
82
  }
@@ -56,6 +85,7 @@ const FieldByWidget = ({ scope, widget, onChange, field }) => {
56
85
  FieldByWidget.propTypes = {
57
86
  onChange: PropTypes.func.isRequired,
58
87
  field: PropTypes.object.isRequired,
88
+ originalField: PropTypes.object,
59
89
  scope: PropTypes.string,
60
90
  widget: PropTypes.string,
61
91
  };
@@ -0,0 +1,133 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { IntlProvider } from "react-intl";
3
+ import FieldByWidget from "../FieldByWidget";
4
+
5
+ const renderWith = (ui) =>
6
+ render(<IntlProvider locale="en">{ui}</IntlProvider>);
7
+
8
+ const fieldWith = (restrictions, extra = {}) => ({
9
+ name: "fecha",
10
+ value: "2026-06-15",
11
+ type: "date",
12
+ ...extra,
13
+ restrictions,
14
+ });
15
+
16
+ describe("FieldByWidget passes the published bounds to date widgets", () => {
17
+ describe("a date field", () => {
18
+ it("reads min and max out of resolved", () => {
19
+ renderWith(
20
+ <FieldByWidget
21
+ widget="date"
22
+ onChange={() => {}}
23
+ field={fieldWith({
24
+ between: {
25
+ mode: "static",
26
+ start: "2026-01-01",
27
+ end: "2026-12-31",
28
+ },
29
+ resolved: { min: "2026-01-01", max: "2026-12-31" },
30
+ })}
31
+ />,
32
+ );
33
+
34
+ const input = screen.getByPlaceholderText("Date");
35
+ expect(input).toHaveAttribute("min", "2026-01-01");
36
+ expect(input).toHaveAttribute("max", "2026-12-31");
37
+ });
38
+
39
+ it("resolves declared bounds when the serving endpoint did not publish them", () => {
40
+ renderWith(
41
+ <FieldByWidget
42
+ widget="date"
43
+ onChange={() => {}}
44
+ field={fieldWith({
45
+ between: {
46
+ mode: "static",
47
+ start: "2026-01-01",
48
+ end: "2026-12-31",
49
+ },
50
+ })}
51
+ />,
52
+ );
53
+
54
+ const input = screen.getByPlaceholderText("Date");
55
+ expect(input).toHaveAttribute("min", "2026-01-01");
56
+ expect(input).toHaveAttribute("max", "2026-12-31");
57
+ });
58
+
59
+ it("leaves the picker unrestricted when there are no restrictions at all", () => {
60
+ renderWith(
61
+ <FieldByWidget
62
+ widget="date"
63
+ onChange={() => {}}
64
+ field={fieldWith(undefined)}
65
+ />,
66
+ );
67
+
68
+ const input = screen.getByPlaceholderText("Date");
69
+ expect(input).not.toHaveAttribute("min");
70
+ expect(input).not.toHaveAttribute("max");
71
+ });
72
+
73
+ it("an open bound stays open", () => {
74
+ renderWith(
75
+ <FieldByWidget
76
+ widget="date"
77
+ onChange={() => {}}
78
+ field={fieldWith({
79
+ max_date: { mode: "static", value: "2026-12-31" },
80
+ resolved: { min: null, max: "2026-12-31" },
81
+ })}
82
+ />,
83
+ );
84
+
85
+ const input = screen.getByPlaceholderText("Date");
86
+ expect(input).toHaveAttribute("max", "2026-12-31");
87
+ expect(input).not.toHaveAttribute("min");
88
+ });
89
+ });
90
+
91
+ describe("a datetime field", () => {
92
+ it("passes the published minute-precision bounds", () => {
93
+ renderWith(
94
+ <FieldByWidget
95
+ widget="datetime"
96
+ onChange={() => {}}
97
+ field={fieldWith(
98
+ {
99
+ max_date: { mode: "static", value: "2026-06-30T10:00:00" },
100
+ resolved: { min: null, max: "2026-06-30T10:00" },
101
+ },
102
+ { name: "momento", type: "datetime", value: "2026-06-15T10:30" },
103
+ )}
104
+ />,
105
+ );
106
+
107
+ const input = screen.getByPlaceholderText("Date Time");
108
+ expect(input).toHaveAttribute("max", "2026-06-30T10:00");
109
+ expect(input).not.toHaveAttribute("min");
110
+ });
111
+ });
112
+
113
+ describe("fields that are not dates", () => {
114
+ it("a string field with restrictions is rendered unchanged", () => {
115
+ renderWith(
116
+ <FieldByWidget
117
+ widget="string"
118
+ onChange={() => {}}
119
+ field={{
120
+ name: "s",
121
+ value: "hola",
122
+ type: "string",
123
+ restrictions: { resolved: { min: "a", max: "b" } },
124
+ }}
125
+ />,
126
+ );
127
+
128
+ const input = screen.getByDisplayValue("hola");
129
+ expect(input).not.toHaveAttribute("min");
130
+ expect(input).not.toHaveAttribute("max");
131
+ });
132
+ });
133
+ });
@@ -0,0 +1,83 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { IntlProvider } from "react-intl";
3
+ import DynamicField from "../DynamicField";
4
+
5
+ const templateField = (extra = {}) => ({
6
+ name: "fecha",
7
+ label: "Fecha",
8
+ type: "date",
9
+ widget: "date",
10
+ cardinality: "?",
11
+ values: null,
12
+ restrictions: {
13
+ between: { mode: "static", start: "2026-01-01", end: "2026-12-31" },
14
+ resolved: { min: "2026-01-01", max: "2026-12-31" },
15
+ },
16
+ default: { value: "", origin: "default" },
17
+ ...extra,
18
+ });
19
+
20
+ const renderField = (field) =>
21
+ render(
22
+ <IntlProvider locale="en">
23
+ <DynamicField field={field} onChange={() => {}} />
24
+ </IntlProvider>,
25
+ );
26
+
27
+ describe("the published bounds reach the calendar", () => {
28
+ it("a date field is restricted by the resolved block", () => {
29
+ renderField({
30
+ ...templateField(),
31
+ value: { value: "2026-06-15", origin: "user" },
32
+ });
33
+
34
+ const input = screen.getByPlaceholderText("Date");
35
+ expect(input).toHaveAttribute("min", "2026-01-01");
36
+ expect(input).toHaveAttribute("max", "2026-12-31");
37
+ });
38
+
39
+ it("a datetime field is restricted by the resolved block", () => {
40
+ renderField({
41
+ ...templateField(
42
+ {
43
+ name: "momento",
44
+ type: "datetime",
45
+ widget: "datetime",
46
+ restrictions: {
47
+ max_date: { mode: "static", value: "2026-06-30T10:00:00" },
48
+ resolved: { min: null, max: "2026-06-30T10:00" },
49
+ },
50
+ },
51
+ {},
52
+ ),
53
+ value: { value: "2026-06-15T10:30", origin: "user" },
54
+ });
55
+
56
+ const input = screen.getByPlaceholderText("Date Time");
57
+ expect(input).toHaveAttribute("max", "2026-06-30T10:00");
58
+ expect(input).not.toHaveAttribute("min");
59
+ });
60
+
61
+ it("a template predating the feature is left unrestricted", () => {
62
+ renderField({
63
+ ...templateField({ restrictions: undefined }),
64
+ value: { value: "1999-01-01", origin: "user" },
65
+ });
66
+
67
+ const input = screen.getByPlaceholderText("Date");
68
+ expect(input).not.toHaveAttribute("min");
69
+ expect(input).not.toHaveAttribute("max");
70
+ expect(input).toHaveValue("1999-01-01");
71
+ });
72
+
73
+ it("a stored value already outside the range is still shown, not silently dropped", () => {
74
+ renderField({
75
+ ...templateField(),
76
+ value: { value: "1999-01-01", origin: "user" },
77
+ });
78
+
79
+ const input = screen.getByPlaceholderText("Date");
80
+ expect(input).toHaveValue("1999-01-01");
81
+ expect(input).toHaveAttribute("max", "2026-12-31");
82
+ });
83
+ });
@@ -0,0 +1,124 @@
1
+ import { render, screen, fireEvent } from "@testing-library/react";
2
+ import { IntlProvider } from "react-intl";
3
+ import { DateField } from "../DateField";
4
+ import { DateTimeField } from "../DateTimeField";
5
+
6
+ const renderWith = (ui) =>
7
+ render(<IntlProvider locale="en">{ui}</IntlProvider>);
8
+
9
+ describe("date widgets expose the published bounds to the native input", () => {
10
+ describe("DateField", () => {
11
+ it("passes min and max through", () => {
12
+ renderWith(
13
+ <DateField
14
+ field={{ name: "fecha", value: "2026-06-15" }}
15
+ min="2026-01-01"
16
+ max="2026-12-31"
17
+ onChange={() => {}}
18
+ />,
19
+ );
20
+
21
+ const input = screen.getByPlaceholderText("Date");
22
+ expect(input).toHaveAttribute("type", "date");
23
+ expect(input).toHaveAttribute("min", "2026-01-01");
24
+ expect(input).toHaveAttribute("max", "2026-12-31");
25
+ });
26
+
27
+ it("applies a one-sided restriction", () => {
28
+ renderWith(
29
+ <DateField
30
+ field={{ name: "fecha", value: "" }}
31
+ max="2026-12-31"
32
+ onChange={() => {}}
33
+ />,
34
+ );
35
+
36
+ const input = screen.getByPlaceholderText("Date");
37
+ expect(input).toHaveAttribute("max", "2026-12-31");
38
+ expect(input).not.toHaveAttribute("min");
39
+ });
40
+
41
+ it("renders no bounds attribute when there is no restriction", () => {
42
+ renderWith(
43
+ <DateField field={{ name: "fecha", value: "" }} onChange={() => {}} />,
44
+ );
45
+
46
+ const input = screen.getByPlaceholderText("Date");
47
+ expect(input).not.toHaveAttribute("min");
48
+ expect(input).not.toHaveAttribute("max");
49
+ });
50
+
51
+ it("still reports what the user typed", () => {
52
+ const onChange = jest.fn();
53
+ renderWith(
54
+ <DateField
55
+ field={{ name: "fecha", value: "" }}
56
+ max="2026-12-31"
57
+ onChange={onChange}
58
+ />,
59
+ );
60
+
61
+ fireEvent.change(screen.getByPlaceholderText("Date"), {
62
+ target: { value: "2026-03-01" },
63
+ });
64
+
65
+ expect(onChange).toHaveBeenCalledWith(
66
+ expect.anything(),
67
+ expect.objectContaining({ name: "fecha", value: "2026-03-01" }),
68
+ );
69
+ });
70
+
71
+ it("lets the user clear the field on purpose", () => {
72
+ const onChange = jest.fn();
73
+ renderWith(
74
+ <DateField
75
+ field={{ name: "fecha", value: "2026-03-01" }}
76
+ max="2026-12-31"
77
+ onChange={onChange}
78
+ />,
79
+ );
80
+
81
+ fireEvent.change(screen.getByPlaceholderText("Date"), {
82
+ target: { value: "" },
83
+ });
84
+
85
+ expect(onChange).toHaveBeenCalledWith(
86
+ expect.anything(),
87
+ expect.objectContaining({ name: "fecha", value: "" }),
88
+ );
89
+ });
90
+ });
91
+
92
+ describe("DateTimeField", () => {
93
+ it("passes the minute-precision bounds through", () => {
94
+ renderWith(
95
+ <DateTimeField
96
+ field={{ name: "momento", value: "2026-06-15T10:30" }}
97
+ min="2026-06-01T00:00"
98
+ max="2026-06-30T23:59"
99
+ onChange={() => {}}
100
+ />,
101
+ );
102
+
103
+ const input = screen.getByPlaceholderText("Date Time");
104
+ expect(input).toHaveAttribute("type", "datetime-local");
105
+ expect(input).toHaveAttribute("min", "2026-06-01T00:00");
106
+ expect(input).toHaveAttribute("max", "2026-06-30T23:59");
107
+ });
108
+
109
+ it("a date-only bound reaches the input as minute precision, not dropped", () => {
110
+ renderWith(
111
+ <DateTimeField
112
+ field={{ name: "momento", value: "" }}
113
+ max="2026-06-30"
114
+ onChange={() => {}}
115
+ />,
116
+ );
117
+
118
+ const input = screen.getByPlaceholderText("Date Time");
119
+ expect(input.getAttribute("max")).toMatch(
120
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/,
121
+ );
122
+ });
123
+ });
124
+ });
@@ -0,0 +1,215 @@
1
+ import { screen, fireEvent } from "@testing-library/react";
2
+ import { renderWithIntl as render } from "@truedat/test/renderWithIntl";
3
+ import DynamicField from "../DynamicField";
4
+
5
+ const mockFormatMessage = jest.fn(({ id }) => id);
6
+
7
+ jest.mock("react-intl", () => ({
8
+ ...jest.requireActual("react-intl"),
9
+ useIntl: () => ({ formatMessage: mockFormatMessage }),
10
+ }));
11
+
12
+ beforeEach(() => mockFormatMessage.mockClear());
13
+
14
+ const field = (restrictions, value) => ({
15
+ name: "fecha",
16
+ label: "Fecha",
17
+ type: "date",
18
+ widget: "date",
19
+ cardinality: "?",
20
+ restrictions,
21
+ value: { value, origin: "user" },
22
+ });
23
+
24
+ const mount = (f) => render(<DynamicField field={f} onChange={jest.fn()} />);
25
+
26
+ describe("a date field with a published restriction", () => {
27
+ const range = {
28
+ between: { mode: "static", start: "2026-01-01", end: "2026-12-31" },
29
+ resolved: { min: "2026-01-01", max: "2026-12-31" },
30
+ };
31
+
32
+ it("marks the field as wrong when a typed date is past the bound", () => {
33
+ const { container } = mount(field(range, "2027-01-01"));
34
+
35
+ expect(container.querySelector(".field.error")).not.toBeNull();
36
+ });
37
+
38
+ it("says which bound was broken", () => {
39
+ mount(field(range, "2027-01-01"));
40
+
41
+ expect(
42
+ screen.getByText("template.field.values.default_before_max"),
43
+ ).toBeInTheDocument();
44
+ });
45
+
46
+ it("names the minimum when the value is too early", () => {
47
+ mount(field(range, "2025-12-31"));
48
+
49
+ expect(
50
+ screen.getByText("template.field.values.default_after_min"),
51
+ ).toBeInTheDocument();
52
+ });
53
+
54
+ it.each([
55
+ ["2027-01-01", "default_before_max", "2026-12-31"],
56
+ ["2025-12-31", "default_after_min", "2026-01-01"],
57
+ ])(
58
+ "passes the breached limit for %s to the formatter",
59
+ (value, key, limit) => {
60
+ mount(field(range, value));
61
+
62
+ expect(mockFormatMessage).toHaveBeenCalledWith(
63
+ { id: `template.field.values.${key}` },
64
+ { limit },
65
+ );
66
+ },
67
+ );
68
+
69
+ it("does not mark partial keyboard input", () => {
70
+ const { container } = mount(field(range, "2026-0"));
71
+
72
+ expect(container.querySelector(".field.error")).toBeNull();
73
+ });
74
+
75
+ it.each([
76
+ ["2026-02-30", "template.field.restrictions.validation.invalid_date"],
77
+ ["2027-01-01T00:00:00Z", "template.field.values.default_before_max"],
78
+ ])("checks a complete imported value (%s)", (value, messageId) => {
79
+ const { container } = mount(field(range, value));
80
+
81
+ expect(container.querySelector(".field.error")).not.toBeNull();
82
+ expect(screen.getByText(messageId)).toBeInTheDocument();
83
+ });
84
+
85
+ it("does not mark a date inside the range", () => {
86
+ const { container } = mount(field(range, "2026-06-15"));
87
+
88
+ expect(container.querySelector(".field.error")).toBeNull();
89
+ });
90
+
91
+ it("accepts the bound's own day", () => {
92
+ const { container } = mount(field(range, "2026-12-31"));
93
+
94
+ expect(container.querySelector(".field.error")).toBeNull();
95
+ });
96
+
97
+ it("never marks an empty value: these records are fillable in part", () => {
98
+ const { container } = mount(field(range, ""));
99
+
100
+ expect(container.querySelector(".field.error")).toBeNull();
101
+ });
102
+
103
+ it("still bounds the picker", () => {
104
+ mount(field(range, "2026-06-15"));
105
+
106
+ const input = screen.getByPlaceholderText("Date");
107
+ expect(input).toHaveAttribute("min", "2026-01-01");
108
+ expect(input).toHaveAttribute("max", "2026-12-31");
109
+ });
110
+
111
+ it("does not swallow what was typed", () => {
112
+ const onChange = jest.fn();
113
+ render(
114
+ <DynamicField field={field(range, "2026-06-15")} onChange={onChange} />,
115
+ );
116
+
117
+ fireEvent.change(screen.getByPlaceholderText("Date"), {
118
+ target: { value: "2027-01-01" },
119
+ });
120
+
121
+ expect(onChange).toHaveBeenCalledWith(
122
+ expect.anything(),
123
+ expect.objectContaining({ value: "2027-01-01" }),
124
+ );
125
+ });
126
+ });
127
+
128
+ describe("a datetime field", () => {
129
+ const dt = {
130
+ max_date: { mode: "static", value: "2026-06-30T10:00" },
131
+ resolved: { min: null, max: "2026-06-30T10:00" },
132
+ };
133
+
134
+ it("checks at minute precision", () => {
135
+ const { container } = mount({
136
+ ...field(dt, "2026-06-30T10:01"),
137
+ type: "datetime",
138
+ widget: "datetime",
139
+ });
140
+
141
+ expect(container.querySelector(".field.error")).not.toBeNull();
142
+ expect(screen.getByPlaceholderText("Date Time")).toHaveAttribute(
143
+ "max",
144
+ "2026-06-30T10:00",
145
+ );
146
+ });
147
+
148
+ it("accepts the bound's own minute", () => {
149
+ const { container } = mount({
150
+ ...field(dt, "2026-06-30T10:00"),
151
+ type: "datetime",
152
+ widget: "datetime",
153
+ });
154
+
155
+ expect(container.querySelector(".field.error")).toBeNull();
156
+ });
157
+ });
158
+
159
+ describe("a stored value that no longer fits", () => {
160
+ it("is shown as wrong rather than hidden", () => {
161
+ const { container } = mount({
162
+ ...field(
163
+ {
164
+ max_date: { mode: "static", value: "2026-05-31" },
165
+ resolved: { min: null, max: "2026-05-31" },
166
+ },
167
+ "2026-06-15",
168
+ ),
169
+ });
170
+
171
+ expect(screen.getByPlaceholderText("Date")).toHaveValue("2026-06-15");
172
+ expect(container.querySelector(".field.error")).not.toBeNull();
173
+ });
174
+ });
175
+
176
+ describe("fields with no restriction", () => {
177
+ it("are never marked", () => {
178
+ const { container } = mount(field(undefined, "2027-01-01"));
179
+
180
+ expect(container.querySelector(".field.error")).toBeNull();
181
+ });
182
+
183
+ it("are never marked when the bounds are open", () => {
184
+ const { container } = mount(
185
+ field(
186
+ {
187
+ min_date: { mode: "static", value: "2026-01-01" },
188
+ resolved: { min: "2026-01-01", max: null },
189
+ },
190
+ "2020-01-01",
191
+ ),
192
+ );
193
+
194
+ expect(container.querySelector(".field.error")).not.toBeNull();
195
+ });
196
+
197
+ it("a string field with restrictions is not treated as a date", () => {
198
+ const { container } = mount({
199
+ name: "s",
200
+ label: "S",
201
+ type: "string",
202
+ widget: "string",
203
+ restrictions: { resolved: { min: "a", max: "b" } },
204
+ value: { value: "hola", origin: "user" },
205
+ });
206
+
207
+ expect(container.querySelector(".field.error")).toBeNull();
208
+ });
209
+
210
+ it("does not show date-format validation for fields with no date bounds", () => {
211
+ const { container } = mount(field({}, "2026-02-30"));
212
+
213
+ expect(container.querySelector(".field.error")).toBeNull();
214
+ });
215
+ });