@@ -454,3 +454,177 @@ describe("Selection module", function()
454454 assert (selection .has_selection_changed (new_selection_diff_pos ) == true )
455455 end )
456456end )
457+
458+ -- Tests for range selection functionality (fix for issue #25)
459+ describe (" Range Selection Tests" , function ()
460+ local selection
461+
462+ before_each (function ()
463+ -- Reset vim state
464+ _G .vim ._buffers = {
465+ [1 ] = {
466+ name = " /test/file.lua" ,
467+ lines = {
468+ " line 1" ,
469+ " line 2" ,
470+ " line 3" ,
471+ " line 4" ,
472+ " line 5" ,
473+ " line 6" ,
474+ " line 7" ,
475+ " line 8" ,
476+ " line 9" ,
477+ " line 10"
478+ }
479+ }
480+ }
481+ _G .vim ._windows = {
482+ [1 ] = {
483+ cursor = { 1 , 0 }
484+ }
485+ }
486+ _G .vim ._current_mode = " n"
487+
488+ -- Add nvim_buf_line_count function
489+ _G .vim .api .nvim_buf_line_count = function (bufnr )
490+ return _G .vim ._buffers [bufnr ] and # _G .vim ._buffers [bufnr ].lines or 0
491+ end
492+
493+ -- Reload the selection module
494+ package.loaded [" claudecode.selection" ] = nil
495+ selection = require (" claudecode.selection" )
496+ end )
497+
498+ describe (" get_range_selection" , function ()
499+ it (" should return valid selection for valid range" , function ()
500+ local result = selection .get_range_selection (2 , 4 )
501+
502+ assert (result ~= nil )
503+ assert (result .text == " line 2\n line 3\n line 4" )
504+ assert (result .filePath == " /test/file.lua" )
505+ assert (result .fileUrl == " file:///test/file.lua" )
506+ assert (result .selection .start .line == 1 ) -- 0-indexed
507+ assert (result .selection .start .character == 0 )
508+ assert (result .selection [" end" ].line == 3 ) -- 0-indexed
509+ assert (result .selection [" end" ].character == 6 ) -- length of "line 4"
510+ assert (result .selection .isEmpty == false )
511+ end )
512+
513+ it (" should return valid selection for single line range" , function ()
514+ local result = selection .get_range_selection (3 , 3 )
515+
516+ assert (result ~= nil )
517+ assert (result .text == " line 3" )
518+ assert (result .selection .start .line == 2 ) -- 0-indexed
519+ assert (result .selection [" end" ].line == 2 ) -- 0-indexed
520+ assert (result .selection .isEmpty == false )
521+ end )
522+
523+ it (" should handle range that exceeds buffer bounds" , function ()
524+ local result = selection .get_range_selection (8 , 15 ) -- buffer only has 10 lines
525+
526+ assert (result ~= nil )
527+ assert (result .text == " line 8\n line 9\n line 10" )
528+ assert (result .selection .start .line == 7 ) -- 0-indexed
529+ assert (result .selection [" end" ].line == 9 ) -- 0-indexed, clamped to buffer size
530+ end )
531+
532+ it (" should return nil for invalid range (line1 > line2)" , function ()
533+ local result = selection .get_range_selection (5 , 3 )
534+ assert (result == nil )
535+ end )
536+
537+ it (" should return nil for invalid range (line1 < 1)" , function ()
538+ local result = selection .get_range_selection (0 , 3 )
539+ assert (result == nil )
540+ end )
541+
542+ it (" should return nil for invalid range (line2 < 1)" , function ()
543+ local result = selection .get_range_selection (2 , 0 )
544+ assert (result == nil )
545+ end )
546+
547+ it (" should return nil for nil parameters" , function ()
548+ local result1 = selection .get_range_selection (nil , 3 )
549+ local result2 = selection .get_range_selection (2 , nil )
550+ local result3 = selection .get_range_selection (nil , nil )
551+
552+ assert (result1 == nil )
553+ assert (result2 == nil )
554+ assert (result3 == nil )
555+ end )
556+
557+ it (" should handle empty buffer" , function ()
558+ _G .vim ._buffers [1 ].lines = {}
559+ local result = selection .get_range_selection (1 , 1 )
560+ assert (result == nil )
561+ end )
562+ end )
563+
564+ describe (" send_at_mention_for_visual_selection with range" , function ()
565+ local mock_server
566+
567+ before_each (function ()
568+ mock_server = {
569+ broadcast = function (event , params )
570+ mock_server .last_broadcast = {
571+ event = event ,
572+ params = params
573+ }
574+ return true
575+ end
576+ }
577+
578+ selection .state .tracking_enabled = true
579+ selection .server = mock_server
580+ end )
581+
582+ it (" should send range selection successfully" , function ()
583+ local result = selection .send_at_mention_for_visual_selection (2 , 4 )
584+
585+ assert (result == true )
586+ assert (mock_server .last_broadcast ~= nil )
587+ assert (mock_server .last_broadcast .event == " at_mentioned" )
588+ assert (mock_server .last_broadcast .params .filePath == " /test/file.lua" )
589+ assert (mock_server .last_broadcast .params .lineStart == 1 ) -- 0-indexed
590+ assert (mock_server .last_broadcast .params .lineEnd == 3 ) -- 0-indexed
591+ end )
592+
593+ it (" should fail for invalid range" , function ()
594+ local result = selection .send_at_mention_for_visual_selection (5 , 3 )
595+ assert (result == false )
596+ end )
597+
598+ it (" should fall back to existing logic when no range provided" , function ()
599+ -- Set up a tracked selection
600+ selection .state .latest_selection = {
601+ text = " tracked text" ,
602+ filePath = " /test/file.lua" ,
603+ fileUrl = " file:///test/file.lua" ,
604+ selection = {
605+ start = { line = 0 , character = 0 },
606+ [" end" ] = { line = 0 , character = 12 },
607+ isEmpty = false
608+ }
609+ }
610+
611+ local result = selection .send_at_mention_for_visual_selection ()
612+
613+ assert (result == true )
614+ assert (mock_server .last_broadcast .params .lineStart == 0 )
615+ assert (mock_server .last_broadcast .params .lineEnd == 0 )
616+ end )
617+
618+ it (" should fail when server is not available" , function ()
619+ selection .server = nil
620+ local result = selection .send_at_mention_for_visual_selection (2 , 4 )
621+ assert (result == false )
622+ end )
623+
624+ it (" should fail when tracking is disabled" , function ()
625+ selection .state .tracking_enabled = false
626+ local result = selection .send_at_mention_for_visual_selection (2 , 4 )
627+ assert (result == false )
628+ end )
629+ end )
630+ end )
0 commit comments