@@ -763,4 +763,233 @@ describe('ViewConfigPanel', () => {
763763 const fb = screen . getByTestId ( 'mock-filter-builder' ) ;
764764 expect ( fb ) . toHaveAttribute ( 'data-field-count' , '5' ) ;
765765 } ) ;
766+
767+ // ── Create mode (mode="create") tests ──
768+
769+ it ( 'renders in create mode with "Create View" title' , ( ) => {
770+ render (
771+ < ViewConfigPanel
772+ open = { true }
773+ onClose = { vi . fn ( ) }
774+ mode = "create"
775+ activeView = { mockActiveView }
776+ objectDef = { mockObjectDef }
777+ />
778+ ) ;
779+
780+ const panel = screen . getByTestId ( 'view-config-panel' ) ;
781+ expect ( panel ) . toHaveAttribute ( 'aria-label' , 'console.objectView.createView' ) ;
782+ } ) ;
783+
784+ it ( 'shows save/discard footer immediately in create mode' , ( ) => {
785+ render (
786+ < ViewConfigPanel
787+ open = { true }
788+ onClose = { vi . fn ( ) }
789+ mode = "create"
790+ activeView = { mockActiveView }
791+ objectDef = { mockObjectDef }
792+ />
793+ ) ;
794+
795+ expect ( screen . getByTestId ( 'view-config-footer' ) ) . toBeInTheDocument ( ) ;
796+ } ) ;
797+
798+ it ( 'calls onCreate (not onSave) when saving in create mode' , ( ) => {
799+ const onCreate = vi . fn ( ) ;
800+ const onSave = vi . fn ( ) ;
801+ render (
802+ < ViewConfigPanel
803+ open = { true }
804+ onClose = { vi . fn ( ) }
805+ mode = "create"
806+ activeView = { mockActiveView }
807+ objectDef = { mockObjectDef }
808+ onSave = { onSave }
809+ onCreate = { onCreate }
810+ />
811+ ) ;
812+
813+ // Change title first
814+ const titleInput = screen . getByTestId ( 'view-title-input' ) ;
815+ fireEvent . change ( titleInput , { target : { value : 'New Kanban' } } ) ;
816+
817+ fireEvent . click ( screen . getByTestId ( 'view-config-save' ) ) ;
818+
819+ expect ( onCreate ) . toHaveBeenCalledOnce ( ) ;
820+ expect ( onCreate . mock . calls [ 0 ] [ 0 ] ) . toMatchObject ( { label : 'New Kanban' } ) ;
821+ expect ( onSave ) . not . toHaveBeenCalled ( ) ;
822+ } ) ;
823+
824+ it ( 'calls onClose when discarding in create mode' , ( ) => {
825+ const onClose = vi . fn ( ) ;
826+ render (
827+ < ViewConfigPanel
828+ open = { true }
829+ onClose = { onClose }
830+ mode = "create"
831+ activeView = { mockActiveView }
832+ objectDef = { mockObjectDef }
833+ />
834+ ) ;
835+
836+ fireEvent . click ( screen . getByTestId ( 'view-config-discard' ) ) ;
837+ expect ( onClose ) . toHaveBeenCalledOnce ( ) ;
838+ } ) ;
839+
840+ it ( 'starts with default label in create mode' , ( ) => {
841+ render (
842+ < ViewConfigPanel
843+ open = { true }
844+ onClose = { vi . fn ( ) }
845+ mode = "create"
846+ activeView = { mockActiveView }
847+ objectDef = { mockObjectDef }
848+ />
849+ ) ;
850+
851+ const titleInput = screen . getByTestId ( 'view-title-input' ) ;
852+ // In create mode, draft starts with the "New View" i18n key
853+ expect ( titleInput ) . toHaveValue ( 'console.objectView.newView' ) ;
854+ } ) ;
855+
856+ // ── Type-specific options tests ──
857+
858+ it ( 'shows kanban groupByField when view type is kanban' , ( ) => {
859+ render (
860+ < ViewConfigPanel
861+ open = { true }
862+ onClose = { vi . fn ( ) }
863+ activeView = { { ...mockActiveView , type : 'kanban' } }
864+ objectDef = { mockObjectDef }
865+ />
866+ ) ;
867+
868+ expect ( screen . getByTestId ( 'type-options-section' ) ) . toBeInTheDocument ( ) ;
869+ expect ( screen . getByTestId ( 'type-opt-kanban-groupByField' ) ) . toBeInTheDocument ( ) ;
870+ } ) ;
871+
872+ it ( 'shows calendar fields when view type is calendar' , ( ) => {
873+ render (
874+ < ViewConfigPanel
875+ open = { true }
876+ onClose = { vi . fn ( ) }
877+ activeView = { { ...mockActiveView , type : 'calendar' } }
878+ objectDef = { mockObjectDef }
879+ />
880+ ) ;
881+
882+ expect ( screen . getByTestId ( 'type-opt-calendar-startDateField' ) ) . toBeInTheDocument ( ) ;
883+ expect ( screen . getByTestId ( 'type-opt-calendar-titleField' ) ) . toBeInTheDocument ( ) ;
884+ } ) ;
885+
886+ it ( 'shows map fields when view type is map' , ( ) => {
887+ render (
888+ < ViewConfigPanel
889+ open = { true }
890+ onClose = { vi . fn ( ) }
891+ activeView = { { ...mockActiveView , type : 'map' } }
892+ objectDef = { mockObjectDef }
893+ />
894+ ) ;
895+
896+ expect ( screen . getByTestId ( 'type-opt-map-latitudeField' ) ) . toBeInTheDocument ( ) ;
897+ expect ( screen . getByTestId ( 'type-opt-map-longitudeField' ) ) . toBeInTheDocument ( ) ;
898+ } ) ;
899+
900+ it ( 'shows gallery imageField when view type is gallery' , ( ) => {
901+ render (
902+ < ViewConfigPanel
903+ open = { true }
904+ onClose = { vi . fn ( ) }
905+ activeView = { { ...mockActiveView , type : 'gallery' } }
906+ objectDef = { mockObjectDef }
907+ />
908+ ) ;
909+
910+ expect ( screen . getByTestId ( 'type-opt-gallery-imageField' ) ) . toBeInTheDocument ( ) ;
911+ } ) ;
912+
913+ it ( 'shows timeline dateField and titleField when view type is timeline' , ( ) => {
914+ render (
915+ < ViewConfigPanel
916+ open = { true }
917+ onClose = { vi . fn ( ) }
918+ activeView = { { ...mockActiveView , type : 'timeline' } }
919+ objectDef = { mockObjectDef }
920+ />
921+ ) ;
922+
923+ expect ( screen . getByTestId ( 'type-opt-timeline-dateField' ) ) . toBeInTheDocument ( ) ;
924+ expect ( screen . getByTestId ( 'type-opt-timeline-titleField' ) ) . toBeInTheDocument ( ) ;
925+ } ) ;
926+
927+ it ( 'shows gantt dateField and titleField when view type is gantt' , ( ) => {
928+ render (
929+ < ViewConfigPanel
930+ open = { true }
931+ onClose = { vi . fn ( ) }
932+ activeView = { { ...mockActiveView , type : 'gantt' } }
933+ objectDef = { mockObjectDef }
934+ />
935+ ) ;
936+
937+ expect ( screen . getByTestId ( 'type-opt-gantt-dateField' ) ) . toBeInTheDocument ( ) ;
938+ expect ( screen . getByTestId ( 'type-opt-gantt-titleField' ) ) . toBeInTheDocument ( ) ;
939+ } ) ;
940+
941+ it ( 'does not show type options section for grid view' , ( ) => {
942+ render (
943+ < ViewConfigPanel
944+ open = { true }
945+ onClose = { vi . fn ( ) }
946+ activeView = { { ...mockActiveView , type : 'grid' } }
947+ objectDef = { mockObjectDef }
948+ />
949+ ) ;
950+
951+ // Grid type options section is a hidden placeholder
952+ const section = screen . getByTestId ( 'type-options-section' ) ;
953+ expect ( section . className ) . toContain ( 'hidden' ) ;
954+ } ) ;
955+
956+ it ( 'updates kanban groupByField via type option select' , ( ) => {
957+ const onViewUpdate = vi . fn ( ) ;
958+ render (
959+ < ViewConfigPanel
960+ open = { true }
961+ onClose = { vi . fn ( ) }
962+ activeView = { { ...mockActiveView , type : 'kanban' } }
963+ objectDef = { mockObjectDef }
964+ onViewUpdate = { onViewUpdate }
965+ />
966+ ) ;
967+
968+ const select = screen . getByTestId ( 'type-opt-kanban-groupByField' ) ;
969+ fireEvent . change ( select , { target : { value : 'stage' } } ) ;
970+
971+ expect ( onViewUpdate ) . toHaveBeenCalledWith ( 'kanban' , expect . objectContaining ( { groupByField : 'stage' } ) ) ;
972+ } ) ;
973+
974+ it ( 'shows type options when view type changes from grid to kanban' , ( ) => {
975+ const onViewUpdate = vi . fn ( ) ;
976+ render (
977+ < ViewConfigPanel
978+ open = { true }
979+ onClose = { vi . fn ( ) }
980+ activeView = { mockActiveView }
981+ objectDef = { mockObjectDef }
982+ onViewUpdate = { onViewUpdate }
983+ />
984+ ) ;
985+
986+ // Initially grid — type options hidden
987+ expect ( screen . getByTestId ( 'type-options-section' ) . className ) . toContain ( 'hidden' ) ;
988+
989+ // Change to kanban
990+ fireEvent . change ( screen . getByTestId ( 'view-type-select' ) , { target : { value : 'kanban' } } ) ;
991+
992+ // Now kanban groupBy should appear
993+ expect ( screen . getByTestId ( 'type-opt-kanban-groupByField' ) ) . toBeInTheDocument ( ) ;
994+ } ) ;
766995} ) ;
0 commit comments