| 1 | module test_driver_module |
| 2 | use text_buffer_module |
| 3 | use editor_state_module |
| 4 | use command_handler_module |
| 5 | use mock_terminal_module |
| 6 | implicit none |
| 7 | private |
| 8 | |
| 9 | public :: create_test_editor, destroy_test_editor |
| 10 | public :: simulate_typing, simulate_key, simulate_key_sequence |
| 11 | public :: get_buffer_text, get_cursor_position, get_line_text |
| 12 | public :: assert_buffer_equals, assert_cursor_at |
| 13 | public :: assert_line_equals |
| 14 | |
| 15 | contains |
| 16 | |
| 17 | subroutine create_test_editor(editor, buffer, initial_text) |
| 18 | type(editor_state_t), intent(out) :: editor |
| 19 | type(buffer_t), intent(out) :: buffer |
| 20 | character(len=*), intent(in), optional :: initial_text |
| 21 | integer :: i |
| 22 | |
| 23 | ! Initialize buffer |
| 24 | call init_buffer(buffer) |
| 25 | |
| 26 | ! Add initial text if provided |
| 27 | if (present(initial_text)) then |
| 28 | do i = 1, len(initial_text) |
| 29 | call buffer_insert(buffer, i, initial_text(i:i)) |
| 30 | end do |
| 31 | end if |
| 32 | |
| 33 | ! Initialize editor state |
| 34 | editor%screen_rows = 24 |
| 35 | editor%screen_cols = 80 |
| 36 | editor%viewport_line = 1 |
| 37 | editor%viewport_column = 1 |
| 38 | allocate(editor%cursors(1)) |
| 39 | editor%cursors(1)%line = 1 |
| 40 | editor%cursors(1)%column = 1 |
| 41 | editor%cursors(1)%desired_column = 1 |
| 42 | editor%cursors(1)%has_selection = .false. |
| 43 | editor%active_cursor = 1 |
| 44 | |
| 45 | ! Initialize command handler |
| 46 | call init_command_handler() |
| 47 | end subroutine create_test_editor |
| 48 | |
| 49 | subroutine destroy_test_editor(editor, buffer) |
| 50 | type(editor_state_t), intent(inout) :: editor |
| 51 | type(buffer_t), intent(inout) :: buffer |
| 52 | |
| 53 | call cleanup_buffer(buffer) |
| 54 | if (allocated(editor%cursors)) deallocate(editor%cursors) |
| 55 | if (allocated(editor%filename)) deallocate(editor%filename) |
| 56 | call cleanup_command_handler() |
| 57 | end subroutine destroy_test_editor |
| 58 | |
| 59 | subroutine simulate_typing(editor, buffer, text) |
| 60 | type(editor_state_t), intent(inout) :: editor |
| 61 | type(buffer_t), intent(inout) :: buffer |
| 62 | character(len=*), intent(in) :: text |
| 63 | integer :: i |
| 64 | character(len=16) :: key_str |
| 65 | logical :: should_quit |
| 66 | |
| 67 | do i = 1, len(text) |
| 68 | ! Convert character to key string |
| 69 | key_str = text(i:i) ! Don't use write statement, just assign directly |
| 70 | ! Don't trim spaces - they're valid input |
| 71 | if (text(i:i) == ' ') then |
| 72 | call handle_key_command(' ', editor, buffer, should_quit) |
| 73 | else |
| 74 | call handle_key_command(trim(key_str), editor, buffer, should_quit) |
| 75 | end if |
| 76 | end do |
| 77 | end subroutine simulate_typing |
| 78 | |
| 79 | subroutine simulate_key(editor, buffer, key_name) |
| 80 | type(editor_state_t), intent(inout) :: editor |
| 81 | type(buffer_t), intent(inout) :: buffer |
| 82 | character(len=*), intent(in) :: key_name |
| 83 | logical :: should_quit |
| 84 | |
| 85 | call handle_key_command(key_name, editor, buffer, should_quit) |
| 86 | end subroutine simulate_key |
| 87 | |
| 88 | subroutine simulate_key_sequence(editor, buffer, keys) |
| 89 | type(editor_state_t), intent(inout) :: editor |
| 90 | type(buffer_t), intent(inout) :: buffer |
| 91 | character(len=*), dimension(:), intent(in) :: keys |
| 92 | integer :: i |
| 93 | |
| 94 | do i = 1, size(keys) |
| 95 | call simulate_key(editor, buffer, keys(i)) |
| 96 | end do |
| 97 | end subroutine simulate_key_sequence |
| 98 | |
| 99 | function get_buffer_text(buffer) result(text) |
| 100 | type(buffer_t), intent(in) :: buffer |
| 101 | character(len=:), allocatable :: text |
| 102 | integer :: i, pos, logical_size |
| 103 | character :: ch |
| 104 | |
| 105 | ! Calculate logical size (total size minus gap) |
| 106 | logical_size = buffer%size - (buffer%gap_end - buffer%gap_start) |
| 107 | |
| 108 | if (logical_size <= 0) then |
| 109 | text = '' |
| 110 | return |
| 111 | end if |
| 112 | |
| 113 | allocate(character(len=logical_size) :: text) |
| 114 | text = repeat(' ', logical_size) ! Initialize with spaces instead of empty string |
| 115 | pos = 0 |
| 116 | |
| 117 | ! Get text before gap |
| 118 | do i = 1, buffer%gap_start - 1 |
| 119 | if (i <= len(buffer%data)) then |
| 120 | ch = buffer%data(i:i) |
| 121 | pos = pos + 1 |
| 122 | if (pos <= len(text)) text(pos:pos) = ch |
| 123 | end if |
| 124 | end do |
| 125 | |
| 126 | ! Get text after gap |
| 127 | do i = buffer%gap_end, buffer%size |
| 128 | if (i <= len(buffer%data)) then |
| 129 | ch = buffer%data(i:i) |
| 130 | if (ch /= char(0)) then ! Skip null characters |
| 131 | pos = pos + 1 |
| 132 | if (pos <= len(text)) text(pos:pos) = ch |
| 133 | end if |
| 134 | end if |
| 135 | end do |
| 136 | |
| 137 | ! Trim to actual content length |
| 138 | if (pos > 0) then |
| 139 | text = text(1:pos) |
| 140 | else |
| 141 | text = '' |
| 142 | end if |
| 143 | end function get_buffer_text |
| 144 | |
| 145 | function get_line_text(buffer, line_num) result(text) |
| 146 | type(buffer_t), intent(in) :: buffer |
| 147 | integer, intent(in) :: line_num |
| 148 | character(len=:), allocatable :: text |
| 149 | |
| 150 | text = buffer_get_line(buffer, line_num) |
| 151 | end function get_line_text |
| 152 | |
| 153 | subroutine get_cursor_position(editor, line, column) |
| 154 | type(editor_state_t), intent(in) :: editor |
| 155 | integer, intent(out) :: line, column |
| 156 | |
| 157 | line = editor%cursors(editor%active_cursor)%line |
| 158 | column = editor%cursors(editor%active_cursor)%column |
| 159 | end subroutine get_cursor_position |
| 160 | |
| 161 | subroutine assert_buffer_equals(buffer, expected, test_name) |
| 162 | type(buffer_t), intent(in) :: buffer |
| 163 | character(len=*), intent(in) :: expected |
| 164 | character(len=*), intent(in), optional :: test_name |
| 165 | character(len=:), allocatable :: actual |
| 166 | character(len=256) :: msg |
| 167 | |
| 168 | actual = get_buffer_text(buffer) |
| 169 | |
| 170 | if (actual /= expected) then |
| 171 | if (present(test_name)) then |
| 172 | write(msg, '(A,A)') trim(test_name), ": Buffer content mismatch" |
| 173 | else |
| 174 | msg = "Buffer content mismatch" |
| 175 | end if |
| 176 | write(*, '(A)') trim(msg) |
| 177 | write(*, '(A,A)') "Expected: '", expected, "'" |
| 178 | write(*, '(A,A)') "Actual: '", actual, "'" |
| 179 | error stop "Test failed" |
| 180 | end if |
| 181 | end subroutine assert_buffer_equals |
| 182 | |
| 183 | subroutine assert_cursor_at(editor, expected_line, expected_col, test_name) |
| 184 | type(editor_state_t), intent(in) :: editor |
| 185 | integer, intent(in) :: expected_line, expected_col |
| 186 | character(len=*), intent(in), optional :: test_name |
| 187 | integer :: actual_line, actual_col |
| 188 | character(len=256) :: msg |
| 189 | |
| 190 | call get_cursor_position(editor, actual_line, actual_col) |
| 191 | |
| 192 | if (actual_line /= expected_line .or. actual_col /= expected_col) then |
| 193 | if (present(test_name)) then |
| 194 | write(msg, '(A,A)') trim(test_name), ": Cursor position mismatch" |
| 195 | else |
| 196 | msg = "Cursor position mismatch" |
| 197 | end if |
| 198 | write(*, '(A)') trim(msg) |
| 199 | write(*, '(A,I0,A,I0,A)') "Expected: (", expected_line, ",", expected_col, ")" |
| 200 | write(*, '(A,I0,A,I0,A)') "Actual: (", actual_line, ",", actual_col, ")" |
| 201 | error stop "Test failed" |
| 202 | end if |
| 203 | end subroutine assert_cursor_at |
| 204 | |
| 205 | subroutine assert_line_equals(buffer, line_num, expected, test_name) |
| 206 | type(buffer_t), intent(in) :: buffer |
| 207 | integer, intent(in) :: line_num |
| 208 | character(len=*), intent(in) :: expected |
| 209 | character(len=*), intent(in), optional :: test_name |
| 210 | character(len=:), allocatable :: actual |
| 211 | character(len=256) :: msg |
| 212 | |
| 213 | actual = get_line_text(buffer, line_num) |
| 214 | |
| 215 | if (actual /= expected) then |
| 216 | if (present(test_name)) then |
| 217 | write(msg, '(A,A,I0,A)') trim(test_name), ": Line ", line_num, " mismatch" |
| 218 | else |
| 219 | write(msg, '(A,I0,A)') "Line ", line_num, " mismatch" |
| 220 | end if |
| 221 | write(*, '(A)') trim(msg) |
| 222 | write(*, '(A,A)') "Expected: '", expected, "'" |
| 223 | write(*, '(A,A)') "Actual: '", actual, "'" |
| 224 | error stop "Test failed" |
| 225 | end if |
| 226 | end subroutine assert_line_equals |
| 227 | |
| 228 | function buffer_get_char_at(buffer, position) result(ch) |
| 229 | type(buffer_t), intent(in) :: buffer |
| 230 | integer, intent(in) :: position |
| 231 | character :: ch |
| 232 | integer :: actual_pos |
| 233 | |
| 234 | if (position < 1 .or. position > buffer%size) then |
| 235 | ch = char(0) |
| 236 | return |
| 237 | end if |
| 238 | |
| 239 | if (position < buffer%gap_start) then |
| 240 | ch = buffer%data(position:position) |
| 241 | else |
| 242 | ! Adjust position for gap |
| 243 | actual_pos = position + (buffer%gap_end - buffer%gap_start) |
| 244 | if (actual_pos <= len(buffer%data)) then |
| 245 | ch = buffer%data(actual_pos:actual_pos) |
| 246 | else |
| 247 | ch = char(0) |
| 248 | end if |
| 249 | end if |
| 250 | end function buffer_get_char_at |
| 251 | |
| 252 | end module test_driver_module |