#include #include #include #include #include #include "dispatcher.h" #include "tui.h" // Test helper functions static AppState create_test_state(void) { AppState state; memset(&state, 0, sizeof(AppState)); // Initialize clips for (int i = 0; i < MAX_CLIPS; i++) { state.clips[i].state = CLIP_EMPTY; state.clips[i].buffer = (float *)calloc(MAX_BUFFER_SIZE, sizeof(float)); assert(state.clips[i].buffer != NULL); state.clips[i].buffer_size = 0; state.clips[i].write_position = 0; state.clips[i].read_position = 0; } // Initialize transport state.transport_state = TRANSPORT_STOPPED; state.clock_source = CLOCK_SOURCE_INTERNAL; state.bpm = 120.0; state.samples_per_beat = (48000 * 60.0) / 120.0; state.clock_count = 0; state.beat_position = 0; state.bar_position = 0; state.sample_position = 0; state.sample_accumulator = 0.0; // Initialize quantize state.quantize_mode = QUANTIZE_OFF; state.quantize_threshold = 0; // Initialize undo state.undo.undo_index = 0; state.undo.redo_index = 0; state.undo.count = 0; state.undo.current_batch_size = 0; for (int i = 0; i < MAX_UNDO_HISTORY; i++) { state.undo.prev_clip_indices[i] = -1; state.undo.batch_sizes[i] = 0; } // JACK info state.sample_rate = 48000; state.running = true; return state; } static void destroy_test_state(AppState *state) { if (state) { for (int i = 0; i < MAX_CLIPS; i++) { free(state->clips[i].buffer); state->clips[i].buffer = NULL; } } } // Test 1: Grid to clip index mapping void test_grid_to_clip_index(void) { printf("Test 1: Grid to clip index mapping... "); // 8x8 grid should map to 64 clips assert(0 * 8 + 0 == 0); // Top-left assert(0 * 8 + 7 == 7); // Top-right assert(7 * 8 + 0 == 56); // Bottom-left assert(7 * 8 + 7 == 63); // Bottom-right assert(3 * 8 + 4 == 28); // Middle printf("PASSED\n"); } // Test 2: Trigger clip via grid position void test_trigger_via_grid(void) { printf("Test 2: Trigger clip via grid position... "); AppState state = create_test_state(); // Simulate pressing 't' on grid position (3, 4) = clip 28 int clip_idx = 3 * 8 + 4; Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clip_idx }; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_RECORDING); destroy_test_state(&state); printf("PASSED\n"); } // Test 3: Reset clip via grid position void test_reset_via_grid(void) { printf("Test 3: Reset clip via grid position... "); AppState state = create_test_state(); // Set up a clip at grid position (1, 2) = clip 10 int clip_idx = 1 * 8 + 2; state.clips[clip_idx].state = CLIP_LOOPING; state.clips[clip_idx].buffer_size = 100; // Simulate pressing 'r' Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip.clip_index = clip_idx }; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_EMPTY); assert(state.clips[clip_idx].buffer_size == 0); destroy_test_state(&state); printf("PASSED\n"); } // Test 4: Scene trigger via grid row void test_scene_via_grid(void) { printf("Test 4: Scene trigger via grid row... "); AppState state = create_test_state(); // Simulate pressing 's' on row 3 int scene_index = 3; Action action = { .type = ACTION_TRIGGER_SCENE, .data.trigger_scene.scene_index = scene_index }; state = reducer(state, action); // All clips in scene 3 should be recording for (int ch = 0; ch < MAX_CHANNELS; ch++) { int clip_idx = CLIP_INDEX(scene_index, ch); assert(state.clips[clip_idx].state == CLIP_RECORDING); } destroy_test_state(&state); printf("PASSED\n"); } // Test 5: Quantize mode cycling void test_quantize_cycling(void) { printf("Test 5: Quantize mode cycling... "); AppState state = create_test_state(); // Simulate pressing 'q' to cycle through modes assert(state.quantize_mode == QUANTIZE_OFF); // Cycle: OFF -> BEAT Action action = { .type = ACTION_SET_QUANTIZE_MODE, .data.set_quantize_mode.mode = QUANTIZE_BEAT }; state = reducer(state, action); assert(state.quantize_mode == QUANTIZE_BEAT); // Cycle: BEAT -> BAR action.data.set_quantize_mode.mode = QUANTIZE_BAR; state = reducer(state, action); assert(state.quantize_mode == QUANTIZE_BAR); // Cycle: BAR -> OFF action.data.set_quantize_mode.mode = QUANTIZE_OFF; state = reducer(state, action); assert(state.quantize_mode == QUANTIZE_OFF); destroy_test_state(&state); printf("PASSED\n"); } // Test 6: Threshold toggling void test_threshold_toggle(void) { printf("Test 6: Threshold toggling... "); AppState state = create_test_state(); // Simulate pressing 'T' to toggle threshold assert(state.quantize_threshold == 0); // Toggle to 1000 Action action = { .type = ACTION_SET_QUANTIZE_THRESHOLD, .data.set_quantize_threshold.threshold = 1000 }; state = reducer(state, action); assert(state.quantize_threshold == 1000); // Toggle back to 0 action.data.set_quantize_threshold.threshold = 0; state = reducer(state, action); assert(state.quantize_threshold == 0); destroy_test_state(&state); printf("PASSED\n"); } // Test 7: Transport reset void test_transport_reset_via_tui(void) { printf("Test 7: Transport reset via TUI... "); AppState state = create_test_state(); // Set up transport state state.transport_state = TRANSPORT_PLAYING; state.clock_count = 100; state.beat_position = 2; state.bar_position = 5; state.sample_position = 10000; // Simulate pressing 'x' Action action = { .type = ACTION_TRANSPORT_STOP }; state = reducer(state, action); assert(state.transport_state == TRANSPORT_STOPPED); assert(state.clock_count == 0); assert(state.beat_position == 0); assert(state.bar_position == 0); assert(state.sample_position == 0); destroy_test_state(&state); printf("PASSED\n"); } // Test 8: Navigation wrapping void test_navigation_wrapping(void) { printf("Test 8: Navigation wrapping... "); // Test that navigation wraps around the grid // Left from column 0 should go to column 7 int col = 0; col = (col - 1 + 8) % 8; assert(col == 7); // Right from column 7 should go to column 0 col = 7; col = (col + 1) % 8; assert(col == 0); // Up from row 0 should go to row 7 int row = 0; row = (row - 1 + 8) % 8; assert(row == 7); // Down from row 7 should go to row 0 row = 7; row = (row + 1) % 8; assert(row == 0); printf("PASSED\n"); } // Test 9: Multiple clips in different states void test_multiple_clip_states(void) { printf("Test 9: Multiple clips in different states... "); AppState state = create_test_state(); // Set up clips in various states state.clips[0].state = CLIP_EMPTY; state.clips[1].state = CLIP_RECORDING; state.clips[2].state = CLIP_LOOPING; state.clips[3].state = CLIP_STOPPED; // Verify states assert(state.clips[0].state == CLIP_EMPTY); assert(state.clips[1].state == CLIP_RECORDING); assert(state.clips[2].state == CLIP_LOOPING); assert(state.clips[3].state == CLIP_STOPPED); destroy_test_state(&state); printf("PASSED\n"); } // Test 10: Buffer size display void test_buffer_size_display(void) { printf("Test 10: Buffer size display... "); AppState state = create_test_state(); // Set up a clip with known buffer size state.clips[5].state = CLIP_LOOPING; state.clips[5].buffer_size = 48000; // 1 second at 48kHz // Verify buffer size assert(state.clips[5].buffer_size == 48000); destroy_test_state(&state); printf("PASSED\n"); } // Test 11: Help toggle void test_help_toggle(void) { printf("Test 11: Help toggle... "); // Test that help flag toggles correctly bool show_help = false; show_help = !show_help; assert(show_help == true); show_help = !show_help; assert(show_help == false); printf("PASSED\n"); } // Test 12: Escape key handling void test_escape_handling(void) { printf("Test 12: Escape key handling... "); // Test that escape key (27) is handled int ch = 27; assert(ch == 27); // Escape // Test that 'Q' is handled ch = 'Q'; assert(ch == 'Q'); printf("PASSED\n"); } // Test 13: TUI init and cleanup (without ncurses) void test_tui_init_cleanup(void) { printf("Test 13: TUI init and cleanup... "); AppState state = create_test_state(); // Verify state is valid assert(state.sample_rate == 48000); assert(state.running == true); destroy_test_state(&state); printf("PASSED (skipped ncurses init)\n"); } // Test 14: State to color mapping void test_state_to_color_mapping(void) { printf("Test 14: State to color mapping... "); // Verify state values match expected color indices assert(CLIP_EMPTY == 0); assert(CLIP_RECORDING == 1); assert(CLIP_LOOPING == 2); assert(CLIP_STOPPED == 3); printf("PASSED\n"); } // Test 15: Full grid coverage void test_full_grid_coverage(void) { printf("Test 15: Full grid coverage... "); AppState state = create_test_state(); // Trigger all 64 clips via grid positions for (int row = 0; row < 8; row++) { for (int col = 0; col < 8; col++) { int clip_idx = row * 8 + col; Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clip_idx }; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_RECORDING); } } // Verify all clips are recording for (int i = 0; i < MAX_CLIPS; i++) { assert(state.clips[i].state == CLIP_RECORDING); } destroy_test_state(&state); printf("PASSED\n"); } // Test 16: Scene trigger from each row void test_scene_from_each_row(void) { printf("Test 16: Scene trigger from each row... "); AppState state = create_test_state(); // Trigger scene from each row for (int row = 0; row < 8; row++) { Action action = { .type = ACTION_TRIGGER_SCENE, .data.trigger_scene.scene_index = row }; state = reducer(state, action); // Verify all clips in this scene are recording for (int ch = 0; ch < MAX_CHANNELS; ch++) { int clip_idx = CLIP_INDEX(row, ch); assert(state.clips[clip_idx].state == CLIP_RECORDING); } } destroy_test_state(&state); printf("PASSED\n"); } // Test 17: Quantize mode cycle through all modes void test_quantize_full_cycle(void) { printf("Test 17: Quantize mode full cycle... "); AppState state = create_test_state(); // Cycle through all modes twice for (int cycle = 0; cycle < 2; cycle++) { Action action = { .type = ACTION_SET_QUANTIZE_MODE, .data.set_quantize_mode.mode = QUANTIZE_OFF }; state = reducer(state, action); assert(state.quantize_mode == QUANTIZE_OFF); action.data.set_quantize_mode.mode = QUANTIZE_BEAT; state = reducer(state, action); assert(state.quantize_mode == QUANTIZE_BEAT); action.data.set_quantize_mode.mode = QUANTIZE_BAR; state = reducer(state, action); assert(state.quantize_mode == QUANTIZE_BAR); } destroy_test_state(&state); printf("PASSED\n"); } // Test 18: Multiple threshold toggles void test_multiple_threshold_toggles(void) { printf("Test 18: Multiple threshold toggles... "); AppState state = create_test_state(); // Toggle threshold multiple times for (int i = 0; i < 5; i++) { if (state.quantize_threshold == 0) { Action action = { .type = ACTION_SET_QUANTIZE_THRESHOLD, .data.set_quantize_threshold.threshold = 1000 }; state = reducer(state, action); assert(state.quantize_threshold == 1000); } else { Action action = { .type = ACTION_SET_QUANTIZE_THRESHOLD, .data.set_quantize_threshold.threshold = 0 }; state = reducer(state, action); assert(state.quantize_threshold == 0); } } destroy_test_state(&state); printf("PASSED\n"); } // Test 19: Transport reset multiple times void test_multiple_transport_resets(void) { printf("Test 19: Multiple transport resets... "); AppState state = create_test_state(); // Reset transport multiple times for (int i = 0; i < 5; i++) { state.transport_state = TRANSPORT_PLAYING; state.clock_count = 100 + i; state.beat_position = i % 4; state.bar_position = i; state.sample_position = 10000 * i; Action action = { .type = ACTION_TRANSPORT_STOP }; state = reducer(state, action); assert(state.transport_state == TRANSPORT_STOPPED); assert(state.clock_count == 0); assert(state.beat_position == 0); assert(state.bar_position == 0); assert(state.sample_position == 0); } destroy_test_state(&state); printf("PASSED\n"); } // Test 20: Navigation with arrow keys void test_arrow_key_navigation(void) { printf("Test 20: Arrow key navigation... "); // Test that arrow keys produce same results as hjkl int row = 3, col = 4; // KEY_LEFT (same as 'h') col = (col - 1 + 8) % 8; assert(col == 3); // KEY_DOWN (same as 'j') row = (row + 1) % 8; assert(row == 4); // KEY_UP (same as 'k') row = (row - 1 + 8) % 8; assert(row == 3); // KEY_RIGHT (same as 'l') col = (col + 1) % 8; assert(col == 4); printf("PASSED\n"); } // Test 21: Command mode parsing - quit command void test_command_mode_quit(void) { printf("Test 21: Command mode quit command... "); // Test that ":q" command is recognized const char *cmd = "q"; assert(strcmp(cmd, "q") == 0); printf("PASSED\n"); } // Test 22: Command mode parsing - empty command void test_command_mode_empty(void) { printf("Test 22: Command mode empty command... "); // Test that empty command doesn't quit const char *cmd = ""; assert(strcmp(cmd, "q") != 0); printf("PASSED\n"); } // Test 23: Command mode parsing - unknown command void test_command_mode_unknown(void) { printf("Test 23: Command mode unknown command... "); // Test that unknown commands don't quit const char *cmd = "unknown"; assert(strcmp(cmd, "q") != 0); printf("PASSED\n"); } // Test 24: Command mode buffer overflow protection void test_command_mode_buffer_overflow(void) { printf("Test 24: Command mode buffer overflow protection... "); // Test that buffer doesn't overflow with long input char cmd_buffer[256]; int cmd_pos = 0; memset(cmd_buffer, 0, sizeof(cmd_buffer)); // Simulate typing more characters than buffer can hold for (int i = 0; i < 300; i++) { if (cmd_pos < (int)sizeof(cmd_buffer) - 1) { cmd_buffer[cmd_pos++] = 'a'; } } cmd_buffer[cmd_pos] = '\0'; // Buffer should not overflow assert(strlen(cmd_buffer) < sizeof(cmd_buffer)); assert(cmd_pos <= (int)sizeof(cmd_buffer) - 1); printf("PASSED\n"); } // Test 25: Command mode backspace handling void test_command_mode_backspace(void) { printf("Test 25: Command mode backspace handling... "); // Test backspace removes characters char cmd_buffer[256]; int cmd_pos = 0; memset(cmd_buffer, 0, sizeof(cmd_buffer)); // Type "test" cmd_buffer[cmd_pos++] = 't'; cmd_buffer[cmd_pos++] = 'e'; cmd_buffer[cmd_pos++] = 's'; cmd_buffer[cmd_pos++] = 't'; cmd_buffer[cmd_pos] = '\0'; assert(strcmp(cmd_buffer, "test") == 0); // Backspace twice cmd_pos--; cmd_buffer[cmd_pos] = '\0'; cmd_pos--; cmd_buffer[cmd_pos] = '\0'; assert(strcmp(cmd_buffer, "te") == 0); printf("PASSED\n"); } // Test 26: Command mode escape cancels void test_command_mode_escape(void) { printf("Test 26: Command mode escape cancels... "); // Test that escape key (27) cancels command mode int ch = 27; assert(ch == 27); // Escape printf("PASSED\n"); } // Test 27: Command mode enter executes void test_command_mode_enter(void) { printf("Test 27: Command mode enter executes... "); // Test that enter key executes command int ch = '\n'; assert(ch == '\n'); ch = '\r'; assert(ch == '\r'); printf("PASSED\n"); } // Test 28: Command mode colon triggers mode void test_command_mode_colon(void) { printf("Test 28: Command mode colon triggers mode... "); // Test that ':' character triggers command mode char ch = ':'; assert(ch == ':'); 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 int current_mode = 1; // MODE_VISUAL 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 = 1; // 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 == 1); 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... "); int current_mode = 1; // MODE_VISUAL // Press Escape current_mode = 0; // MODE_NORMAL assert(current_mode == 0); printf("PASSED\n"); } // Test 32: Visual line selection void test_visual_line_selection(void) { printf("Test 32: Visual line selection... "); int selected_row = 3; int current_mode = 0; // MODE_NORMAL int visual_start_row = 0, visual_start_col = 0; int visual_end_row = 0, visual_end_col = 0; // Press 'V' current_mode = 1; // 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 == 1); 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... "); int current_mode = 0; // MODE_NORMAL int selected_row = 3, selected_col = 4; // Enter move mode current_mode = 2; // MODE_MOVE assert(current_mode == 2); // 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... "); int current_mode = 2; // MODE_MOVE // Press Enter current_mode = 0; // MODE_NORMAL assert(current_mode == 0); 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... "); int current_mode = 2; // MODE_MOVE // Press Escape current_mode = 0; // MODE_NORMAL assert(current_mode == 0); printf("PASSED\n"); } // Test 36: Delete (reset) single clip void test_delete_single_clip(void) { printf("Test 36: Delete (reset) single clip... "); AppState state = create_test_state(); // Set up a looping clip int clip_idx = 10; state.clips[clip_idx].state = CLIP_LOOPING; state.clips[clip_idx].buffer_size = 100; state.clips[clip_idx].write_position = 100; state.clips[clip_idx].read_position = 50; // Simulate pressing 'd' on selected clip Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip.clip_index = clip_idx }; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_EMPTY); assert(state.clips[clip_idx].buffer_size == 0); assert(state.clips[clip_idx].write_position == 0); assert(state.clips[clip_idx].read_position == 0); destroy_test_state(&state); printf("PASSED\n"); } // Test 37: Delete (reset) multiple clips via visual selection void test_delete_visual_selection(void) { printf("Test 37: Delete visual selection... "); AppState state = create_test_state(); // 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++) { state.clips[clips[i]].state = CLIP_LOOPING; state.clips[clips[i]].buffer_size = 100; state.clips[clips[i]].write_position = 100; state.clips[clips[i]].read_position = 50; } // Simulate deleting the selection for (int i = 0; i < 4; i++) { Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip.clip_index = clips[i] }; state = reducer(state, action); } for (int i = 0; i < 4; i++) { assert(state.clips[clips[i]].state == CLIP_EMPTY); assert(state.clips[clips[i]].buffer_size == 0); } destroy_test_state(&state); printf("PASSED\n"); } // Test 38: Yank single clip void test_yank_single_clip(void) { printf("Test 38: Yank single clip... "); AppState state = create_test_state(); // Set up a clip int clip_idx = 15; state.clips[clip_idx].state = CLIP_LOOPING; state.clips[clip_idx].buffer_size = 100; // Simulate yanking the clip - should stop it (looping -> stopped) Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clip_idx }; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_STOPPED); destroy_test_state(&state); printf("PASSED\n"); } // Test 39: Yank multiple clips via visual selection void test_yank_visual_selection(void) { printf("Test 39: Yank visual selection... "); AppState state = create_test_state(); // Set up clips in a 2x2 selection int clips[] = {18, 19, 26, 27}; for (int i = 0; i < 4; i++) { state.clips[clips[i]].state = CLIP_LOOPING; state.clips[clips[i]].buffer_size = 100; } // Simulate yanking the selection - should stop all clips for (int i = 0; i < 4; i++) { Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clips[i] }; state = reducer(state, action); } for (int i = 0; i < 4; i++) { assert(state.clips[clips[i]].state == CLIP_STOPPED); } destroy_test_state(&state); printf("PASSED\n"); } // Test 40: Paste clips void test_paste_clips(void) { printf("Test 40: Paste clips... "); AppState state = create_test_state(); // Simulate yanking clip at position (1, 1) = clip 9 int yank_buffer[] = {9}; 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 three times to go empty -> recording -> looping -> stopped 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); Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = new_clip_idx }; state = reducer(state, action); state = reducer(state, action); state = reducer(state, action); assert(state.clips[27].state == CLIP_STOPPED); destroy_test_state(&state); printf("PASSED\n"); } // Test 41: Paste with bounds checking void test_paste_bounds_checking(void) { printf("Test 41: Paste bounds checking... "); AppState state = create_test_state(); // Yank clip at position (7, 7) = clip 63 (bottom-right) int yank_buffer[] = {63}; 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_state(&state); 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... "); AppState state = create_test_state(); int selected_row = 3; // Play next scene int next_row = (selected_row + 1) % 8; Action action = { .type = ACTION_TRIGGER_SCENE, .data.trigger_scene.scene_index = next_row }; state = reducer(state, action); 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(state.clips[clip_idx].state == CLIP_RECORDING); } destroy_test_state(&state); printf("PASSED\n"); } // Test 46: Play previous scene void test_play_prev_scene(void) { printf("Test 46: Play previous scene... "); AppState state = create_test_state(); int selected_row = 3; // Play previous scene int prev_row = (selected_row - 1 + 8) % 8; Action action = { .type = ACTION_TRIGGER_SCENE, .data.trigger_scene.scene_index = prev_row }; state = reducer(state, action); 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(state.clips[clip_idx].state == CLIP_RECORDING); } destroy_test_state(&state); 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... "); AppState state = create_test_state(); 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); Action action = { .type = ACTION_TRIGGER_SCENE, .data.trigger_scene.scene_index = next_row }; state = reducer(state, action); for (int ch = 0; ch < MAX_CHANNELS; ch++) { int clip_idx = CLIP_INDEX(0, ch); assert(state.clips[clip_idx].state == CLIP_RECORDING); } destroy_test_state(&state); 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... "); AppState state = create_test_state(); 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); Action action = { .type = ACTION_TRIGGER_SCENE, .data.trigger_scene.scene_index = prev_row }; state = reducer(state, action); for (int ch = 0; ch < MAX_CHANNELS; ch++) { int clip_idx = CLIP_INDEX(7, ch); assert(state.clips[clip_idx].state == CLIP_RECORDING); } destroy_test_state(&state); 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... "); AppState state = create_test_state(); // Set up clips in visual selection int clips[] = {18, 19, 26, 27}; for (int i = 0; i < 4; i++) { state.clips[clips[i]].state = CLIP_LOOPING; state.clips[clips[i]].buffer_size = 100; } // Simulate visual mode delete for (int i = 0; i < 4; i++) { Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip.clip_index = clips[i] }; state = reducer(state, action); } // Verify clips are reset for (int i = 0; i < 4; i++) { assert(state.clips[clips[i]].state == CLIP_EMPTY); } // Simulate returning to normal mode int current_mode = 0; // MODE_NORMAL assert(current_mode == 0); destroy_test_state(&state); 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... "); AppState state = create_test_state(); // Set up clips in visual selection int clips[] = {18, 19, 26, 27}; for (int i = 0; i < 4; i++) { state.clips[clips[i]].state = CLIP_LOOPING; state.clips[clips[i]].buffer_size = 100; } // Simulate yanking the selection - should stop all clips for (int i = 0; i < 4; i++) { Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clips[i] }; state = reducer(state, action); } // Verify clips are stopped for (int i = 0; i < 4; i++) { assert(state.clips[clips[i]].state == CLIP_STOPPED); } // Simulate returning to normal mode int current_mode = 0; // MODE_NORMAL assert(current_mode == 0); destroy_test_state(&state); 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"); } // Test 53: Undo single clip trigger void test_undo_single_trigger(void) { printf("Test 53: Undo single clip trigger... "); AppState state = create_test_state();; // Start with empty clip int clip_idx = 10; assert(state.clips[clip_idx].state == CLIP_EMPTY); // Trigger clip (empty -> recording) Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clip_idx }; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_RECORDING); // Undo: should go back to empty action.type = ACTION_UNDO; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_EMPTY); printf("PASSED\n"); destroy_test_state(&state); } // Test 54: Undo multiple clip triggers void test_undo_multiple_triggers(void) { printf("Test 54: Undo multiple clip triggers... "); AppState state = create_test_state(); int clip1 = 10, clip2 = 11, clip3 = 12; // Trigger three clips Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clip1 }; state = reducer(state, action); assert(state.clips[clip1].state == CLIP_RECORDING); action.data.trigger_clip.clip_index = clip2; state = reducer(state, action); assert(state.clips[clip2].state == CLIP_RECORDING); action.data.trigger_clip.clip_index = clip3; state = reducer(state, action); assert(state.clips[clip3].state == CLIP_RECORDING); // Undo last action: clip3 should go back to empty action.type = ACTION_UNDO; state = reducer(state, action); assert(state.clips[clip3].state == CLIP_EMPTY); assert(state.clips[clip2].state == CLIP_RECORDING); assert(state.clips[clip1].state == CLIP_RECORDING); // Undo again: clip2 should go back to empty state = reducer(state, action); assert(state.clips[clip2].state == CLIP_EMPTY); assert(state.clips[clip1].state == CLIP_RECORDING); printf("PASSED\n"); destroy_test_state(&state); } // Test 55: Redo single undo void test_redo_single_undo(void) { printf("Test 55: Redo single undo... "); AppState state = create_test_state(); int clip_idx = 10; // Trigger clip Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clip_idx }; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_RECORDING); // Undo action.type = ACTION_UNDO; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_EMPTY); // Redo: should go back to recording action.type = ACTION_REDO; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_RECORDING); printf("PASSED\n"); destroy_test_state(&state); } // Test 56: Redo after multiple undos void test_redo_multiple_undos(void) { printf("Test 56: Redo after multiple undos... "); AppState state = create_test_state(); int clip1 = 10, clip2 = 11; // Trigger two clips Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clip1 }; state = reducer(state, action); action.data.trigger_clip.clip_index = clip2; state = reducer(state, action); assert(state.clips[clip1].state == CLIP_RECORDING); assert(state.clips[clip2].state == CLIP_RECORDING); // Undo twice action.type = ACTION_UNDO; state = reducer(state, action); state = reducer(state, action); assert(state.clips[clip1].state == CLIP_EMPTY); assert(state.clips[clip2].state == CLIP_EMPTY); // Redo twice action.type = ACTION_REDO; state = reducer(state, action); assert(state.clips[clip1].state == CLIP_RECORDING); assert(state.clips[clip2].state == CLIP_EMPTY); state = reducer(state, action); assert(state.clips[clip1].state == CLIP_RECORDING); assert(state.clips[clip2].state == CLIP_RECORDING); printf("PASSED\n"); destroy_test_state(&state); } // Test 57: Undo scene trigger void test_undo_scene_trigger(void) { printf("Test 57: Undo scene trigger... "); AppState state = create_test_state(); int scene_idx = 3; // Trigger scene Action action = { .type = ACTION_TRIGGER_SCENE, .data.trigger_scene.scene_index = scene_idx }; state = reducer(state, action); // All clips in scene should be recording for (int ch = 0; ch < MAX_CHANNELS; ch++) { int clip_idx = CLIP_INDEX(scene_idx, ch); assert(state.clips[clip_idx].state == CLIP_RECORDING); } // Undo: all clips should go back to empty action.type = ACTION_UNDO; state = reducer(state, action); for (int ch = 0; ch < MAX_CHANNELS; ch++) { int clip_idx = CLIP_INDEX(scene_idx, ch); assert(state.clips[clip_idx].state == CLIP_EMPTY); } printf("PASSED\n"); destroy_test_state(&state); } // Test 58: Undo clip state cycle (empty -> recording -> looping -> stopped) void test_undo_clip_state_cycle(void) { printf("Test 58: Undo clip state cycle... "); AppState state = create_test_state(); int clip_idx = 10; // Cycle through all states Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clip_idx }; state = reducer(state, action); // empty -> recording assert(state.clips[clip_idx].state == CLIP_RECORDING); state = reducer(state, action); // recording -> looping assert(state.clips[clip_idx].state == CLIP_LOOPING); state = reducer(state, action); // looping -> stopped assert(state.clips[clip_idx].state == CLIP_STOPPED); // Undo three times to go back to empty action.type = ACTION_UNDO; state = reducer(state, action); // stopped -> looping assert(state.clips[clip_idx].state == CLIP_LOOPING); state = reducer(state, action); // looping -> recording assert(state.clips[clip_idx].state == CLIP_RECORDING); state = reducer(state, action); // recording -> empty assert(state.clips[clip_idx].state == CLIP_EMPTY); printf("PASSED\n"); destroy_test_state(&state); } // Test 59: Undo after new action clears redo history void test_undo_clears_redo_on_new_action(void) { printf("Test 59: Undo after new action clears redo history... "); AppState state = create_test_state(); int clip1 = 10, clip2 = 11; // Trigger clip1 Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clip1 }; state = reducer(state, action); assert(state.clips[clip1].state == CLIP_RECORDING); // Undo action.type = ACTION_UNDO; state = reducer(state, action); assert(state.clips[clip1].state == CLIP_EMPTY); // Redo should work action.type = ACTION_REDO; state = reducer(state, action); assert(state.clips[clip1].state == CLIP_RECORDING); // Undo again action.type = ACTION_UNDO; state = reducer(state, action); assert(state.clips[clip1].state == CLIP_EMPTY); // Now do a new action (trigger clip2) action.type = ACTION_TRIGGER_CLIP; action.data.trigger_clip.clip_index = clip2; state = reducer(state, action); assert(state.clips[clip2].state == CLIP_RECORDING); // Redo should NOT work now (redo history cleared) action.type = ACTION_REDO; state = reducer(state, action); assert(state.clips[clip1].state == CLIP_EMPTY); // Should still be empty printf("PASSED\n"); destroy_test_state(&state); } // Test 60: Undo reset clip void test_undo_reset_clip(void) { printf("Test 60: Undo reset clip... "); AppState state = create_test_state(); int clip_idx = 10; // Set up a looping clip with data state.clips[clip_idx].state = CLIP_LOOPING; state.clips[clip_idx].buffer_size = 100; state.clips[clip_idx].write_position = 100; state.clips[clip_idx].read_position = 50; // Reset the clip Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip.clip_index = clip_idx }; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_EMPTY); assert(state.clips[clip_idx].buffer_size == 0); // Undo: should restore clip to previous state action.type = ACTION_UNDO; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_LOOPING); assert(state.clips[clip_idx].buffer_size == 100); assert(state.clips[clip_idx].write_position == 100); assert(state.clips[clip_idx].read_position == 50); printf("PASSED\n"); destroy_test_state(&state); } // Test 64: Undo/redo with paste operation void test_undo_paste(void) { printf("Test 64: Undo paste operation... "); AppState state = create_test_state(); int clip_idx = 27; // Simulate paste: trigger clip three times to go empty -> recording -> looping -> stopped Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clip_idx }; state = reducer(state, action); state = reducer(state, action); state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_STOPPED); // Undo: should go back to looping (undo last trigger: stopped -> looping) action.type = ACTION_UNDO; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_LOOPING); // Undo again: should go back to recording state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_RECORDING); // Undo again: should go back to empty state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_EMPTY); // Redo three times: should go back to stopped action.type = ACTION_REDO; state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_RECORDING); state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_LOOPING); state = reducer(state, action); assert(state.clips[clip_idx].state == CLIP_STOPPED); printf("PASSED\n"); destroy_test_state(&state); } int main(void) { printf("Running TUI tests...\n\n"); test_grid_to_clip_index(); test_trigger_via_grid(); test_reset_via_grid(); test_scene_via_grid(); test_quantize_cycling(); test_threshold_toggle(); test_transport_reset_via_tui(); test_navigation_wrapping(); test_multiple_clip_states(); test_buffer_size_display(); test_help_toggle(); test_escape_handling(); test_tui_init_cleanup(); test_state_to_color_mapping(); test_full_grid_coverage(); test_scene_from_each_row(); test_quantize_full_cycle(); test_multiple_threshold_toggles(); test_multiple_transport_resets(); test_arrow_key_navigation(); test_command_mode_quit(); test_command_mode_empty(); test_command_mode_unknown(); test_command_mode_buffer_overflow(); test_command_mode_backspace(); 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(); test_undo_single_trigger(); test_undo_multiple_triggers(); test_redo_single_undo(); test_redo_multiple_undos(); test_undo_scene_trigger(); test_undo_clip_state_cycle(); test_undo_clears_redo_on_new_action(); test_undo_reset_clip(); test_undo_paste(); printf("\nAll TUI tests passed!\n"); return 0; }