| 1 | program test_basic_editing |
| 2 | use test_framework |
| 3 | use test_driver_module |
| 4 | use text_buffer_module |
| 5 | use editor_state_module |
| 6 | implicit none |
| 7 | |
| 8 | type(test_suite) :: suite |
| 9 | type(test_case), allocatable :: tests(:) |
| 10 | |
| 11 | ! Define test suite |
| 12 | suite%name = "Basic Editing Tests" |
| 13 | |
| 14 | ! Allocate and configure tests |
| 15 | allocate(tests(10)) |
| 16 | |
| 17 | tests(1)%name = "Insert characters" |
| 18 | tests(1)%test_procedure => test_insert_chars |
| 19 | |
| 20 | tests(2)%name = "Backspace" |
| 21 | tests(2)%test_procedure => test_backspace |
| 22 | |
| 23 | tests(3)%name = "Delete forward" |
| 24 | tests(3)%test_procedure => test_delete |
| 25 | |
| 26 | tests(4)%name = "Enter creates newline" |
| 27 | tests(4)%test_procedure => test_enter_newline |
| 28 | |
| 29 | tests(5)%name = "Tab inserts spaces" |
| 30 | tests(5)%test_procedure => test_tab_spaces |
| 31 | |
| 32 | tests(6)%name = "Cursor movement" |
| 33 | tests(6)%test_procedure => test_cursor_movement |
| 34 | |
| 35 | tests(7)%name = "Home/End navigation" |
| 36 | tests(7)%test_procedure => test_home_end |
| 37 | |
| 38 | tests(8)%name = "Word jumping" |
| 39 | tests(8)%test_procedure => test_word_jump |
| 40 | |
| 41 | tests(9)%name = "Auto-indent on enter" |
| 42 | tests(9)%test_procedure => test_auto_indent |
| 43 | |
| 44 | tests(10)%name = "Smart home behavior" |
| 45 | tests(10)%test_procedure => test_smart_home |
| 46 | |
| 47 | suite%tests = tests |
| 48 | |
| 49 | ! Run the test suite |
| 50 | call run_tests(suite) |
| 51 | |
| 52 | contains |
| 53 | |
| 54 | subroutine test_insert_chars() |
| 55 | type(editor_state_t) :: editor |
| 56 | type(buffer_t) :: buffer |
| 57 | |
| 58 | call create_test_editor(editor, buffer) |
| 59 | call simulate_typing(editor, buffer, "Hello World") |
| 60 | call assert_buffer_equals(buffer, "Hello World", "Insert text") |
| 61 | call assert_cursor_at(editor, 1, 12, "Cursor after insert") |
| 62 | call destroy_test_editor(editor, buffer) |
| 63 | end subroutine test_insert_chars |
| 64 | |
| 65 | subroutine test_backspace() |
| 66 | type(editor_state_t) :: editor |
| 67 | type(buffer_t) :: buffer |
| 68 | |
| 69 | call create_test_editor(editor, buffer, "Hello") |
| 70 | editor%cursors(1)%column = 6 ! Position after "Hello" |
| 71 | call simulate_key(editor, buffer, "backspace") |
| 72 | call assert_buffer_equals(buffer, "Hell", "Backspace deletes") |
| 73 | call assert_cursor_at(editor, 1, 5, "Cursor after backspace") |
| 74 | call destroy_test_editor(editor, buffer) |
| 75 | end subroutine test_backspace |
| 76 | |
| 77 | subroutine test_delete() |
| 78 | type(editor_state_t) :: editor |
| 79 | type(buffer_t) :: buffer |
| 80 | |
| 81 | call create_test_editor(editor, buffer, "Hello") |
| 82 | editor%cursors(1)%column = 3 ! Position at 'l' |
| 83 | call simulate_key(editor, buffer, "delete") |
| 84 | call assert_buffer_equals(buffer, "Helo", "Delete forward") |
| 85 | call assert_cursor_at(editor, 1, 3, "Cursor stays put") |
| 86 | call destroy_test_editor(editor, buffer) |
| 87 | end subroutine test_delete |
| 88 | |
| 89 | subroutine test_enter_newline() |
| 90 | type(editor_state_t) :: editor |
| 91 | type(buffer_t) :: buffer |
| 92 | character(len=:), allocatable :: content |
| 93 | |
| 94 | call create_test_editor(editor, buffer, "Hello") |
| 95 | editor%cursors(1)%column = 3 ! Position after "He" |
| 96 | call simulate_key(editor, buffer, "enter") |
| 97 | content = get_buffer_text(buffer) |
| 98 | |
| 99 | ! Check that we have "He\nllo" |
| 100 | call assert_true(content == "He" // char(10) // "llo", "Enter splits line") |
| 101 | call assert_cursor_at(editor, 2, 1, "Cursor on new line") |
| 102 | call destroy_test_editor(editor, buffer) |
| 103 | end subroutine test_enter_newline |
| 104 | |
| 105 | subroutine test_tab_spaces() |
| 106 | type(editor_state_t) :: editor |
| 107 | type(buffer_t) :: buffer |
| 108 | |
| 109 | call create_test_editor(editor, buffer) |
| 110 | call simulate_key(editor, buffer, "tab") |
| 111 | call assert_buffer_equals(buffer, " ", "Tab inserts 4 spaces") |
| 112 | call assert_cursor_at(editor, 1, 5, "Cursor after tab") |
| 113 | call destroy_test_editor(editor, buffer) |
| 114 | end subroutine test_tab_spaces |
| 115 | |
| 116 | subroutine test_cursor_movement() |
| 117 | type(editor_state_t) :: editor |
| 118 | type(buffer_t) :: buffer |
| 119 | |
| 120 | call create_test_editor(editor, buffer, "Hello" // char(10) // "World") |
| 121 | |
| 122 | ! Test arrow keys |
| 123 | call simulate_key(editor, buffer, "right") |
| 124 | call assert_cursor_at(editor, 1, 2, "Right arrow") |
| 125 | |
| 126 | call simulate_key(editor, buffer, "down") |
| 127 | call assert_cursor_at(editor, 2, 2, "Down arrow") |
| 128 | |
| 129 | call simulate_key(editor, buffer, "left") |
| 130 | call assert_cursor_at(editor, 2, 1, "Left arrow") |
| 131 | |
| 132 | call simulate_key(editor, buffer, "up") |
| 133 | call assert_cursor_at(editor, 1, 1, "Up arrow") |
| 134 | |
| 135 | call destroy_test_editor(editor, buffer) |
| 136 | end subroutine test_cursor_movement |
| 137 | |
| 138 | subroutine test_home_end() |
| 139 | type(editor_state_t) :: editor |
| 140 | type(buffer_t) :: buffer |
| 141 | |
| 142 | call create_test_editor(editor, buffer, "Hello World") |
| 143 | editor%cursors(1)%column = 6 ! Middle of line |
| 144 | |
| 145 | call simulate_key(editor, buffer, "home") |
| 146 | call assert_cursor_at(editor, 1, 1, "Home key") |
| 147 | |
| 148 | call simulate_key(editor, buffer, "end") |
| 149 | call assert_cursor_at(editor, 1, 12, "End key") |
| 150 | |
| 151 | call destroy_test_editor(editor, buffer) |
| 152 | end subroutine test_home_end |
| 153 | |
| 154 | subroutine test_word_jump() |
| 155 | type(editor_state_t) :: editor |
| 156 | type(buffer_t) :: buffer |
| 157 | |
| 158 | call create_test_editor(editor, buffer, "Hello World Test") |
| 159 | |
| 160 | call simulate_key(editor, buffer, "alt-right") |
| 161 | call assert_cursor_at(editor, 1, 6, "Jump to end of 'Hello'") |
| 162 | |
| 163 | call simulate_key(editor, buffer, "alt-right") |
| 164 | call assert_cursor_at(editor, 1, 12, "Jump to end of 'World'") |
| 165 | |
| 166 | call simulate_key(editor, buffer, "alt-left") |
| 167 | call assert_cursor_at(editor, 1, 7, "Jump back to 'World'") |
| 168 | |
| 169 | call destroy_test_editor(editor, buffer) |
| 170 | end subroutine test_word_jump |
| 171 | |
| 172 | subroutine test_auto_indent() |
| 173 | type(editor_state_t) :: editor |
| 174 | type(buffer_t) :: buffer |
| 175 | |
| 176 | call create_test_editor(editor, buffer, " Hello") |
| 177 | editor%cursors(1)%column = 10 ! After "Hello" |
| 178 | |
| 179 | call simulate_key(editor, buffer, "enter") |
| 180 | call simulate_typing(editor, buffer, "World") |
| 181 | |
| 182 | call assert_line_equals(buffer, 1, " Hello", "First line preserved") |
| 183 | call assert_line_equals(buffer, 2, " World", "Auto-indented line") |
| 184 | |
| 185 | call destroy_test_editor(editor, buffer) |
| 186 | end subroutine test_auto_indent |
| 187 | |
| 188 | subroutine test_smart_home() |
| 189 | type(editor_state_t) :: editor |
| 190 | type(buffer_t) :: buffer |
| 191 | |
| 192 | call create_test_editor(editor, buffer, " Hello") |
| 193 | editor%cursors(1)%column = 9 ! At 'o' |
| 194 | |
| 195 | ! First press goes to first non-whitespace |
| 196 | call simulate_key(editor, buffer, "ctrl-a") |
| 197 | call assert_cursor_at(editor, 1, 5, "Smart home to first char") |
| 198 | |
| 199 | ! Second press goes to column 1 |
| 200 | call simulate_key(editor, buffer, "ctrl-a") |
| 201 | call assert_cursor_at(editor, 1, 1, "Smart home to column 1") |
| 202 | |
| 203 | ! Third press goes back to first non-whitespace |
| 204 | call simulate_key(editor, buffer, "ctrl-a") |
| 205 | call assert_cursor_at(editor, 1, 5, "Smart home toggles") |
| 206 | |
| 207 | call destroy_test_editor(editor, buffer) |
| 208 | end subroutine test_smart_home |
| 209 | |
| 210 | end program test_basic_editing |