@@ -24,7 +24,7 @@ vi.mock('@object-ui/components', async (importOriginal) => {
2424 return {
2525 ...actual ,
2626 cn : ( ...inputs : any [ ] ) => inputs . filter ( Boolean ) . join ( ' ' ) ,
27- Button : ( { children, onClick } : any ) => < button onClick = { onClick } > { children } </ button > ,
27+ Button : ( { children, onClick, title } : any ) => < button onClick = { onClick } title = { title } > { children } </ button > ,
2828 Input : ( props : any ) => < input { ...props } data-testid = "mock-input" /> ,
2929 ToggleGroup : ( { children, value, onValueChange } : any ) => < div data-value = { value } onChange = { onValueChange } > { children } </ div > ,
3030 ToggleGroupItem : ( { children, value } : any ) => < button data-value = { value } > { children } </ button > ,
@@ -50,20 +50,33 @@ vi.mock('@object-ui/components', async (importOriginal) => {
5050 } ,
5151 Empty : ( { children } : any ) => < div data-testid = "empty" > { children } </ div > ,
5252 EmptyTitle : ( { children } : any ) => < div data-testid = "empty-title" > { children } </ div > ,
53- EmptyDescription : ( { children } : any ) => < div data-testid = "empty-description" > { children } </ div >
53+ EmptyDescription : ( { children } : any ) => < div data-testid = "empty-description" > { children } </ div > ,
54+ // Simple dropdown mocks — render children directly (no Radix portal)
55+ DropdownMenu : ( { children } : any ) => < div data-testid = "dropdown-menu" > { children } </ div > ,
56+ DropdownMenuTrigger : ( { children } : any ) => < > { children } </ > ,
57+ DropdownMenuContent : ( { children } : any ) => < div data-testid = "dropdown-content" > { children } </ div > ,
58+ DropdownMenuItem : ( { children, onClick } : any ) => < button onClick = { onClick } > { children } </ button > ,
59+ DropdownMenuSeparator : ( ) => < hr /> ,
5460 } ;
5561} ) ;
5662
5763// Mock React Router
5864const mockUseParams = vi . fn ( ) ;
5965const mockSetSearchParams = vi . fn ( ) ;
66+ const mockNavigate = vi . fn ( ) ;
6067// Default mock implementation
6168let mockSearchParams = new URLSearchParams ( ) ;
6269
6370vi . mock ( 'react-router-dom' , ( ) => ( {
6471 useParams : ( ) => mockUseParams ( ) ,
6572 useSearchParams : ( ) => [ mockSearchParams , mockSetSearchParams ] ,
66- useNavigate : ( ) => vi . fn ( ) ,
73+ useNavigate : ( ) => mockNavigate ,
74+ } ) ) ;
75+
76+ // Mock auth — default to non-admin; tests can override via mockAuthUser
77+ let mockAuthUser : { id : string ; name : string ; role : string } | null = null ;
78+ vi . mock ( '@object-ui/auth' , ( ) => ( {
79+ useAuth : ( ) => ( { user : mockAuthUser } ) ,
6780} ) ) ;
6881
6982describe ( 'ObjectView Component' , ( ) => {
@@ -108,6 +121,7 @@ describe('ObjectView Component', () => {
108121 beforeEach ( ( ) => {
109122 vi . clearAllMocks ( ) ;
110123 mockSearchParams = new URLSearchParams ( ) ; // Reset params
124+ mockAuthUser = null ; // Default to non-admin
111125 } ) ;
112126
113127 it ( 'renders error when object is not found' , ( ) => {
@@ -171,4 +185,66 @@ describe('ObjectView Component', () => {
171185 expect ( screen . getByTestId ( 'object-calendar' ) ) . toBeInTheDocument ( ) ;
172186 expect ( screen . getByText ( 'Calendar View: due_date' ) ) . toBeInTheDocument ( ) ;
173187 } ) ;
188+
189+ it ( 'shows design tools for admin users without toggle' , ( ) => {
190+ mockAuthUser = { id : 'u1' , name : 'Admin' , role : 'admin' } ;
191+ mockUseParams . mockReturnValue ( { objectName : 'opportunity' } ) ;
192+
193+ render ( < ObjectView dataSource = { mockDataSource } objects = { mockObjects } onEdit = { vi . fn ( ) } /> ) ;
194+
195+ // Design tools (wrench button) should be visible directly for admin
196+ expect ( screen . getByTitle ( 'console.objectView.designTools' ) ) . toBeInTheDocument ( ) ;
197+ // No "Enter Design Mode" toggle should exist
198+ expect ( screen . queryByText ( 'console.objectView.enterDesignMode' ) ) . not . toBeInTheDocument ( ) ;
199+ } ) ;
200+
201+ it ( 'hides design tools for non-admin users' , ( ) => {
202+ mockAuthUser = { id : 'u2' , name : 'Viewer' , role : 'viewer' } ;
203+ mockUseParams . mockReturnValue ( { objectName : 'opportunity' } ) ;
204+
205+ render ( < ObjectView dataSource = { mockDataSource } objects = { mockObjects } onEdit = { vi . fn ( ) } /> ) ;
206+
207+ // Design tools button should not be visible
208+ expect ( screen . queryByTitle ( 'console.objectView.designTools' ) ) . not . toBeInTheDocument ( ) ;
209+ } ) ;
210+
211+ it ( 'hides design tools when user is not authenticated' , ( ) => {
212+ mockAuthUser = null ;
213+ mockUseParams . mockReturnValue ( { objectName : 'opportunity' } ) ;
214+
215+ render ( < ObjectView dataSource = { mockDataSource } objects = { mockObjects } onEdit = { vi . fn ( ) } /> ) ;
216+
217+ expect ( screen . queryByTitle ( 'console.objectView.designTools' ) ) . not . toBeInTheDocument ( ) ;
218+ } ) ;
219+
220+ it ( 'navigates to view designer with relative path from nested view route' , ( ) => {
221+ mockAuthUser = { id : 'u1' , name : 'Admin' , role : 'admin' } ;
222+ mockUseParams . mockReturnValue ( { objectName : 'opportunity' , viewId : 'pipeline' } ) ;
223+
224+ render ( < ObjectView dataSource = { mockDataSource } objects = { mockObjects } onEdit = { vi . fn ( ) } /> ) ;
225+
226+ // Click the design tools button, then "Add View"
227+ const designBtn = screen . getByTitle ( 'console.objectView.designTools' ) ;
228+ fireEvent . click ( designBtn ) ;
229+
230+ const addViewBtn = screen . getByText ( 'console.objectView.addView' ) ;
231+ fireEvent . click ( addViewBtn ) ;
232+
233+ expect ( mockNavigate ) . toHaveBeenCalledWith ( '../../views/new' , { relative : 'path' } ) ;
234+ } ) ;
235+
236+ it ( 'navigates to view designer with relative path from root object route' , ( ) => {
237+ mockAuthUser = { id : 'u1' , name : 'Admin' , role : 'admin' } ;
238+ mockUseParams . mockReturnValue ( { objectName : 'opportunity' } ) ;
239+
240+ render ( < ObjectView dataSource = { mockDataSource } objects = { mockObjects } onEdit = { vi . fn ( ) } /> ) ;
241+
242+ const designBtn = screen . getByTitle ( 'console.objectView.designTools' ) ;
243+ fireEvent . click ( designBtn ) ;
244+
245+ const addViewBtn = screen . getByText ( 'console.objectView.addView' ) ;
246+ fireEvent . click ( addViewBtn ) ;
247+
248+ expect ( mockNavigate ) . toHaveBeenCalledWith ( 'views/new' , { relative : 'path' } ) ;
249+ } ) ;
174250} ) ;
0 commit comments