diff --git a/test_tui.c b/test_tui.c index 9c775db..bcc1a77 100644 --- a/test_tui.c +++ b/test_tui.c @@ -2,70 +2,61 @@ #include #include #include -#include "engine.h" -#include #include +#include "dispatcher.h" #include "tui.h" -// Mode definitions (mirrored from tui.c) -typedef enum { - MODE_NORMAL, - MODE_VISUAL, - MODE_MOVE -} UIMode; - -// Test helper -static Engine *create_test_engine(void) { - Engine *engine = (Engine *)calloc(1, sizeof(Engine)); - assert(engine != NULL); - - engine->control_channel = 0; - engine->sample_rate = 48000; - engine->quantize_mode = QUANTIZE_OFF; - engine->quantize_threshold = 0; - engine->queued_triggers = NULL; - - // Initialize command queue - command_queue_init(&engine->command_queue); - - // Initialize atomic state mirrors - atomic_store(&engine->quantize_mode_atomic, (int)QUANTIZE_OFF); - atomic_store(&engine->quantize_threshold_atomic, 0); - - // Initialize transport - engine->transport = (Transport *)calloc(1, sizeof(Transport)); - assert(engine->transport != NULL); - transport_init(engine->transport, 48000); +// 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++) { - engine->clips[i].state = CLIP_EMPTY; - engine->clips[i].buffer = (float *)calloc(MAX_BUFFER_SIZE, sizeof(float)); - assert(engine->clips[i].buffer != NULL); - engine->clips[i].buffer_size = 0; - engine->clips[i].write_position = 0; - engine->clips[i].read_position = 0; + 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; } - return engine; + // Initialize transport + state.transport_state = TRANSPORT_STOPPED; + state.clock_source = CLOCK_SOURCE_INTERNAL; + state.bpm = DEFAULT_BPM; + state.samples_per_beat = (48000 * 60.0) / DEFAULT_BPM; + 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; + for (int i = 0; i < MAX_UNDO_HISTORY; i++) { + state.undo.prev_clip_indices[i] = -1; + } + + // JACK info + state.sample_rate = 48000; + state.running = true; + + return state; } -static void destroy_test_engine(Engine *engine) { - if (engine) { - QueuedTrigger *qt = engine->queued_triggers; - while (qt) { - QueuedTrigger *next = qt->next; - free(qt); - qt = next; - } - - if (engine->transport) { - transport_cleanup(engine->transport); - free(engine->transport); - } +static void destroy_test_state(AppState *state) { + if (state) { for (int i = 0; i < MAX_CLIPS; i++) { - free(engine->clips[i].buffer); + free(state->clips[i].buffer); + state->clips[i].buffer = NULL; } - free(engine); } } @@ -86,130 +77,132 @@ void test_grid_to_clip_index(void) { // Test 2: Trigger clip via grid position void test_trigger_via_grid(void) { printf("Test 2: Trigger clip via grid position... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Simulate pressing 't' on grid position (3, 4) = clip 28 int clip_idx = 3 * 8 + 4; - engine_trigger_clip(engine, clip_idx); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_RECORDING); + Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clip_idx }; + state = reducer(state, action); - destroy_test_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Set up a clip at grid position (1, 2) = clip 10 int clip_idx = 1 * 8 + 2; - engine->clips[clip_idx].state = CLIP_LOOPING; - engine->clips[clip_idx].buffer_size = 100; + state.clips[clip_idx].state = CLIP_LOOPING; + state.clips[clip_idx].buffer_size = 100; // Simulate pressing 'r' - 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); + Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip.clip_index = clip_idx }; + state = reducer(state, action); - destroy_test_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Simulate pressing 's' on row 3 int scene_index = 3; - engine_trigger_scene(engine, scene_index); - engine_process_commands(engine); + 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(engine->clips[clip_idx].state == CLIP_RECORDING); + assert(state.clips[clip_idx].state == CLIP_RECORDING); } - destroy_test_engine(engine); + destroy_test_state(&state); printf("PASSED\n"); } // Test 5: Quantize mode cycling void test_quantize_cycling(void) { printf("Test 5: Quantize mode cycling... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Simulate pressing 'q' to cycle through modes - assert(engine->quantize_mode == QUANTIZE_OFF); + assert(state.quantize_mode == QUANTIZE_OFF); // Cycle: OFF -> BEAT - engine_set_quantize_mode(engine, QUANTIZE_BEAT); - engine_process_commands(engine); - assert(engine->quantize_mode == QUANTIZE_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 - engine_set_quantize_mode(engine, QUANTIZE_BAR); - engine_process_commands(engine); - assert(engine->quantize_mode == QUANTIZE_BAR); + action.data.set_quantize_mode.mode = QUANTIZE_BAR; + state = reducer(state, action); + assert(state.quantize_mode == QUANTIZE_BAR); // Cycle: BAR -> OFF - engine_set_quantize_mode(engine, QUANTIZE_OFF); - engine_process_commands(engine); - assert(engine->quantize_mode == QUANTIZE_OFF); + action.data.set_quantize_mode.mode = QUANTIZE_OFF; + state = reducer(state, action); + assert(state.quantize_mode == QUANTIZE_OFF); - destroy_test_engine(engine); + destroy_test_state(&state); printf("PASSED\n"); } // Test 6: Threshold toggling void test_threshold_toggle(void) { printf("Test 6: Threshold toggling... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Simulate pressing 'T' to toggle threshold - assert(engine->quantize_threshold == 0); + assert(state.quantize_threshold == 0); // Toggle to 1000 - engine_set_quantize_threshold(engine, 1000); - engine_process_commands(engine); - assert(engine->quantize_threshold == 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 - engine_set_quantize_threshold(engine, 0); - engine_process_commands(engine); - assert(engine->quantize_threshold == 0); + action.data.set_quantize_threshold.threshold = 0; + state = reducer(state, action); + assert(state.quantize_threshold == 0); - destroy_test_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Set up transport state - engine->transport->state = TRANSPORT_PLAYING; - engine->transport->clock_count = 100; - engine->transport->beat_position = 2; - engine->transport->bar_position = 5; - engine->transport->sample_position = 10000; + state.transport_state = TRANSPORT_PLAYING; + state.clock_count = 100; + state.beat_position = 2; + state.bar_position = 5; + state.sample_position = 10000; // Simulate pressing 'x' - engine_transport_stop(engine); - engine_process_commands(engine); + Action action = { .type = ACTION_TRANSPORT_STOP }; + state = reducer(state, action); - assert(engine->transport->state == TRANSPORT_STOPPED); - assert(engine->transport->clock_count == 0); - assert(engine->transport->beat_position == 0); - assert(engine->transport->bar_position == 0); - assert(engine->transport->sample_position == 0); + 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_engine(engine); + destroy_test_state(&state); printf("PASSED\n"); } @@ -244,37 +237,37 @@ void test_navigation_wrapping(void) { // Test 9: Multiple clips in different states void test_multiple_clip_states(void) { printf("Test 9: Multiple clips in different states... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Set up clips in various states - engine->clips[0].state = CLIP_EMPTY; - engine->clips[1].state = CLIP_RECORDING; - engine->clips[2].state = CLIP_LOOPING; - engine->clips[3].state = CLIP_STOPPED; + 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(engine->clips[0].state == CLIP_EMPTY); - assert(engine->clips[1].state == CLIP_RECORDING); - assert(engine->clips[2].state == CLIP_LOOPING); - assert(engine->clips[3].state == CLIP_STOPPED); + 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_engine(engine); + destroy_test_state(&state); printf("PASSED\n"); } // Test 10: Buffer size display void test_buffer_size_display(void) { printf("Test 10: Buffer size display... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Set up a clip with known buffer size - engine->clips[5].state = CLIP_LOOPING; - engine->clips[5].buffer_size = 48000; // 1 second at 48kHz + state.clips[5].state = CLIP_LOOPING; + state.clips[5].buffer_size = 48000; // 1 second at 48kHz // Verify buffer size - assert(engine->clips[5].buffer_size == 48000); + assert(state.clips[5].buffer_size == 48000); - destroy_test_engine(engine); + destroy_test_state(&state); printf("PASSED\n"); } @@ -312,14 +305,13 @@ void test_escape_handling(void) { // Test 13: TUI init and cleanup (without ncurses) void test_tui_init_cleanup(void) { printf("Test 13: TUI init and cleanup... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); - // We can't actually call tui_init/tui_cleanup in test environment - // but we can verify the engine is valid - assert(engine != NULL); - assert(engine->sample_rate == 48000); + // Verify state is valid + assert(state.sample_rate == 48000); + assert(state.running == true); - destroy_test_engine(engine); + destroy_test_state(&state); printf("PASSED (skipped ncurses init)\n"); } @@ -339,118 +331,118 @@ void test_state_to_color_mapping(void) { // Test 15: Full grid coverage void test_full_grid_coverage(void) { printf("Test 15: Full grid coverage... "); - Engine *engine = create_test_engine(); + 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; - engine_trigger_clip(engine, clip_idx); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_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); } } // Verify all clips are recording for (int i = 0; i < MAX_CLIPS; i++) { - assert(engine->clips[i].state == CLIP_RECORDING); + assert(state.clips[i].state == CLIP_RECORDING); } - destroy_test_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Trigger scene from each row for (int row = 0; row < 8; row++) { - engine_trigger_scene(engine, row); - engine_process_commands(engine); + 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(engine->clips[clip_idx].state == CLIP_RECORDING); + assert(state.clips[clip_idx].state == CLIP_RECORDING); } } - destroy_test_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Cycle through all modes twice for (int cycle = 0; cycle < 2; cycle++) { - engine_set_quantize_mode(engine, QUANTIZE_OFF); - engine_process_commands(engine); - assert(engine->quantize_mode == QUANTIZE_OFF); + Action action = { .type = ACTION_SET_QUANTIZE_MODE, .data.set_quantize_mode.mode = QUANTIZE_OFF }; + state = reducer(state, action); + assert(state.quantize_mode == QUANTIZE_OFF); - engine_set_quantize_mode(engine, QUANTIZE_BEAT); - engine_process_commands(engine); - assert(engine->quantize_mode == QUANTIZE_BEAT); + action.data.set_quantize_mode.mode = QUANTIZE_BEAT; + state = reducer(state, action); + assert(state.quantize_mode == QUANTIZE_BEAT); - engine_set_quantize_mode(engine, QUANTIZE_BAR); - engine_process_commands(engine); - assert(engine->quantize_mode == QUANTIZE_BAR); + action.data.set_quantize_mode.mode = QUANTIZE_BAR; + state = reducer(state, action); + assert(state.quantize_mode == QUANTIZE_BAR); } - destroy_test_engine(engine); + destroy_test_state(&state); printf("PASSED\n"); } // Test 18: Multiple threshold toggles void test_multiple_threshold_toggles(void) { printf("Test 18: Multiple threshold toggles... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Toggle threshold multiple times for (int i = 0; i < 5; i++) { - if (engine->quantize_threshold == 0) { - engine_set_quantize_threshold(engine, 1000); - engine_process_commands(engine); - assert(engine->quantize_threshold == 1000); + 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 { - engine_set_quantize_threshold(engine, 0); - engine_process_commands(engine); - assert(engine->quantize_threshold == 0); + Action action = { .type = ACTION_SET_QUANTIZE_THRESHOLD, .data.set_quantize_threshold.threshold = 0 }; + state = reducer(state, action); + assert(state.quantize_threshold == 0); } } - destroy_test_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Reset transport multiple times for (int i = 0; i < 5; i++) { - engine->transport->state = TRANSPORT_PLAYING; - engine->transport->clock_count = 100 + i; - engine->transport->beat_position = i % 4; - engine->transport->bar_position = i; - engine->transport->sample_position = 10000 * 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; - engine_transport_stop(engine); - engine_process_commands(engine); + Action action = { .type = ACTION_TRANSPORT_STOP }; + state = reducer(state, action); - assert(engine->transport->state == TRANSPORT_STOPPED); - assert(engine->transport->clock_count == 0); - assert(engine->transport->beat_position == 0); - assert(engine->transport->bar_position == 0); - assert(engine->transport->sample_position == 0); + 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_engine(engine); + destroy_test_state(&state); printf("PASSED\n"); } @@ -605,19 +597,19 @@ 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 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 = MODE_VISUAL; + 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 == MODE_VISUAL); + assert(current_mode == 1); assert(visual_start_row == 3); assert(visual_start_col == 4); assert(visual_end_row == 3); @@ -659,11 +651,11 @@ void test_visual_mode_selection(void) { void test_visual_mode_escape(void) { printf("Test 31: Visual mode escape returns to normal... "); - UIMode current_mode = MODE_VISUAL; + int current_mode = 1; // MODE_VISUAL // Press Escape - current_mode = MODE_NORMAL; - assert(current_mode == MODE_NORMAL); + current_mode = 0; // MODE_NORMAL + assert(current_mode == 0); printf("PASSED\n"); } @@ -673,18 +665,18 @@ void test_visual_line_selection(void) { printf("Test 32: Visual line selection... "); int selected_row = 3; - UIMode current_mode = MODE_NORMAL; + 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 = MODE_VISUAL; + 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 == MODE_VISUAL); + assert(current_mode == 1); assert(visual_start_row == 3); assert(visual_start_col == 0); assert(visual_end_row == 3); @@ -697,12 +689,12 @@ void test_visual_line_selection(void) { void test_move_mode_navigation(void) { printf("Test 33: Move mode navigation... "); - UIMode current_mode = MODE_NORMAL; + int current_mode = 0; // MODE_NORMAL int selected_row = 3, selected_col = 4; // Enter move mode - current_mode = MODE_MOVE; - assert(current_mode == MODE_MOVE); + current_mode = 2; // MODE_MOVE + assert(current_mode == 2); // Move left selected_col = (selected_col - 1 + 8) % 8; @@ -727,11 +719,11 @@ void test_move_mode_navigation(void) { void test_move_mode_enter(void) { printf("Test 34: Move mode returns to normal on enter... "); - UIMode current_mode = MODE_MOVE; + int current_mode = 2; // MODE_MOVE // Press Enter - current_mode = MODE_NORMAL; - assert(current_mode == MODE_NORMAL); + current_mode = 0; // MODE_NORMAL + assert(current_mode == 0); printf("PASSED\n"); } @@ -740,11 +732,11 @@ void test_move_mode_enter(void) { void test_move_mode_escape(void) { printf("Test 35: Move mode returns to normal on escape... "); - UIMode current_mode = MODE_MOVE; + int current_mode = 2; // MODE_MOVE // Press Escape - current_mode = MODE_NORMAL; - assert(current_mode == MODE_NORMAL); + current_mode = 0; // MODE_NORMAL + assert(current_mode == 0); printf("PASSED\n"); } @@ -752,107 +744,107 @@ void test_move_mode_escape(void) { // Test 36: Delete (reset) single clip void test_delete_single_clip(void) { printf("Test 36: Delete (reset) single clip... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // 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; + 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 - engine_reset_clip(engine, clip_idx); - engine_process_commands(engine); + Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip.clip_index = clip_idx }; + state = reducer(state, action); - 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); + 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_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + 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++) { - 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; + 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++) { - engine_reset_clip(engine, clips[i]); + Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip.clip_index = clips[i] }; + state = reducer(state, action); } - 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); + assert(state.clips[clips[i]].state == CLIP_EMPTY); + assert(state.clips[clips[i]].buffer_size == 0); } - destroy_test_engine(engine); + destroy_test_state(&state); 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(); + AppState state = create_test_state(); // Set up a clip int clip_idx = 15; - engine->clips[clip_idx].state = CLIP_LOOPING; - engine->clips[clip_idx].buffer_size = 100; + state.clips[clip_idx].state = CLIP_LOOPING; + state.clips[clip_idx].buffer_size = 100; - // Simulate yanking the clip - should stop it - engine_trigger_clip(engine, clip_idx); // Toggle to stop - engine_process_commands(engine); + // 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(engine->clips[clip_idx].state == CLIP_STOPPED); + assert(state.clips[clip_idx].state == CLIP_STOPPED); - destroy_test_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + 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++) { - engine->clips[clips[i]].state = CLIP_LOOPING; - engine->clips[clips[i]].buffer_size = 100; + 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++) { - engine_trigger_clip(engine, clips[i]); // Toggle to stop + Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clips[i] }; + state = reducer(state, action); } - engine_process_commands(engine); for (int i = 0; i < 4; i++) { - assert(engine->clips[clips[i]].state == CLIP_STOPPED); + assert(state.clips[clips[i]].state == CLIP_STOPPED); } - destroy_test_engine(engine); + destroy_test_state(&state); printf("PASSED\n"); } // Test 40: Paste clips void test_paste_clips(void) { printf("Test 40: Paste clips... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Simulate yanking clip at position (1, 1) = clip 9 int yank_buffer[] = {9}; @@ -878,20 +870,20 @@ void test_paste_clips(void) { assert(new_col == 3); assert(new_clip_idx == 27); - engine_trigger_clip(engine, new_clip_idx); - engine_trigger_clip(engine, new_clip_idx); - engine_trigger_clip(engine, new_clip_idx); - engine_process_commands(engine); - assert(engine->clips[27].state == CLIP_STOPPED); + 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_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Yank clip at position (7, 7) = clip 63 (bottom-right) int yank_buffer[] = {63}; @@ -924,7 +916,7 @@ void test_paste_bounds_checking(void) { new_col = 0 + col_offset; assert(new_row >= 0 && new_row < 8 && new_col >= 0 && new_col < 8); - destroy_test_engine(engine); + destroy_test_state(&state); printf("PASSED\n"); } @@ -1007,55 +999,55 @@ void test_go_to_unset_mark(void) { // Test 45: Play next scene void test_play_next_scene(void) { printf("Test 45: Play next scene... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); int selected_row = 3; // Play next scene int next_row = (selected_row + 1) % 8; - engine_trigger_scene(engine, next_row); - engine_process_commands(engine); + 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(engine->clips[clip_idx].state == CLIP_RECORDING); + assert(state.clips[clip_idx].state == CLIP_RECORDING); } - destroy_test_engine(engine); + destroy_test_state(&state); 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(); + AppState state = create_test_state(); 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); + 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(engine->clips[clip_idx].state == CLIP_RECORDING); + assert(state.clips[clip_idx].state == CLIP_RECORDING); } - destroy_test_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); int selected_row = 7; // Last row @@ -1063,22 +1055,22 @@ void test_play_next_scene_wrap(void) { int next_row = (selected_row + 1) % 8; assert(next_row == 0); - engine_trigger_scene(engine, next_row); - engine_process_commands(engine); + 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(engine->clips[clip_idx].state == CLIP_RECORDING); + assert(state.clips[clip_idx].state == CLIP_RECORDING); } - destroy_test_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); int selected_row = 0; // First row @@ -1086,77 +1078,77 @@ void test_play_prev_scene_wrap(void) { int prev_row = (selected_row - 1 + 8) % 8; assert(prev_row == 7); - engine_trigger_scene(engine, prev_row); - engine_process_commands(engine); + 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(engine->clips[clip_idx].state == CLIP_RECORDING); + assert(state.clips[clip_idx].state == CLIP_RECORDING); } - destroy_test_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // 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; + 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++) { - engine_reset_clip(engine, clips[i]); + Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip.clip_index = clips[i] }; + state = reducer(state, action); } - engine_process_commands(engine); // Verify clips are reset for (int i = 0; i < 4; i++) { - assert(engine->clips[clips[i]].state == CLIP_EMPTY); + assert(state.clips[clips[i]].state == CLIP_EMPTY); } // Simulate returning to normal mode - UIMode current_mode = MODE_NORMAL; - assert(current_mode == MODE_NORMAL); + int current_mode = 0; // MODE_NORMAL + assert(current_mode == 0); - destroy_test_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // 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; + 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++) { - engine_trigger_clip(engine, clips[i]); // Toggle to stop + Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clips[i] }; + state = reducer(state, action); } - engine_process_commands(engine); // Verify clips are stopped for (int i = 0; i < 4; i++) { - assert(engine->clips[clips[i]].state == CLIP_STOPPED); + assert(state.clips[clips[i]].state == CLIP_STOPPED); } // Simulate returning to normal mode - UIMode current_mode = MODE_NORMAL; - assert(current_mode == MODE_NORMAL); + int current_mode = 0; // MODE_NORMAL + assert(current_mode == 0); - destroy_test_engine(engine); + destroy_test_state(&state); printf("PASSED\n"); } @@ -1211,392 +1203,382 @@ void test_remark_existing_mark(void) { // Test 53: Undo single clip trigger void test_undo_single_trigger(void) { printf("Test 53: Undo single clip trigger... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state();; // Start with empty clip int clip_idx = 10; - assert(engine->clips[clip_idx].state == CLIP_EMPTY); + assert(state.clips[clip_idx].state == CLIP_EMPTY); // Trigger clip (empty -> recording) - engine_trigger_clip(engine, clip_idx); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_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 - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_EMPTY); + action.type = ACTION_UNDO; + state = reducer(state, action); + assert(state.clips[clip_idx].state == CLIP_EMPTY); printf("PASSED\n"); - destroy_test_engine(engine); + destroy_test_state(&state); } // Test 54: Undo multiple clip triggers void test_undo_multiple_triggers(void) { printf("Test 54: Undo multiple clip triggers... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); int clip1 = 10, clip2 = 11, clip3 = 12; // Trigger three clips - engine_trigger_clip(engine, clip1); - engine_process_commands(engine); - assert(engine->clips[clip1].state == CLIP_RECORDING); + Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clip1 }; + state = reducer(state, action); + assert(state.clips[clip1].state == CLIP_RECORDING); - engine_trigger_clip(engine, clip2); - engine_process_commands(engine); - assert(engine->clips[clip2].state == CLIP_RECORDING); + action.data.trigger_clip.clip_index = clip2; + state = reducer(state, action); + assert(state.clips[clip2].state == CLIP_RECORDING); - engine_trigger_clip(engine, clip3); - engine_process_commands(engine); - assert(engine->clips[clip3].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 - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip3].state == CLIP_EMPTY); - assert(engine->clips[clip2].state == CLIP_RECORDING); - assert(engine->clips[clip1].state == CLIP_RECORDING); + 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 - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip2].state == CLIP_EMPTY); - assert(engine->clips[clip1].state == CLIP_RECORDING); + state = reducer(state, action); + assert(state.clips[clip2].state == CLIP_EMPTY); + assert(state.clips[clip1].state == CLIP_RECORDING); printf("PASSED\n"); - destroy_test_engine(engine); + destroy_test_state(&state); } // Test 55: Redo single undo void test_redo_single_undo(void) { printf("Test 55: Redo single undo... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); int clip_idx = 10; // Trigger clip - engine_trigger_clip(engine, clip_idx); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_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 - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_EMPTY); + action.type = ACTION_UNDO; + state = reducer(state, action); + assert(state.clips[clip_idx].state == CLIP_EMPTY); // Redo: should go back to recording - engine_redo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_RECORDING); + action.type = ACTION_REDO; + state = reducer(state, action); + assert(state.clips[clip_idx].state == CLIP_RECORDING); printf("PASSED\n"); - destroy_test_engine(engine); + destroy_test_state(&state); } // Test 56: Redo after multiple undos void test_redo_multiple_undos(void) { printf("Test 56: Redo after multiple undos... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); int clip1 = 10, clip2 = 11; // Trigger two clips - engine_trigger_clip(engine, clip1); - engine_process_commands(engine); - engine_trigger_clip(engine, clip2); - engine_process_commands(engine); - assert(engine->clips[clip1].state == CLIP_RECORDING); - assert(engine->clips[clip2].state == CLIP_RECORDING); + 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 - engine_undo_action(engine); - engine_process_commands(engine); - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip1].state == CLIP_EMPTY); - assert(engine->clips[clip2].state == CLIP_EMPTY); + 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 - engine_redo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip1].state == CLIP_RECORDING); - assert(engine->clips[clip2].state == CLIP_EMPTY); + action.type = ACTION_REDO; + state = reducer(state, action); + assert(state.clips[clip1].state == CLIP_RECORDING); + assert(state.clips[clip2].state == CLIP_EMPTY); - engine_redo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip1].state == CLIP_RECORDING); - assert(engine->clips[clip2].state == CLIP_RECORDING); + state = reducer(state, action); + assert(state.clips[clip1].state == CLIP_RECORDING); + assert(state.clips[clip2].state == CLIP_RECORDING); printf("PASSED\n"); - destroy_test_engine(engine); + destroy_test_state(&state); } // Test 57: Undo scene trigger void test_undo_scene_trigger(void) { printf("Test 57: Undo scene trigger... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); int scene_idx = 3; // Trigger scene - engine_trigger_scene(engine, scene_idx); - engine_process_commands(engine); + 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(engine->clips[clip_idx].state == CLIP_RECORDING); + assert(state.clips[clip_idx].state == CLIP_RECORDING); } // Undo: all clips should go back to empty - engine_undo_action(engine); - engine_process_commands(engine); + 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(engine->clips[clip_idx].state == CLIP_EMPTY); + assert(state.clips[clip_idx].state == CLIP_EMPTY); } printf("PASSED\n"); - destroy_test_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); int clip_idx = 10; // Cycle through all states - engine_trigger_clip(engine, clip_idx); // empty -> recording - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_RECORDING); + 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); - engine_trigger_clip(engine, clip_idx); // recording -> looping - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_LOOPING); + state = reducer(state, action); // recording -> looping + assert(state.clips[clip_idx].state == CLIP_LOOPING); - engine_trigger_clip(engine, clip_idx); // looping -> stopped - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_STOPPED); + state = reducer(state, action); // looping -> stopped + assert(state.clips[clip_idx].state == CLIP_STOPPED); // Undo three times to go back to empty - engine_undo_action(engine); // stopped -> looping - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_LOOPING); + action.type = ACTION_UNDO; + state = reducer(state, action); // stopped -> looping + assert(state.clips[clip_idx].state == CLIP_LOOPING); - engine_undo_action(engine); // looping -> recording - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_RECORDING); + state = reducer(state, action); // looping -> recording + assert(state.clips[clip_idx].state == CLIP_RECORDING); - engine_undo_action(engine); // recording -> empty - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_EMPTY); + state = reducer(state, action); // recording -> empty + assert(state.clips[clip_idx].state == CLIP_EMPTY); printf("PASSED\n"); - destroy_test_engine(engine); + 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... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); int clip1 = 10, clip2 = 11; // Trigger clip1 - engine_trigger_clip(engine, clip1); - engine_process_commands(engine); - assert(engine->clips[clip1].state == CLIP_RECORDING); + Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip.clip_index = clip1 }; + state = reducer(state, action); + assert(state.clips[clip1].state == CLIP_RECORDING); // Undo - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip1].state == CLIP_EMPTY); + action.type = ACTION_UNDO; + state = reducer(state, action); + assert(state.clips[clip1].state == CLIP_EMPTY); // Redo should work - engine_redo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip1].state == CLIP_RECORDING); + action.type = ACTION_REDO; + state = reducer(state, action); + assert(state.clips[clip1].state == CLIP_RECORDING); // Undo again - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip1].state == CLIP_EMPTY); + action.type = ACTION_UNDO; + state = reducer(state, action); + assert(state.clips[clip1].state == CLIP_EMPTY); // Now do a new action (trigger clip2) - engine_trigger_clip(engine, clip2); - engine_process_commands(engine); - assert(engine->clips[clip2].state == CLIP_RECORDING); + 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) - engine_redo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip1].state == CLIP_EMPTY); // Should still be empty + action.type = ACTION_REDO; + state = reducer(state, action); + assert(state.clips[clip1].state == CLIP_EMPTY); // Should still be empty printf("PASSED\n"); - destroy_test_engine(engine); + destroy_test_state(&state); } // Test 60: Undo reset clip void test_undo_reset_clip(void) { printf("Test 60: Undo reset clip... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); int clip_idx = 10; // Set up a looping clip with data - 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; + 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 - 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); + 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 - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_LOOPING); - assert(engine->clips[clip_idx].buffer_size == 100); - assert(engine->clips[clip_idx].write_position == 100); - assert(engine->clips[clip_idx].read_position == 50); + 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_engine(engine); + destroy_test_state(&state); } // Test 61: Undo/redo with quantize mode change void test_undo_quantize_change(void) { printf("Test 61: Undo quantize mode change... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); - assert(engine->quantize_mode == QUANTIZE_OFF); + assert(state.quantize_mode == QUANTIZE_OFF); // Change quantize mode - engine_set_quantize_mode(engine, QUANTIZE_BEAT); - engine_process_commands(engine); - assert(engine->quantize_mode == QUANTIZE_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); // Undo: should go back to OFF - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->quantize_mode == QUANTIZE_OFF); + action.type = ACTION_UNDO; + state = reducer(state, action); + assert(state.quantize_mode == QUANTIZE_OFF); // Redo: should go back to BEAT - engine_redo_action(engine); - engine_process_commands(engine); - assert(engine->quantize_mode == QUANTIZE_BEAT); + action.type = ACTION_REDO; + state = reducer(state, action); + assert(state.quantize_mode == QUANTIZE_BEAT); printf("PASSED\n"); - destroy_test_engine(engine); + destroy_test_state(&state); } // Test 62: Undo/redo with threshold change void test_undo_threshold_change(void) { printf("Test 62: Undo threshold change... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); - assert(engine->quantize_threshold == 0); + assert(state.quantize_threshold == 0); // Change threshold - engine_set_quantize_threshold(engine, 1000); - engine_process_commands(engine); - assert(engine->quantize_threshold == 1000); + Action action = { .type = ACTION_SET_QUANTIZE_THRESHOLD, .data.set_quantize_threshold.threshold = 1000 }; + state = reducer(state, action); + assert(state.quantize_threshold == 1000); // Undo: should go back to 0 - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->quantize_threshold == 0); + action.type = ACTION_UNDO; + state = reducer(state, action); + assert(state.quantize_threshold == 0); // Redo: should go back to 1000 - engine_redo_action(engine); - engine_process_commands(engine); - assert(engine->quantize_threshold == 1000); + action.type = ACTION_REDO; + state = reducer(state, action); + assert(state.quantize_threshold == 1000); printf("PASSED\n"); - destroy_test_engine(engine); + destroy_test_state(&state); } // Test 63: Undo/redo with transport reset void test_undo_transport_reset(void) { printf("Test 63: Undo transport reset... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); // Set up transport state - engine->transport->state = TRANSPORT_PLAYING; - engine->transport->clock_count = 100; - engine->transport->beat_position = 2; - engine->transport->bar_position = 5; - engine->transport->sample_position = 10000; + state.transport_state = TRANSPORT_PLAYING; + state.clock_count = 100; + state.beat_position = 2; + state.bar_position = 5; + state.sample_position = 10000; // Reset transport - engine_transport_stop(engine); - engine_process_commands(engine); - assert(engine->transport->state == TRANSPORT_STOPPED); - assert(engine->transport->clock_count == 0); + Action action = { .type = ACTION_TRANSPORT_STOP }; + state = reducer(state, action); + assert(state.transport_state == TRANSPORT_STOPPED); + assert(state.clock_count == 0); // Undo: should restore transport state - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->transport->state == TRANSPORT_PLAYING); - assert(engine->transport->clock_count == 100); - assert(engine->transport->beat_position == 2); - assert(engine->transport->bar_position == 5); - assert(engine->transport->sample_position == 10000); + action.type = ACTION_UNDO; + state = reducer(state, action); + assert(state.transport_state == TRANSPORT_PLAYING); + assert(state.clock_count == 100); + assert(state.beat_position == 2); + assert(state.bar_position == 5); + assert(state.sample_position == 10000); printf("PASSED\n"); - destroy_test_engine(engine); + destroy_test_state(&state); } // Test 64: Undo/redo with paste operation void test_undo_paste(void) { printf("Test 64: Undo paste operation... "); - Engine *engine = create_test_engine(); + AppState state = create_test_state(); int clip_idx = 27; // Simulate paste: trigger clip three times to go empty -> recording -> looping -> stopped - engine_trigger_clip(engine, clip_idx); - engine_trigger_clip(engine, clip_idx); - engine_trigger_clip(engine, clip_idx); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_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) - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_LOOPING); + action.type = ACTION_UNDO; + state = reducer(state, action); + assert(state.clips[clip_idx].state == CLIP_LOOPING); // Undo again: should go back to recording - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_RECORDING); + state = reducer(state, action); + assert(state.clips[clip_idx].state == CLIP_RECORDING); // Undo again: should go back to empty - engine_undo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_EMPTY); + state = reducer(state, action); + assert(state.clips[clip_idx].state == CLIP_EMPTY); // Redo three times: should go back to stopped - engine_redo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_RECORDING); + action.type = ACTION_REDO; + state = reducer(state, action); + assert(state.clips[clip_idx].state == CLIP_RECORDING); - engine_redo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_LOOPING); + state = reducer(state, action); + assert(state.clips[clip_idx].state == CLIP_LOOPING); - engine_redo_action(engine); - engine_process_commands(engine); - assert(engine->clips[clip_idx].state == CLIP_STOPPED); + state = reducer(state, action); + assert(state.clips[clip_idx].state == CLIP_STOPPED); printf("PASSED\n"); - destroy_test_engine(engine); + destroy_test_state(&state); } int main(void) {