@@ -68,6 +68,8 @@ describe("ChatTextArea", () => {
6868 apiConfiguration : {
6969 apiProvider : "anthropic" ,
7070 } ,
71+ taskHistory : [ ] ,
72+ cwd : "/test/workspace" ,
7173 } )
7274 } )
7375
@@ -76,6 +78,8 @@ describe("ChatTextArea", () => {
7678 ; ( useExtensionState as jest . Mock ) . mockReturnValue ( {
7779 filePaths : [ ] ,
7880 openedTabs : [ ] ,
81+ taskHistory : [ ] ,
82+ cwd : "/test/workspace" ,
7983 } )
8084 render ( < ChatTextArea { ...defaultProps } sendingDisabled = { true } /> )
8185 const enhanceButton = getEnhancePromptButton ( )
@@ -94,6 +98,8 @@ describe("ChatTextArea", () => {
9498 filePaths : [ ] ,
9599 openedTabs : [ ] ,
96100 apiConfiguration,
101+ taskHistory : [ ] ,
102+ cwd : "/test/workspace" ,
97103 } )
98104
99105 render ( < ChatTextArea { ...defaultProps } inputValue = "Test prompt" /> )
@@ -114,6 +120,8 @@ describe("ChatTextArea", () => {
114120 apiConfiguration : {
115121 apiProvider : "openrouter" ,
116122 } ,
123+ taskHistory : [ ] ,
124+ cwd : "/test/workspace" ,
117125 } )
118126
119127 render ( < ChatTextArea { ...defaultProps } inputValue = "" /> )
@@ -131,6 +139,8 @@ describe("ChatTextArea", () => {
131139 apiConfiguration : {
132140 apiProvider : "openrouter" ,
133141 } ,
142+ taskHistory : [ ] ,
143+ cwd : "/test/workspace" ,
134144 } )
135145
136146 render ( < ChatTextArea { ...defaultProps } inputValue = "Test prompt" /> )
@@ -155,6 +165,8 @@ describe("ChatTextArea", () => {
155165 apiProvider : "openrouter" ,
156166 newSetting : "test" ,
157167 } ,
168+ taskHistory : [ ] ,
169+ cwd : "/test/workspace" ,
158170 } )
159171
160172 rerender ( < ChatTextArea { ...defaultProps } /> )
@@ -408,6 +420,249 @@ describe("ChatTextArea", () => {
408420 // Verify setInputValue was not called
409421 expect ( setInputValue ) . not . toHaveBeenCalled ( )
410422 } )
423+
424+ describe ( "prompt history navigation" , ( ) => {
425+ const mockTaskHistory = [
426+ { task : "First prompt" , workspace : "/test/workspace" } ,
427+ { task : "Second prompt" , workspace : "/test/workspace" } ,
428+ { task : "Third prompt" , workspace : "/test/workspace" } ,
429+ ]
430+
431+ beforeEach ( ( ) => {
432+ ; ( useExtensionState as jest . Mock ) . mockReturnValue ( {
433+ filePaths : [ ] ,
434+ openedTabs : [ ] ,
435+ apiConfiguration : {
436+ apiProvider : "anthropic" ,
437+ } ,
438+ taskHistory : mockTaskHistory ,
439+ cwd : "/test/workspace" ,
440+ } )
441+ } )
442+
443+ it ( "should navigate to previous prompt on arrow up" , ( ) => {
444+ const setInputValue = jest . fn ( )
445+ const { container } = render (
446+ < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "" /> ,
447+ )
448+
449+ const textarea = container . querySelector ( "textarea" ) !
450+
451+ // Simulate arrow up key press
452+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
453+
454+ // Should set the most recent prompt (last in array)
455+ expect ( setInputValue ) . toHaveBeenCalledWith ( "First prompt" )
456+ } )
457+
458+ it ( "should navigate through history with multiple arrow up presses" , ( ) => {
459+ const setInputValue = jest . fn ( )
460+ const { container } = render (
461+ < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "" /> ,
462+ )
463+
464+ const textarea = container . querySelector ( "textarea" ) !
465+
466+ // First arrow up - most recent prompt
467+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
468+ expect ( setInputValue ) . toHaveBeenCalledWith ( "First prompt" )
469+
470+ // Update input value to simulate the state change
471+ setInputValue . mockClear ( )
472+
473+ // Second arrow up - previous prompt
474+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
475+ expect ( setInputValue ) . toHaveBeenCalledWith ( "Second prompt" )
476+ } )
477+
478+ it ( "should navigate forward with arrow down" , ( ) => {
479+ const setInputValue = jest . fn ( )
480+ const { container } = render (
481+ < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "" /> ,
482+ )
483+
484+ const textarea = container . querySelector ( "textarea" ) !
485+
486+ // Go back in history first (index 0 -> "First prompt", then index 1 -> "Second prompt")
487+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
488+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
489+ setInputValue . mockClear ( )
490+
491+ // Navigate forward (from index 1 back to index 0)
492+ fireEvent . keyDown ( textarea , { key : "ArrowDown" } )
493+ expect ( setInputValue ) . toHaveBeenCalledWith ( "First prompt" )
494+ } )
495+
496+ it ( "should preserve current input when starting navigation" , ( ) => {
497+ const setInputValue = jest . fn ( )
498+ const { container } = render (
499+ < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "Current input" /> ,
500+ )
501+
502+ const textarea = container . querySelector ( "textarea" ) !
503+
504+ // Navigate to history
505+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
506+ expect ( setInputValue ) . toHaveBeenCalledWith ( "First prompt" )
507+
508+ setInputValue . mockClear ( )
509+
510+ // Navigate back to current input
511+ fireEvent . keyDown ( textarea , { key : "ArrowDown" } )
512+ fireEvent . keyDown ( textarea , { key : "ArrowDown" } )
513+ expect ( setInputValue ) . toHaveBeenCalledWith ( "Current input" )
514+ } )
515+
516+ it ( "should reset history navigation when user types" , ( ) => {
517+ const setInputValue = jest . fn ( )
518+ const { container } = render (
519+ < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "" /> ,
520+ )
521+
522+ const textarea = container . querySelector ( "textarea" ) !
523+
524+ // Navigate to history
525+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
526+ setInputValue . mockClear ( )
527+
528+ // Type something
529+ fireEvent . change ( textarea , { target : { value : "New input" , selectionStart : 9 } } )
530+
531+ // Should reset history navigation
532+ expect ( setInputValue ) . toHaveBeenCalledWith ( "New input" )
533+ } )
534+
535+ it ( "should reset history navigation when sending message" , ( ) => {
536+ const onSend = jest . fn ( )
537+ const setInputValue = jest . fn ( )
538+ const { container } = render (
539+ < ChatTextArea
540+ { ...defaultProps }
541+ onSend = { onSend }
542+ setInputValue = { setInputValue }
543+ inputValue = "Test message"
544+ /> ,
545+ )
546+
547+ const textarea = container . querySelector ( "textarea" ) !
548+
549+ // Navigate to history first
550+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
551+ setInputValue . mockClear ( )
552+
553+ // Send message
554+ fireEvent . keyDown ( textarea , { key : "Enter" } )
555+
556+ expect ( onSend ) . toHaveBeenCalled ( )
557+ } )
558+
559+ it ( "should navigate history when cursor is at first line" , ( ) => {
560+ const setInputValue = jest . fn ( )
561+ const { container } = render (
562+ < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "" /> ,
563+ )
564+
565+ const textarea = container . querySelector ( "textarea" ) !
566+
567+ // Clear any calls from initial render
568+ setInputValue . mockClear ( )
569+
570+ // With empty input, cursor is at first line by default
571+ // Arrow up should navigate history
572+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
573+ expect ( setInputValue ) . toHaveBeenCalledWith ( "First prompt" )
574+ } )
575+
576+ it ( "should filter history by current workspace" , ( ) => {
577+ const mixedTaskHistory = [
578+ { task : "Workspace 1 prompt" , workspace : "/test/workspace" } ,
579+ { task : "Other workspace prompt" , workspace : "/other/workspace" } ,
580+ { task : "Workspace 1 prompt 2" , workspace : "/test/workspace" } ,
581+ ]
582+
583+ ; ( useExtensionState as jest . Mock ) . mockReturnValue ( {
584+ filePaths : [ ] ,
585+ openedTabs : [ ] ,
586+ apiConfiguration : {
587+ apiProvider : "anthropic" ,
588+ } ,
589+ taskHistory : mixedTaskHistory ,
590+ cwd : "/test/workspace" ,
591+ } )
592+
593+ const setInputValue = jest . fn ( )
594+ const { container } = render (
595+ < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "" /> ,
596+ )
597+
598+ const textarea = container . querySelector ( "textarea" ) !
599+
600+ // Should only show prompts from current workspace
601+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
602+ expect ( setInputValue ) . toHaveBeenCalledWith ( "Workspace 1 prompt" )
603+
604+ setInputValue . mockClear ( )
605+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
606+ expect ( setInputValue ) . toHaveBeenCalledWith ( "Workspace 1 prompt 2" )
607+ } )
608+
609+ it ( "should handle empty task history gracefully" , ( ) => {
610+ ; ( useExtensionState as jest . Mock ) . mockReturnValue ( {
611+ filePaths : [ ] ,
612+ openedTabs : [ ] ,
613+ apiConfiguration : {
614+ apiProvider : "anthropic" ,
615+ } ,
616+ taskHistory : [ ] ,
617+ cwd : "/test/workspace" ,
618+ } )
619+
620+ const setInputValue = jest . fn ( )
621+ const { container } = render (
622+ < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "" /> ,
623+ )
624+
625+ const textarea = container . querySelector ( "textarea" ) !
626+
627+ // Should not crash or call setInputValue
628+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
629+ expect ( setInputValue ) . not . toHaveBeenCalled ( )
630+ } )
631+
632+ it ( "should ignore empty or whitespace-only tasks" , ( ) => {
633+ const taskHistoryWithEmpty = [
634+ { task : "Valid prompt" , workspace : "/test/workspace" } ,
635+ { task : "" , workspace : "/test/workspace" } ,
636+ { task : " " , workspace : "/test/workspace" } ,
637+ { task : "Another valid prompt" , workspace : "/test/workspace" } ,
638+ ]
639+
640+ ; ( useExtensionState as jest . Mock ) . mockReturnValue ( {
641+ filePaths : [ ] ,
642+ openedTabs : [ ] ,
643+ apiConfiguration : {
644+ apiProvider : "anthropic" ,
645+ } ,
646+ taskHistory : taskHistoryWithEmpty ,
647+ cwd : "/test/workspace" ,
648+ } )
649+
650+ const setInputValue = jest . fn ( )
651+ const { container } = render (
652+ < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "" /> ,
653+ )
654+
655+ const textarea = container . querySelector ( "textarea" ) !
656+
657+ // Should skip empty tasks
658+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
659+ expect ( setInputValue ) . toHaveBeenCalledWith ( "Valid prompt" )
660+
661+ setInputValue . mockClear ( )
662+ fireEvent . keyDown ( textarea , { key : "ArrowUp" } )
663+ expect ( setInputValue ) . toHaveBeenCalledWith ( "Another valid prompt" )
664+ } )
665+ } )
411666 } )
412667
413668 describe ( "selectApiConfig" , ( ) => {
0 commit comments