@@ -734,3 +734,132 @@ describe('UserCellRenderer', () => {
734734 expect ( screen . getByTitle ( 'Bob' ) ) . toBeInTheDocument ( ) ;
735735 } ) ;
736736} ) ;
737+
738+ // =========================================================================
739+ // 9. humanizeLabel
740+ // =========================================================================
741+ import { humanizeLabel , PercentCellRenderer } from '../index' ;
742+
743+ describe ( 'humanizeLabel' , ( ) => {
744+ it ( 'should convert snake_case to Title Case' , ( ) => {
745+ expect ( humanizeLabel ( 'in_progress' ) ) . toBe ( 'In Progress' ) ;
746+ } ) ;
747+
748+ it ( 'should convert kebab-case to Title Case' , ( ) => {
749+ expect ( humanizeLabel ( 'high-priority' ) ) . toBe ( 'High Priority' ) ;
750+ } ) ;
751+
752+ it ( 'should handle single word' , ( ) => {
753+ expect ( humanizeLabel ( 'active' ) ) . toBe ( 'Active' ) ;
754+ } ) ;
755+
756+ it ( 'should handle already Title Case' , ( ) => {
757+ expect ( humanizeLabel ( 'Active' ) ) . toBe ( 'Active' ) ;
758+ } ) ;
759+
760+ it ( 'should handle multiple underscores' , ( ) => {
761+ expect ( humanizeLabel ( 'not_yet_started' ) ) . toBe ( 'Not Yet Started' ) ;
762+ } ) ;
763+
764+ it ( 'should handle empty string' , ( ) => {
765+ expect ( humanizeLabel ( '' ) ) . toBe ( '' ) ;
766+ } ) ;
767+
768+ it ( 'should handle mixed separators' , ( ) => {
769+ expect ( humanizeLabel ( 'in_progress-now' ) ) . toBe ( 'In Progress Now' ) ;
770+ } ) ;
771+ } ) ;
772+
773+ // =========================================================================
774+ // 10. SelectCellRenderer humanizeLabel fallback
775+ // =========================================================================
776+ describe ( 'SelectCellRenderer humanizeLabel fallback' , ( ) => {
777+ it ( 'should humanize snake_case value when no option label exists' , ( ) => {
778+ render (
779+ < SelectCellRenderer
780+ value = "in_progress"
781+ field = { { name : 'status' , type : 'select' , options : [ ] } as any }
782+ />
783+ ) ;
784+ expect ( screen . getByText ( 'In Progress' ) ) . toBeInTheDocument ( ) ;
785+ } ) ;
786+
787+ it ( 'should prefer explicit option.label over humanized fallback' , ( ) => {
788+ render (
789+ < SelectCellRenderer
790+ value = "in_progress"
791+ field = { {
792+ name : 'status' ,
793+ type : 'select' ,
794+ options : [ { value : 'in_progress' , label : 'WIP' } ] ,
795+ } as any }
796+ />
797+ ) ;
798+ expect ( screen . getByText ( 'WIP' ) ) . toBeInTheDocument ( ) ;
799+ expect ( screen . queryByText ( 'In Progress' ) ) . not . toBeInTheDocument ( ) ;
800+ } ) ;
801+
802+ it ( 'should humanize snake_case values in arrays' , ( ) => {
803+ render (
804+ < SelectCellRenderer
805+ value = { [ 'not_started' , 'in_progress' ] }
806+ field = { { name : 'status' , type : 'select' , options : [ ] } as any }
807+ />
808+ ) ;
809+ expect ( screen . getByText ( 'Not Started' ) ) . toBeInTheDocument ( ) ;
810+ expect ( screen . getByText ( 'In Progress' ) ) . toBeInTheDocument ( ) ;
811+ } ) ;
812+ } ) ;
813+
814+ // =========================================================================
815+ // 11. PercentCellRenderer with progress-type fields
816+ // =========================================================================
817+ describe ( 'PercentCellRenderer progress-type fields' , ( ) => {
818+ it ( 'should render progress field value 75 as 75% with correct bar' , ( ) => {
819+ const { container } = render (
820+ < PercentCellRenderer
821+ value = { 75 }
822+ field = { { name : 'progress' , type : 'percent' } as any }
823+ />
824+ ) ;
825+ expect ( screen . getByText ( '75%' ) ) . toBeInTheDocument ( ) ;
826+ const bar = container . querySelector ( '[role="progressbar"]' ) ;
827+ expect ( bar ) . toHaveAttribute ( 'aria-valuenow' , '75' ) ;
828+ } ) ;
829+
830+ it ( 'should render completion field value 50 as 50% with correct bar' , ( ) => {
831+ const { container } = render (
832+ < PercentCellRenderer
833+ value = { 50 }
834+ field = { { name : 'completion' , type : 'percent' } as any }
835+ />
836+ ) ;
837+ expect ( screen . getByText ( '50%' ) ) . toBeInTheDocument ( ) ;
838+ const bar = container . querySelector ( '[role="progressbar"]' ) ;
839+ expect ( bar ) . toHaveAttribute ( 'aria-valuenow' , '50' ) ;
840+ } ) ;
841+
842+ it ( 'should render probability field value 0.75 as 75%' , ( ) => {
843+ const { container } = render (
844+ < PercentCellRenderer
845+ value = { 0.75 }
846+ field = { { name : 'probability' , type : 'percent' } as any }
847+ />
848+ ) ;
849+ expect ( screen . getByText ( '75%' ) ) . toBeInTheDocument ( ) ;
850+ const bar = container . querySelector ( '[role="progressbar"]' ) ;
851+ expect ( bar ) . toHaveAttribute ( 'aria-valuenow' , '75' ) ;
852+ } ) ;
853+
854+ it ( 'should render rate field value 0.5 as 50%' , ( ) => {
855+ const { container } = render (
856+ < PercentCellRenderer
857+ value = { 0.5 }
858+ field = { { name : 'rate' , type : 'percent' } as any }
859+ />
860+ ) ;
861+ expect ( screen . getByText ( '50%' ) ) . toBeInTheDocument ( ) ;
862+ const bar = container . querySelector ( '[role="progressbar"]' ) ;
863+ expect ( bar ) . toHaveAttribute ( 'aria-valuenow' , '50' ) ;
864+ } ) ;
865+ } ) ;
0 commit comments