@object-ui/plugin-detail 3.3.0 → 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 (134) hide show
  1. package/CHANGELOG.md +11 -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-Xuieq0ZI.js → AvatarField-Dy2XGlPz.js} +16 -15
  6. package/dist/{BooleanField-DwfMKknK.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-B0HTmmD7.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-DVTimsc3.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-rRi_P0N0.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-2CnhcWI0.js → SignatureField-B1wh3f5A.js} +18 -17
  31. package/dist/{SliderField-DEpMVXko.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.js +1912 -1728
  40. package/dist/index.umd.cjs +38 -47
  41. package/dist/packages/plugin-detail/src/DetailSection.d.ts.map +1 -1
  42. package/dist/packages/plugin-detail/src/DetailView.d.ts +24 -0
  43. package/dist/packages/plugin-detail/src/DetailView.d.ts.map +1 -1
  44. package/dist/packages/plugin-detail/src/RelatedList.d.ts +8 -0
  45. package/dist/packages/plugin-detail/src/RelatedList.d.ts.map +1 -1
  46. package/dist/packages/plugin-detail/src/useDetailTranslation.d.ts.map +1 -1
  47. package/dist/plugin-detail.css +1 -2
  48. package/dist/rolldown-runtime-DnwLefa7.js +23 -0
  49. package/dist/{src-C56Ly5uG.js → src-DyUKLvMN.js} +18271 -26636
  50. package/dist/{useFieldTranslation-CkxqyB82.js → useFieldTranslation-BRgjC1oq.js} +1 -1
  51. package/package.json +33 -11
  52. package/.turbo/turbo-build.log +0 -64
  53. package/dist/AddressField-CDLSeyNx.js +0 -93
  54. package/dist/AutoNumberField-CtE7suf5.js +0 -14
  55. package/dist/CodeField-CfwgRxx2.js +0 -22
  56. package/dist/ColorField-YKHA7dBD.js +0 -37
  57. package/dist/CurrencyField-tvS3fPAF.js +0 -51
  58. package/dist/DateField-BKqXpkOh.js +0 -21
  59. package/dist/DateTimeField-CR-nJCE7.js +0 -32
  60. package/dist/EmailField-CgvW1Qal.js +0 -28
  61. package/dist/FileField-BVAme2ML.js +0 -151
  62. package/dist/FormulaField-DamJ2VaG.js +0 -14
  63. package/dist/GeolocationField-C99z7ZBM.js +0 -113
  64. package/dist/GridField-C9JbpTx_.js +0 -51
  65. package/dist/ImageField-CDANtgVV.js +0 -75
  66. package/dist/LocationField-ZSyZ0O-h.js +0 -35
  67. package/dist/LookupField-B3hQJt95.js +0 -903
  68. package/dist/LookupField-D00z6gn_.js +0 -2
  69. package/dist/NumberField-DL2QAL7X.js +0 -26
  70. package/dist/ObjectField-JYvUnuRO.js +0 -52
  71. package/dist/PercentField-DjR6BSpw.js +0 -63
  72. package/dist/PhoneField-CX1JL-jp.js +0 -28
  73. package/dist/QRCodeField-CH_1pU6R.js +0 -72
  74. package/dist/RichTextField-CJqLWlrb.js +0 -32
  75. package/dist/SelectField-DGoDoRM_.js +0 -30
  76. package/dist/SelectField-XBVI50AD.js +0 -2
  77. package/dist/SummaryField-7ch9aqAu.js +0 -19
  78. package/dist/TextAreaField-Cmw1oXcw.js +0 -36
  79. package/dist/TextField-OTLa3p51.js +0 -29
  80. package/dist/TimeField-DKPoNWoR.js +0 -21
  81. package/dist/UrlField-CxbmzP9f.js +0 -33
  82. package/dist/UserField-ChvwUkMK.js +0 -78
  83. package/dist/VectorField-BVClL8Vw.js +0 -36
  84. package/src/ActivityTimeline.tsx +0 -184
  85. package/src/CommentAttachment.tsx +0 -194
  86. package/src/CommentInput.tsx +0 -81
  87. package/src/DetailSection.tsx +0 -340
  88. package/src/DetailTabs.tsx +0 -73
  89. package/src/DetailView.stories.tsx +0 -334
  90. package/src/DetailView.tsx +0 -823
  91. package/src/DiffView.tsx +0 -233
  92. package/src/FieldChangeItem.tsx +0 -46
  93. package/src/HeaderHighlight.tsx +0 -88
  94. package/src/InlineCreateRelated.tsx +0 -291
  95. package/src/MentionAutocomplete.tsx +0 -123
  96. package/src/PointInTimeRestore.tsx +0 -261
  97. package/src/ReactionPicker.tsx +0 -106
  98. package/src/RecordActivityTimeline.tsx +0 -433
  99. package/src/RecordChatterPanel.tsx +0 -209
  100. package/src/RecordComments.tsx +0 -217
  101. package/src/RecordNavigationEnhanced.tsx +0 -213
  102. package/src/RelatedList.tsx +0 -413
  103. package/src/RelationshipGraph.tsx +0 -286
  104. package/src/RichTextCommentInput.tsx +0 -350
  105. package/src/SectionGroup.tsx +0 -101
  106. package/src/SubscriptionToggle.tsx +0 -62
  107. package/src/ThreadedReplies.tsx +0 -163
  108. package/src/__tests__/ActivityTimeline.test.tsx +0 -119
  109. package/src/__tests__/ActivityTimelineFiltering.test.tsx +0 -143
  110. package/src/__tests__/CommentInput.test.tsx +0 -57
  111. package/src/__tests__/DetailSection.test.tsx +0 -490
  112. package/src/__tests__/DetailView.test.tsx +0 -694
  113. package/src/__tests__/FieldChangeItem.test.tsx +0 -119
  114. package/src/__tests__/HeaderHighlight.test.tsx +0 -213
  115. package/src/__tests__/MentionAutocomplete.test.tsx +0 -97
  116. package/src/__tests__/ReactionPicker.test.tsx +0 -113
  117. package/src/__tests__/RecordActivityTimeline.test.tsx +0 -395
  118. package/src/__tests__/RecordChatterPanel.test.tsx +0 -265
  119. package/src/__tests__/RecordComments.test.tsx +0 -96
  120. package/src/__tests__/RecordCommentsPinSearch.test.tsx +0 -133
  121. package/src/__tests__/RelatedList.test.tsx +0 -160
  122. package/src/__tests__/SectionGroup.test.tsx +0 -101
  123. package/src/__tests__/SubscriptionToggle.test.tsx +0 -84
  124. package/src/__tests__/ThreadedReplies.test.tsx +0 -212
  125. package/src/__tests__/autoLayout.test.ts +0 -228
  126. package/src/__tests__/phase12-features.test.tsx +0 -583
  127. package/src/__tests__/roadmap-features.test.tsx +0 -478
  128. package/src/autoLayout.ts +0 -128
  129. package/src/index.tsx +0 -149
  130. package/src/useDetailTranslation.ts +0 -183
  131. package/tsconfig.json +0 -18
  132. package/vite.config.ts +0 -57
  133. package/vitest.config.ts +0 -13
  134. package/vitest.setup.ts +0 -1
@@ -1,96 +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 } from '@testing-library/react';
11
- import '@testing-library/jest-dom';
12
- import { RecordComments } from '../RecordComments';
13
- import type { CommentEntry } from '@object-ui/types';
14
-
15
- const mockComments: CommentEntry[] = [
16
- {
17
- id: '1',
18
- text: 'This is the first comment',
19
- author: 'Alice',
20
- createdAt: '2026-02-16T08:00:00Z',
21
- },
22
- {
23
- id: '2',
24
- text: 'Second comment here',
25
- author: 'Bob',
26
- avatarUrl: 'https://example.com/bob.jpg',
27
- createdAt: '2026-02-16T09:00:00Z',
28
- },
29
- ];
30
-
31
- describe('RecordComments', () => {
32
- it('should render comments heading with count', () => {
33
- render(<RecordComments comments={mockComments} />);
34
- expect(screen.getByText('Comments')).toBeInTheDocument();
35
- expect(screen.getByText('(2)')).toBeInTheDocument();
36
- });
37
-
38
- it('should render comment authors and text', () => {
39
- render(<RecordComments comments={mockComments} />);
40
- expect(screen.getByText('Alice')).toBeInTheDocument();
41
- expect(screen.getByText('This is the first comment')).toBeInTheDocument();
42
- expect(screen.getByText('Bob')).toBeInTheDocument();
43
- expect(screen.getByText('Second comment here')).toBeInTheDocument();
44
- });
45
-
46
- it('should show "No comments yet" when empty', () => {
47
- render(<RecordComments comments={[]} />);
48
- expect(screen.getByText('No comments yet')).toBeInTheDocument();
49
- });
50
-
51
- it('should render comment input when onAddComment is provided', () => {
52
- const onAdd = vi.fn();
53
- render(<RecordComments comments={[]} onAddComment={onAdd} />);
54
- const textarea = screen.getByPlaceholderText(/Add a comment/);
55
- expect(textarea).toBeInTheDocument();
56
- });
57
-
58
- it('should not render comment input when onAddComment is not provided', () => {
59
- render(<RecordComments comments={[]} />);
60
- const textarea = screen.queryByPlaceholderText(/Add a comment/);
61
- expect(textarea).not.toBeInTheDocument();
62
- });
63
-
64
- it('should call onAddComment when submit button is clicked', async () => {
65
- const onAdd = vi.fn().mockResolvedValue(undefined);
66
- render(<RecordComments comments={[]} onAddComment={onAdd} />);
67
-
68
- const textarea = screen.getByPlaceholderText(/Add a comment/);
69
- fireEvent.change(textarea, { target: { value: 'New comment' } });
70
-
71
- const submitButton = screen.getByRole('button');
72
- fireEvent.click(submitButton);
73
-
74
- expect(onAdd).toHaveBeenCalledWith('New comment');
75
- });
76
-
77
- it('should disable submit button when textarea is empty', () => {
78
- const onAdd = vi.fn();
79
- render(<RecordComments comments={[]} onAddComment={onAdd} />);
80
- const submitButton = screen.getByRole('button');
81
- expect(submitButton).toBeDisabled();
82
- });
83
-
84
- it('should render avatar initials when no avatarUrl', () => {
85
- render(<RecordComments comments={[mockComments[0]]} />);
86
- // Alice's initial
87
- expect(screen.getByText('A')).toBeInTheDocument();
88
- });
89
-
90
- it('should render avatar image when avatarUrl is provided', () => {
91
- render(<RecordComments comments={[mockComments[1]]} />);
92
- const img = screen.getByAltText('Bob');
93
- expect(img).toBeInTheDocument();
94
- expect(img).toHaveAttribute('src', 'https://example.com/bob.jpg');
95
- });
96
- });
@@ -1,133 +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 } from '@testing-library/react';
11
- import '@testing-library/jest-dom';
12
- import { RecordComments } from '../RecordComments';
13
- import type { CommentEntry } from '@object-ui/types';
14
-
15
- const mockComments: CommentEntry[] = [
16
- {
17
- id: '1',
18
- text: 'First comment about the project',
19
- author: 'Alice',
20
- createdAt: '2026-02-16T08:00:00Z',
21
- },
22
- {
23
- id: '2',
24
- text: 'Second comment on this record',
25
- author: 'Bob',
26
- avatarUrl: 'https://example.com/bob.jpg',
27
- createdAt: '2026-02-16T09:00:00Z',
28
- pinned: true,
29
- },
30
- {
31
- id: '3',
32
- text: 'Third comment here',
33
- author: 'Charlie',
34
- createdAt: '2026-02-16T10:00:00Z',
35
- },
36
- ];
37
-
38
- describe('RecordComments - Pinning', () => {
39
- it('shows "Pinned" label on pinned comments', () => {
40
- render(<RecordComments comments={mockComments} />);
41
- expect(screen.getByText('Pinned')).toBeInTheDocument();
42
- });
43
-
44
- it('sorts pinned comments to the top', () => {
45
- const { container } = render(<RecordComments comments={mockComments} />);
46
- // Bob (pinned) should appear first
47
- const authors = container.querySelectorAll('.text-sm.font-medium');
48
- const authorTexts = Array.from(authors).map(el => el.textContent);
49
- expect(authorTexts[0]).toBe('Bob');
50
- });
51
-
52
- it('renders pin/unpin button when onTogglePin is provided', () => {
53
- const onTogglePin = vi.fn();
54
- render(<RecordComments comments={mockComments} onTogglePin={onTogglePin} />);
55
-
56
- // Should show "Unpin" for Bob (pinned) and "Pin" for others
57
- expect(screen.getByText('Unpin')).toBeInTheDocument();
58
- expect(screen.getAllByText('Pin')).toHaveLength(2);
59
- });
60
-
61
- it('does not render pin button when onTogglePin is not provided', () => {
62
- render(<RecordComments comments={mockComments} />);
63
- expect(screen.queryByText('Unpin')).not.toBeInTheDocument();
64
- expect(screen.queryAllByText('Pin')).toHaveLength(0);
65
- });
66
-
67
- it('calls onTogglePin when pin button is clicked', () => {
68
- const onTogglePin = vi.fn();
69
- render(<RecordComments comments={mockComments} onTogglePin={onTogglePin} />);
70
-
71
- const unpinBtn = screen.getByText('Unpin');
72
- fireEvent.click(unpinBtn);
73
-
74
- expect(onTogglePin).toHaveBeenCalledWith('2');
75
- });
76
- });
77
-
78
- describe('RecordComments - Search', () => {
79
- it('does not render search input when searchable is false/not provided', () => {
80
- render(<RecordComments comments={mockComments} />);
81
- expect(screen.queryByLabelText('Search comments…')).not.toBeInTheDocument();
82
- });
83
-
84
- it('renders search input when searchable is true', () => {
85
- render(<RecordComments comments={mockComments} searchable />);
86
- expect(screen.getByLabelText('Search comments…')).toBeInTheDocument();
87
- });
88
-
89
- it('filters comments by text when searching', () => {
90
- render(<RecordComments comments={mockComments} searchable />);
91
-
92
- const searchInput = screen.getByLabelText('Search comments…');
93
- fireEvent.change(searchInput, { target: { value: 'project' } });
94
-
95
- expect(screen.getByText('First comment about the project')).toBeInTheDocument();
96
- expect(screen.queryByText('Second comment on this record')).not.toBeInTheDocument();
97
- expect(screen.queryByText('Third comment here')).not.toBeInTheDocument();
98
- });
99
-
100
- it('filters comments by author when searching', () => {
101
- render(<RecordComments comments={mockComments} searchable />);
102
-
103
- const searchInput = screen.getByLabelText('Search comments…');
104
- fireEvent.change(searchInput, { target: { value: 'Charlie' } });
105
-
106
- expect(screen.getByText('Third comment here')).toBeInTheDocument();
107
- expect(screen.queryByText('First comment about the project')).not.toBeInTheDocument();
108
- });
109
-
110
- it('shows "No matching comments" when search has no results', () => {
111
- render(<RecordComments comments={mockComments} searchable />);
112
-
113
- const searchInput = screen.getByLabelText('Search comments…');
114
- fireEvent.change(searchInput, { target: { value: 'zzzznonexistent' } });
115
-
116
- expect(screen.getByText('No matching comments')).toBeInTheDocument();
117
- });
118
-
119
- it('clears search when clear button is clicked', () => {
120
- render(<RecordComments comments={mockComments} searchable />);
121
-
122
- const searchInput = screen.getByLabelText('Search comments…');
123
- fireEvent.change(searchInput, { target: { value: 'project' } });
124
-
125
- const clearBtn = screen.getByLabelText('Clear search');
126
- fireEvent.click(clearBtn);
127
-
128
- // All comments should be visible again
129
- expect(screen.getByText('First comment about the project')).toBeInTheDocument();
130
- expect(screen.getByText('Second comment on this record')).toBeInTheDocument();
131
- expect(screen.getByText('Third comment here')).toBeInTheDocument();
132
- });
133
- });
@@ -1,160 +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 { RelatedList } from '../RelatedList';
12
-
13
- describe('RelatedList', () => {
14
- it('should render title', () => {
15
- render(<RelatedList title="Contacts" type="table" data={[]} />);
16
- expect(screen.getByText('Contacts')).toBeInTheDocument();
17
- });
18
-
19
- it('should show record count badge for empty list', () => {
20
- render(<RelatedList title="Contacts" type="table" data={[]} />);
21
- expect(screen.getByText('0')).toBeInTheDocument();
22
- });
23
-
24
- it('should show record count badge for one item', () => {
25
- render(<RelatedList title="Contacts" type="table" data={[{ id: 1, name: 'Alice' }]} />);
26
- expect(screen.getByText('1')).toBeInTheDocument();
27
- });
28
-
29
- it('should show record count badge for multiple items', () => {
30
- const data = [
31
- { id: 1, name: 'Alice' },
32
- { id: 2, name: 'Bob' },
33
- ];
34
- render(<RelatedList title="Orders" type="table" data={data} />);
35
- expect(screen.getByText('2')).toBeInTheDocument();
36
- });
37
-
38
- it('should show "No related records found" for empty data', () => {
39
- render(<RelatedList title="Contacts" type="table" data={[]} />);
40
- expect(screen.getByText('No related records found')).toBeInTheDocument();
41
- });
42
-
43
- it('should render New button when onNew callback is provided', () => {
44
- const onNew = vi.fn();
45
- render(<RelatedList title="Contacts" type="table" data={[]} onNew={onNew} />);
46
- const newButton = screen.getByText('New');
47
- expect(newButton).toBeInTheDocument();
48
- fireEvent.click(newButton);
49
- expect(onNew).toHaveBeenCalledTimes(1);
50
- });
51
-
52
- it('should render View All button when onViewAll callback is provided', () => {
53
- const onViewAll = vi.fn();
54
- render(<RelatedList title="Contacts" type="table" data={[]} onViewAll={onViewAll} />);
55
- const viewAllButton = screen.getByText('View All');
56
- expect(viewAllButton).toBeInTheDocument();
57
- fireEvent.click(viewAllButton);
58
- expect(onViewAll).toHaveBeenCalledTimes(1);
59
- });
60
-
61
- it('should not render New or View All buttons when callbacks are not provided', () => {
62
- render(<RelatedList title="Contacts" type="table" data={[]} />);
63
- expect(screen.queryByText('New')).not.toBeInTheDocument();
64
- expect(screen.queryByText('View All')).not.toBeInTheDocument();
65
- });
66
-
67
- it('should auto-generate columns from object schema when api and dataSource provided but no columns', async () => {
68
- const mockDataSource = {
69
- getObjectSchema: vi.fn().mockResolvedValue({
70
- name: 'order_item',
71
- fields: {
72
- product: { type: 'string', label: 'Product' },
73
- quantity: { type: 'number', label: 'Quantity' },
74
- id: { type: 'string', label: 'ID' },
75
- },
76
- }),
77
- find: vi.fn(),
78
- } as any;
79
-
80
- const data = [{ product: 'Widget', quantity: 5 }];
81
- render(
82
- <RelatedList
83
- title="Order Items"
84
- type="table"
85
- api="order_item"
86
- data={data}
87
- dataSource={mockDataSource}
88
- />,
89
- );
90
-
91
- await waitFor(() => {
92
- expect(mockDataSource.getObjectSchema).toHaveBeenCalledWith('order_item');
93
- });
94
-
95
- // Verify columns are generated from schema (excluding id)
96
- await waitFor(() => {
97
- expect(screen.getByText('Product')).toBeInTheDocument();
98
- expect(screen.getByText('Quantity')).toBeInTheDocument();
99
- });
100
- // id should be filtered out
101
- expect(screen.queryByText('ID')).not.toBeInTheDocument();
102
- });
103
-
104
- it('should not fetch object schema when explicit columns are provided', () => {
105
- const mockDataSource = {
106
- getObjectSchema: vi.fn(),
107
- find: vi.fn(),
108
- } as any;
109
-
110
- const columns = [{ accessorKey: 'name', header: 'Name' }];
111
- render(
112
- <RelatedList
113
- title="Contacts"
114
- type="table"
115
- api="contact"
116
- data={[{ name: 'Alice' }]}
117
- columns={columns}
118
- dataSource={mockDataSource}
119
- />,
120
- );
121
-
122
- expect(mockDataSource.getObjectSchema).not.toHaveBeenCalled();
123
- });
124
-
125
- it('should render collapsed state when collapsible and defaultCollapsed are true', () => {
126
- const data = [{ id: 1, name: 'Alice' }];
127
- render(
128
- <RelatedList title="Contacts" type="table" data={data} collapsible defaultCollapsed />,
129
- );
130
- expect(screen.getByText('Contacts')).toBeInTheDocument();
131
- // Content should be hidden when collapsed
132
- expect(screen.queryByText('Alice')).not.toBeInTheDocument();
133
- });
134
-
135
- it('should expand collapsed card when header is clicked', () => {
136
- render(
137
- <RelatedList title="Contacts" type="table" data={[]} collapsible defaultCollapsed />,
138
- );
139
- // Initially collapsed - content should be hidden
140
- expect(screen.queryByText('No related records found')).not.toBeInTheDocument();
141
- // Click the header to expand
142
- fireEvent.click(screen.getByText('Contacts'));
143
- // Content should now be visible
144
- expect(screen.getByText('No related records found')).toBeInTheDocument();
145
- });
146
-
147
- it('should show content by default when collapsible is true but defaultCollapsed is false', () => {
148
- render(
149
- <RelatedList title="Contacts" type="table" data={[]} collapsible />,
150
- );
151
- expect(screen.getByText('No related records found')).toBeInTheDocument();
152
- });
153
-
154
- it('should show content when collapsible is false (default)', () => {
155
- render(
156
- <RelatedList title="Contacts" type="table" data={[]} />,
157
- );
158
- expect(screen.getByText('No related records found')).toBeInTheDocument();
159
- });
160
- });
@@ -1,101 +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 } from '@testing-library/react';
11
- import { SectionGroup } from '../SectionGroup';
12
- import type { SectionGroup as SectionGroupType } from '@object-ui/types';
13
-
14
- describe('SectionGroup', () => {
15
- const baseGroup: SectionGroupType = {
16
- title: 'Address Information',
17
- sections: [
18
- {
19
- title: 'Billing',
20
- fields: [
21
- { name: 'billingStreet', label: 'Street' },
22
- { name: 'billingCity', label: 'City' },
23
- ],
24
- },
25
- {
26
- title: 'Shipping',
27
- fields: [
28
- { name: 'shippingStreet', label: 'Street' },
29
- { name: 'shippingCity', label: 'City' },
30
- ],
31
- },
32
- ],
33
- };
34
-
35
- const data = {
36
- billingStreet: '123 Main St',
37
- billingCity: 'Springfield',
38
- shippingStreet: '456 Oak Ave',
39
- shippingCity: 'Shelbyville',
40
- };
41
-
42
- it('should render group title', () => {
43
- render(<SectionGroup group={baseGroup} data={data} />);
44
- expect(screen.getByText('Address Information')).toBeInTheDocument();
45
- });
46
-
47
- it('should render child section titles', () => {
48
- render(<SectionGroup group={baseGroup} data={data} />);
49
- expect(screen.getByText('Billing')).toBeInTheDocument();
50
- expect(screen.getByText('Shipping')).toBeInTheDocument();
51
- });
52
-
53
- it('should render field values in child sections', () => {
54
- render(<SectionGroup group={baseGroup} data={data} />);
55
- expect(screen.getByText('123 Main St')).toBeInTheDocument();
56
- expect(screen.getByText('Springfield')).toBeInTheDocument();
57
- });
58
-
59
- it('should be collapsible by default', () => {
60
- render(<SectionGroup group={baseGroup} data={data} />);
61
- // The group should render a collapsible trigger
62
- const trigger = screen.getByText('Address Information');
63
- expect(trigger.closest('[data-state]') || trigger.closest('div')).toBeTruthy();
64
- });
65
-
66
- it('should start collapsed when defaultCollapsed is true', () => {
67
- const collapsedGroup = { ...baseGroup, defaultCollapsed: true };
68
- render(<SectionGroup group={collapsedGroup} data={data} />);
69
- // Title should still be visible
70
- expect(screen.getByText('Address Information')).toBeInTheDocument();
71
- // Child section content should be hidden (in collapsed state)
72
- expect(screen.queryByText('123 Main St')).not.toBeInTheDocument();
73
- });
74
-
75
- it('should expand when clicked while collapsed', () => {
76
- const collapsedGroup = { ...baseGroup, defaultCollapsed: true };
77
- render(<SectionGroup group={collapsedGroup} data={data} />);
78
-
79
- // Click the trigger to expand
80
- fireEvent.click(screen.getByText('Address Information'));
81
-
82
- // Content should now be visible
83
- expect(screen.getByText('123 Main St')).toBeInTheDocument();
84
- });
85
-
86
- it('should render description when provided', () => {
87
- const groupWithDesc = { ...baseGroup, description: 'Billing and shipping addresses', collapsible: false };
88
- render(<SectionGroup group={groupWithDesc} data={data} />);
89
- expect(screen.getByText('Billing and shipping addresses')).toBeInTheDocument();
90
- });
91
-
92
- it('should not be collapsible when collapsible is false', () => {
93
- const nonCollapsible = { ...baseGroup, collapsible: false };
94
- const { container } = render(<SectionGroup group={nonCollapsible} data={data} />);
95
- // The top-level group heading should not have a cursor-pointer collapsible trigger
96
- const heading = screen.getByText('Address Information');
97
- const parentDiv = heading.closest('div');
98
- // Non-collapsible group renders a static border-b div, not a CollapsibleTrigger
99
- expect(parentDiv?.className).not.toContain('cursor-pointer');
100
- });
101
- });
@@ -1,84 +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 } from '@testing-library/react';
11
- import '@testing-library/jest-dom';
12
- import { SubscriptionToggle } from '../SubscriptionToggle';
13
- import type { RecordSubscription } from '@object-ui/types';
14
-
15
- describe('SubscriptionToggle', () => {
16
- it('should render subscribed state', () => {
17
- const sub: RecordSubscription = { recordId: '1', subscribed: true };
18
- render(<SubscriptionToggle subscription={sub} />);
19
- const btn = screen.getByRole('button');
20
- expect(btn).toHaveAttribute('aria-label', 'Unsubscribe from notifications');
21
- });
22
-
23
- it('should render unsubscribed state', () => {
24
- const sub: RecordSubscription = { recordId: '1', subscribed: false };
25
- render(<SubscriptionToggle subscription={sub} />);
26
- const btn = screen.getByRole('button');
27
- expect(btn).toHaveAttribute('aria-label', 'Subscribe to notifications');
28
- });
29
-
30
- it('should call onToggle with new state when clicked', () => {
31
- const onToggle = vi.fn();
32
- const sub: RecordSubscription = { recordId: '1', subscribed: false };
33
- render(<SubscriptionToggle subscription={sub} onToggle={onToggle} />);
34
- fireEvent.click(screen.getByRole('button'));
35
- expect(onToggle).toHaveBeenCalledWith(true);
36
- });
37
-
38
- it('should call onToggle with false when unsubscribing', () => {
39
- const onToggle = vi.fn();
40
- const sub: RecordSubscription = { recordId: '1', subscribed: true };
41
- render(<SubscriptionToggle subscription={sub} onToggle={onToggle} />);
42
- fireEvent.click(screen.getByRole('button'));
43
- expect(onToggle).toHaveBeenCalledWith(false);
44
- });
45
-
46
- it('should be disabled when no onToggle provided', () => {
47
- const sub: RecordSubscription = { recordId: '1', subscribed: true };
48
- render(<SubscriptionToggle subscription={sub} />);
49
- expect(screen.getByRole('button')).toBeDisabled();
50
- });
51
-
52
- it('should show title for subscribed state', () => {
53
- const sub: RecordSubscription = { recordId: '1', subscribed: true };
54
- render(<SubscriptionToggle subscription={sub} onToggle={() => {}} />);
55
- expect(screen.getByRole('button')).toHaveAttribute('title', 'Subscribed — click to unsubscribe');
56
- });
57
-
58
- it('should show title for unsubscribed state', () => {
59
- const sub: RecordSubscription = { recordId: '1', subscribed: false };
60
- render(<SubscriptionToggle subscription={sub} onToggle={() => {}} />);
61
- expect(screen.getByRole('button')).toHaveAttribute('title', 'Subscribe to notifications');
62
- });
63
-
64
- it('should be disabled during loading after click', async () => {
65
- let resolveToggle: () => void;
66
- const onToggle = vi.fn(() => new Promise<void>((r) => { resolveToggle = r; }));
67
- const sub: RecordSubscription = { recordId: '1', subscribed: false };
68
- render(<SubscriptionToggle subscription={sub} onToggle={onToggle} />);
69
- fireEvent.click(screen.getByRole('button'));
70
- expect(screen.getByRole('button')).toBeDisabled();
71
- resolveToggle!();
72
- await vi.waitFor(() => {
73
- expect(screen.getByRole('button')).not.toBeDisabled();
74
- });
75
- });
76
-
77
- it('should update aria-label based on subscribed state', () => {
78
- const sub: RecordSubscription = { recordId: '1', subscribed: true };
79
- const { rerender } = render(<SubscriptionToggle subscription={sub} onToggle={() => {}} />);
80
- expect(screen.getByRole('button')).toHaveAttribute('aria-label', 'Unsubscribe from notifications');
81
- rerender(<SubscriptionToggle subscription={{ ...sub, subscribed: false }} onToggle={() => {}} />);
82
- expect(screen.getByRole('button')).toHaveAttribute('aria-label', 'Subscribe to notifications');
83
- });
84
- });