@cosmicdrift/kumiko-renderer 0.328.0 → 0.328.1
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.
- package/dist/app/action-form-shim.d.ts +14 -0
- package/{src/app/action-form-shim.ts → dist/app/action-form-shim.js} +26 -46
- package/dist/app/app-features-context.d.ts +8 -0
- package/dist/app/app-features-context.js +16 -0
- package/dist/app/column-renderers.d.ts +30 -0
- package/dist/app/column-renderers.js +34 -0
- package/dist/app/config-edit-shim.d.ts +11 -0
- package/{src/app/config-edit-shim.ts → dist/app/config-edit-shim.js} +12 -25
- package/dist/app/content-editors.d.ts +38 -0
- package/dist/app/content-editors.js +26 -0
- package/dist/app/content-preview.d.ts +11 -0
- package/dist/app/content-preview.js +30 -0
- package/dist/app/custom-screens.d.ts +11 -0
- package/dist/app/custom-screens.js +20 -0
- package/dist/app/dashboard-body.d.ts +16 -0
- package/dist/app/dashboard-body.js +11 -0
- package/dist/app/extension-form-submit.d.ts +32 -0
- package/dist/app/extension-form-submit.js +73 -0
- package/dist/app/extension-sections.d.ts +72 -0
- package/dist/app/extension-sections.js +34 -0
- package/dist/app/feature-schema.d.ts +4 -0
- package/{src/app/feature-schema.ts → dist/app/feature-schema.js} +12 -22
- package/dist/app/form-schema.d.ts +4 -0
- package/dist/app/form-schema.js +85 -0
- package/dist/app/kumiko-screen.d.ts +43 -0
- package/dist/app/kumiko-screen.js +2206 -0
- package/dist/app/layout-fields.d.ts +3 -0
- package/dist/app/layout-fields.js +20 -0
- package/dist/app/list-facets.d.ts +48 -0
- package/dist/app/list-facets.js +185 -0
- package/dist/app/nav.d.ts +61 -0
- package/dist/app/nav.js +139 -0
- package/dist/app/projection-detail-shim.d.ts +8 -0
- package/dist/app/projection-detail-shim.js +71 -0
- package/dist/app/projection-list-shim.d.ts +13 -0
- package/{src/app/projection-list-shim.ts → dist/app/projection-list-shim.js} +26 -41
- package/dist/app/qn.d.ts +7 -0
- package/{src/app/qn.ts → dist/app/qn.js} +9 -11
- package/dist/app/qualify-screen-id.d.ts +2 -0
- package/{src/app/qualify-screen-id.ts → dist/app/qualify-screen-id.js} +5 -6
- package/dist/app/reference-facet-bridge.d.ts +10 -0
- package/dist/app/reference-facet-bridge.js +22 -0
- package/dist/app/return-to.d.ts +31 -0
- package/dist/app/return-to.js +188 -0
- package/dist/app/row-actions.d.ts +77 -0
- package/dist/app/row-actions.js +416 -0
- package/dist/app/screen-access.d.ts +8 -0
- package/dist/app/screen-access.js +48 -0
- package/dist/app/secret-mint-body.d.ts +10 -0
- package/dist/app/secret-mint-body.js +140 -0
- package/dist/app/secrets-edit-body.d.ts +8 -0
- package/dist/app/secrets-edit-body.js +76 -0
- package/dist/app/use-embedded-screen.d.ts +6 -0
- package/dist/app/use-embedded-screen.js +19 -0
- package/dist/app/variable-chips.d.ts +7 -0
- package/dist/app/variable-chips.js +16 -0
- package/dist/app/write-failed-error.d.ts +6 -0
- package/dist/app/write-failed-error.js +21 -0
- package/dist/components/embedded-list-field.d.ts +10 -0
- package/dist/components/embedded-list-field.js +259 -0
- package/dist/components/grid-cell-for-field.d.ts +25 -0
- package/dist/components/grid-cell-for-field.js +31 -0
- package/dist/components/multi-select-checkboxes.d.ts +14 -0
- package/dist/components/multi-select-checkboxes.js +36 -0
- package/dist/components/reference-create-dialog.d.ts +13 -0
- package/dist/components/reference-create-dialog.js +46 -0
- package/dist/components/related-list-section.d.ts +25 -0
- package/dist/components/related-list-section.js +273 -0
- package/dist/components/render-edit-action-button.d.ts +22 -0
- package/dist/components/render-edit-action-button.js +43 -0
- package/dist/components/render-edit-logic.d.ts +6 -0
- package/dist/components/render-edit-logic.js +63 -0
- package/dist/components/render-edit-types.d.ts +209 -0
- package/dist/components/render-edit-types.js +1 -0
- package/dist/components/render-edit.d.ts +6 -0
- package/dist/components/render-edit.js +1169 -0
- package/dist/components/render-field.d.ts +38 -0
- package/dist/components/render-field.js +544 -0
- package/dist/components/render-list.d.ts +117 -0
- package/dist/components/render-list.js +232 -0
- package/dist/components/write-form-section.d.ts +17 -0
- package/dist/components/write-form-section.js +104 -0
- package/dist/context/dispatcher-context.d.ts +10 -0
- package/{src/context/dispatcher-context.tsx → dist/context/dispatcher-context.js} +16 -29
- package/dist/context/draft-storage-context.d.ts +12 -0
- package/dist/context/draft-storage-context.js +18 -0
- package/dist/context/user-roles-context.d.ts +7 -0
- package/{src/context/user-roles-context.tsx → dist/context/user-roles-context.js} +7 -15
- package/dist/format-when.d.ts +1 -0
- package/{src/format-when.ts → dist/format-when.js} +7 -7
- package/dist/hooks/reference-limits.d.ts +3 -0
- package/{src/hooks/reference-limits.ts → dist/hooks/reference-limits.js} +0 -1
- package/dist/hooks/use-ai-text.d.ts +52 -0
- package/dist/hooks/use-ai-text.js +90 -0
- package/dist/hooks/use-disclosure.d.ts +7 -0
- package/dist/hooks/use-disclosure.js +8 -0
- package/dist/hooks/use-form.d.ts +7 -0
- package/dist/hooks/use-form.js +36 -0
- package/dist/hooks/use-list-url-state.d.ts +43 -0
- package/dist/hooks/use-list-url-state.js +131 -0
- package/dist/hooks/use-mutation.d.ts +9 -0
- package/dist/hooks/use-mutation.js +37 -0
- package/dist/hooks/use-query.d.ts +14 -0
- package/dist/hooks/use-query.js +118 -0
- package/dist/hooks/use-reference-lookup.d.ts +14 -0
- package/dist/hooks/use-reference-lookup.js +63 -0
- package/dist/hooks/use-store.d.ts +3 -0
- package/dist/hooks/use-store.js +38 -0
- package/dist/hooks/use-stream-handler.d.ts +14 -0
- package/dist/hooks/use-stream-handler.js +93 -0
- package/dist/i18n-defaults.d.ts +2 -0
- package/dist/i18n-defaults.js +201 -0
- package/dist/i18n.d.ts +64 -0
- package/dist/i18n.js +166 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +55 -0
- package/dist/inside-drawer.d.ts +2 -0
- package/{src/inside-drawer.ts → dist/inside-drawer.js} +2 -5
- package/dist/page-header-slot.d.ts +2 -0
- package/{src/page-header-slot.ts → dist/page-header-slot.js} +2 -5
- package/dist/primitives.d.ts +1351 -0
- package/dist/primitives.js +73 -0
- package/dist/screen-fills-height.d.ts +3 -0
- package/dist/screen-fills-height.js +3 -0
- package/dist/share-links.d.ts +4 -0
- package/dist/share-links.js +11 -0
- package/dist/sort-by-accessor.d.ts +5 -0
- package/dist/sort-by-accessor.js +16 -0
- package/dist/sse/live-events.d.ts +23 -0
- package/dist/sse/live-events.js +28 -0
- package/dist/tokens.d.ts +83 -0
- package/dist/tokens.js +80 -0
- package/package.json +16 -9
- package/src/__tests__/error-i18n-defaults.test.ts +0 -82
- package/src/__tests__/format-when.test.ts +0 -12
- package/src/__tests__/i18n.test.tsx +0 -283
- package/src/__tests__/merge-search-params-into-initial.test.ts +0 -525
- package/src/__tests__/qn.test.ts +0 -83
- package/src/__tests__/share-links.test.ts +0 -26
- package/src/__tests__/sort-by-accessor.test.ts +0 -48
- package/src/__tests__/stringify-nav-params.test.ts +0 -33
- package/src/__tests__/use-list-url-state.test.tsx +0 -250
- package/src/app/__tests__/action-form-field-labels.test.tsx +0 -92
- package/src/app/__tests__/action-form-shim.test.ts +0 -137
- package/src/app/__tests__/build-list-query-payload.test.ts +0 -107
- package/src/app/__tests__/config-edit-shim.test.ts +0 -57
- package/src/app/__tests__/content-editors.test.tsx +0 -128
- package/src/app/__tests__/content-preview.test.tsx +0 -184
- package/src/app/__tests__/entity-edit-create-search-params.test.tsx +0 -236
- package/src/app/__tests__/entity-edit-fill-height.test.tsx +0 -118
- package/src/app/__tests__/entity-edit-jsonb-embedded-submit.test.tsx +0 -239
- package/src/app/__tests__/entity-list-create-singleton.test.tsx +0 -128
- package/src/app/__tests__/entity-list-row-action-entity-target.test.tsx +0 -424
- package/src/app/__tests__/entity-list-row-action-icon.test.tsx +0 -248
- package/src/app/__tests__/entity-list-row-action-kumiko-actions-view.test.tsx +0 -162
- package/src/app/__tests__/entity-list-row-action-refetch.test.tsx +0 -401
- package/src/app/__tests__/entity-list-screen-padding.test.tsx +0 -128
- package/src/app/__tests__/feature-schema.test.ts +0 -40
- package/src/app/__tests__/form-schema.test.ts +0 -247
- package/src/app/__tests__/list-daterange-facet.test.ts +0 -165
- package/src/app/__tests__/list-filter-facets.test.tsx +0 -521
- package/src/app/__tests__/nav.test.ts +0 -198
- package/src/app/__tests__/projection-detail-actions.test.tsx +0 -719
- package/src/app/__tests__/projection-detail-reference-field.test.tsx +0 -165
- package/src/app/__tests__/projection-detail-shim.test.ts +0 -188
- package/src/app/__tests__/projection-detail-singleton.test.tsx +0 -191
- package/src/app/__tests__/projection-list-bad-shape.test.tsx +0 -152
- package/src/app/__tests__/projection-list-reference-column.test.tsx +0 -219
- package/src/app/__tests__/projection-list-search-sort.test.tsx +0 -212
- package/src/app/__tests__/qualified-names.test.ts +0 -37
- package/src/app/__tests__/return-to.test.ts +0 -396
- package/src/app/__tests__/row-actions-navigate-tab.test.ts +0 -223
- package/src/app/__tests__/row-default-edit-action.test.tsx +0 -324
- package/src/app/__tests__/screen-access-allows.test.ts +0 -34
- package/src/app/__tests__/secret-mint-body.test.tsx +0 -404
- package/src/app/__tests__/variable-chips.test.tsx +0 -70
- package/src/app/app-features-context.tsx +0 -25
- package/src/app/column-renderers.tsx +0 -68
- package/src/app/content-editors.tsx +0 -85
- package/src/app/content-preview.tsx +0 -67
- package/src/app/custom-screens.tsx +0 -29
- package/src/app/dashboard-body.tsx +0 -33
- package/src/app/extension-form-submit.tsx +0 -123
- package/src/app/extension-sections.tsx +0 -111
- package/src/app/form-schema.ts +0 -91
- package/src/app/kumiko-screen.tsx +0 -3801
- package/src/app/layout-fields.ts +0 -31
- package/src/app/list-facets.ts +0 -259
- package/src/app/nav.tsx +0 -258
- package/src/app/projection-detail-shim.ts +0 -86
- package/src/app/reference-facet-bridge.tsx +0 -58
- package/src/app/return-to.tsx +0 -234
- package/src/app/row-actions.ts +0 -640
- package/src/app/screen-access.ts +0 -63
- package/src/app/secret-mint-body.tsx +0 -259
- package/src/app/secrets-edit-body.tsx +0 -194
- package/src/app/use-embedded-screen.ts +0 -27
- package/src/app/variable-chips.tsx +0 -39
- package/src/app/write-failed-error.ts +0 -27
- package/src/components/__tests__/embedded-list-field.test.tsx +0 -531
- package/src/components/__tests__/multi-select-checkboxes.test.tsx +0 -162
- package/src/components/__tests__/related-list-section-empty-state.test.tsx +0 -143
- package/src/components/__tests__/related-list-section.test.tsx +0 -1733
- package/src/components/__tests__/render-edit-action-button.test.tsx +0 -205
- package/src/components/__tests__/render-edit-hidden-section.test.tsx +0 -128
- package/src/components/__tests__/render-edit-logic.test.ts +0 -314
- package/src/components/__tests__/render-edit-screen-description.test.tsx +0 -152
- package/src/components/__tests__/render-edit-submit-actions.test.tsx +0 -460
- package/src/components/__tests__/render-field-app-locale.test.tsx +0 -142
- package/src/components/__tests__/render-field-enum-option.test.tsx +0 -99
- package/src/components/__tests__/render-field-icon-derivation.test.tsx +0 -119
- package/src/components/__tests__/render-field-json-format.test.tsx +0 -164
- package/src/components/__tests__/render-field-money-embedded-list-consistency.test.tsx +0 -212
- package/src/components/__tests__/render-field-money-roundtrip.test.tsx +0 -217
- package/src/components/__tests__/render-field-number-grouping.test.tsx +0 -76
- package/src/components/__tests__/render-field-number-unit.test.tsx +0 -227
- package/src/components/__tests__/render-field-password-format.test.tsx +0 -101
- package/src/components/__tests__/render-field-select-display.test.tsx +0 -134
- package/src/components/__tests__/render-field-unit-format.test.tsx +0 -107
- package/src/components/__tests__/render-field-unsupported-types.test.tsx +0 -195
- package/src/components/__tests__/render-list-toolbar-icon-only.test.tsx +0 -146
- package/src/components/__tests__/write-form-section.test.tsx +0 -375
- package/src/components/embedded-list-field.tsx +0 -354
- package/src/components/grid-cell-for-field.tsx +0 -88
- package/src/components/multi-select-checkboxes.tsx +0 -93
- package/src/components/reference-create-dialog.tsx +0 -91
- package/src/components/related-list-section.tsx +0 -491
- package/src/components/render-edit-action-button.tsx +0 -109
- package/src/components/render-edit-logic.ts +0 -85
- package/src/components/render-edit-types.ts +0 -226
- package/src/components/render-edit.tsx +0 -1871
- package/src/components/render-field.tsx +0 -951
- package/src/components/render-list.tsx +0 -593
- package/src/components/write-form-section.tsx +0 -198
- package/src/context/draft-storage-context.tsx +0 -39
- package/src/hooks/__tests__/use-ai-text.test.tsx +0 -177
- package/src/hooks/__tests__/use-disclosure.test.tsx +0 -25
- package/src/hooks/__tests__/use-form.test.tsx +0 -65
- package/src/hooks/__tests__/use-mutation.test.tsx +0 -77
- package/src/hooks/__tests__/use-query.test.tsx +0 -149
- package/src/hooks/__tests__/use-stream-handler.test.tsx +0 -107
- package/src/hooks/use-ai-text.ts +0 -172
- package/src/hooks/use-disclosure.ts +0 -20
- package/src/hooks/use-form.ts +0 -78
- package/src/hooks/use-list-url-state.ts +0 -207
- package/src/hooks/use-mutation.ts +0 -61
- package/src/hooks/use-query.ts +0 -166
- package/src/hooks/use-reference-lookup.ts +0 -76
- package/src/hooks/use-store.ts +0 -48
- package/src/hooks/use-stream-handler.ts +0 -134
- package/src/i18n-defaults.ts +0 -240
- package/src/i18n.tsx +0 -286
- package/src/index.ts +0 -279
- package/src/primitives.tsx +0 -1567
- package/src/screen-fills-height.ts +0 -3
- package/src/share-links.ts +0 -12
- package/src/sort-by-accessor.ts +0 -20
- package/src/sse/live-events.tsx +0 -56
- package/src/tokens.tsx +0 -170
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { fireEvent, render, screen as rtlScreen, waitFor } from "@testing-library/react";
|
|
3
|
-
import type { ComponentType } from "react";
|
|
4
|
-
import type { ButtonProps, DialogProps } from "../../primitives";
|
|
5
|
-
import { RenderEditActionButton } from "../render-edit-action-button";
|
|
6
|
-
import type { RenderEditAction } from "../render-edit-types";
|
|
7
|
-
|
|
8
|
-
const TestButton: ComponentType<ButtonProps> = ({ children, onClick, testId, type, loading }) => (
|
|
9
|
-
<button
|
|
10
|
-
type={type ?? "button"}
|
|
11
|
-
data-testid={testId}
|
|
12
|
-
data-loading={loading ? "1" : "0"}
|
|
13
|
-
onClick={() => {
|
|
14
|
-
void onClick?.();
|
|
15
|
-
}}
|
|
16
|
-
>
|
|
17
|
-
{children}
|
|
18
|
-
</button>
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
const TestDialog: ComponentType<DialogProps> = ({
|
|
22
|
-
open,
|
|
23
|
-
onOpenChange,
|
|
24
|
-
title,
|
|
25
|
-
description,
|
|
26
|
-
confirmLabel,
|
|
27
|
-
variant,
|
|
28
|
-
onConfirm,
|
|
29
|
-
testId,
|
|
30
|
-
}) =>
|
|
31
|
-
open ? (
|
|
32
|
-
<div data-testid={testId} data-variant={variant ?? "default"}>
|
|
33
|
-
<span data-testid={`${testId}-title`}>{title}</span>
|
|
34
|
-
{description !== undefined && (
|
|
35
|
-
<span data-testid={`${testId}-description`}>{description}</span>
|
|
36
|
-
)}
|
|
37
|
-
<button
|
|
38
|
-
type="button"
|
|
39
|
-
data-testid={`${testId}-confirm`}
|
|
40
|
-
onClick={() => {
|
|
41
|
-
void (async () => {
|
|
42
|
-
await onConfirm();
|
|
43
|
-
onOpenChange(false);
|
|
44
|
-
})();
|
|
45
|
-
}}
|
|
46
|
-
>
|
|
47
|
-
{confirmLabel ?? "Confirm"}
|
|
48
|
-
</button>
|
|
49
|
-
<button type="button" data-testid={`${testId}-cancel`} onClick={() => onOpenChange(false)}>
|
|
50
|
-
Cancel
|
|
51
|
-
</button>
|
|
52
|
-
</div>
|
|
53
|
-
) : null;
|
|
54
|
-
|
|
55
|
-
function renderAction(action: RenderEditAction, onError: (text: string | null) => void = () => {}) {
|
|
56
|
-
return render(
|
|
57
|
-
<RenderEditActionButton
|
|
58
|
-
action={action}
|
|
59
|
-
Button={TestButton}
|
|
60
|
-
Dialog={TestDialog}
|
|
61
|
-
onError={onError}
|
|
62
|
-
/>,
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
describe("RenderEditActionButton", () => {
|
|
67
|
-
test("secondary action without confirm runs onPress immediately", async () => {
|
|
68
|
-
let pressed = 0;
|
|
69
|
-
renderAction({
|
|
70
|
-
id: "ping",
|
|
71
|
-
label: "Ping",
|
|
72
|
-
onPress: async () => {
|
|
73
|
-
pressed += 1;
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
expect(rtlScreen.queryByTestId("render-edit-action-ping-dialog")).toBeNull();
|
|
78
|
-
fireEvent.click(rtlScreen.getByTestId("render-edit-action-ping"));
|
|
79
|
-
await waitFor(() => expect(pressed).toBe(1));
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("explicit confirm text opens dialog; confirm runs onPress, cancel does not", async () => {
|
|
83
|
-
let pressed = 0;
|
|
84
|
-
renderAction({
|
|
85
|
-
id: "archive",
|
|
86
|
-
label: "Archive",
|
|
87
|
-
confirm: "Really archive?",
|
|
88
|
-
confirmLabel: "Yes, archive",
|
|
89
|
-
onPress: async () => {
|
|
90
|
-
pressed += 1;
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
fireEvent.click(rtlScreen.getByTestId("render-edit-action-archive"));
|
|
95
|
-
expect(rtlScreen.getByTestId("render-edit-action-archive-dialog")).toBeTruthy();
|
|
96
|
-
expect(rtlScreen.getByTestId("render-edit-action-archive-dialog-description").textContent).toBe(
|
|
97
|
-
"Really archive?",
|
|
98
|
-
);
|
|
99
|
-
expect(rtlScreen.getByTestId("render-edit-action-archive-dialog-confirm").textContent).toBe(
|
|
100
|
-
"Yes, archive",
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
fireEvent.click(rtlScreen.getByTestId("render-edit-action-archive-dialog-cancel"));
|
|
104
|
-
expect(rtlScreen.queryByTestId("render-edit-action-archive-dialog")).toBeNull();
|
|
105
|
-
expect(pressed).toBe(0);
|
|
106
|
-
|
|
107
|
-
fireEvent.click(rtlScreen.getByTestId("render-edit-action-archive"));
|
|
108
|
-
fireEvent.click(rtlScreen.getByTestId("render-edit-action-archive-dialog-confirm"));
|
|
109
|
-
await waitFor(() => expect(pressed).toBe(1));
|
|
110
|
-
await waitFor(() =>
|
|
111
|
-
expect(rtlScreen.queryByTestId("render-edit-action-archive-dialog")).toBeNull(),
|
|
112
|
-
);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("danger style forces confirm dialog even without confirm text", async () => {
|
|
116
|
-
let pressed = 0;
|
|
117
|
-
renderAction({
|
|
118
|
-
id: "delete",
|
|
119
|
-
label: "Delete",
|
|
120
|
-
style: "danger",
|
|
121
|
-
onPress: async () => {
|
|
122
|
-
pressed += 1;
|
|
123
|
-
},
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
fireEvent.click(rtlScreen.getByTestId("render-edit-action-delete"));
|
|
127
|
-
const dialog = rtlScreen.getByTestId("render-edit-action-delete-dialog");
|
|
128
|
-
expect(dialog.getAttribute("data-variant")).toBe("danger");
|
|
129
|
-
expect(rtlScreen.queryByTestId("render-edit-action-delete-dialog-description")).toBeNull();
|
|
130
|
-
expect(pressed).toBe(0);
|
|
131
|
-
|
|
132
|
-
expect(rtlScreen.getByTestId("render-edit-action-delete-dialog-confirm").textContent).toBe(
|
|
133
|
-
"Delete",
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
fireEvent.click(rtlScreen.getByTestId("render-edit-action-delete-dialog-confirm"));
|
|
137
|
-
await waitFor(() => expect(pressed).toBe(1));
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// fw#2752: schema-driven navigate/drawer actions set confirmRequired: false
|
|
141
|
-
// to opt a danger-styled action out of the forced dialog — the colour still
|
|
142
|
-
// marks it destructive, but the target form is itself the confirmation.
|
|
143
|
-
test("danger style with confirmRequired=false fires onPress directly, no dialog", async () => {
|
|
144
|
-
let pressed = 0;
|
|
145
|
-
renderAction({
|
|
146
|
-
id: "open-terminate-form",
|
|
147
|
-
label: "Terminate",
|
|
148
|
-
style: "danger",
|
|
149
|
-
confirmRequired: false,
|
|
150
|
-
onPress: async () => {
|
|
151
|
-
pressed += 1;
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
fireEvent.click(rtlScreen.getByTestId("render-edit-action-open-terminate-form"));
|
|
156
|
-
expect(rtlScreen.queryByTestId("render-edit-action-open-terminate-form-dialog")).toBeNull();
|
|
157
|
-
await waitFor(() => expect(pressed).toBe(1));
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
test("onPress failure reports via onError", async () => {
|
|
161
|
-
const errors: Array<string | null> = [];
|
|
162
|
-
renderAction(
|
|
163
|
-
{
|
|
164
|
-
id: "boom",
|
|
165
|
-
label: "Boom",
|
|
166
|
-
onPress: async () => {
|
|
167
|
-
throw new Error("action exploded");
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
(text) => {
|
|
171
|
-
errors.push(text);
|
|
172
|
-
},
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
fireEvent.click(rtlScreen.getByTestId("render-edit-action-boom"));
|
|
176
|
-
await waitFor(() => expect(errors).toContain("action exploded"));
|
|
177
|
-
// Cleared at the start of trigger, then set on failure.
|
|
178
|
-
expect(errors[0]).toBeNull();
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test("sets loading while onPress is in flight", async () => {
|
|
182
|
-
let resolvePress!: () => void;
|
|
183
|
-
const pressPromise = new Promise<void>((resolve) => {
|
|
184
|
-
resolvePress = resolve;
|
|
185
|
-
});
|
|
186
|
-
renderAction({
|
|
187
|
-
id: "slow",
|
|
188
|
-
label: "Slow",
|
|
189
|
-
onPress: () => pressPromise,
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
fireEvent.click(rtlScreen.getByTestId("render-edit-action-slow"));
|
|
193
|
-
await waitFor(() =>
|
|
194
|
-
expect(rtlScreen.getByTestId("render-edit-action-slow").getAttribute("data-loading")).toBe(
|
|
195
|
-
"1",
|
|
196
|
-
),
|
|
197
|
-
);
|
|
198
|
-
resolvePress();
|
|
199
|
-
await waitFor(() =>
|
|
200
|
-
expect(rtlScreen.getByTestId("render-edit-action-slow").getAttribute("data-loading")).toBe(
|
|
201
|
-
"0",
|
|
202
|
-
),
|
|
203
|
-
);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
// Regression coverage for the hidden-section guards in render-edit.tsx
|
|
2
|
-
// (kumiko-framework#1731, from PR-review finding #1690): nothing previously
|
|
3
|
-
// asserted that a fully-hidden section actually stays out of the DOM.
|
|
4
|
-
//
|
|
5
|
-
// Two cases, verified by mutation-testing each guard in isolation:
|
|
6
|
-
// - "Hidden": a "fields" section whose only field is condition-hidden — the
|
|
7
|
-
// issue's literal ask. Redundantly guarded: both the
|
|
8
|
-
// `filterEditSections(...).filter(...)` pass and the render loop's
|
|
9
|
-
// `if (!section.visible) return null;` independently suppress it, so this
|
|
10
|
-
// case only goes red if BOTH guards are removed.
|
|
11
|
-
// - "Empty": a "fields" section declared with zero fields. fw#1901 keeps it
|
|
12
|
-
// past the filter above on purpose (it isn't "hidden", it just has
|
|
13
|
-
// nothing to hide), so its `visible` stays vacuously false and
|
|
14
|
-
// `if (!section.visible) return null;` is the ONLY thing nulling it out —
|
|
15
|
-
// this case is what actually pins that line, verified red when it's
|
|
16
|
-
// neutralized. Not a shape a booted app can produce (boot-validator
|
|
17
|
-
// rejects `fields.length === 0`), but RenderEdit's props aren't
|
|
18
|
-
// boot-validated, so it's a real call shape worth guarding.
|
|
19
|
-
|
|
20
|
-
import { describe, expect, test } from "bun:test";
|
|
21
|
-
import type {
|
|
22
|
-
EntityDefinition,
|
|
23
|
-
EntityEditScreenDefinition,
|
|
24
|
-
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
25
|
-
import { type RenderResult, render } from "@testing-library/react";
|
|
26
|
-
import type { ComponentType, ReactNode } from "react";
|
|
27
|
-
import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
|
|
28
|
-
import { kumikoDefaultTranslations } from "../../i18n-defaults";
|
|
29
|
-
import { type CorePrimitives, PrimitivesProvider, type SectionProps } from "../../primitives";
|
|
30
|
-
import { RenderEdit } from "../render-edit";
|
|
31
|
-
|
|
32
|
-
const noop = (): ReactNode => null;
|
|
33
|
-
const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
|
|
34
|
-
const renderSection: ComponentType<SectionProps> = ({ testId, children }) => (
|
|
35
|
-
<div data-testid={testId}>{children}</div>
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
function testPrimitives(): CorePrimitives {
|
|
39
|
-
return {
|
|
40
|
-
Button: noop,
|
|
41
|
-
Banner: noop,
|
|
42
|
-
Field: passChildren,
|
|
43
|
-
Input: noop,
|
|
44
|
-
DataTable: noop,
|
|
45
|
-
Form: passChildren,
|
|
46
|
-
Section: renderSection,
|
|
47
|
-
Card: passChildren,
|
|
48
|
-
Grid: passChildren,
|
|
49
|
-
GridCell: passChildren,
|
|
50
|
-
Text: passChildren,
|
|
51
|
-
Heading: noop,
|
|
52
|
-
Dialog: noop,
|
|
53
|
-
Modal: noop,
|
|
54
|
-
Lightbox: noop,
|
|
55
|
-
ConfigSourceBadge: noop,
|
|
56
|
-
ConfigCascadeView: noop,
|
|
57
|
-
Link: noop,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function buildEntity(): EntityDefinition {
|
|
62
|
-
return {
|
|
63
|
-
fields: {
|
|
64
|
-
name: { type: "text", maxLength: 200, required: false, searchable: false, sortable: false },
|
|
65
|
-
secret: { type: "text", maxLength: 200, required: false, searchable: false, sortable: false },
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function renderEdit(screen: EntityEditScreenDefinition): RenderResult {
|
|
71
|
-
return render(
|
|
72
|
-
<LocaleProvider
|
|
73
|
-
resolver={createStaticLocaleResolver({ locale: "en-US" })}
|
|
74
|
-
fallbackBundles={[kumikoDefaultTranslations]}
|
|
75
|
-
>
|
|
76
|
-
<PrimitivesProvider value={testPrimitives()}>
|
|
77
|
-
<RenderEdit
|
|
78
|
-
screen={screen}
|
|
79
|
-
entity={buildEntity()}
|
|
80
|
-
featureName="widgets"
|
|
81
|
-
initial={{ name: "", secret: "" }}
|
|
82
|
-
/>
|
|
83
|
-
</PrimitivesProvider>
|
|
84
|
-
</LocaleProvider>,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
describe("RenderEdit — section with every field condition-hidden", () => {
|
|
89
|
-
test("the visible section renders, the fully-hidden one does not reach the DOM", () => {
|
|
90
|
-
const screen: EntityEditScreenDefinition = {
|
|
91
|
-
id: "widget-edit",
|
|
92
|
-
type: "entityEdit",
|
|
93
|
-
entity: "widget",
|
|
94
|
-
layout: {
|
|
95
|
-
sections: [
|
|
96
|
-
{ title: "Visible", fields: ["name"] },
|
|
97
|
-
{ title: "Hidden", fields: [{ field: "secret", visible: false }] },
|
|
98
|
-
],
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const { getByTestId, queryByTestId } = renderEdit(screen);
|
|
103
|
-
|
|
104
|
-
expect(getByTestId("section-Visible")).toBeDefined();
|
|
105
|
-
expect(queryByTestId("section-Hidden")).toBeNull();
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe("RenderEdit — section declared with zero fields", () => {
|
|
110
|
-
test("the visible section renders, the empty one does not reach the DOM", () => {
|
|
111
|
-
const screen: EntityEditScreenDefinition = {
|
|
112
|
-
id: "widget-edit",
|
|
113
|
-
type: "entityEdit",
|
|
114
|
-
entity: "widget",
|
|
115
|
-
layout: {
|
|
116
|
-
sections: [
|
|
117
|
-
{ title: "Visible", fields: ["name"] },
|
|
118
|
-
{ title: "Empty", fields: [] },
|
|
119
|
-
],
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const { getByTestId, queryByTestId } = renderEdit(screen);
|
|
124
|
-
|
|
125
|
-
expect(getByTestId("section-Visible")).toBeDefined();
|
|
126
|
-
expect(queryByTestId("section-Empty")).toBeNull();
|
|
127
|
-
});
|
|
128
|
-
});
|
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import type {
|
|
3
|
-
DispatcherError,
|
|
4
|
-
EditFieldViewModel,
|
|
5
|
-
EditSectionViewModel,
|
|
6
|
-
SubmitResult,
|
|
7
|
-
} from "@cosmicdrift/kumiko-headless";
|
|
8
|
-
import {
|
|
9
|
-
filterEditSections,
|
|
10
|
-
hasEditableSection,
|
|
11
|
-
hasIssueWithoutRenderedField,
|
|
12
|
-
resolveExtensionEntityId,
|
|
13
|
-
shouldNotifyCaller,
|
|
14
|
-
} from "../render-edit-logic";
|
|
15
|
-
|
|
16
|
-
const error: DispatcherError = {
|
|
17
|
-
code: "internal_error",
|
|
18
|
-
httpStatus: 500,
|
|
19
|
-
i18nKey: "kumiko.errors.internal",
|
|
20
|
-
message: "boom",
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const success: SubmitResult<unknown> = { validationBlocked: false, isSuccess: true, data: {} };
|
|
24
|
-
const writeFailure: SubmitResult<unknown> = { validationBlocked: false, isSuccess: false, error };
|
|
25
|
-
const validationBlocked: SubmitResult<unknown> = { validationBlocked: true, isSuccess: false };
|
|
26
|
-
|
|
27
|
-
describe("resolveExtensionEntityId", () => {
|
|
28
|
-
test("explicit id prop wins over vm.id", () => {
|
|
29
|
-
expect(resolveExtensionEntityId("entity-42", "row-7")).toBe("entity-42");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// The #345/1 regression: mount fell back to vm.id but persist did not, so a
|
|
33
|
-
// custom-fields section mounted editable against the existing row yet saved
|
|
34
|
-
// to null → silent data loss. Both sites now resolve this same value.
|
|
35
|
-
test("undefined prop → vm.id fallback (update form carries the row id)", () => {
|
|
36
|
-
expect(resolveExtensionEntityId(undefined, "row-42")).toBe("row-42");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("undefined prop + no vm.id (create mode) → null", () => {
|
|
40
|
-
expect(resolveExtensionEntityId(undefined, null)).toBeNull();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("explicit null prop forces null even when vm.id is present", () => {
|
|
44
|
-
expect(resolveExtensionEntityId(null, "row-42")).toBeNull();
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe("shouldNotifyCaller", () => {
|
|
49
|
-
// The one case the #345/1 fix changes: a successful entity write whose
|
|
50
|
-
// extension persist failed must NOT notify (caller would navigate away and
|
|
51
|
-
// unmount the extension-error banner → silent custom-field data loss). The
|
|
52
|
-
// pre-fix code notified unconditionally, so this row flips false↔true.
|
|
53
|
-
test("entity success + extension persist failed → suppress callback", () => {
|
|
54
|
-
expect(shouldNotifyCaller(success, false)).toBe(false);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("entity success + extensions persisted → notify", () => {
|
|
58
|
-
expect(shouldNotifyCaller(success, true)).toBe(true);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("entity write failure → notify (caller needs the error result)", () => {
|
|
62
|
-
expect(shouldNotifyCaller(writeFailure, true)).toBe(true);
|
|
63
|
-
// extensions never run on a failed entity write, but the flag must not
|
|
64
|
-
// swallow the failure callback regardless of its value.
|
|
65
|
-
expect(shouldNotifyCaller(writeFailure, false)).toBe(true);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("validation blocked → notify", () => {
|
|
69
|
-
expect(shouldNotifyCaller(validationBlocked, true)).toBe(true);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const field = (readOnly: boolean): EditFieldViewModel => ({
|
|
74
|
-
field: "f",
|
|
75
|
-
label: "F",
|
|
76
|
-
type: "text",
|
|
77
|
-
value: "",
|
|
78
|
-
visible: true,
|
|
79
|
-
readOnly,
|
|
80
|
-
required: false,
|
|
81
|
-
});
|
|
82
|
-
const fieldsSection = (...readOnly: boolean[]): EditSectionViewModel => ({
|
|
83
|
-
kind: "fields",
|
|
84
|
-
columns: 1,
|
|
85
|
-
visible: true,
|
|
86
|
-
fields: readOnly.map(field),
|
|
87
|
-
});
|
|
88
|
-
const extensionSection: EditSectionViewModel = {
|
|
89
|
-
kind: "extension",
|
|
90
|
-
title: "Custom",
|
|
91
|
-
component: {},
|
|
92
|
-
contributesToFormSubmit: false,
|
|
93
|
-
};
|
|
94
|
-
const composedExtensionSection: EditSectionViewModel = {
|
|
95
|
-
...extensionSection,
|
|
96
|
-
contributesToFormSubmit: true,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
describe("hasEditableSection", () => {
|
|
100
|
-
// A read-only inspector detail (export-job/download-attempt) marks every field
|
|
101
|
-
// readOnly + has no create/delete — there is nothing to submit, so the Save
|
|
102
|
-
// button must not render at all (a disabled one reads as a broken control).
|
|
103
|
-
test("every field readOnly → false (no Save button)", () => {
|
|
104
|
-
expect(hasEditableSection([fieldsSection(true, true, true)])).toBe(false);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("one editable field → true", () => {
|
|
108
|
-
expect(hasEditableSection([fieldsSection(true, false)])).toBe(true);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("editable field in a later section → true", () => {
|
|
112
|
-
expect(hasEditableSection([fieldsSection(true), fieldsSection(false)])).toBe(true);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("extension section without form-submit opt-in → false", () => {
|
|
116
|
-
expect(hasEditableSection([extensionSection])).toBe(false);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("extension section with contributesToFormSubmit → true", () => {
|
|
120
|
-
expect(hasEditableSection([composedExtensionSection])).toBe(true);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("no sections → false", () => {
|
|
124
|
-
expect(hasEditableSection([])).toBe(false);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// writeForm owns its own submit button (write-form-section-submit) —
|
|
128
|
-
// counting it here would additionally render the screen-level Save button
|
|
129
|
-
// (render-edit-submit) on a projectionDetail, which has no form submit to
|
|
130
|
-
// wire it to.
|
|
131
|
-
test("writeForm section → false (has its own submit, not the screen's)", () => {
|
|
132
|
-
const writeFormSection: EditSectionViewModel = {
|
|
133
|
-
kind: "writeForm",
|
|
134
|
-
columns: 1,
|
|
135
|
-
handler: "orders:write:add-note",
|
|
136
|
-
fields: [field(false)],
|
|
137
|
-
};
|
|
138
|
-
expect(hasEditableSection([writeFormSection])).toBe(false);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test("hidden section with editable field → false (user cannot see the field)", () => {
|
|
142
|
-
const hiddenSectionWithEditableField: EditSectionViewModel = {
|
|
143
|
-
kind: "fields",
|
|
144
|
-
columns: 1,
|
|
145
|
-
visible: false,
|
|
146
|
-
fields: [field(false)],
|
|
147
|
-
};
|
|
148
|
-
expect(hasEditableSection([hiddenSectionWithEditableField])).toBe(false);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("visible section with only a field-hidden editable field → false", () => {
|
|
152
|
-
const section: EditSectionViewModel = {
|
|
153
|
-
kind: "fields",
|
|
154
|
-
columns: 1,
|
|
155
|
-
visible: true,
|
|
156
|
-
fields: [
|
|
157
|
-
{
|
|
158
|
-
field: "f",
|
|
159
|
-
label: "F",
|
|
160
|
-
type: "text",
|
|
161
|
-
value: "",
|
|
162
|
-
visible: false,
|
|
163
|
-
readOnly: false,
|
|
164
|
-
required: false,
|
|
165
|
-
},
|
|
166
|
-
],
|
|
167
|
-
};
|
|
168
|
-
expect(hasEditableSection([section])).toBe(false);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// The #1689 regression: the section is visible because field A is visible
|
|
172
|
-
// (readOnly), but the only editable field B is hidden by its own
|
|
173
|
-
// FieldCondition. Old code checked section.visible + !readOnly only, so it
|
|
174
|
-
// saw B's !readOnly and returned true — Save appeared over zero editable
|
|
175
|
-
// visible fields.
|
|
176
|
-
test("visible section, visible readOnly field + hidden editable field → false", () => {
|
|
177
|
-
const section: EditSectionViewModel = {
|
|
178
|
-
kind: "fields",
|
|
179
|
-
columns: 1,
|
|
180
|
-
visible: true,
|
|
181
|
-
fields: [
|
|
182
|
-
{
|
|
183
|
-
field: "a",
|
|
184
|
-
label: "A",
|
|
185
|
-
type: "text",
|
|
186
|
-
value: "",
|
|
187
|
-
visible: true,
|
|
188
|
-
readOnly: true,
|
|
189
|
-
required: false,
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
field: "b",
|
|
193
|
-
label: "B",
|
|
194
|
-
type: "text",
|
|
195
|
-
value: "",
|
|
196
|
-
visible: false,
|
|
197
|
-
readOnly: false,
|
|
198
|
-
required: false,
|
|
199
|
-
},
|
|
200
|
-
],
|
|
201
|
-
};
|
|
202
|
-
expect(hasEditableSection([section])).toBe(false);
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
const namedField = (name: string): EditFieldViewModel => ({
|
|
207
|
-
field: name,
|
|
208
|
-
label: name,
|
|
209
|
-
type: "text",
|
|
210
|
-
value: "",
|
|
211
|
-
visible: true,
|
|
212
|
-
readOnly: false,
|
|
213
|
-
required: false,
|
|
214
|
-
});
|
|
215
|
-
const namedFieldsSection = (...names: string[]): EditSectionViewModel => ({
|
|
216
|
-
kind: "fields",
|
|
217
|
-
columns: 1,
|
|
218
|
-
visible: true,
|
|
219
|
-
fields: names.map(namedField),
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
describe("filterEditSections", () => {
|
|
223
|
-
test("fieldsFilter undefined → returns the same array reference (unchanged behavior)", () => {
|
|
224
|
-
const sections = [namedFieldsSection("a", "b")];
|
|
225
|
-
expect(filterEditSections(sections, undefined)).toBe(sections);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
test("mixed section → only the filtered-in fields remain in section.fields", () => {
|
|
229
|
-
const sections = [namedFieldsSection("a", "b", "c")];
|
|
230
|
-
const result = filterEditSections(sections, ["a", "c"]);
|
|
231
|
-
expect(result).toHaveLength(1);
|
|
232
|
-
expect(result[0]?.kind).toBe("fields");
|
|
233
|
-
expect(result[0]?.kind === "fields" ? result[0].fields.map((f) => f.field) : []).toEqual([
|
|
234
|
-
"a",
|
|
235
|
-
"c",
|
|
236
|
-
]);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
test("section whose filtered field list becomes empty is dropped entirely, not rendered empty", () => {
|
|
240
|
-
const sections = [namedFieldsSection("a", "b"), namedFieldsSection("c")];
|
|
241
|
-
const result = filterEditSections(sections, ["c"]);
|
|
242
|
-
expect(result).toHaveLength(1);
|
|
243
|
-
expect(result[0]?.kind === "fields" ? result[0].fields.map((f) => f.field) : []).toEqual(["c"]);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test("extension section always survives the filter regardless of its content", () => {
|
|
247
|
-
const sections = [extensionSection, namedFieldsSection("a")];
|
|
248
|
-
const result = filterEditSections(sections, ["zzz"]);
|
|
249
|
-
expect(result).toEqual([extensionSection]);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
describe("hasIssueWithoutRenderedField", () => {
|
|
254
|
-
test("issue path matches a rendered field → false", () => {
|
|
255
|
-
expect(hasIssueWithoutRenderedField(["title"], [namedFieldsSection("title", "body")])).toBe(
|
|
256
|
-
false,
|
|
257
|
-
);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// Root-level refine issues (`version`/`id`/`changes`) never match a rendered
|
|
261
|
-
// field name — the banner is the only place that can show them.
|
|
262
|
-
test("root-level refine path (version) → true", () => {
|
|
263
|
-
expect(hasIssueWithoutRenderedField(["version"], [namedFieldsSection("title")])).toBe(true);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
test("(root) path → true", () => {
|
|
267
|
-
expect(hasIssueWithoutRenderedField(["(root)"], [namedFieldsSection("title")])).toBe(true);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
test("path matches a field hidden by its own condition → true", () => {
|
|
271
|
-
const section: EditSectionViewModel = {
|
|
272
|
-
kind: "fields",
|
|
273
|
-
columns: 1,
|
|
274
|
-
visible: true,
|
|
275
|
-
fields: [
|
|
276
|
-
{
|
|
277
|
-
field: "a",
|
|
278
|
-
label: "A",
|
|
279
|
-
type: "text",
|
|
280
|
-
value: "",
|
|
281
|
-
visible: true,
|
|
282
|
-
readOnly: false,
|
|
283
|
-
required: false,
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
field: "b",
|
|
287
|
-
label: "B",
|
|
288
|
-
type: "text",
|
|
289
|
-
value: "",
|
|
290
|
-
visible: false,
|
|
291
|
-
readOnly: false,
|
|
292
|
-
required: false,
|
|
293
|
-
},
|
|
294
|
-
],
|
|
295
|
-
};
|
|
296
|
-
expect(hasIssueWithoutRenderedField(["b"], [section])).toBe(true);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
test("dotted path (tasks.2.title) matches on its first segment → false", () => {
|
|
300
|
-
expect(hasIssueWithoutRenderedField(["tasks.2.title"], [namedFieldsSection("tasks")])).toBe(
|
|
301
|
-
false,
|
|
302
|
-
);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
test("mixed: one matched, one unmatched → true", () => {
|
|
306
|
-
expect(hasIssueWithoutRenderedField(["title", "version"], [namedFieldsSection("title")])).toBe(
|
|
307
|
-
true,
|
|
308
|
-
);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test("empty issue list → true (single-section path always shows the banner then)", () => {
|
|
312
|
-
expect(hasIssueWithoutRenderedField([], [namedFieldsSection("title")])).toBe(true);
|
|
313
|
-
});
|
|
314
|
-
});
|