@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.
Files changed (209) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +21 -1
  3. package/dist/AddressField-LgHnO2Lk.js +98 -0
  4. package/dist/AutoNumberField-xZCrU0eW.js +14 -0
  5. package/dist/{AvatarField-YGj51ozd.js → AvatarField-Dy2XGlPz.js} +16 -15
  6. package/dist/{BooleanField-CaA898Tk.js → BooleanField-C0Clfka5.js} +11 -10
  7. package/dist/CodeField-CHUa07B6.js +23 -0
  8. package/dist/ColorField-vxHqEhcS.js +38 -0
  9. package/dist/CurrencyField-DiWjYWDo.js +49 -0
  10. package/dist/DateField-DGaRPM4P.js +22 -0
  11. package/dist/DateTimeField-8QnpsI_h.js +30 -0
  12. package/dist/EmailField-CkVgMbpI.js +26 -0
  13. package/dist/FileField-5UPV7uek.js +149 -0
  14. package/dist/FormulaField-BUgt6-Pi.js +17 -0
  15. package/dist/GeolocationField-D9T_jgG6.js +118 -0
  16. package/dist/GridField-DE_HwiIN.js +49 -0
  17. package/dist/ImageField-Dswnqtzf.js +73 -0
  18. package/dist/LocationField-gjqbE6na.js +36 -0
  19. package/dist/LookupField-BcS3LRKc.js +901 -0
  20. package/dist/{MasterDetailField-I1A9oEGC.js → MasterDetailField-BF6_-X3A.js} +20 -19
  21. package/dist/NumberField-Dj2rYmrS.js +27 -0
  22. package/dist/ObjectField-BymIojwd.js +50 -0
  23. package/dist/{PasswordField-DBtluGJ1.js → PasswordField-ED_Xgqz-.js} +8 -7
  24. package/dist/PercentField-D-JKOxKC.js +61 -0
  25. package/dist/PhoneField-DSCaGYq7.js +26 -0
  26. package/dist/QRCodeField-CtcOUapi.js +73 -0
  27. package/dist/{RatingField-B_Mnr63i.js → RatingField-BDnyQFWy.js} +10 -9
  28. package/dist/RichTextField-CH6LVZQA.js +33 -0
  29. package/dist/SelectField-DE4dpkMV.js +36 -0
  30. package/dist/{SignatureField-CddhEK9u.js → SignatureField-B1wh3f5A.js} +18 -17
  31. package/dist/{SliderField-Df5hMzNc.js → SliderField-zoTCKh9n.js} +2 -1
  32. package/dist/SummaryField-BeBVT6VN.js +22 -0
  33. package/dist/TextAreaField-rfUGrRxh.js +37 -0
  34. package/dist/TextField-C_yM7ATQ.js +30 -0
  35. package/dist/TimeField-BcQmBZi9.js +22 -0
  36. package/dist/UrlField-BakaF6NI.js +31 -0
  37. package/dist/UserField-zS7y3eKb.js +76 -0
  38. package/dist/VectorField-CTZ4myDM.js +34 -0
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +1741 -1504
  41. package/dist/index.umd.cjs +43 -51
  42. package/dist/packages/plugin-detail/src/ActivityTimeline.d.ts.map +1 -0
  43. package/dist/packages/plugin-detail/src/CommentAttachment.d.ts.map +1 -0
  44. package/dist/packages/plugin-detail/src/CommentInput.d.ts.map +1 -0
  45. package/dist/packages/plugin-detail/src/DetailSection.d.ts.map +1 -0
  46. package/dist/packages/plugin-detail/src/DetailTabs.d.ts.map +1 -0
  47. package/dist/packages/plugin-detail/src/DetailView.d.ts +47 -0
  48. package/dist/packages/plugin-detail/src/DetailView.d.ts.map +1 -0
  49. package/dist/packages/plugin-detail/src/DetailView.stories.d.ts.map +1 -0
  50. package/dist/packages/plugin-detail/src/DiffView.d.ts.map +1 -0
  51. package/dist/packages/plugin-detail/src/FieldChangeItem.d.ts.map +1 -0
  52. package/dist/packages/plugin-detail/src/HeaderHighlight.d.ts.map +1 -0
  53. package/dist/packages/plugin-detail/src/InlineCreateRelated.d.ts.map +1 -0
  54. package/dist/packages/plugin-detail/src/MentionAutocomplete.d.ts.map +1 -0
  55. package/dist/packages/plugin-detail/src/PointInTimeRestore.d.ts.map +1 -0
  56. package/dist/packages/plugin-detail/src/ReactionPicker.d.ts.map +1 -0
  57. package/dist/packages/plugin-detail/src/RecordActivityTimeline.d.ts.map +1 -0
  58. package/dist/packages/plugin-detail/src/RecordChatterPanel.d.ts.map +1 -0
  59. package/dist/packages/plugin-detail/src/RecordComments.d.ts.map +1 -0
  60. package/dist/packages/plugin-detail/src/RecordNavigationEnhanced.d.ts.map +1 -0
  61. package/dist/{src → packages/plugin-detail/src}/RelatedList.d.ts +8 -0
  62. package/dist/packages/plugin-detail/src/RelatedList.d.ts.map +1 -0
  63. package/dist/packages/plugin-detail/src/RelationshipGraph.d.ts.map +1 -0
  64. package/dist/packages/plugin-detail/src/RichTextCommentInput.d.ts.map +1 -0
  65. package/dist/packages/plugin-detail/src/SectionGroup.d.ts.map +1 -0
  66. package/dist/packages/plugin-detail/src/SubscriptionToggle.d.ts.map +1 -0
  67. package/dist/packages/plugin-detail/src/ThreadedReplies.d.ts.map +1 -0
  68. package/dist/packages/plugin-detail/src/autoLayout.d.ts.map +1 -0
  69. package/dist/packages/plugin-detail/src/index.d.ts.map +1 -0
  70. package/dist/packages/plugin-detail/src/useDetailTranslation.d.ts.map +1 -0
  71. package/dist/plugin-detail.css +1 -2
  72. package/dist/rolldown-runtime-DnwLefa7.js +23 -0
  73. package/dist/{src-CXr1-vVl.js → src-DyUKLvMN.js} +29788 -37711
  74. package/dist/useFieldTranslation-BRgjC1oq.js +9 -0
  75. package/package.json +34 -12
  76. package/.turbo/turbo-build.log +0 -61
  77. package/dist/AddressField-DBkEyMcG.js +0 -93
  78. package/dist/AutoNumberField-Baa191z-.js +0 -14
  79. package/dist/CodeField-BU51nl1L.js +0 -22
  80. package/dist/ColorField-Cnf6ZM7c.js +0 -37
  81. package/dist/CurrencyField-Wg-XOId2.js +0 -51
  82. package/dist/DateField-Cth1ky_m.js +0 -21
  83. package/dist/DateTimeField-B0m6FhHL.js +0 -32
  84. package/dist/EmailField-Do7qT_L_.js +0 -28
  85. package/dist/FileField-aRJAdbQb.js +0 -151
  86. package/dist/FormulaField-DTMkagFx.js +0 -14
  87. package/dist/GeolocationField-RqpHWTEv.js +0 -113
  88. package/dist/GridField-D4IH0cpo.js +0 -51
  89. package/dist/ImageField-BYCFajjr.js +0 -75
  90. package/dist/LocationField-Bi_ew9sd.js +0 -35
  91. package/dist/LookupField-BjwlDPtt.js +0 -902
  92. package/dist/NumberField-D_NucQlp.js +0 -26
  93. package/dist/ObjectField-CG-LaM65.js +0 -52
  94. package/dist/PercentField-B6sO_J3i.js +0 -63
  95. package/dist/PhoneField-CcQAWwR6.js +0 -28
  96. package/dist/QRCodeField-CEjWs-J5.js +0 -72
  97. package/dist/RichTextField-qOEJl5Ai.js +0 -32
  98. package/dist/SelectField-C8hWu3gm.js +0 -30
  99. package/dist/SummaryField-DgiFm-Cr.js +0 -19
  100. package/dist/TextAreaField-DuriTqsD.js +0 -36
  101. package/dist/TextField-CGNSl7RU.js +0 -29
  102. package/dist/TimeField-YO58ctFg.js +0 -21
  103. package/dist/UrlField-1-BMM1jn.js +0 -33
  104. package/dist/UserField-B6GqxP_S.js +0 -78
  105. package/dist/VectorField-BkEjbSt0.js +0 -36
  106. package/dist/src/ActivityTimeline.d.ts.map +0 -1
  107. package/dist/src/CommentAttachment.d.ts.map +0 -1
  108. package/dist/src/CommentInput.d.ts.map +0 -1
  109. package/dist/src/DetailSection.d.ts.map +0 -1
  110. package/dist/src/DetailTabs.d.ts.map +0 -1
  111. package/dist/src/DetailView.d.ts +0 -23
  112. package/dist/src/DetailView.d.ts.map +0 -1
  113. package/dist/src/DetailView.stories.d.ts.map +0 -1
  114. package/dist/src/DiffView.d.ts.map +0 -1
  115. package/dist/src/FieldChangeItem.d.ts.map +0 -1
  116. package/dist/src/HeaderHighlight.d.ts.map +0 -1
  117. package/dist/src/InlineCreateRelated.d.ts.map +0 -1
  118. package/dist/src/MentionAutocomplete.d.ts.map +0 -1
  119. package/dist/src/PointInTimeRestore.d.ts.map +0 -1
  120. package/dist/src/ReactionPicker.d.ts.map +0 -1
  121. package/dist/src/RecordActivityTimeline.d.ts.map +0 -1
  122. package/dist/src/RecordChatterPanel.d.ts.map +0 -1
  123. package/dist/src/RecordComments.d.ts.map +0 -1
  124. package/dist/src/RecordNavigationEnhanced.d.ts.map +0 -1
  125. package/dist/src/RelatedList.d.ts.map +0 -1
  126. package/dist/src/RelationshipGraph.d.ts.map +0 -1
  127. package/dist/src/RichTextCommentInput.d.ts.map +0 -1
  128. package/dist/src/SectionGroup.d.ts.map +0 -1
  129. package/dist/src/SubscriptionToggle.d.ts.map +0 -1
  130. package/dist/src/ThreadedReplies.d.ts.map +0 -1
  131. package/dist/src/autoLayout.d.ts.map +0 -1
  132. package/dist/src/index.d.ts.map +0 -1
  133. package/dist/src/useDetailTranslation.d.ts.map +0 -1
  134. package/src/ActivityTimeline.tsx +0 -184
  135. package/src/CommentAttachment.tsx +0 -192
  136. package/src/CommentInput.tsx +0 -81
  137. package/src/DetailSection.tsx +0 -340
  138. package/src/DetailTabs.tsx +0 -73
  139. package/src/DetailView.stories.tsx +0 -334
  140. package/src/DetailView.tsx +0 -823
  141. package/src/DiffView.tsx +0 -231
  142. package/src/FieldChangeItem.tsx +0 -46
  143. package/src/HeaderHighlight.tsx +0 -88
  144. package/src/InlineCreateRelated.tsx +0 -291
  145. package/src/MentionAutocomplete.tsx +0 -123
  146. package/src/PointInTimeRestore.tsx +0 -261
  147. package/src/ReactionPicker.tsx +0 -106
  148. package/src/RecordActivityTimeline.tsx +0 -429
  149. package/src/RecordChatterPanel.tsx +0 -207
  150. package/src/RecordComments.tsx +0 -215
  151. package/src/RecordNavigationEnhanced.tsx +0 -211
  152. package/src/RelatedList.tsx +0 -413
  153. package/src/RelationshipGraph.tsx +0 -286
  154. package/src/RichTextCommentInput.tsx +0 -348
  155. package/src/SectionGroup.tsx +0 -101
  156. package/src/SubscriptionToggle.tsx +0 -60
  157. package/src/ThreadedReplies.tsx +0 -161
  158. package/src/__tests__/ActivityTimeline.test.tsx +0 -119
  159. package/src/__tests__/ActivityTimelineFiltering.test.tsx +0 -143
  160. package/src/__tests__/CommentInput.test.tsx +0 -57
  161. package/src/__tests__/DetailSection.test.tsx +0 -490
  162. package/src/__tests__/DetailView.test.tsx +0 -694
  163. package/src/__tests__/FieldChangeItem.test.tsx +0 -119
  164. package/src/__tests__/HeaderHighlight.test.tsx +0 -213
  165. package/src/__tests__/MentionAutocomplete.test.tsx +0 -97
  166. package/src/__tests__/ReactionPicker.test.tsx +0 -113
  167. package/src/__tests__/RecordActivityTimeline.test.tsx +0 -395
  168. package/src/__tests__/RecordChatterPanel.test.tsx +0 -265
  169. package/src/__tests__/RecordComments.test.tsx +0 -96
  170. package/src/__tests__/RecordCommentsPinSearch.test.tsx +0 -133
  171. package/src/__tests__/RelatedList.test.tsx +0 -160
  172. package/src/__tests__/SectionGroup.test.tsx +0 -101
  173. package/src/__tests__/SubscriptionToggle.test.tsx +0 -84
  174. package/src/__tests__/ThreadedReplies.test.tsx +0 -212
  175. package/src/__tests__/autoLayout.test.ts +0 -228
  176. package/src/__tests__/phase12-features.test.tsx +0 -583
  177. package/src/__tests__/roadmap-features.test.tsx +0 -478
  178. package/src/autoLayout.ts +0 -128
  179. package/src/index.tsx +0 -149
  180. package/src/useDetailTranslation.ts +0 -114
  181. package/tsconfig.json +0 -18
  182. package/vite.config.ts +0 -56
  183. package/vitest.config.ts +0 -13
  184. package/vitest.setup.ts +0 -1
  185. /package/dist/{src → packages/plugin-detail/src}/ActivityTimeline.d.ts +0 -0
  186. /package/dist/{src → packages/plugin-detail/src}/CommentAttachment.d.ts +0 -0
  187. /package/dist/{src → packages/plugin-detail/src}/CommentInput.d.ts +0 -0
  188. /package/dist/{src → packages/plugin-detail/src}/DetailSection.d.ts +0 -0
  189. /package/dist/{src → packages/plugin-detail/src}/DetailTabs.d.ts +0 -0
  190. /package/dist/{src → packages/plugin-detail/src}/DetailView.stories.d.ts +0 -0
  191. /package/dist/{src → packages/plugin-detail/src}/DiffView.d.ts +0 -0
  192. /package/dist/{src → packages/plugin-detail/src}/FieldChangeItem.d.ts +0 -0
  193. /package/dist/{src → packages/plugin-detail/src}/HeaderHighlight.d.ts +0 -0
  194. /package/dist/{src → packages/plugin-detail/src}/InlineCreateRelated.d.ts +0 -0
  195. /package/dist/{src → packages/plugin-detail/src}/MentionAutocomplete.d.ts +0 -0
  196. /package/dist/{src → packages/plugin-detail/src}/PointInTimeRestore.d.ts +0 -0
  197. /package/dist/{src → packages/plugin-detail/src}/ReactionPicker.d.ts +0 -0
  198. /package/dist/{src → packages/plugin-detail/src}/RecordActivityTimeline.d.ts +0 -0
  199. /package/dist/{src → packages/plugin-detail/src}/RecordChatterPanel.d.ts +0 -0
  200. /package/dist/{src → packages/plugin-detail/src}/RecordComments.d.ts +0 -0
  201. /package/dist/{src → packages/plugin-detail/src}/RecordNavigationEnhanced.d.ts +0 -0
  202. /package/dist/{src → packages/plugin-detail/src}/RelationshipGraph.d.ts +0 -0
  203. /package/dist/{src → packages/plugin-detail/src}/RichTextCommentInput.d.ts +0 -0
  204. /package/dist/{src → packages/plugin-detail/src}/SectionGroup.d.ts +0 -0
  205. /package/dist/{src → packages/plugin-detail/src}/SubscriptionToggle.d.ts +0 -0
  206. /package/dist/{src → packages/plugin-detail/src}/ThreadedReplies.d.ts +0 -0
  207. /package/dist/{src → packages/plugin-detail/src}/autoLayout.d.ts +0 -0
  208. /package/dist/{src → packages/plugin-detail/src}/index.d.ts +0 -0
  209. /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
- });