@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,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
|
-
});
|