@@ -17,6 +17,27 @@ vi.mock('@object-ui/plugin-calendar', () => ({
1717 ObjectCalendar : ( props : any ) => < div data-testid = "object-calendar" > Calendar View: { props . schema . dateField } </ div >
1818} ) ) ;
1919
20+ // Mock ListView to a simple component that renders schema properties as test IDs
21+ // This isolates the config panel → view data flow from ListView's internal async effects
22+ vi . mock ( '@object-ui/plugin-list' , ( ) => ( {
23+ ListView : ( props : any ) => {
24+ const viewType = props . schema ?. viewType || 'grid' ;
25+ return (
26+ < div data-testid = "list-view" data-view-type = { viewType } >
27+ { viewType === 'grid' && < div data-testid = "object-grid" > Grid View: { props . schema ?. objectName } </ div > }
28+ { viewType === 'kanban' && < div data-testid = "object-kanban" > Kanban View: { props . schema ?. options ?. kanban ?. groupField || props . schema ?. groupBy } </ div > }
29+ { viewType === 'calendar' && < div data-testid = "object-calendar" > Calendar View: { props . schema ?. options ?. calendar ?. startDateField || props . schema ?. startDateField } </ div > }
30+ { props . schema ?. showRecordCount && < div data-testid = "schema-showRecordCount" > showRecordCount</ div > }
31+ { props . schema ?. allowPrinting && < div data-testid = "schema-allowPrinting" > allowPrinting</ div > }
32+ { props . schema ?. navigation ?. mode && < div data-testid = "schema-navigation-mode" > { props . schema . navigation . mode } </ div > }
33+ { props . schema ?. selection ?. type && < div data-testid = "schema-selection-type" > { props . schema . selection . type } </ div > }
34+ { props . schema ?. addRecord ?. enabled && < div data-testid = "schema-addRecord-enabled" > addRecord</ div > }
35+ { props . schema ?. addRecordViaForm && < div data-testid = "schema-addRecordViaForm" > addRecordViaForm</ div > }
36+ </ div >
37+ ) ;
38+ } ,
39+ } ) ) ;
40+
2041vi . mock ( '@object-ui/components' , async ( importOriginal ) => {
2142 const React = await import ( 'react' ) ;
2243 const MockTabsContext = React . createContext ( { onValueChange : ( _v : any ) => { } } ) ;
@@ -543,4 +564,200 @@ describe('ObjectView Component', () => {
543564 expect ( breadcrumbItems . length ) . toBeGreaterThanOrEqual ( 1 ) ;
544565 } ) ;
545566 } ) ;
567+
568+ it ( 'does not remount PluginObjectView on config panel changes (no key={refreshKey})' , async ( ) => {
569+ mockAuthUser = { id : 'u1' , name : 'Admin' , role : 'admin' } ;
570+ mockUseParams . mockReturnValue ( { objectName : 'opportunity' } ) ;
571+
572+ render ( < ObjectView dataSource = { mockDataSource } objects = { mockObjects } onEdit = { vi . fn ( ) } /> ) ;
573+
574+ // The grid should be rendered initially
575+ expect ( screen . getByTestId ( 'object-grid' ) ) . toBeInTheDocument ( ) ;
576+
577+ // Open config panel
578+ fireEvent . click ( screen . getByTitle ( 'console.objectView.designTools' ) ) ;
579+ fireEvent . click ( screen . getByText ( 'console.objectView.editView' ) ) ;
580+
581+ // Make a change
582+ const titleInput = await screen . findByDisplayValue ( 'All Opportunities' ) ;
583+ fireEvent . change ( titleInput , { target : { value : 'Changed Live' } } ) ;
584+
585+ // The breadcrumb updates immediately (live preview) — this verifies that
586+ // viewDraft → activeView data flow propagates config changes without save.
587+ await vi . waitFor ( ( ) => {
588+ expect ( screen . getByText ( 'Changed Live' ) ) . toBeInTheDocument ( ) ;
589+ } ) ;
590+
591+ // Grid persists after config change (no remount)
592+ expect ( screen . getByTestId ( 'object-grid' ) ) . toBeInTheDocument ( ) ;
593+ } ) ;
594+
595+ it ( 'propagates showRecordCount toggle to ListView schema in real-time' , async ( ) => {
596+ mockAuthUser = { id : 'u1' , name : 'Admin' , role : 'admin' } ;
597+ mockUseParams . mockReturnValue ( { objectName : 'opportunity' } ) ;
598+
599+ render ( < ObjectView dataSource = { mockDataSource } objects = { mockObjects } onEdit = { vi . fn ( ) } /> ) ;
600+
601+ // showRecordCount should not be set initially (default is not explicitly true)
602+ expect ( screen . queryByTestId ( 'schema-showRecordCount' ) ) . not . toBeInTheDocument ( ) ;
603+
604+ // Open config panel
605+ fireEvent . click ( screen . getByTitle ( 'console.objectView.designTools' ) ) ;
606+ fireEvent . click ( screen . getByText ( 'console.objectView.editView' ) ) ;
607+
608+ // Toggle showRecordCount on
609+ const recordCountSwitch = screen . getByTestId ( 'toggle-showRecordCount' ) ;
610+ fireEvent . click ( recordCountSwitch ) ;
611+
612+ // Verify the schema property propagated to ListView immediately (live preview)
613+ await vi . waitFor ( ( ) => {
614+ expect ( screen . getByTestId ( 'schema-showRecordCount' ) ) . toBeInTheDocument ( ) ;
615+ } ) ;
616+ } ) ;
617+
618+ it ( 'propagates allowPrinting toggle to ListView schema in real-time' , async ( ) => {
619+ mockAuthUser = { id : 'u1' , name : 'Admin' , role : 'admin' } ;
620+ mockUseParams . mockReturnValue ( { objectName : 'opportunity' } ) ;
621+
622+ render ( < ObjectView dataSource = { mockDataSource } objects = { mockObjects } onEdit = { vi . fn ( ) } /> ) ;
623+
624+ // allowPrinting should not be set initially
625+ expect ( screen . queryByTestId ( 'schema-allowPrinting' ) ) . not . toBeInTheDocument ( ) ;
626+
627+ // Open config panel
628+ fireEvent . click ( screen . getByTitle ( 'console.objectView.designTools' ) ) ;
629+ fireEvent . click ( screen . getByText ( 'console.objectView.editView' ) ) ;
630+
631+ // Toggle allowPrinting on
632+ const printSwitch = screen . getByTestId ( 'toggle-allowPrinting' ) ;
633+ fireEvent . click ( printSwitch ) ;
634+
635+ // Verify the schema property propagated to ListView immediately (live preview)
636+ await vi . waitFor ( ( ) => {
637+ expect ( screen . getByTestId ( 'schema-allowPrinting' ) ) . toBeInTheDocument ( ) ;
638+ } ) ;
639+ } ) ;
640+
641+ it ( 'propagates multiple config changes without requiring save' , async ( ) => {
642+ mockAuthUser = { id : 'u1' , name : 'Admin' , role : 'admin' } ;
643+ mockUseParams . mockReturnValue ( { objectName : 'opportunity' } ) ;
644+
645+ render ( < ObjectView dataSource = { mockDataSource } objects = { mockObjects } onEdit = { vi . fn ( ) } /> ) ;
646+
647+ // Open config panel
648+ fireEvent . click ( screen . getByTitle ( 'console.objectView.designTools' ) ) ;
649+ fireEvent . click ( screen . getByText ( 'console.objectView.editView' ) ) ;
650+
651+ // Toggle showSearch off
652+ const searchSwitch = screen . getByTestId ( 'toggle-showSearch' ) ;
653+ fireEvent . click ( searchSwitch ) ;
654+
655+ // Toggle showSort off
656+ const sortSwitch = screen . getByTestId ( 'toggle-showSort' ) ;
657+ fireEvent . click ( sortSwitch ) ;
658+
659+ // Both should reflect changes immediately without save
660+ await vi . waitFor ( ( ) => {
661+ expect ( screen . getByTestId ( 'toggle-showSearch' ) . getAttribute ( 'aria-checked' ) ) . toBe ( 'false' ) ;
662+ expect ( screen . getByTestId ( 'toggle-showSort' ) . getAttribute ( 'aria-checked' ) ) . toBe ( 'false' ) ;
663+ } ) ;
664+
665+ // The grid should still be rendered (live preview, no remount)
666+ expect ( screen . getByTestId ( 'object-grid' ) ) . toBeInTheDocument ( ) ;
667+ } ) ;
668+
669+ it ( 'uses activeView.navigation for detail overlay with priority over objectDef' , ( ) => {
670+ mockAuthUser = { id : 'u1' , name : 'Admin' , role : 'admin' } ;
671+ const objectsWithNav = [
672+ {
673+ ...mockObjects [ 0 ] ,
674+ navigation : { mode : 'drawer' as const } ,
675+ listViews : {
676+ all : {
677+ label : 'All Opportunities' ,
678+ type : 'grid' ,
679+ columns : [ 'name' , 'stage' ] ,
680+ navigation : { mode : 'modal' as const } ,
681+ } ,
682+ pipeline : { label : 'Pipeline' , type : 'kanban' , kanban : { groupField : 'stage' } , columns : [ 'name' ] }
683+ }
684+ }
685+ ] ;
686+ mockUseParams . mockReturnValue ( { objectName : 'opportunity' } ) ;
687+
688+ // Render the component — activeView.navigation should override objectDef.navigation
689+ render ( < ObjectView dataSource = { mockDataSource } objects = { objectsWithNav } onEdit = { vi . fn ( ) } /> ) ;
690+
691+ // The component should render without errors and ListView should receive
692+ // the view-level navigation config (modal) instead of object-level (drawer)
693+ expect ( screen . getByTestId ( 'object-grid' ) ) . toBeInTheDocument ( ) ;
694+ expect ( screen . getByTestId ( 'schema-navigation-mode' ) ) . toHaveTextContent ( 'modal' ) ;
695+ } ) ;
696+
697+ it ( 'propagates selection mode change to ListView schema in real-time' , async ( ) => {
698+ mockAuthUser = { id : 'u1' , name : 'Admin' , role : 'admin' } ;
699+ mockUseParams . mockReturnValue ( { objectName : 'opportunity' } ) ;
700+
701+ render ( < ObjectView dataSource = { mockDataSource } objects = { mockObjects } onEdit = { vi . fn ( ) } /> ) ;
702+
703+ // Open config panel
704+ fireEvent . click ( screen . getByTitle ( 'console.objectView.designTools' ) ) ;
705+ fireEvent . click ( screen . getByText ( 'console.objectView.editView' ) ) ;
706+
707+ // Change selection mode to 'single'
708+ const selectionSelect = screen . getByTestId ( 'select-selection-type' ) ;
709+ fireEvent . change ( selectionSelect , { target : { value : 'single' } } ) ;
710+
711+ // Verify the selection type propagated to ListView immediately (live preview)
712+ await vi . waitFor ( ( ) => {
713+ expect ( screen . getByTestId ( 'schema-selection-type' ) ) . toHaveTextContent ( 'single' ) ;
714+ } ) ;
715+ } ) ;
716+
717+ it ( 'propagates addRecord toggle to ListView schema in real-time' , async ( ) => {
718+ mockAuthUser = { id : 'u1' , name : 'Admin' , role : 'admin' } ;
719+ mockUseParams . mockReturnValue ( { objectName : 'opportunity' } ) ;
720+
721+ render ( < ObjectView dataSource = { mockDataSource } objects = { mockObjects } onEdit = { vi . fn ( ) } /> ) ;
722+
723+ // addRecord should not be enabled initially
724+ expect ( screen . queryByTestId ( 'schema-addRecord-enabled' ) ) . not . toBeInTheDocument ( ) ;
725+
726+ // Open config panel
727+ fireEvent . click ( screen . getByTitle ( 'console.objectView.designTools' ) ) ;
728+ fireEvent . click ( screen . getByText ( 'console.objectView.editView' ) ) ;
729+
730+ // Toggle addRecord on
731+ const addRecordSwitch = screen . getByTestId ( 'toggle-addRecord-enabled' ) ;
732+ fireEvent . click ( addRecordSwitch ) ;
733+
734+ // Verify addRecord and addRecordViaForm propagated to ListView immediately
735+ await vi . waitFor ( ( ) => {
736+ expect ( screen . getByTestId ( 'schema-addRecord-enabled' ) ) . toBeInTheDocument ( ) ;
737+ expect ( screen . getByTestId ( 'schema-addRecordViaForm' ) ) . toBeInTheDocument ( ) ;
738+ } ) ;
739+ } ) ;
740+
741+ it ( 'propagates navigation mode change from config panel to ListView schema in real-time' , async ( ) => {
742+ mockAuthUser = { id : 'u1' , name : 'Admin' , role : 'admin' } ;
743+ mockUseParams . mockReturnValue ( { objectName : 'opportunity' } ) ;
744+
745+ render ( < ObjectView dataSource = { mockDataSource } objects = { mockObjects } onEdit = { vi . fn ( ) } /> ) ;
746+
747+ // navigation mode should not be set initially (no explicit mode on default view)
748+ expect ( screen . queryByTestId ( 'schema-navigation-mode' ) ) . not . toBeInTheDocument ( ) ;
749+
750+ // Open config panel
751+ fireEvent . click ( screen . getByTitle ( 'console.objectView.designTools' ) ) ;
752+ fireEvent . click ( screen . getByText ( 'console.objectView.editView' ) ) ;
753+
754+ // Change navigation mode to 'modal'
755+ const navSelect = screen . getByTestId ( 'select-navigation-mode' ) ;
756+ fireEvent . change ( navSelect , { target : { value : 'modal' } } ) ;
757+
758+ // Verify navigation mode propagated to ListView schema immediately (live preview)
759+ await vi . waitFor ( ( ) => {
760+ expect ( screen . getByTestId ( 'schema-navigation-mode' ) ) . toHaveTextContent ( 'modal' ) ;
761+ } ) ;
762+ } ) ;
546763} ) ;
0 commit comments