1+ import { describe , it , expect , vi , beforeEach , afterEach } from 'vitest' ;
2+ import React from 'react' ;
3+ import { render , fireEvent , screen } from '@testing-library/react' ;
4+ import CommandInput from './input' ;
5+ import { CommandHistory } from '../CommandHistory' ;
6+
7+ // Mock CommandHistory
8+ vi . mock ( '../CommandHistory' , ( ) => ( {
9+ CommandHistory : class MockCommandHistory {
10+ private history : string [ ] = [ ] ;
11+ private index : number = - 1 ;
12+
13+ addCommand = vi . fn ( ) ;
14+ navigateUp = vi . fn ( ) ;
15+ navigateDown = vi . fn ( ) ;
16+ getHistory = vi . fn ( ( ) => [ ] ) ;
17+ }
18+ } ) ) ;
19+
20+ // Mock localStorage
21+ const localStorageMock = ( ( ) => {
22+ let store : Record < string , string > = { } ;
23+ return {
24+ getItem : vi . fn ( ( key : string ) => store [ key ] || null ) ,
25+ setItem : vi . fn ( ( key : string , value : string ) => {
26+ store [ key ] = value . toString ( ) ;
27+ } ) ,
28+ clear : vi . fn ( ( ) => {
29+ store = { } ;
30+ } )
31+ } ;
32+ } ) ( ) ;
33+ Object . defineProperty ( window , 'localStorage' , { value : localStorageMock } ) ;
34+
35+ describe ( 'CommandInput Component' , ( ) => {
36+ const onSendMock = vi . fn ( ) ;
37+ const inputRef = React . createRef < HTMLTextAreaElement > ( ) ;
38+
39+ beforeEach ( ( ) => {
40+ vi . clearAllMocks ( ) ;
41+ localStorageMock . clear ( ) ;
42+ } ) ;
43+
44+ it ( 'renders a textarea and a button' , ( ) => {
45+ render ( < CommandInput onSend = { onSendMock } inputRef = { inputRef } /> ) ;
46+
47+ expect ( screen . getByRole ( 'textbox' ) ) . toBeTruthy ( ) ;
48+ expect ( screen . getByRole ( 'button' , { name : / s e n d / i } ) ) . toBeTruthy ( ) ;
49+ } ) ;
50+
51+ it ( 'updates input value when user types' , ( ) => {
52+ render ( < CommandInput onSend = { onSendMock } inputRef = { inputRef } /> ) ;
53+
54+ const textarea = screen . getByRole ( 'textbox' ) ;
55+ fireEvent . change ( textarea , { target : { value : 'hello world' } } ) ;
56+
57+ expect ( ( textarea as HTMLTextAreaElement ) . value ) . toBe ( 'hello world' ) ;
58+ } ) ;
59+
60+ it ( 'calls onSend when send button is clicked' , ( ) => {
61+ render ( < CommandInput onSend = { onSendMock } inputRef = { inputRef } /> ) ;
62+
63+ const textarea = screen . getByRole ( 'textbox' ) ;
64+ fireEvent . change ( textarea , { target : { value : 'look' } } ) ;
65+
66+ const sendButton = screen . getByRole ( 'button' , { name : / s e n d / i } ) ;
67+ fireEvent . click ( sendButton ) ;
68+
69+ expect ( onSendMock ) . toHaveBeenCalledWith ( 'look' ) ;
70+ expect ( ( textarea as HTMLTextAreaElement ) . value ) . toBe ( '' ) ; // Input should clear after sending
71+ } ) ;
72+
73+ it ( 'calls onSend when Enter key is pressed' , ( ) => {
74+ render ( < CommandInput onSend = { onSendMock } inputRef = { inputRef } /> ) ;
75+
76+ const textarea = screen . getByRole ( 'textbox' ) ;
77+ fireEvent . change ( textarea , { target : { value : 'examine sword' } } ) ;
78+ fireEvent . keyDown ( textarea , { key : 'Enter' } ) ;
79+
80+ expect ( onSendMock ) . toHaveBeenCalledWith ( 'examine sword' ) ;
81+ expect ( ( textarea as HTMLTextAreaElement ) . value ) . toBe ( '' ) ; // Input should clear after sending
82+ } ) ;
83+
84+ it ( 'does not send empty input' , ( ) => {
85+ render ( < CommandInput onSend = { onSendMock } inputRef = { inputRef } /> ) ;
86+
87+ const textarea = screen . getByRole ( 'textbox' ) ;
88+ fireEvent . change ( textarea , { target : { value : ' ' } } ) ; // Just spaces
89+ fireEvent . keyDown ( textarea , { key : 'Enter' } ) ;
90+
91+ expect ( onSendMock ) . not . toHaveBeenCalled ( ) ;
92+ } ) ;
93+
94+ it ( 'adds command to history when sent' , ( ) => {
95+ // Since CommandHistory is mocked at the module level, we'll just verify
96+ // that the command is sent via onSend
97+ render ( < CommandInput onSend = { onSendMock } inputRef = { inputRef } /> ) ;
98+
99+ const textarea = screen . getByRole ( 'textbox' ) ;
100+ fireEvent . change ( textarea , { target : { value : 'look' } } ) ;
101+ fireEvent . keyDown ( textarea , { key : 'Enter' } ) ;
102+
103+ expect ( onSendMock ) . toHaveBeenCalledWith ( 'look' ) ;
104+ } ) ;
105+
106+ it ( 'handles arrow key navigation' , ( ) => {
107+ // This test just verifies that the component handles arrow key presses
108+ // without errors - we can't directly test the navigation functionality
109+ // since CommandHistory is fully mocked
110+ render ( < CommandInput onSend = { onSendMock } inputRef = { inputRef } /> ) ;
111+
112+ const textarea = screen . getByRole ( 'textbox' ) ;
113+
114+ // First send a command
115+ fireEvent . change ( textarea , { target : { value : 'command1' } } ) ;
116+ fireEvent . keyDown ( textarea , { key : 'Enter' } ) ;
117+
118+ // Verify no errors when navigating with arrow keys
119+ expect ( ( ) => {
120+ fireEvent . keyDown ( textarea , { key : 'ArrowUp' } ) ;
121+ fireEvent . keyDown ( textarea , { key : 'ArrowDown' } ) ;
122+ } ) . not . toThrow ( ) ;
123+ } ) ;
124+
125+ it ( 'loads command history from localStorage on mount' , ( ) => {
126+ // Setup saved history
127+ localStorageMock . getItem . mockReturnValueOnce ( JSON . stringify ( [ 'command1' , 'command2' ] ) ) ;
128+
129+ render ( < CommandInput onSend = { onSendMock } inputRef = { inputRef } /> ) ;
130+
131+ // Check that localStorage was accessed with the correct key
132+ expect ( localStorageMock . getItem ) . toHaveBeenCalledWith ( 'command_history' ) ;
133+ } ) ;
134+
135+ it ( 'saves command history to localStorage' , ( ) => {
136+ render ( < CommandInput onSend = { onSendMock } inputRef = { inputRef } /> ) ;
137+
138+ const textarea = screen . getByRole ( 'textbox' ) ;
139+ fireEvent . change ( textarea , { target : { value : 'save this command' } } ) ;
140+ fireEvent . keyDown ( textarea , { key : 'Enter' } ) ;
141+
142+ expect ( localStorageMock . setItem ) . toHaveBeenCalledWith ( 'command_history' , expect . any ( String ) ) ;
143+ } ) ;
144+
145+ it ( 'limits command history size when saving' , ( ) => {
146+ // Mock getHistory to return a large array
147+ const largeMockHistory = Array ( 1010 ) . fill ( 0 ) . map ( ( _ , i ) => `command${ i } ` ) ;
148+ const mockCommandHistory = new CommandHistory ( ) ;
149+ vi . spyOn ( mockCommandHistory , 'getHistory' ) . mockReturnValue ( largeMockHistory ) ;
150+ vi . spyOn ( React , 'useRef' ) . mockReturnValue ( { current : mockCommandHistory } ) ;
151+
152+ render ( < CommandInput onSend = { onSendMock } inputRef = { inputRef } /> ) ;
153+
154+ const textarea = screen . getByRole ( 'textbox' ) ;
155+ fireEvent . change ( textarea , { target : { value : 'one more command' } } ) ;
156+ fireEvent . keyDown ( textarea , { key : 'Enter' } ) ;
157+
158+ // Should have saved only the last 1000 commands
159+ const savedValue = JSON . parse ( localStorageMock . setItem . mock . calls [ 0 ] [ 1 ] ) ;
160+ expect ( savedValue . length ) . toBeLessThanOrEqual ( 1000 ) ;
161+ } ) ;
162+ } ) ;
0 commit comments