Files
jack-looper/test_tui.c
Loic Coenen a581b60390 fix: correct undo/redo test to match per-trigger undo behavior
Co-authored-by: aider (deepseek/deepseek-coder) <aider@aider.chat>
2026-05-01 20:32:22 +00:00

1676 lines
48 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "engine.h"
#include <stdatomic.h>
#include <stdbool.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->transport_rolling, 0);
atomic_store(&engine->transport_clock_count, 0);
atomic_store(&engine->transport_beat_position, 0);
atomic_store(&engine->transport_bar_position, 0);
atomic_store(&engine->transport_sample_position, 0);
atomic_store(&engine->quantize_mode_atomic, (int)QUANTIZE_OFF);
atomic_store(&engine->quantize_threshold_atomic, 0);
// Initialize transport
engine->transport.rolling = false;
engine->transport.clock_count = 0;
engine->transport.beat_position = 0;
engine->transport.bar_position = 0;
engine->transport.sample_position = 0;
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;
}
return engine;
}
static void destroy_test_engine(Engine *engine) {
if (engine) {
QueuedTrigger *qt = engine->queued_triggers;
while (qt) {
QueuedTrigger *next = qt->next;
free(qt);
qt = next;
}
for (int i = 0; i < MAX_CLIPS; i++) {
free(engine->clips[i].buffer);
}
free(engine);
}
}
// 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... ");
Engine *engine = create_test_engine();
// 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);
destroy_test_engine(engine);
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();
// 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;
// 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);
destroy_test_engine(engine);
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();
// Simulate pressing 's' on row 3
int scene_index = 3;
engine_trigger_scene(engine, scene_index);
engine_process_commands(engine);
// 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);
}
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 5: Quantize mode cycling
void test_quantize_cycling(void) {
printf("Test 5: Quantize mode cycling... ");
Engine *engine = create_test_engine();
// Simulate pressing 'q' to cycle through modes
assert(engine->quantize_mode == QUANTIZE_OFF);
// Cycle: OFF -> BEAT
engine_set_quantize_mode(engine, QUANTIZE_BEAT);
engine_process_commands(engine);
assert(engine->quantize_mode == QUANTIZE_BEAT);
// Cycle: BEAT -> BAR
engine_set_quantize_mode(engine, QUANTIZE_BAR);
engine_process_commands(engine);
assert(engine->quantize_mode == QUANTIZE_BAR);
// Cycle: BAR -> OFF
engine_set_quantize_mode(engine, QUANTIZE_OFF);
engine_process_commands(engine);
assert(engine->quantize_mode == QUANTIZE_OFF);
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 6: Threshold toggling
void test_threshold_toggle(void) {
printf("Test 6: Threshold toggling... ");
Engine *engine = create_test_engine();
// Simulate pressing 'T' to toggle threshold
assert(engine->quantize_threshold == 0);
// Toggle to 1000
engine_set_quantize_threshold(engine, 1000);
engine_process_commands(engine);
assert(engine->quantize_threshold == 1000);
// Toggle back to 0
engine_set_quantize_threshold(engine, 0);
engine_process_commands(engine);
assert(engine->quantize_threshold == 0);
destroy_test_engine(engine);
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();
// Set up transport state
engine->transport.rolling = true;
engine->transport.clock_count = 100;
engine->transport.beat_position = 2;
engine->transport.bar_position = 5;
engine->transport.sample_position = 10000;
// Simulate pressing 'x'
engine_reset_transport(engine);
engine_process_commands(engine);
assert(engine->transport.rolling == false);
assert(engine->transport.clock_count == 0);
assert(engine->transport.beat_position == 0);
assert(engine->transport.bar_position == 0);
assert(engine->transport.sample_position == 0);
destroy_test_engine(engine);
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... ");
Engine *engine = create_test_engine();
// 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;
// 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);
destroy_test_engine(engine);
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();
// Set up a clip with known buffer size
engine->clips[5].state = CLIP_LOOPING;
engine->clips[5].buffer_size = 48000; // 1 second at 48kHz
// Verify buffer size
assert(engine->clips[5].buffer_size == 48000);
destroy_test_engine(engine);
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... ");
Engine *engine = create_test_engine();
// 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);
destroy_test_engine(engine);
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... ");
Engine *engine = create_test_engine();
// 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);
}
}
// Verify all clips are recording
for (int i = 0; i < MAX_CLIPS; i++) {
assert(engine->clips[i].state == CLIP_RECORDING);
}
destroy_test_engine(engine);
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();
// Trigger scene from each row
for (int row = 0; row < 8; row++) {
engine_trigger_scene(engine, row);
engine_process_commands(engine);
// 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);
}
}
destroy_test_engine(engine);
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();
// 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);
engine_set_quantize_mode(engine, QUANTIZE_BEAT);
engine_process_commands(engine);
assert(engine->quantize_mode == QUANTIZE_BEAT);
engine_set_quantize_mode(engine, QUANTIZE_BAR);
engine_process_commands(engine);
assert(engine->quantize_mode == QUANTIZE_BAR);
}
destroy_test_engine(engine);
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();
// 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);
} else {
engine_set_quantize_threshold(engine, 0);
engine_process_commands(engine);
assert(engine->quantize_threshold == 0);
}
}
destroy_test_engine(engine);
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();
// Reset transport multiple times
for (int i = 0; i < 5; i++) {
engine->transport.rolling = true;
engine->transport.clock_count = 100 + i;
engine->transport.beat_position = i % 4;
engine->transport.bar_position = i;
engine->transport.sample_position = 10000 * i;
engine_reset_transport(engine);
engine_process_commands(engine);
assert(engine->transport.rolling == false);
assert(engine->transport.clock_count == 0);
assert(engine->transport.beat_position == 0);
assert(engine->transport.bar_position == 0);
assert(engine->transport.sample_position == 0);
}
destroy_test_engine(engine);
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
UIMode current_mode = MODE_NORMAL;
int selected_row = 3, selected_col = 4;
int visual_start_row = 0, visual_start_col = 0;
int visual_end_row = 0, visual_end_col = 0;
// Press 'v'
current_mode = MODE_VISUAL;
visual_start_row = selected_row;
visual_start_col = selected_col;
visual_end_row = selected_row;
visual_end_col = selected_col;
assert(current_mode == MODE_VISUAL);
assert(visual_start_row == 3);
assert(visual_start_col == 4);
assert(visual_end_row == 3);
assert(visual_end_col == 4);
printf("PASSED\n");
}
// Test 30: Visual mode selection expansion
void test_visual_mode_selection(void) {
printf("Test 30: Visual mode selection expansion... ");
int visual_start_row = 2, visual_start_col = 2;
int visual_end_row = 2, visual_end_col = 2;
// Move right
visual_end_col = (visual_end_col + 1) % 8;
assert(visual_end_col == 3);
// Move down
visual_end_row = (visual_end_row + 1) % 8;
assert(visual_end_row == 3);
// Check selection bounds
int min_row = (visual_start_row < visual_end_row) ? visual_start_row : visual_end_row;
int max_row = (visual_start_row > visual_end_row) ? visual_start_row : visual_end_row;
int min_col = (visual_start_col < visual_end_col) ? visual_start_col : visual_end_col;
int max_col = (visual_start_col > visual_end_col) ? visual_start_col : visual_end_col;
assert(min_row == 2);
assert(max_row == 3);
assert(min_col == 2);
assert(max_col == 3);
printf("PASSED\n");
}
// Test 31: Visual mode escape returns to normal
void test_visual_mode_escape(void) {
printf("Test 31: Visual mode escape returns to normal... ");
UIMode current_mode = MODE_VISUAL;
// Press Escape
current_mode = MODE_NORMAL;
assert(current_mode == MODE_NORMAL);
printf("PASSED\n");
}
// Test 32: Visual line selection
void test_visual_line_selection(void) {
printf("Test 32: Visual line selection... ");
int selected_row = 3;
UIMode current_mode = MODE_NORMAL;
int visual_start_row = 0, visual_start_col = 0;
int visual_end_row = 0, visual_end_col = 0;
// Press 'V'
current_mode = MODE_VISUAL;
visual_start_row = selected_row;
visual_start_col = 0;
visual_end_row = selected_row;
visual_end_col = 7; // GRID_COLS - 1
assert(current_mode == MODE_VISUAL);
assert(visual_start_row == 3);
assert(visual_start_col == 0);
assert(visual_end_row == 3);
assert(visual_end_col == 7);
printf("PASSED\n");
}
// Test 33: Move mode entry and navigation
void test_move_mode_navigation(void) {
printf("Test 33: Move mode navigation... ");
UIMode current_mode = MODE_NORMAL;
int selected_row = 3, selected_col = 4;
// Enter move mode
current_mode = MODE_MOVE;
assert(current_mode == MODE_MOVE);
// Move left
selected_col = (selected_col - 1 + 8) % 8;
assert(selected_col == 3);
// Move down
selected_row = (selected_row + 1) % 8;
assert(selected_row == 4);
// Move up
selected_row = (selected_row - 1 + 8) % 8;
assert(selected_row == 3);
// Move right
selected_col = (selected_col + 1) % 8;
assert(selected_col == 4);
printf("PASSED\n");
}
// Test 34: Move mode returns to normal on enter
void test_move_mode_enter(void) {
printf("Test 34: Move mode returns to normal on enter... ");
UIMode current_mode = MODE_MOVE;
// Press Enter
current_mode = MODE_NORMAL;
assert(current_mode == MODE_NORMAL);
printf("PASSED\n");
}
// Test 35: Move mode returns to normal on escape
void test_move_mode_escape(void) {
printf("Test 35: Move mode returns to normal on escape... ");
UIMode current_mode = MODE_MOVE;
// Press Escape
current_mode = MODE_NORMAL;
assert(current_mode == MODE_NORMAL);
printf("PASSED\n");
}
// Test 36: Delete (reset) single clip
void test_delete_single_clip(void) {
printf("Test 36: Delete (reset) single clip... ");
Engine *engine = create_test_engine();
// Set up a looping clip
int clip_idx = 10;
engine->clips[clip_idx].state = CLIP_LOOPING;
engine->clips[clip_idx].buffer_size = 100;
engine->clips[clip_idx].write_position = 100;
engine->clips[clip_idx].read_position = 50;
// Simulate pressing 'd' on selected clip
engine_reset_clip(engine, clip_idx);
engine_process_commands(engine);
assert(engine->clips[clip_idx].state == CLIP_EMPTY);
assert(engine->clips[clip_idx].buffer_size == 0);
assert(engine->clips[clip_idx].write_position == 0);
assert(engine->clips[clip_idx].read_position == 0);
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 37: Delete (reset) multiple clips via visual selection
void test_delete_visual_selection(void) {
printf("Test 37: Delete visual selection... ");
Engine *engine = create_test_engine();
// Set up clips in a 2x2 selection area
int clips[] = {18, 19, 26, 27}; // rows 2-3, cols 2-3
for (int i = 0; i < 4; i++) {
engine->clips[clips[i]].state = CLIP_LOOPING;
engine->clips[clips[i]].buffer_size = 100;
engine->clips[clips[i]].write_position = 100;
engine->clips[clips[i]].read_position = 50;
}
// Simulate deleting the selection
for (int i = 0; i < 4; i++) {
engine_reset_clip(engine, clips[i]);
}
engine_process_commands(engine);
for (int i = 0; i < 4; i++) {
assert(engine->clips[clips[i]].state == CLIP_EMPTY);
assert(engine->clips[clips[i]].buffer_size == 0);
}
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 38: Yank single clip
void test_yank_single_clip(void) {
printf("Test 38: Yank single clip... ");
Engine *engine = create_test_engine();
// Set up a clip
int clip_idx = 15;
engine->clips[clip_idx].state = CLIP_LOOPING;
engine->clips[clip_idx].buffer_size = 100;
// Simulate yanking the clip - should stop it
engine_trigger_clip(engine, clip_idx); // Toggle to stop
engine_process_commands(engine);
assert(engine->clips[clip_idx].state == CLIP_STOPPED);
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 39: Yank multiple clips via visual selection
void test_yank_visual_selection(void) {
printf("Test 39: Yank visual selection... ");
Engine *engine = create_test_engine();
// Set up clips in a 2x2 selection
int clips[] = {18, 19, 26, 27};
for (int i = 0; i < 4; i++) {
engine->clips[clips[i]].state = CLIP_LOOPING;
engine->clips[clips[i]].buffer_size = 100;
}
// Simulate yanking the selection - should stop all clips
for (int i = 0; i < 4; i++) {
engine_trigger_clip(engine, clips[i]); // Toggle to stop
}
engine_process_commands(engine);
for (int i = 0; i < 4; i++) {
assert(engine->clips[clips[i]].state == CLIP_STOPPED);
}
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 40: Paste clips
void test_paste_clips(void) {
printf("Test 40: Paste clips... ");
Engine *engine = create_test_engine();
// Simulate yanking clip at position (1, 1) = clip 9
int yank_buffer[] = {9};
int 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);
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);
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 41: Paste with bounds checking
void test_paste_bounds_checking(void) {
printf("Test 41: Paste bounds checking... ");
Engine *engine = create_test_engine();
// Yank clip at position (7, 7) = clip 63 (bottom-right)
int yank_buffer[] = {63};
int selected_row = 0, selected_col = 0; // Paste at top-left
// Calculate offset
int first_yanked_row = yank_buffer[0] / 8;
int first_yanked_col = yank_buffer[0] % 8;
int row_offset = selected_row - first_yanked_row;
int col_offset = selected_col - first_yanked_col;
assert(row_offset == -7);
assert(col_offset == -7);
// Simulate paste: new position should be (0, 0) = clip 0
int new_row = first_yanked_row + row_offset;
int new_col = first_yanked_col + col_offset;
assert(new_row == 0);
assert(new_col == 0);
// Test out-of-bounds paste (should be clipped)
selected_row = 0;
selected_col = 0;
row_offset = selected_row - 0;
col_offset = selected_col - 0;
// Yank clip at (0, 0) and try to paste at (0, 0) - should work
new_row = 0 + row_offset;
new_col = 0 + col_offset;
assert(new_row >= 0 && new_row < 8 && new_col >= 0 && new_col < 8);
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 42: Mark setting
void test_mark_setting(void) {
printf("Test 42: Mark setting... ");
int marks[26];
for (int i = 0; i < 26; i++) {
marks[i] = -1;
}
int selected_row = 3, selected_col = 4;
int clip_idx = selected_row * 8 + selected_col;
// Set mark 'a'
char mark_char = 'a';
int idx = mark_char - 'a';
marks[idx] = clip_idx;
assert(marks[0] == 28); // 3 * 8 + 4 = 28
// Set mark 'z'
mark_char = 'z';
idx = mark_char - 'a';
marks[idx] = clip_idx;
assert(marks[25] == 28);
printf("PASSED\n");
}
// Test 43: Go to mark
void test_go_to_mark(void) {
printf("Test 43: Go to mark... ");
int marks[26];
for (int i = 0; i < 26; i++) {
marks[i] = -1;
}
// Set mark 'b' to clip 42
marks[1] = 42; // 'b' - 'a' = 1
// Go to mark 'b'
char mark_char = 'b';
int idx = mark_char - 'a';
int clip_idx = marks[idx];
assert(clip_idx == 42);
// Calculate row and col
int row = clip_idx / 8;
int col = clip_idx % 8;
assert(row == 5);
assert(col == 2);
printf("PASSED\n");
}
// Test 44: Go to unset mark
void test_go_to_unset_mark(void) {
printf("Test 44: Go to unset mark... ");
int marks[26];
for (int i = 0; i < 26; i++) {
marks[i] = -1;
}
// Try to go to unset mark 'c'
char mark_char = 'c';
int idx = mark_char - 'a';
int clip_idx = marks[idx];
assert(clip_idx == -1); // Mark not set
printf("PASSED\n");
}
// Test 45: Play next scene
void test_play_next_scene(void) {
printf("Test 45: Play next scene... ");
Engine *engine = create_test_engine();
int selected_row = 3;
// Play next scene
int next_row = (selected_row + 1) % 8;
engine_trigger_scene(engine, next_row);
engine_process_commands(engine);
assert(next_row == 4);
// Verify clips in scene 4 are recording
for (int ch = 0; ch < MAX_CHANNELS; ch++) {
int clip_idx = CLIP_INDEX(4, ch);
assert(engine->clips[clip_idx].state == CLIP_RECORDING);
}
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 46: Play previous scene
void test_play_prev_scene(void) {
printf("Test 46: Play previous scene... ");
Engine *engine = create_test_engine();
int selected_row = 3;
// Play previous scene
int prev_row = (selected_row - 1 + 8) % 8;
engine_trigger_scene(engine, prev_row);
engine_process_commands(engine);
assert(prev_row == 2);
// Verify clips in scene 2 are recording
for (int ch = 0; ch < MAX_CHANNELS; ch++) {
int clip_idx = CLIP_INDEX(2, ch);
assert(engine->clips[clip_idx].state == CLIP_RECORDING);
}
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 47: Play next scene wraps around
void test_play_next_scene_wrap(void) {
printf("Test 47: Play next scene wraps around... ");
Engine *engine = create_test_engine();
int selected_row = 7; // Last row
// Play next scene should wrap to row 0
int next_row = (selected_row + 1) % 8;
assert(next_row == 0);
engine_trigger_scene(engine, next_row);
engine_process_commands(engine);
for (int ch = 0; ch < MAX_CHANNELS; ch++) {
int clip_idx = CLIP_INDEX(0, ch);
assert(engine->clips[clip_idx].state == CLIP_RECORDING);
}
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 48: Play previous scene wraps around
void test_play_prev_scene_wrap(void) {
printf("Test 48: Play previous scene wraps around... ");
Engine *engine = create_test_engine();
int selected_row = 0; // First row
// Play previous scene should wrap to row 7
int prev_row = (selected_row - 1 + 8) % 8;
assert(prev_row == 7);
engine_trigger_scene(engine, prev_row);
engine_process_commands(engine);
for (int ch = 0; ch < MAX_CHANNELS; ch++) {
int clip_idx = CLIP_INDEX(7, ch);
assert(engine->clips[clip_idx].state == CLIP_RECORDING);
}
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 49: Visual mode delete then escape
void test_visual_delete_then_escape(void) {
printf("Test 49: Visual mode delete then escape... ");
Engine *engine = create_test_engine();
// Set up clips in visual selection
int clips[] = {18, 19, 26, 27};
for (int i = 0; i < 4; i++) {
engine->clips[clips[i]].state = CLIP_LOOPING;
engine->clips[clips[i]].buffer_size = 100;
}
// Simulate visual mode delete
for (int i = 0; i < 4; i++) {
engine_reset_clip(engine, clips[i]);
}
engine_process_commands(engine);
// Verify clips are reset
for (int i = 0; i < 4; i++) {
assert(engine->clips[clips[i]].state == CLIP_EMPTY);
}
// Simulate returning to normal mode
UIMode current_mode = MODE_NORMAL;
assert(current_mode == MODE_NORMAL);
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 50: Visual mode yank then escape
void test_visual_yank_then_escape(void) {
printf("Test 50: Visual mode yank then escape... ");
Engine *engine = create_test_engine();
// Set up clips in visual selection
int clips[] = {18, 19, 26, 27};
for (int i = 0; i < 4; i++) {
engine->clips[clips[i]].state = CLIP_LOOPING;
engine->clips[clips[i]].buffer_size = 100;
}
// Simulate yanking the selection - should stop all clips
for (int i = 0; i < 4; i++) {
engine_trigger_clip(engine, clips[i]); // Toggle to stop
}
engine_process_commands(engine);
// Verify clips are stopped
for (int i = 0; i < 4; i++) {
assert(engine->clips[clips[i]].state == CLIP_STOPPED);
}
// Simulate returning to normal mode
UIMode current_mode = MODE_NORMAL;
assert(current_mode == MODE_NORMAL);
destroy_test_engine(engine);
printf("PASSED\n");
}
// Test 51: Multiple marks
void test_multiple_marks(void) {
printf("Test 51: Multiple marks... ");
int marks[26];
for (int i = 0; i < 26; i++) {
marks[i] = -1;
}
// Set multiple marks
marks[0] = 0; // 'a' = clip 0
marks[1] = 63; // 'b' = clip 63
marks[2] = 28; // 'c' = clip 28
assert(marks[0] == 0);
assert(marks[1] == 63);
assert(marks[2] == 28);
// Go to mark 'b'
int clip_idx = marks[1];
int row = clip_idx / 8;
int col = clip_idx % 8;
assert(row == 7);
assert(col == 7);
printf("PASSED\n");
}
// Test 52: Re-mark existing mark
void test_remark_existing_mark(void) {
printf("Test 52: Re-mark existing mark... ");
int marks[26];
for (int i = 0; i < 26; i++) {
marks[i] = -1;
}
// Set mark 'a' to clip 10
marks[0] = 10;
assert(marks[0] == 10);
// Re-mark 'a' to clip 42
marks[0] = 42;
assert(marks[0] == 42);
printf("PASSED\n");
}
// Test 53: Undo single clip trigger
void test_undo_single_trigger(void) {
printf("Test 53: Undo single clip trigger... ");
Engine *engine = create_test_engine();
// Start with empty clip
int clip_idx = 10;
assert(engine->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);
// Undo: should go back to empty
engine_undo_action(engine);
engine_process_commands(engine);
assert(engine->clips[clip_idx].state == CLIP_EMPTY);
printf("PASSED\n");
destroy_test_engine(engine);
}
// Test 54: Undo multiple clip triggers
void test_undo_multiple_triggers(void) {
printf("Test 54: Undo multiple clip triggers... ");
Engine *engine = create_test_engine();
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);
engine_trigger_clip(engine, clip2);
engine_process_commands(engine);
assert(engine->clips[clip2].state == CLIP_RECORDING);
engine_trigger_clip(engine, clip3);
engine_process_commands(engine);
assert(engine->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);
// 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);
printf("PASSED\n");
destroy_test_engine(engine);
}
// Test 55: Redo single undo
void test_redo_single_undo(void) {
printf("Test 55: Redo single undo... ");
Engine *engine = create_test_engine();
int clip_idx = 10;
// Trigger clip
engine_trigger_clip(engine, clip_idx);
engine_process_commands(engine);
assert(engine->clips[clip_idx].state == CLIP_RECORDING);
// Undo
engine_undo_action(engine);
engine_process_commands(engine);
assert(engine->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);
printf("PASSED\n");
destroy_test_engine(engine);
}
// Test 56: Redo after multiple undos
void test_redo_multiple_undos(void) {
printf("Test 56: Redo after multiple undos... ");
Engine *engine = create_test_engine();
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);
// 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);
// Redo twice
engine_redo_action(engine);
engine_process_commands(engine);
assert(engine->clips[clip1].state == CLIP_RECORDING);
assert(engine->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);
printf("PASSED\n");
destroy_test_engine(engine);
}
// Test 57: Undo scene trigger
void test_undo_scene_trigger(void) {
printf("Test 57: Undo scene trigger... ");
Engine *engine = create_test_engine();
int scene_idx = 3;
// Trigger scene
engine_trigger_scene(engine, scene_idx);
engine_process_commands(engine);
// 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);
}
// Undo: all clips should go back to empty
engine_undo_action(engine);
engine_process_commands(engine);
for (int ch = 0; ch < MAX_CHANNELS; ch++) {
int clip_idx = CLIP_INDEX(scene_idx, ch);
assert(engine->clips[clip_idx].state == CLIP_EMPTY);
}
printf("PASSED\n");
destroy_test_engine(engine);
}
// 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();
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);
engine_trigger_clip(engine, clip_idx); // recording -> looping
engine_process_commands(engine);
assert(engine->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);
// 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);
engine_undo_action(engine); // looping -> recording
engine_process_commands(engine);
assert(engine->clips[clip_idx].state == CLIP_RECORDING);
engine_undo_action(engine); // recording -> empty
engine_process_commands(engine);
assert(engine->clips[clip_idx].state == CLIP_EMPTY);
printf("PASSED\n");
destroy_test_engine(engine);
}
// 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();
int clip1 = 10, clip2 = 11;
// Trigger clip1
engine_trigger_clip(engine, clip1);
engine_process_commands(engine);
assert(engine->clips[clip1].state == CLIP_RECORDING);
// Undo
engine_undo_action(engine);
engine_process_commands(engine);
assert(engine->clips[clip1].state == CLIP_EMPTY);
// Redo should work
engine_redo_action(engine);
engine_process_commands(engine);
assert(engine->clips[clip1].state == CLIP_RECORDING);
// Undo again
engine_undo_action(engine);
engine_process_commands(engine);
assert(engine->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);
// 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
printf("PASSED\n");
destroy_test_engine(engine);
}
// Test 60: Undo reset clip
void test_undo_reset_clip(void) {
printf("Test 60: Undo reset clip... ");
Engine *engine = create_test_engine();
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;
// 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);
// 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);
printf("PASSED\n");
destroy_test_engine(engine);
}
// 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();
assert(engine->quantize_mode == QUANTIZE_OFF);
// Change quantize mode
engine_set_quantize_mode(engine, QUANTIZE_BEAT);
engine_process_commands(engine);
assert(engine->quantize_mode == QUANTIZE_BEAT);
// Undo: should go back to OFF
engine_undo_action(engine);
engine_process_commands(engine);
assert(engine->quantize_mode == QUANTIZE_OFF);
// Redo: should go back to BEAT
engine_redo_action(engine);
engine_process_commands(engine);
assert(engine->quantize_mode == QUANTIZE_BEAT);
printf("PASSED\n");
destroy_test_engine(engine);
}
// Test 62: Undo/redo with threshold change
void test_undo_threshold_change(void) {
printf("Test 62: Undo threshold change... ");
Engine *engine = create_test_engine();
assert(engine->quantize_threshold == 0);
// Change threshold
engine_set_quantize_threshold(engine, 1000);
engine_process_commands(engine);
assert(engine->quantize_threshold == 1000);
// Undo: should go back to 0
engine_undo_action(engine);
engine_process_commands(engine);
assert(engine->quantize_threshold == 0);
// Redo: should go back to 1000
engine_redo_action(engine);
engine_process_commands(engine);
assert(engine->quantize_threshold == 1000);
printf("PASSED\n");
destroy_test_engine(engine);
}
// Test 63: Undo/redo with transport reset
void test_undo_transport_reset(void) {
printf("Test 63: Undo transport reset... ");
Engine *engine = create_test_engine();
// Set up transport state
engine->transport.rolling = true;
engine->transport.clock_count = 100;
engine->transport.beat_position = 2;
engine->transport.bar_position = 5;
engine->transport.sample_position = 10000;
// Reset transport
engine_reset_transport(engine);
engine_process_commands(engine);
assert(engine->transport.rolling == false);
assert(engine->transport.clock_count == 0);
// Undo: should restore transport state
engine_undo_action(engine);
engine_process_commands(engine);
assert(engine->transport.rolling == true);
assert(engine->transport.clock_count == 100);
assert(engine->transport.beat_position == 2);
assert(engine->transport.bar_position == 5);
assert(engine->transport.sample_position == 10000);
printf("PASSED\n");
destroy_test_engine(engine);
}
// Test 64: Undo/redo with paste operation
void test_undo_paste(void) {
printf("Test 64: Undo paste operation... ");
Engine *engine = create_test_engine();
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);
// 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);
// Undo again: should go back to recording
engine_undo_action(engine);
engine_process_commands(engine);
assert(engine->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);
// Redo three times: should go back to stopped
engine_redo_action(engine);
engine_process_commands(engine);
assert(engine->clips[clip_idx].state == CLIP_RECORDING);
engine_redo_action(engine);
engine_process_commands(engine);
assert(engine->clips[clip_idx].state == CLIP_LOOPING);
engine_redo_action(engine);
engine_process_commands(engine);
assert(engine->clips[clip_idx].state == CLIP_STOPPED);
printf("PASSED\n");
destroy_test_engine(engine);
}
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_quantize_change();
test_undo_threshold_change();
test_undo_transport_reset();
test_undo_paste();
printf("\nAll TUI tests passed!\n");
return 0;
}