@@ -454,3 +454,177 @@ describe("Selection module", function()
454
454
assert (selection .has_selection_changed (new_selection_diff_pos ) == true )
455
455
end )
456
456
end )
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