@@ -414,6 +414,193 @@ describe("DataTable", () => {
414414 expect ( sortedCells [ 2 ] ) . toHaveTextContent ( "20" ) ;
415415 } ) ;
416416
417+ it ( "sortAlways: first click goes to ascending" , async ( ) => {
418+ render ( DataTable , {
419+ props : {
420+ sortable : true ,
421+ sortAlways : true ,
422+ headers,
423+ rows,
424+ } ,
425+ } ) ;
426+
427+ const nameHeader = screen . getByText ( "Name" ) ;
428+ await user . click ( nameHeader ) ;
429+
430+ const tableRows = screen
431+ . getAllByRole ( "row" )
432+ . filter ( ( row ) => row . closest ( "tbody" ) !== null ) ;
433+ expect ( within ( tableRows [ 0 ] ) . getAllByRole ( "cell" ) [ 0 ] ) . toHaveTextContent (
434+ "Load Balancer 1" ,
435+ ) ;
436+ } ) ;
437+
438+ it ( "sortAlways: toggles between ascending and descending only" , async ( ) => {
439+ render ( DataTable , {
440+ props : {
441+ sortable : true ,
442+ sortAlways : true ,
443+ headers,
444+ rows,
445+ } ,
446+ } ) ;
447+
448+ const nameHeader = screen . getByText ( "Name" ) ;
449+
450+ // Click 1: none -> ascending
451+ await user . click ( nameHeader ) ;
452+ let tableRows = screen
453+ . getAllByRole ( "row" )
454+ . filter ( ( row ) => row . closest ( "tbody" ) !== null ) ;
455+ expect ( within ( tableRows [ 0 ] ) . getAllByRole ( "cell" ) [ 0 ] ) . toHaveTextContent (
456+ "Load Balancer 1" ,
457+ ) ;
458+
459+ // Click 2: ascending -> descending
460+ await user . click ( nameHeader ) ;
461+ tableRows = screen
462+ . getAllByRole ( "row" )
463+ . filter ( ( row ) => row . closest ( "tbody" ) !== null ) ;
464+ expect ( within ( tableRows [ 0 ] ) . getAllByRole ( "cell" ) [ 0 ] ) . toHaveTextContent (
465+ "Load Balancer 3" ,
466+ ) ;
467+
468+ // Click 3: descending -> ascending (NOT back to none)
469+ await user . click ( nameHeader ) ;
470+ tableRows = screen
471+ . getAllByRole ( "row" )
472+ . filter ( ( row ) => row . closest ( "tbody" ) !== null ) ;
473+ expect ( within ( tableRows [ 0 ] ) . getAllByRole ( "cell" ) [ 0 ] ) . toHaveTextContent (
474+ "Load Balancer 1" ,
475+ ) ;
476+ } ) ;
477+
478+ it ( "sortAlways: switches columns without resetting to none" , async ( ) => {
479+ render ( DataTable , {
480+ props : {
481+ sortable : true ,
482+ sortAlways : true ,
483+ headers,
484+ rows,
485+ } ,
486+ } ) ;
487+
488+ const nameHeader = screen . getByText ( "Name" ) ;
489+ const portHeader = screen . getByText ( "Port" ) ;
490+
491+ // Sort by name ascending
492+ await user . click ( nameHeader ) ;
493+ let tableRows = screen
494+ . getAllByRole ( "row" )
495+ . filter ( ( row ) => row . closest ( "tbody" ) !== null ) ;
496+ expect ( within ( tableRows [ 0 ] ) . getAllByRole ( "cell" ) [ 0 ] ) . toHaveTextContent (
497+ "Load Balancer 1" ,
498+ ) ;
499+
500+ // Switch to port – should sort ascending
501+ await user . click ( portHeader ) ;
502+ tableRows = screen
503+ . getAllByRole ( "row" )
504+ . filter ( ( row ) => row . closest ( "tbody" ) !== null ) ;
505+ expect ( within ( tableRows [ 0 ] ) . getAllByRole ( "cell" ) [ 2 ] ) . toHaveTextContent (
506+ "80" ,
507+ ) ;
508+ } ) ;
509+
510+ it ( "without sortAlways: third click resets to original order" , async ( ) => {
511+ render ( DataTable , {
512+ props : {
513+ sortable : true ,
514+ headers,
515+ rows,
516+ } ,
517+ } ) ;
518+
519+ const nameHeader = screen . getByText ( "Name" ) ;
520+
521+ // Click 1: ascending
522+ await user . click ( nameHeader ) ;
523+ // Click 2: descending
524+ await user . click ( nameHeader ) ;
525+ // Click 3: none (original order)
526+ await user . click ( nameHeader ) ;
527+
528+ const tableRows = screen
529+ . getAllByRole ( "row" )
530+ . filter ( ( row ) => row . closest ( "tbody" ) !== null ) ;
531+ // Original order: Load Balancer 3, Load Balancer 1, Load Balancer 2
532+ expect ( within ( tableRows [ 0 ] ) . getAllByRole ( "cell" ) [ 0 ] ) . toHaveTextContent (
533+ "Load Balancer 3" ,
534+ ) ;
535+ } ) ;
536+
537+ it ( "header.sortAlways overrides table: column with sortAlways: true stays sorted" , async ( ) => {
538+ render ( DataTable , {
539+ props : {
540+ sortable : true ,
541+ headers : [
542+ { key : "name" , value : "Name" , sortAlways : true } ,
543+ { key : "protocol" , value : "Protocol" } ,
544+ { key : "port" , value : "Port" , sortAlways : false } ,
545+ ] ,
546+ rows,
547+ } ,
548+ } ) ;
549+
550+ const nameHeader = screen . getByText ( "Name" ) ;
551+ const portHeader = screen . getByText ( "Port" ) ;
552+
553+ // Name has sortAlways: true (override) – third click stays sorted
554+ await user . click ( nameHeader ) ;
555+ await user . click ( nameHeader ) ;
556+ await user . click ( nameHeader ) ;
557+ let tableRows = screen
558+ . getAllByRole ( "row" )
559+ . filter ( ( row ) => row . closest ( "tbody" ) !== null ) ;
560+ expect ( within ( tableRows [ 0 ] ) . getAllByRole ( "cell" ) [ 0 ] ) . toHaveTextContent (
561+ "Load Balancer 1" ,
562+ ) ;
563+
564+ // Port has sortAlways: false (override) – third click unsorts
565+ await user . click ( portHeader ) ;
566+ await user . click ( portHeader ) ;
567+ await user . click ( portHeader ) ;
568+ tableRows = screen
569+ . getAllByRole ( "row" )
570+ . filter ( ( row ) => row . closest ( "tbody" ) !== null ) ;
571+ expect ( within ( tableRows [ 0 ] ) . getAllByRole ( "cell" ) [ 0 ] ) . toHaveTextContent (
572+ "Load Balancer 3" ,
573+ ) ;
574+ } ) ;
575+
576+ it ( "header.sortAlways overrides table: column with sortAlways: false allows unsort when table has sortAlways: true" , async ( ) => {
577+ render ( DataTable , {
578+ props : {
579+ sortable : true ,
580+ sortAlways : true ,
581+ headers : [
582+ { key : "name" , value : "Name" } ,
583+ { key : "port" , value : "Port" , sortAlways : false } ,
584+ ] ,
585+ rows,
586+ } ,
587+ } ) ;
588+
589+ const portHeader = screen . getByText ( "Port" ) ;
590+
591+ // Port has sortAlways: false – overrides table, third click unsorts
592+ await user . click ( portHeader ) ;
593+ await user . click ( portHeader ) ;
594+ await user . click ( portHeader ) ;
595+
596+ const tableRows = screen
597+ . getAllByRole ( "row" )
598+ . filter ( ( row ) => row . closest ( "tbody" ) !== null ) ;
599+ expect ( within ( tableRows [ 0 ] ) . getAllByRole ( "cell" ) [ 0 ] ) . toHaveTextContent (
600+ "Load Balancer 3" ,
601+ ) ;
602+ } ) ;
603+
417604 // Selection tests
418605 it ( "handles selectable rows" , async ( ) => {
419606 const { container } = render ( DataTable , {
0 commit comments