From e6d5d3d0fbf10c2895fa3ae415f8b9992d5163f3 Mon Sep 17 00:00:00 2001 From: Loic Coenen Date: Fri, 1 May 2026 19:20:08 +0000 Subject: [PATCH] test: add tests for vim-like visual mode, move mode, yank/paste, marks, and scene navigation Co-authored-by: aider (deepseek/deepseek-coder) --- test_tui.c | 636 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 636 insertions(+) diff --git a/test_tui.c b/test_tui.c index 522092e..a688d8f 100644 --- a/test_tui.c +++ b/test_tui.c @@ -595,6 +595,618 @@ void test_command_mode_colon(void) { printf("PASSED\n"); } +// Test 29: Visual mode entry +void test_visual_mode_entry(void) { + printf("Test 29: Visual mode entry... "); + + // Simulate pressing 'v' to enter visual mode + UIMode current_mode = MODE_NORMAL; + int selected_row = 3, selected_col = 4; + int visual_start_row = 0, visual_start_col = 0; + int visual_end_row = 0, visual_end_col = 0; + + // Press 'v' + current_mode = MODE_VISUAL; + visual_start_row = selected_row; + visual_start_col = selected_col; + visual_end_row = selected_row; + visual_end_col = selected_col; + + assert(current_mode == MODE_VISUAL); + assert(visual_start_row == 3); + assert(visual_start_col == 4); + assert(visual_end_row == 3); + assert(visual_end_col == 4); + + printf("PASSED\n"); +} + +// Test 30: Visual mode selection expansion +void test_visual_mode_selection(void) { + printf("Test 30: Visual mode selection expansion... "); + + int visual_start_row = 2, visual_start_col = 2; + int visual_end_row = 2, visual_end_col = 2; + + // Move right + visual_end_col = (visual_end_col + 1) % 8; + assert(visual_end_col == 3); + + // Move down + visual_end_row = (visual_end_row + 1) % 8; + assert(visual_end_row == 3); + + // Check selection bounds + int min_row = (visual_start_row < visual_end_row) ? visual_start_row : visual_end_row; + int max_row = (visual_start_row > visual_end_row) ? visual_start_row : visual_end_row; + int min_col = (visual_start_col < visual_end_col) ? visual_start_col : visual_end_col; + int max_col = (visual_start_col > visual_end_col) ? visual_start_col : visual_end_col; + + assert(min_row == 2); + assert(max_row == 3); + assert(min_col == 2); + assert(max_col == 3); + + printf("PASSED\n"); +} + +// Test 31: Visual mode escape returns to normal +void test_visual_mode_escape(void) { + printf("Test 31: Visual mode escape returns to normal... "); + + UIMode current_mode = MODE_VISUAL; + + // Press Escape + current_mode = MODE_NORMAL; + assert(current_mode == MODE_NORMAL); + + printf("PASSED\n"); +} + +// Test 32: Visual line selection +void test_visual_line_selection(void) { + printf("Test 32: Visual line selection... "); + + int selected_row = 3; + UIMode current_mode = MODE_NORMAL; + int visual_start_row = 0, visual_start_col = 0; + int visual_end_row = 0, visual_end_col = 0; + + // Press 'V' + current_mode = MODE_VISUAL; + visual_start_row = selected_row; + visual_start_col = 0; + visual_end_row = selected_row; + visual_end_col = 7; // GRID_COLS - 1 + + assert(current_mode == MODE_VISUAL); + assert(visual_start_row == 3); + assert(visual_start_col == 0); + assert(visual_end_row == 3); + assert(visual_end_col == 7); + + printf("PASSED\n"); +} + +// Test 33: Move mode entry and navigation +void test_move_mode_navigation(void) { + printf("Test 33: Move mode navigation... "); + + UIMode current_mode = MODE_NORMAL; + int selected_row = 3, selected_col = 4; + + // Enter move mode + current_mode = MODE_MOVE; + assert(current_mode == MODE_MOVE); + + // Move left + selected_col = (selected_col - 1 + 8) % 8; + assert(selected_col == 3); + + // Move down + selected_row = (selected_row + 1) % 8; + assert(selected_row == 4); + + // Move up + selected_row = (selected_row - 1 + 8) % 8; + assert(selected_row == 3); + + // Move right + selected_col = (selected_col + 1) % 8; + assert(selected_col == 4); + + printf("PASSED\n"); +} + +// Test 34: Move mode returns to normal on enter +void test_move_mode_enter(void) { + printf("Test 34: Move mode returns to normal on enter... "); + + UIMode current_mode = MODE_MOVE; + + // Press Enter + current_mode = MODE_NORMAL; + assert(current_mode == MODE_NORMAL); + + printf("PASSED\n"); +} + +// Test 35: Move mode returns to normal on escape +void test_move_mode_escape(void) { + printf("Test 35: Move mode returns to normal on escape... "); + + UIMode current_mode = MODE_MOVE; + + // Press Escape + current_mode = MODE_NORMAL; + assert(current_mode == MODE_NORMAL); + + printf("PASSED\n"); +} + +// Test 36: Delete (reset) single clip +void test_delete_single_clip(void) { + printf("Test 36: Delete (reset) single clip... "); + Engine *engine = create_test_engine(); + + // Set up a looping clip + int clip_idx = 10; + engine->clips[clip_idx].state = CLIP_LOOPING; + engine->clips[clip_idx].buffer_size = 100; + engine->clips[clip_idx].write_position = 100; + engine->clips[clip_idx].read_position = 50; + + // Simulate pressing 'd' on selected clip + engine_reset_clip(engine, clip_idx); + engine_process_commands(engine); + + assert(engine->clips[clip_idx].state == CLIP_EMPTY); + assert(engine->clips[clip_idx].buffer_size == 0); + assert(engine->clips[clip_idx].write_position == 0); + assert(engine->clips[clip_idx].read_position == 0); + + destroy_test_engine(engine); + printf("PASSED\n"); +} + +// Test 37: Delete (reset) multiple clips via visual selection +void test_delete_visual_selection(void) { + printf("Test 37: Delete visual selection... "); + Engine *engine = create_test_engine(); + + // Set up clips in a 2x2 selection area + int clips[] = {18, 19, 26, 27}; // rows 2-3, cols 2-3 + for (int i = 0; i < 4; i++) { + engine->clips[clips[i]].state = CLIP_LOOPING; + engine->clips[clips[i]].buffer_size = 100; + engine->clips[clips[i]].write_position = 100; + engine->clips[clips[i]].read_position = 50; + } + + // Simulate deleting the selection + for (int i = 0; i < 4; i++) { + engine_reset_clip(engine, clips[i]); + } + engine_process_commands(engine); + + for (int i = 0; i < 4; i++) { + assert(engine->clips[clips[i]].state == CLIP_EMPTY); + assert(engine->clips[clips[i]].buffer_size == 0); + } + + destroy_test_engine(engine); + printf("PASSED\n"); +} + +// Test 38: Yank single clip +void test_yank_single_clip(void) { + printf("Test 38: Yank single clip... "); + Engine *engine = create_test_engine(); + + // Set up a clip + int clip_idx = 15; + engine->clips[clip_idx].state = CLIP_LOOPING; + engine->clips[clip_idx].buffer_size = 100; + + // Simulate yanking the clip + int yank_buffer[1] = {clip_idx}; + int yank_count = 1; + + assert(yank_buffer[0] == 15); + assert(yank_count == 1); + + destroy_test_engine(engine); + printf("PASSED\n"); +} + +// Test 39: Yank multiple clips via visual selection +void test_yank_visual_selection(void) { + printf("Test 39: Yank visual selection... "); + Engine *engine = create_test_engine(); + + // Set up clips in a 2x2 selection + int clips[] = {18, 19, 26, 27}; + for (int i = 0; i < 4; i++) { + engine->clips[clips[i]].state = CLIP_LOOPING; + engine->clips[clips[i]].buffer_size = 100; + } + + // Simulate yanking the selection + int yank_buffer[4]; + int yank_count = 4; + for (int i = 0; i < 4; i++) { + yank_buffer[i] = clips[i]; + } + + assert(yank_count == 4); + assert(yank_buffer[0] == 18); + assert(yank_buffer[1] == 19); + assert(yank_buffer[2] == 26); + assert(yank_buffer[3] == 27); + + destroy_test_engine(engine); + printf("PASSED\n"); +} + +// Test 40: Paste clips +void test_paste_clips(void) { + printf("Test 40: Paste clips... "); + Engine *engine = create_test_engine(); + + // Simulate yanking clip at position (1, 1) = clip 9 + int yank_buffer[] = {9}; + int yank_count = 1; + int selected_row = 3, selected_col = 3; // Paste at position (3, 3) = clip 27 + + // Calculate offset + int first_yanked_row = yank_buffer[0] / 8; + int first_yanked_col = yank_buffer[0] % 8; + int row_offset = selected_row - first_yanked_row; + int col_offset = selected_col - first_yanked_col; + + assert(first_yanked_row == 1); + assert(first_yanked_col == 1); + assert(row_offset == 2); + assert(col_offset == 2); + + // Simulate paste: trigger clip at new position + int new_row = first_yanked_row + row_offset; + int new_col = first_yanked_col + col_offset; + int new_clip_idx = new_row * 8 + new_col; + + assert(new_row == 3); + assert(new_col == 3); + assert(new_clip_idx == 27); + + engine_trigger_clip(engine, new_clip_idx); + engine_process_commands(engine); + assert(engine->clips[27].state == CLIP_RECORDING); + + destroy_test_engine(engine); + printf("PASSED\n"); +} + +// Test 41: Paste with bounds checking +void test_paste_bounds_checking(void) { + printf("Test 41: Paste bounds checking... "); + Engine *engine = create_test_engine(); + + // Yank clip at position (7, 7) = clip 63 (bottom-right) + int yank_buffer[] = {63}; + int yank_count = 1; + int selected_row = 0, selected_col = 0; // Paste at top-left + + // Calculate offset + int first_yanked_row = yank_buffer[0] / 8; + int first_yanked_col = yank_buffer[0] % 8; + int row_offset = selected_row - first_yanked_row; + int col_offset = selected_col - first_yanked_col; + + assert(row_offset == -7); + assert(col_offset == -7); + + // Simulate paste: new position should be (0, 0) = clip 0 + int new_row = first_yanked_row + row_offset; + int new_col = first_yanked_col + col_offset; + + assert(new_row == 0); + assert(new_col == 0); + + // Test out-of-bounds paste (should be clipped) + selected_row = 0; + selected_col = 0; + row_offset = selected_row - 0; + col_offset = selected_col - 0; + + // Yank clip at (0, 0) and try to paste at (0, 0) - should work + new_row = 0 + row_offset; + new_col = 0 + col_offset; + assert(new_row >= 0 && new_row < 8 && new_col >= 0 && new_col < 8); + + destroy_test_engine(engine); + printf("PASSED\n"); +} + +// Test 42: Mark setting +void test_mark_setting(void) { + printf("Test 42: Mark setting... "); + + int marks[26]; + for (int i = 0; i < 26; i++) { + marks[i] = -1; + } + + int selected_row = 3, selected_col = 4; + int clip_idx = selected_row * 8 + selected_col; + + // Set mark 'a' + char mark_char = 'a'; + int idx = mark_char - 'a'; + marks[idx] = clip_idx; + + assert(marks[0] == 28); // 3 * 8 + 4 = 28 + + // Set mark 'z' + mark_char = 'z'; + idx = mark_char - 'a'; + marks[idx] = clip_idx; + + assert(marks[25] == 28); + + printf("PASSED\n"); +} + +// Test 43: Go to mark +void test_go_to_mark(void) { + printf("Test 43: Go to mark... "); + + int marks[26]; + for (int i = 0; i < 26; i++) { + marks[i] = -1; + } + + // Set mark 'b' to clip 42 + marks[1] = 42; // 'b' - 'a' = 1 + + // Go to mark 'b' + char mark_char = 'b'; + int idx = mark_char - 'a'; + int clip_idx = marks[idx]; + + assert(clip_idx == 42); + + // Calculate row and col + int row = clip_idx / 8; + int col = clip_idx % 8; + assert(row == 5); + assert(col == 2); + + printf("PASSED\n"); +} + +// Test 44: Go to unset mark +void test_go_to_unset_mark(void) { + printf("Test 44: Go to unset mark... "); + + int marks[26]; + for (int i = 0; i < 26; i++) { + marks[i] = -1; + } + + // Try to go to unset mark 'c' + char mark_char = 'c'; + int idx = mark_char - 'a'; + int clip_idx = marks[idx]; + + assert(clip_idx == -1); // Mark not set + + printf("PASSED\n"); +} + +// Test 45: Play next scene +void test_play_next_scene(void) { + printf("Test 45: Play next scene... "); + Engine *engine = create_test_engine(); + + int selected_row = 3; + + // Play next scene + int next_row = (selected_row + 1) % 8; + engine_trigger_scene(engine, next_row); + engine_process_commands(engine); + + assert(next_row == 4); + + // Verify clips in scene 4 are recording + for (int ch = 0; ch < MAX_CHANNELS; ch++) { + int clip_idx = CLIP_INDEX(4, ch); + assert(engine->clips[clip_idx].state == CLIP_RECORDING); + } + + destroy_test_engine(engine); + printf("PASSED\n"); +} + +// Test 46: Play previous scene +void test_play_prev_scene(void) { + printf("Test 46: Play previous scene... "); + Engine *engine = create_test_engine(); + + int selected_row = 3; + + // Play previous scene + int prev_row = (selected_row - 1 + 8) % 8; + engine_trigger_scene(engine, prev_row); + engine_process_commands(engine); + + assert(prev_row == 2); + + // Verify clips in scene 2 are recording + for (int ch = 0; ch < MAX_CHANNELS; ch++) { + int clip_idx = CLIP_INDEX(2, ch); + assert(engine->clips[clip_idx].state == CLIP_RECORDING); + } + + destroy_test_engine(engine); + printf("PASSED\n"); +} + +// Test 47: Play next scene wraps around +void test_play_next_scene_wrap(void) { + printf("Test 47: Play next scene wraps around... "); + Engine *engine = create_test_engine(); + + int selected_row = 7; // Last row + + // Play next scene should wrap to row 0 + int next_row = (selected_row + 1) % 8; + assert(next_row == 0); + + engine_trigger_scene(engine, next_row); + engine_process_commands(engine); + + for (int ch = 0; ch < MAX_CHANNELS; ch++) { + int clip_idx = CLIP_INDEX(0, ch); + assert(engine->clips[clip_idx].state == CLIP_RECORDING); + } + + destroy_test_engine(engine); + printf("PASSED\n"); +} + +// Test 48: Play previous scene wraps around +void test_play_prev_scene_wrap(void) { + printf("Test 48: Play previous scene wraps around... "); + Engine *engine = create_test_engine(); + + int selected_row = 0; // First row + + // Play previous scene should wrap to row 7 + int prev_row = (selected_row - 1 + 8) % 8; + assert(prev_row == 7); + + engine_trigger_scene(engine, prev_row); + engine_process_commands(engine); + + for (int ch = 0; ch < MAX_CHANNELS; ch++) { + int clip_idx = CLIP_INDEX(7, ch); + assert(engine->clips[clip_idx].state == CLIP_RECORDING); + } + + destroy_test_engine(engine); + printf("PASSED\n"); +} + +// Test 49: Visual mode delete then escape +void test_visual_delete_then_escape(void) { + printf("Test 49: Visual mode delete then escape... "); + Engine *engine = create_test_engine(); + + // Set up clips in visual selection + int clips[] = {18, 19, 26, 27}; + for (int i = 0; i < 4; i++) { + engine->clips[clips[i]].state = CLIP_LOOPING; + engine->clips[clips[i]].buffer_size = 100; + } + + // Simulate visual mode delete + for (int i = 0; i < 4; i++) { + engine_reset_clip(engine, clips[i]); + } + engine_process_commands(engine); + + // Verify clips are reset + for (int i = 0; i < 4; i++) { + assert(engine->clips[clips[i]].state == CLIP_EMPTY); + } + + // Simulate returning to normal mode + UIMode current_mode = MODE_NORMAL; + assert(current_mode == MODE_NORMAL); + + destroy_test_engine(engine); + printf("PASSED\n"); +} + +// Test 50: Visual mode yank then escape +void test_visual_yank_then_escape(void) { + printf("Test 50: Visual mode yank then escape... "); + Engine *engine = create_test_engine(); + + // Set up clips in visual selection + int clips[] = {18, 19, 26, 27}; + for (int i = 0; i < 4; i++) { + engine->clips[clips[i]].state = CLIP_LOOPING; + engine->clips[clips[i]].buffer_size = 100; + } + + // Simulate yanking the selection + int yank_buffer[4]; + int yank_count = 4; + for (int i = 0; i < 4; i++) { + yank_buffer[i] = clips[i]; + } + + assert(yank_count == 4); + assert(yank_buffer[0] == 18); + assert(yank_buffer[3] == 27); + + // Simulate returning to normal mode + UIMode current_mode = MODE_NORMAL; + assert(current_mode == MODE_NORMAL); + + destroy_test_engine(engine); + printf("PASSED\n"); +} + +// Test 51: Multiple marks +void test_multiple_marks(void) { + printf("Test 51: Multiple marks... "); + + int marks[26]; + for (int i = 0; i < 26; i++) { + marks[i] = -1; + } + + // Set multiple marks + marks[0] = 0; // 'a' = clip 0 + marks[1] = 63; // 'b' = clip 63 + marks[2] = 28; // 'c' = clip 28 + + assert(marks[0] == 0); + assert(marks[1] == 63); + assert(marks[2] == 28); + + // Go to mark 'b' + int clip_idx = marks[1]; + int row = clip_idx / 8; + int col = clip_idx % 8; + assert(row == 7); + assert(col == 7); + + printf("PASSED\n"); +} + +// Test 52: Re-mark existing mark +void test_remark_existing_mark(void) { + printf("Test 52: Re-mark existing mark... "); + + int marks[26]; + for (int i = 0; i < 26; i++) { + marks[i] = -1; + } + + // Set mark 'a' to clip 10 + marks[0] = 10; + assert(marks[0] == 10); + + // Re-mark 'a' to clip 42 + marks[0] = 42; + assert(marks[0] == 42); + + printf("PASSED\n"); +} + int main(void) { printf("Running TUI tests...\n\n"); @@ -626,6 +1238,30 @@ int main(void) { test_command_mode_escape(); test_command_mode_enter(); test_command_mode_colon(); + test_visual_mode_entry(); + test_visual_mode_selection(); + test_visual_mode_escape(); + test_visual_line_selection(); + test_move_mode_navigation(); + test_move_mode_enter(); + test_move_mode_escape(); + test_delete_single_clip(); + test_delete_visual_selection(); + test_yank_single_clip(); + test_yank_visual_selection(); + test_paste_clips(); + test_paste_bounds_checking(); + test_mark_setting(); + test_go_to_mark(); + test_go_to_unset_mark(); + test_play_next_scene(); + test_play_prev_scene(); + test_play_next_scene_wrap(); + test_play_prev_scene_wrap(); + test_visual_delete_then_escape(); + test_visual_yank_then_escape(); + test_multiple_marks(); + test_remark_existing_mark(); printf("\nAll TUI tests passed!\n"); return 0;