@object-ui/plugin-detail 3.1.5 → 3.3.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/CHANGELOG.md +31 -0
- package/README.md +21 -1
- package/dist/AddressField-LgHnO2Lk.js +98 -0
- package/dist/AutoNumberField-xZCrU0eW.js +14 -0
- package/dist/{AvatarField-YGj51ozd.js → AvatarField-Dy2XGlPz.js} +16 -15
- package/dist/{BooleanField-CaA898Tk.js → BooleanField-C0Clfka5.js} +11 -10
- package/dist/CodeField-CHUa07B6.js +23 -0
- package/dist/ColorField-vxHqEhcS.js +38 -0
- package/dist/CurrencyField-DiWjYWDo.js +49 -0
- package/dist/DateField-DGaRPM4P.js +22 -0
- package/dist/DateTimeField-8QnpsI_h.js +30 -0
- package/dist/EmailField-CkVgMbpI.js +26 -0
- package/dist/FileField-5UPV7uek.js +149 -0
- package/dist/FormulaField-BUgt6-Pi.js +17 -0
- package/dist/GeolocationField-D9T_jgG6.js +118 -0
- package/dist/GridField-DE_HwiIN.js +49 -0
- package/dist/ImageField-Dswnqtzf.js +73 -0
- package/dist/LocationField-gjqbE6na.js +36 -0
- package/dist/LookupField-BcS3LRKc.js +901 -0
- package/dist/{MasterDetailField-I1A9oEGC.js → MasterDetailField-BF6_-X3A.js} +20 -19
- package/dist/NumberField-Dj2rYmrS.js +27 -0
- package/dist/ObjectField-BymIojwd.js +50 -0
- package/dist/{PasswordField-DBtluGJ1.js → PasswordField-ED_Xgqz-.js} +8 -7
- package/dist/PercentField-D-JKOxKC.js +61 -0
- package/dist/PhoneField-DSCaGYq7.js +26 -0
- package/dist/QRCodeField-CtcOUapi.js +73 -0
- package/dist/{RatingField-B_Mnr63i.js → RatingField-BDnyQFWy.js} +10 -9
- package/dist/RichTextField-CH6LVZQA.js +33 -0
- package/dist/SelectField-DE4dpkMV.js +36 -0
- package/dist/{SignatureField-CddhEK9u.js → SignatureField-B1wh3f5A.js} +18 -17
- package/dist/{SliderField-Df5hMzNc.js → SliderField-zoTCKh9n.js} +2 -1
- package/dist/SummaryField-BeBVT6VN.js +22 -0
- package/dist/TextAreaField-rfUGrRxh.js +37 -0
- package/dist/TextField-C_yM7ATQ.js +30 -0
- package/dist/TimeField-BcQmBZi9.js +22 -0
- package/dist/UrlField-BakaF6NI.js +31 -0
- package/dist/UserField-zS7y3eKb.js +76 -0
- package/dist/VectorField-CTZ4myDM.js +34 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1741 -1504
- package/dist/index.umd.cjs +43 -51
- package/dist/packages/plugin-detail/src/ActivityTimeline.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/CommentAttachment.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/CommentInput.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailSection.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailTabs.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailView.d.ts +47 -0
- package/dist/packages/plugin-detail/src/DetailView.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailView.stories.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DiffView.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/FieldChangeItem.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/HeaderHighlight.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/InlineCreateRelated.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/MentionAutocomplete.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/PointInTimeRestore.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/ReactionPicker.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordActivityTimeline.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordChatterPanel.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordComments.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordNavigationEnhanced.d.ts.map +1 -0
- package/dist/{src → packages/plugin-detail/src}/RelatedList.d.ts +8 -0
- package/dist/packages/plugin-detail/src/RelatedList.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RelationshipGraph.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RichTextCommentInput.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/SectionGroup.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/SubscriptionToggle.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/ThreadedReplies.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/autoLayout.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/index.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/useDetailTranslation.d.ts.map +1 -0
- package/dist/plugin-detail.css +1 -2
- package/dist/rolldown-runtime-DnwLefa7.js +23 -0
- package/dist/{src-CXr1-vVl.js → src-DyUKLvMN.js} +29788 -37711
- package/dist/useFieldTranslation-BRgjC1oq.js +9 -0
- package/package.json +34 -12
- package/.turbo/turbo-build.log +0 -61
- package/dist/AddressField-DBkEyMcG.js +0 -93
- package/dist/AutoNumberField-Baa191z-.js +0 -14
- package/dist/CodeField-BU51nl1L.js +0 -22
- package/dist/ColorField-Cnf6ZM7c.js +0 -37
- package/dist/CurrencyField-Wg-XOId2.js +0 -51
- package/dist/DateField-Cth1ky_m.js +0 -21
- package/dist/DateTimeField-B0m6FhHL.js +0 -32
- package/dist/EmailField-Do7qT_L_.js +0 -28
- package/dist/FileField-aRJAdbQb.js +0 -151
- package/dist/FormulaField-DTMkagFx.js +0 -14
- package/dist/GeolocationField-RqpHWTEv.js +0 -113
- package/dist/GridField-D4IH0cpo.js +0 -51
- package/dist/ImageField-BYCFajjr.js +0 -75
- package/dist/LocationField-Bi_ew9sd.js +0 -35
- package/dist/LookupField-BjwlDPtt.js +0 -902
- package/dist/NumberField-D_NucQlp.js +0 -26
- package/dist/ObjectField-CG-LaM65.js +0 -52
- package/dist/PercentField-B6sO_J3i.js +0 -63
- package/dist/PhoneField-CcQAWwR6.js +0 -28
- package/dist/QRCodeField-CEjWs-J5.js +0 -72
- package/dist/RichTextField-qOEJl5Ai.js +0 -32
- package/dist/SelectField-C8hWu3gm.js +0 -30
- package/dist/SummaryField-DgiFm-Cr.js +0 -19
- package/dist/TextAreaField-DuriTqsD.js +0 -36
- package/dist/TextField-CGNSl7RU.js +0 -29
- package/dist/TimeField-YO58ctFg.js +0 -21
- package/dist/UrlField-1-BMM1jn.js +0 -33
- package/dist/UserField-B6GqxP_S.js +0 -78
- package/dist/VectorField-BkEjbSt0.js +0 -36
- package/dist/src/ActivityTimeline.d.ts.map +0 -1
- package/dist/src/CommentAttachment.d.ts.map +0 -1
- package/dist/src/CommentInput.d.ts.map +0 -1
- package/dist/src/DetailSection.d.ts.map +0 -1
- package/dist/src/DetailTabs.d.ts.map +0 -1
- package/dist/src/DetailView.d.ts +0 -23
- package/dist/src/DetailView.d.ts.map +0 -1
- package/dist/src/DetailView.stories.d.ts.map +0 -1
- package/dist/src/DiffView.d.ts.map +0 -1
- package/dist/src/FieldChangeItem.d.ts.map +0 -1
- package/dist/src/HeaderHighlight.d.ts.map +0 -1
- package/dist/src/InlineCreateRelated.d.ts.map +0 -1
- package/dist/src/MentionAutocomplete.d.ts.map +0 -1
- package/dist/src/PointInTimeRestore.d.ts.map +0 -1
- package/dist/src/ReactionPicker.d.ts.map +0 -1
- package/dist/src/RecordActivityTimeline.d.ts.map +0 -1
- package/dist/src/RecordChatterPanel.d.ts.map +0 -1
- package/dist/src/RecordComments.d.ts.map +0 -1
- package/dist/src/RecordNavigationEnhanced.d.ts.map +0 -1
- package/dist/src/RelatedList.d.ts.map +0 -1
- package/dist/src/RelationshipGraph.d.ts.map +0 -1
- package/dist/src/RichTextCommentInput.d.ts.map +0 -1
- package/dist/src/SectionGroup.d.ts.map +0 -1
- package/dist/src/SubscriptionToggle.d.ts.map +0 -1
- package/dist/src/ThreadedReplies.d.ts.map +0 -1
- package/dist/src/autoLayout.d.ts.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/useDetailTranslation.d.ts.map +0 -1
- package/src/ActivityTimeline.tsx +0 -184
- package/src/CommentAttachment.tsx +0 -192
- package/src/CommentInput.tsx +0 -81
- package/src/DetailSection.tsx +0 -340
- package/src/DetailTabs.tsx +0 -73
- package/src/DetailView.stories.tsx +0 -334
- package/src/DetailView.tsx +0 -823
- package/src/DiffView.tsx +0 -231
- package/src/FieldChangeItem.tsx +0 -46
- package/src/HeaderHighlight.tsx +0 -88
- package/src/InlineCreateRelated.tsx +0 -291
- package/src/MentionAutocomplete.tsx +0 -123
- package/src/PointInTimeRestore.tsx +0 -261
- package/src/ReactionPicker.tsx +0 -106
- package/src/RecordActivityTimeline.tsx +0 -429
- package/src/RecordChatterPanel.tsx +0 -207
- package/src/RecordComments.tsx +0 -215
- package/src/RecordNavigationEnhanced.tsx +0 -211
- package/src/RelatedList.tsx +0 -413
- package/src/RelationshipGraph.tsx +0 -286
- package/src/RichTextCommentInput.tsx +0 -348
- package/src/SectionGroup.tsx +0 -101
- package/src/SubscriptionToggle.tsx +0 -60
- package/src/ThreadedReplies.tsx +0 -161
- package/src/__tests__/ActivityTimeline.test.tsx +0 -119
- package/src/__tests__/ActivityTimelineFiltering.test.tsx +0 -143
- package/src/__tests__/CommentInput.test.tsx +0 -57
- package/src/__tests__/DetailSection.test.tsx +0 -490
- package/src/__tests__/DetailView.test.tsx +0 -694
- package/src/__tests__/FieldChangeItem.test.tsx +0 -119
- package/src/__tests__/HeaderHighlight.test.tsx +0 -213
- package/src/__tests__/MentionAutocomplete.test.tsx +0 -97
- package/src/__tests__/ReactionPicker.test.tsx +0 -113
- package/src/__tests__/RecordActivityTimeline.test.tsx +0 -395
- package/src/__tests__/RecordChatterPanel.test.tsx +0 -265
- package/src/__tests__/RecordComments.test.tsx +0 -96
- package/src/__tests__/RecordCommentsPinSearch.test.tsx +0 -133
- package/src/__tests__/RelatedList.test.tsx +0 -160
- package/src/__tests__/SectionGroup.test.tsx +0 -101
- package/src/__tests__/SubscriptionToggle.test.tsx +0 -84
- package/src/__tests__/ThreadedReplies.test.tsx +0 -212
- package/src/__tests__/autoLayout.test.ts +0 -228
- package/src/__tests__/phase12-features.test.tsx +0 -583
- package/src/__tests__/roadmap-features.test.tsx +0 -478
- package/src/autoLayout.ts +0 -128
- package/src/index.tsx +0 -149
- package/src/useDetailTranslation.ts +0 -114
- package/tsconfig.json +0 -18
- package/vite.config.ts +0 -56
- package/vitest.config.ts +0 -13
- package/vitest.setup.ts +0 -1
- /package/dist/{src → packages/plugin-detail/src}/ActivityTimeline.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/CommentAttachment.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/CommentInput.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DetailSection.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DetailTabs.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DetailView.stories.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DiffView.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/FieldChangeItem.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/HeaderHighlight.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/InlineCreateRelated.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/MentionAutocomplete.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/PointInTimeRestore.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/ReactionPicker.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordActivityTimeline.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordChatterPanel.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordComments.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordNavigationEnhanced.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RelationshipGraph.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RichTextCommentInput.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/SectionGroup.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/SubscriptionToggle.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/ThreadedReplies.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/autoLayout.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/index.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/useDetailTranslation.d.ts +0 -0
|
@@ -1,694 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
10
|
-
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
11
|
-
import { DetailView } from '../DetailView';
|
|
12
|
-
import type { DetailViewSchema } from '@object-ui/types';
|
|
13
|
-
|
|
14
|
-
describe('DetailView', () => {
|
|
15
|
-
it('should be exported', () => {
|
|
16
|
-
expect(DetailView).toBeDefined();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should be a function', () => {
|
|
20
|
-
expect(typeof DetailView).toBe('function');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should render with basic schema', () => {
|
|
24
|
-
const schema: DetailViewSchema = {
|
|
25
|
-
type: 'detail-view',
|
|
26
|
-
title: 'Contact Details',
|
|
27
|
-
data: {
|
|
28
|
-
name: 'John Doe',
|
|
29
|
-
email: 'john@example.com',
|
|
30
|
-
},
|
|
31
|
-
fields: [
|
|
32
|
-
{ name: 'name', label: 'Name' },
|
|
33
|
-
{ name: 'email', label: 'Email' },
|
|
34
|
-
],
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const { container } = render(<DetailView schema={schema} />);
|
|
38
|
-
expect(container).toBeTruthy();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should render title', () => {
|
|
42
|
-
const schema: DetailViewSchema = {
|
|
43
|
-
type: 'detail-view',
|
|
44
|
-
title: 'Contact Details',
|
|
45
|
-
data: { name: 'John Doe' },
|
|
46
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
render(<DetailView schema={schema} />);
|
|
50
|
-
expect(screen.getByText('Contact Details')).toBeInTheDocument();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should render back button when showBack is true', () => {
|
|
54
|
-
const onBack = vi.fn();
|
|
55
|
-
const schema: DetailViewSchema = {
|
|
56
|
-
type: 'detail-view',
|
|
57
|
-
title: 'Contact Details',
|
|
58
|
-
data: { name: 'John Doe' },
|
|
59
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
60
|
-
showBack: true,
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
render(<DetailView schema={schema} onBack={onBack} />);
|
|
64
|
-
|
|
65
|
-
const buttons = screen.getAllByRole('button');
|
|
66
|
-
const backButton = buttons.find(btn =>
|
|
67
|
-
btn.querySelector('svg') !== null
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
expect(backButton).toBeTruthy();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should call onBack when back button is clicked', () => {
|
|
74
|
-
const onBack = vi.fn();
|
|
75
|
-
const schema: DetailViewSchema = {
|
|
76
|
-
type: 'detail-view',
|
|
77
|
-
title: 'Contact Details',
|
|
78
|
-
data: { name: 'John Doe' },
|
|
79
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
80
|
-
showBack: true,
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
render(<DetailView schema={schema} onBack={onBack} />);
|
|
84
|
-
|
|
85
|
-
const buttons = screen.getAllByRole('button');
|
|
86
|
-
const backButton = buttons.find(btn =>
|
|
87
|
-
btn.querySelector('svg') !== null
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
if (backButton) {
|
|
91
|
-
fireEvent.click(backButton);
|
|
92
|
-
expect(onBack).toHaveBeenCalled();
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should render edit button when showEdit is true', () => {
|
|
97
|
-
const schema: DetailViewSchema = {
|
|
98
|
-
type: 'detail-view',
|
|
99
|
-
title: 'Contact Details',
|
|
100
|
-
data: { name: 'John Doe' },
|
|
101
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
102
|
-
showEdit: true,
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
render(<DetailView schema={schema} />);
|
|
106
|
-
|
|
107
|
-
// Edit button should be present
|
|
108
|
-
const buttons = screen.getAllByRole('button');
|
|
109
|
-
expect(buttons.length).toBeGreaterThan(0);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should call onEdit when edit button is clicked', () => {
|
|
113
|
-
const onEdit = vi.fn();
|
|
114
|
-
const schema: DetailViewSchema = {
|
|
115
|
-
type: 'detail-view',
|
|
116
|
-
title: 'Contact Details',
|
|
117
|
-
data: { name: 'John Doe' },
|
|
118
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
119
|
-
showEdit: true,
|
|
120
|
-
// Disable back button to ensure it's not the first button found if using generic search
|
|
121
|
-
showBack: false
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
render(<DetailView schema={schema} onEdit={onEdit} />);
|
|
125
|
-
|
|
126
|
-
// Find button with text "Edit"
|
|
127
|
-
const editButton = screen.getByRole('button', { name: /edit/i });
|
|
128
|
-
|
|
129
|
-
if (editButton) {
|
|
130
|
-
fireEvent.click(editButton);
|
|
131
|
-
expect(onEdit).toHaveBeenCalled();
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should render delete button when showDelete is true', () => {
|
|
136
|
-
const schema: DetailViewSchema = {
|
|
137
|
-
type: 'detail-view',
|
|
138
|
-
title: 'Contact Details',
|
|
139
|
-
data: { name: 'John Doe' },
|
|
140
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
141
|
-
showDelete: true,
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
render(<DetailView schema={schema} />);
|
|
145
|
-
|
|
146
|
-
const buttons = screen.getAllByRole('button');
|
|
147
|
-
expect(buttons.length).toBeGreaterThan(0);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should render sections when provided', () => {
|
|
151
|
-
const schema: DetailViewSchema = {
|
|
152
|
-
type: 'detail-view',
|
|
153
|
-
title: 'Contact Details',
|
|
154
|
-
data: {
|
|
155
|
-
name: 'John Doe',
|
|
156
|
-
email: 'john@example.com',
|
|
157
|
-
phone: '123-456-7890',
|
|
158
|
-
},
|
|
159
|
-
sections: [
|
|
160
|
-
{
|
|
161
|
-
title: 'Basic Information',
|
|
162
|
-
fields: [
|
|
163
|
-
{ name: 'name', label: 'Name' },
|
|
164
|
-
{ name: 'email', label: 'Email' },
|
|
165
|
-
],
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
title: 'Contact Information',
|
|
169
|
-
fields: [
|
|
170
|
-
{ name: 'phone', label: 'Phone' },
|
|
171
|
-
],
|
|
172
|
-
},
|
|
173
|
-
],
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
render(<DetailView schema={schema} />);
|
|
177
|
-
|
|
178
|
-
expect(screen.getByText('Basic Information')).toBeInTheDocument();
|
|
179
|
-
expect(screen.getByText('Contact Information')).toBeInTheDocument();
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('should render tabs when provided', () => {
|
|
183
|
-
const schema: DetailViewSchema = {
|
|
184
|
-
type: 'detail-view',
|
|
185
|
-
title: 'Account Details',
|
|
186
|
-
data: { name: 'Acme Corp' },
|
|
187
|
-
tabs: [
|
|
188
|
-
{
|
|
189
|
-
key: 'details',
|
|
190
|
-
label: 'Details',
|
|
191
|
-
content: {
|
|
192
|
-
type: 'text',
|
|
193
|
-
text: 'Details content',
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
key: 'activity',
|
|
198
|
-
label: 'Activity',
|
|
199
|
-
content: {
|
|
200
|
-
type: 'text',
|
|
201
|
-
text: 'Activity content',
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
],
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
render(<DetailView schema={schema} />);
|
|
208
|
-
|
|
209
|
-
expect(screen.getByText('Details')).toBeInTheDocument();
|
|
210
|
-
expect(screen.getByText('Activity')).toBeInTheDocument();
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('should render related lists when provided', () => {
|
|
214
|
-
const schema: DetailViewSchema = {
|
|
215
|
-
type: 'detail-view',
|
|
216
|
-
title: 'Account Details',
|
|
217
|
-
data: { name: 'Acme Corp' },
|
|
218
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
219
|
-
related: [
|
|
220
|
-
{
|
|
221
|
-
title: 'Contacts',
|
|
222
|
-
type: 'table',
|
|
223
|
-
data: [],
|
|
224
|
-
},
|
|
225
|
-
],
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
render(<DetailView schema={schema} />);
|
|
229
|
-
|
|
230
|
-
expect(screen.getByText('Contacts')).toBeInTheDocument();
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('should show loading skeleton when loading is true', () => {
|
|
234
|
-
const schema: DetailViewSchema = {
|
|
235
|
-
type: 'detail-view',
|
|
236
|
-
title: 'Contact Details',
|
|
237
|
-
data: { name: 'John Doe' },
|
|
238
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
239
|
-
loading: true,
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
const { container } = render(<DetailView schema={schema} />);
|
|
243
|
-
|
|
244
|
-
// Check for skeleton elements (they typically have animate-pulse class)
|
|
245
|
-
// DetailedView uses Skeleton component which has animate-pulse class
|
|
246
|
-
const skeletons = container.querySelectorAll('.animate-pulse');
|
|
247
|
-
expect(skeletons.length).toBeGreaterThan(0);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('should render prev/next navigation when recordNavigation is provided', () => {
|
|
251
|
-
const onNavigate = vi.fn();
|
|
252
|
-
const schema: DetailViewSchema = {
|
|
253
|
-
type: 'detail-view',
|
|
254
|
-
title: 'Contact Details',
|
|
255
|
-
data: { name: 'John Doe' },
|
|
256
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
257
|
-
showBack: false,
|
|
258
|
-
recordNavigation: {
|
|
259
|
-
recordIds: ['id1', 'id2', 'id3'],
|
|
260
|
-
currentIndex: 1,
|
|
261
|
-
onNavigate,
|
|
262
|
-
},
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
render(<DetailView schema={schema} />);
|
|
266
|
-
|
|
267
|
-
// Should show position indicator
|
|
268
|
-
expect(screen.getByText('2 of 3')).toBeInTheDocument();
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it('should call onNavigate with previous record id', () => {
|
|
272
|
-
const onNavigate = vi.fn();
|
|
273
|
-
const schema: DetailViewSchema = {
|
|
274
|
-
type: 'detail-view',
|
|
275
|
-
title: 'Contact Details',
|
|
276
|
-
data: { name: 'John Doe' },
|
|
277
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
278
|
-
showBack: false,
|
|
279
|
-
recordNavigation: {
|
|
280
|
-
recordIds: ['id1', 'id2', 'id3'],
|
|
281
|
-
currentIndex: 1,
|
|
282
|
-
onNavigate,
|
|
283
|
-
},
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const { container } = render(<DetailView schema={schema} />);
|
|
287
|
-
|
|
288
|
-
// Find the prev button (first in the navigation group)
|
|
289
|
-
const navButtons = container.querySelectorAll('button');
|
|
290
|
-
// The prev button is the one that contains a chevron-left icon and is not disabled
|
|
291
|
-
const prevButton = Array.from(navButtons).find(btn =>
|
|
292
|
-
btn.querySelector('.lucide-chevron-left')
|
|
293
|
-
);
|
|
294
|
-
expect(prevButton).toBeTruthy();
|
|
295
|
-
fireEvent.click(prevButton!);
|
|
296
|
-
expect(onNavigate).toHaveBeenCalledWith('id1');
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
it('should call onNavigate with next record id', () => {
|
|
300
|
-
const onNavigate = vi.fn();
|
|
301
|
-
const schema: DetailViewSchema = {
|
|
302
|
-
type: 'detail-view',
|
|
303
|
-
title: 'Contact Details',
|
|
304
|
-
data: { name: 'John Doe' },
|
|
305
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
306
|
-
showBack: false,
|
|
307
|
-
recordNavigation: {
|
|
308
|
-
recordIds: ['id1', 'id2', 'id3'],
|
|
309
|
-
currentIndex: 1,
|
|
310
|
-
onNavigate,
|
|
311
|
-
},
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
const { container } = render(<DetailView schema={schema} />);
|
|
315
|
-
|
|
316
|
-
const nextButton = Array.from(container.querySelectorAll('button')).find(btn =>
|
|
317
|
-
btn.querySelector('.lucide-chevron-right')
|
|
318
|
-
);
|
|
319
|
-
expect(nextButton).toBeTruthy();
|
|
320
|
-
fireEvent.click(nextButton!);
|
|
321
|
-
expect(onNavigate).toHaveBeenCalledWith('id3');
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('should disable prev button at first record', () => {
|
|
325
|
-
const schema: DetailViewSchema = {
|
|
326
|
-
type: 'detail-view',
|
|
327
|
-
title: 'Contact Details',
|
|
328
|
-
data: { name: 'John Doe' },
|
|
329
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
330
|
-
showBack: false,
|
|
331
|
-
recordNavigation: {
|
|
332
|
-
recordIds: ['id1', 'id2'],
|
|
333
|
-
currentIndex: 0,
|
|
334
|
-
onNavigate: vi.fn(),
|
|
335
|
-
},
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
const { container } = render(<DetailView schema={schema} />);
|
|
339
|
-
|
|
340
|
-
const prevButton = Array.from(container.querySelectorAll('button')).find(btn =>
|
|
341
|
-
btn.querySelector('.lucide-chevron-left')
|
|
342
|
-
);
|
|
343
|
-
expect(prevButton).toBeTruthy();
|
|
344
|
-
expect(prevButton!).toBeDisabled();
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it('should disable next button at last record', () => {
|
|
348
|
-
const schema: DetailViewSchema = {
|
|
349
|
-
type: 'detail-view',
|
|
350
|
-
title: 'Contact Details',
|
|
351
|
-
data: { name: 'John Doe' },
|
|
352
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
353
|
-
showBack: false,
|
|
354
|
-
recordNavigation: {
|
|
355
|
-
recordIds: ['id1', 'id2'],
|
|
356
|
-
currentIndex: 1,
|
|
357
|
-
onNavigate: vi.fn(),
|
|
358
|
-
},
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
const { container } = render(<DetailView schema={schema} />);
|
|
362
|
-
|
|
363
|
-
const nextButton = Array.from(container.querySelectorAll('button')).find(btn =>
|
|
364
|
-
btn.querySelector('.lucide-chevron-right')
|
|
365
|
-
);
|
|
366
|
-
expect(nextButton).toBeTruthy();
|
|
367
|
-
expect(nextButton!).toBeDisabled();
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
it('should render comments section when comments are provided', () => {
|
|
371
|
-
const schema: DetailViewSchema = {
|
|
372
|
-
type: 'detail-view',
|
|
373
|
-
title: 'Contact Details',
|
|
374
|
-
data: { name: 'John Doe' },
|
|
375
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
376
|
-
comments: [
|
|
377
|
-
{
|
|
378
|
-
id: '1',
|
|
379
|
-
text: 'Great contact!',
|
|
380
|
-
author: 'Alice',
|
|
381
|
-
createdAt: '2026-02-16T08:00:00Z',
|
|
382
|
-
},
|
|
383
|
-
],
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
render(<DetailView schema={schema} />);
|
|
387
|
-
|
|
388
|
-
expect(screen.getByText('Comments')).toBeInTheDocument();
|
|
389
|
-
expect(screen.getByText('Great contact!')).toBeInTheDocument();
|
|
390
|
-
expect(screen.getByText('Alice')).toBeInTheDocument();
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
it('should render activity timeline when activities are provided', () => {
|
|
394
|
-
const schema: DetailViewSchema = {
|
|
395
|
-
type: 'detail-view',
|
|
396
|
-
title: 'Contact Details',
|
|
397
|
-
data: { name: 'John Doe' },
|
|
398
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
399
|
-
activities: [
|
|
400
|
-
{
|
|
401
|
-
id: '1',
|
|
402
|
-
type: 'create',
|
|
403
|
-
user: 'Bob',
|
|
404
|
-
timestamp: '2026-02-15T10:00:00Z',
|
|
405
|
-
},
|
|
406
|
-
{
|
|
407
|
-
id: '2',
|
|
408
|
-
type: 'field_change',
|
|
409
|
-
field: 'email',
|
|
410
|
-
oldValue: 'old@test.com',
|
|
411
|
-
newValue: 'new@test.com',
|
|
412
|
-
user: 'Alice',
|
|
413
|
-
timestamp: '2026-02-16T09:00:00Z',
|
|
414
|
-
},
|
|
415
|
-
],
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
render(<DetailView schema={schema} />);
|
|
419
|
-
|
|
420
|
-
expect(screen.getByText('Activity')).toBeInTheDocument();
|
|
421
|
-
expect(screen.getByText('Bob')).toBeInTheDocument();
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
it('should render primaryField value as header title', () => {
|
|
425
|
-
const schema: DetailViewSchema = {
|
|
426
|
-
type: 'detail-view',
|
|
427
|
-
title: 'Contact',
|
|
428
|
-
primaryField: 'name',
|
|
429
|
-
data: { name: 'John Doe', email: 'john@example.com' },
|
|
430
|
-
fields: [
|
|
431
|
-
{ name: 'name', label: 'Name' },
|
|
432
|
-
{ name: 'email', label: 'Email' },
|
|
433
|
-
],
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
render(<DetailView schema={schema} />);
|
|
437
|
-
// The h1 heading should show the primary field value
|
|
438
|
-
const heading = screen.getByRole('heading', { level: 1 });
|
|
439
|
-
expect(heading.textContent).toBe('John Doe');
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
it('should fall back to title when primaryField value is empty', () => {
|
|
443
|
-
const schema: DetailViewSchema = {
|
|
444
|
-
type: 'detail-view',
|
|
445
|
-
title: 'Contact',
|
|
446
|
-
primaryField: 'name',
|
|
447
|
-
data: { email: 'john@example.com' },
|
|
448
|
-
fields: [
|
|
449
|
-
{ name: 'name', label: 'Name' },
|
|
450
|
-
{ name: 'email', label: 'Email' },
|
|
451
|
-
],
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
render(<DetailView schema={schema} />);
|
|
455
|
-
const heading = screen.getByRole('heading', { level: 1 });
|
|
456
|
-
expect(heading.textContent).toBe('Contact');
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
it('should render summaryFields as badges', () => {
|
|
460
|
-
const schema: DetailViewSchema = {
|
|
461
|
-
type: 'detail-view',
|
|
462
|
-
title: 'Contact',
|
|
463
|
-
primaryField: 'name',
|
|
464
|
-
summaryFields: ['status', 'department'],
|
|
465
|
-
data: { name: 'Jane Doe', status: 'Active', department: 'Engineering' },
|
|
466
|
-
fields: [
|
|
467
|
-
{ name: 'name', label: 'Name' },
|
|
468
|
-
{ name: 'status', label: 'Status' },
|
|
469
|
-
{ name: 'department', label: 'Department' },
|
|
470
|
-
],
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
render(<DetailView schema={schema} />);
|
|
474
|
-
const heading = screen.getByRole('heading', { level: 1 });
|
|
475
|
-
expect(heading.textContent).toBe('Jane Doe');
|
|
476
|
-
// Summary badges should be present (they appear both as badges and as field values)
|
|
477
|
-
const activeElements = screen.getAllByText('Active');
|
|
478
|
-
expect(activeElements.length).toBeGreaterThanOrEqual(1);
|
|
479
|
-
const engElements = screen.getAllByText('Engineering');
|
|
480
|
-
expect(engElements.length).toBeGreaterThanOrEqual(1);
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
it('should not render summary badge for empty values', () => {
|
|
484
|
-
const schema: DetailViewSchema = {
|
|
485
|
-
type: 'detail-view',
|
|
486
|
-
title: 'Contact',
|
|
487
|
-
summaryFields: ['status', 'department'],
|
|
488
|
-
data: { name: 'Jane Doe', status: 'Active', department: null },
|
|
489
|
-
fields: [
|
|
490
|
-
{ name: 'name', label: 'Name' },
|
|
491
|
-
{ name: 'status', label: 'Status' },
|
|
492
|
-
],
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
const { container } = render(<DetailView schema={schema} />);
|
|
496
|
-
// The header area should have a badge for 'Active' but not 'department'
|
|
497
|
-
// Find badges within the header
|
|
498
|
-
const headerBadges = container.querySelectorAll('.border-b .rounded-full');
|
|
499
|
-
const badgeTexts = Array.from(headerBadges).map(b => b.textContent);
|
|
500
|
-
expect(badgeTexts).toContain('Active');
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
it('should show "Record not found" when data is null after loading', async () => {
|
|
504
|
-
const mockDataSource = {
|
|
505
|
-
findOne: vi.fn().mockResolvedValue(null),
|
|
506
|
-
} as any;
|
|
507
|
-
|
|
508
|
-
const schema: DetailViewSchema = {
|
|
509
|
-
type: 'detail-view',
|
|
510
|
-
title: 'Contact Details',
|
|
511
|
-
objectName: 'contact',
|
|
512
|
-
resourceId: 'nonexistent-id',
|
|
513
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
514
|
-
};
|
|
515
|
-
|
|
516
|
-
const { findByText } = render(<DetailView schema={schema} dataSource={mockDataSource} />);
|
|
517
|
-
expect(await findByText('Record not found')).toBeInTheDocument();
|
|
518
|
-
expect(await findByText(/does not exist or may have been deleted/)).toBeInTheDocument();
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
it('should show "Go back" button in "Record not found" state when showBack is true', async () => {
|
|
522
|
-
const mockDataSource = {
|
|
523
|
-
findOne: vi.fn().mockResolvedValue(null),
|
|
524
|
-
} as any;
|
|
525
|
-
const onBack = vi.fn();
|
|
526
|
-
|
|
527
|
-
const schema: DetailViewSchema = {
|
|
528
|
-
type: 'detail-view',
|
|
529
|
-
title: 'Contact Details',
|
|
530
|
-
objectName: 'contact',
|
|
531
|
-
resourceId: 'nonexistent-id',
|
|
532
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
533
|
-
showBack: true,
|
|
534
|
-
};
|
|
535
|
-
|
|
536
|
-
const { findByText } = render(<DetailView schema={schema} dataSource={mockDataSource} onBack={onBack} />);
|
|
537
|
-
const goBackBtn = await findByText('Go back');
|
|
538
|
-
fireEvent.click(goBackBtn);
|
|
539
|
-
expect(onBack).toHaveBeenCalled();
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
it('should try fallback with alternate ID when first findOne throws an error', async () => {
|
|
543
|
-
let callCount = 0;
|
|
544
|
-
const mockDataSource = {
|
|
545
|
-
findOne: vi.fn().mockImplementation((_obj: string, id: string) => {
|
|
546
|
-
callCount++;
|
|
547
|
-
if (callCount === 1) {
|
|
548
|
-
// First call throws (simulate server error)
|
|
549
|
-
return Promise.reject(new Error('Server error'));
|
|
550
|
-
}
|
|
551
|
-
// Second call (fallback) succeeds
|
|
552
|
-
return Promise.resolve({ name: 'Alice' });
|
|
553
|
-
}),
|
|
554
|
-
} as any;
|
|
555
|
-
|
|
556
|
-
const schema: DetailViewSchema = {
|
|
557
|
-
type: 'detail-view',
|
|
558
|
-
title: 'Contact Details',
|
|
559
|
-
objectName: 'contact',
|
|
560
|
-
resourceId: 'contact-123',
|
|
561
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
const { findByText } = render(<DetailView schema={schema} dataSource={mockDataSource} />);
|
|
565
|
-
// The fallback should find the record using the stripped ID
|
|
566
|
-
expect(await findByText('Alice')).toBeInTheDocument();
|
|
567
|
-
// findOne should be called twice: first with original ID, then with stripped prefix
|
|
568
|
-
expect(mockDataSource.findOne).toHaveBeenCalledTimes(2);
|
|
569
|
-
expect(mockDataSource.findOne).toHaveBeenNthCalledWith(1, 'contact', 'contact-123');
|
|
570
|
-
expect(mockDataSource.findOne).toHaveBeenNthCalledWith(2, 'contact', '123');
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
it('should call findOne with $expand when objectSchema has lookup fields', async () => {
|
|
574
|
-
const mockDataSource = {
|
|
575
|
-
getObjectSchema: vi.fn().mockResolvedValue({
|
|
576
|
-
fields: {
|
|
577
|
-
name: { type: 'text' },
|
|
578
|
-
customer: { type: 'lookup', reference_to: 'contact' },
|
|
579
|
-
account: { type: 'master_detail', reference_to: 'account' },
|
|
580
|
-
},
|
|
581
|
-
}),
|
|
582
|
-
findOne: vi.fn().mockResolvedValue({ name: 'Order 1', customer: { name: 'Alice' }, account: { name: 'Acme' } }),
|
|
583
|
-
} as any;
|
|
584
|
-
|
|
585
|
-
const schema: DetailViewSchema = {
|
|
586
|
-
type: 'detail-view',
|
|
587
|
-
title: 'Order Details',
|
|
588
|
-
objectName: 'order',
|
|
589
|
-
resourceId: 'order-1',
|
|
590
|
-
fields: [
|
|
591
|
-
{ name: 'name', label: 'Name' },
|
|
592
|
-
{ name: 'customer', label: 'Customer' },
|
|
593
|
-
{ name: 'account', label: 'Account' },
|
|
594
|
-
],
|
|
595
|
-
};
|
|
596
|
-
|
|
597
|
-
render(<DetailView schema={schema} dataSource={mockDataSource} />);
|
|
598
|
-
|
|
599
|
-
await waitFor(() => {
|
|
600
|
-
expect(mockDataSource.getObjectSchema).toHaveBeenCalledWith('order');
|
|
601
|
-
expect(mockDataSource.findOne).toHaveBeenCalledWith(
|
|
602
|
-
'order',
|
|
603
|
-
'order-1',
|
|
604
|
-
expect.objectContaining({ $expand: expect.arrayContaining(['customer', 'account']) }),
|
|
605
|
-
);
|
|
606
|
-
});
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
it('should call findOne without $expand when objectSchema has no lookup fields', async () => {
|
|
610
|
-
const mockDataSource = {
|
|
611
|
-
getObjectSchema: vi.fn().mockResolvedValue({
|
|
612
|
-
fields: {
|
|
613
|
-
name: { type: 'text' },
|
|
614
|
-
email: { type: 'text' },
|
|
615
|
-
},
|
|
616
|
-
}),
|
|
617
|
-
findOne: vi.fn().mockResolvedValue({ name: 'Alice', email: 'alice@example.com' }),
|
|
618
|
-
} as any;
|
|
619
|
-
|
|
620
|
-
const schema: DetailViewSchema = {
|
|
621
|
-
type: 'detail-view',
|
|
622
|
-
title: 'Contact Details',
|
|
623
|
-
objectName: 'contact',
|
|
624
|
-
resourceId: 'c1',
|
|
625
|
-
fields: [
|
|
626
|
-
{ name: 'name', label: 'Name' },
|
|
627
|
-
{ name: 'email', label: 'Email' },
|
|
628
|
-
],
|
|
629
|
-
};
|
|
630
|
-
|
|
631
|
-
render(<DetailView schema={schema} dataSource={mockDataSource} />);
|
|
632
|
-
|
|
633
|
-
await waitFor(() => {
|
|
634
|
-
// When no lookup fields exist, findOne should be called without $expand params
|
|
635
|
-
expect(mockDataSource.findOne).toHaveBeenCalledWith('contact', 'c1');
|
|
636
|
-
});
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
it('should still work when getObjectSchema is not available on dataSource', async () => {
|
|
640
|
-
const mockDataSource = {
|
|
641
|
-
findOne: vi.fn().mockResolvedValue({ name: 'Bob' }),
|
|
642
|
-
} as any;
|
|
643
|
-
|
|
644
|
-
const schema: DetailViewSchema = {
|
|
645
|
-
type: 'detail-view',
|
|
646
|
-
title: 'Contact Details',
|
|
647
|
-
objectName: 'contact',
|
|
648
|
-
resourceId: 'c1',
|
|
649
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
const { findByText } = render(<DetailView schema={schema} dataSource={mockDataSource} />);
|
|
653
|
-
expect(await findByText('Bob')).toBeInTheDocument();
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
it('should use i18n fallback for "Record not found" text', async () => {
|
|
657
|
-
const mockDataSource = {
|
|
658
|
-
findOne: vi.fn().mockResolvedValue(null),
|
|
659
|
-
} as any;
|
|
660
|
-
|
|
661
|
-
const schema: DetailViewSchema = {
|
|
662
|
-
type: 'detail-view',
|
|
663
|
-
title: 'Contact Details',
|
|
664
|
-
objectName: 'contact',
|
|
665
|
-
resourceId: 'nonexistent-id',
|
|
666
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
667
|
-
};
|
|
668
|
-
|
|
669
|
-
const { findByText } = render(<DetailView schema={schema} dataSource={mockDataSource} />);
|
|
670
|
-
// These use the default English translations from useDetailTranslation fallback
|
|
671
|
-
expect(await findByText('Record not found')).toBeInTheDocument();
|
|
672
|
-
expect(await findByText('Go back')).toBeInTheDocument();
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
it('should use i18n fallback for related section heading', () => {
|
|
676
|
-
const schema: DetailViewSchema = {
|
|
677
|
-
type: 'detail-view',
|
|
678
|
-
title: 'Account Details',
|
|
679
|
-
data: { name: 'Acme Corp' },
|
|
680
|
-
fields: [{ name: 'name', label: 'Name' }],
|
|
681
|
-
related: [
|
|
682
|
-
{
|
|
683
|
-
title: 'Contacts',
|
|
684
|
-
type: 'table',
|
|
685
|
-
data: [],
|
|
686
|
-
},
|
|
687
|
-
],
|
|
688
|
-
};
|
|
689
|
-
|
|
690
|
-
render(<DetailView schema={schema} />);
|
|
691
|
-
// The "Related" heading uses t('detail.related')
|
|
692
|
-
expect(screen.getByText('Related')).toBeInTheDocument();
|
|
693
|
-
});
|
|
694
|
-
});
|