/** * @file test_tui_workflows.c * @brief Scripted regressions through the production TUI workflow functions. */ #include /* The workflows remain file-private in production. Including their translation * unit lets this test drive the exact code paths with the terminal port mocked, * without adding a public test API to Trainlog. */ #include "../src/tui.c" struct TrainlogTerminal { int events[128]; size_t event_count; size_t event_index; char output[32768]; size_t output_used; }; struct TrainlogPanel { int unused; }; #define CHECK(condition) do { if (!(condition)) { \ (void)fprintf(stderr, "CHECK failed at %s:%d: %s\n", \ __FILE__, __LINE__, #condition); return false; } } while (0) static void script(TrainlogTerminal *terminal, const int *events, size_t count) { (void)memset(terminal, 0, sizeof(*terminal)); (void)memcpy(terminal->events, events, count * sizeof(events[0])); terminal->event_count = count; } TrainlogTerminal *trainlog_terminal_create(void) { return NULL; } void trainlog_terminal_destroy(TrainlogTerminal *terminal) { (void)terminal; } int trainlog_terminal_rows(const TrainlogTerminal *terminal) { (void)terminal; return 24; } int trainlog_terminal_columns(const TrainlogTerminal *terminal) { (void)terminal; return 80; } void trainlog_terminal_erase(TrainlogTerminal *terminal) { (void)terminal; } void trainlog_terminal_render(TrainlogTerminal *terminal) { (void)terminal; } void trainlog_terminal_style_on(TrainlogTerminal *terminal, TrainlogTextStyle style) { (void)terminal; (void)style; } void trainlog_terminal_style_off(TrainlogTerminal *terminal, TrainlogTextStyle style) { (void)terminal; (void)style; } void trainlog_terminal_printf(TrainlogTerminal *terminal, int row, int column, const char *format, ...) { va_list arguments; int written; (void)row; (void)column; if (terminal->output_used >= sizeof(terminal->output)) return; va_start(arguments, format); written = vsnprintf(terminal->output + terminal->output_used, sizeof(terminal->output) - terminal->output_used, format, arguments); va_end(arguments); if (written > 0 && (size_t)written < sizeof(terminal->output) - terminal->output_used) { terminal->output_used += (size_t)written; terminal->output[terminal->output_used++] = '\n'; terminal->output[terminal->output_used] = '\0'; } } void trainlog_terminal_putn(TrainlogTerminal *terminal, const char *text, size_t length) { (void)terminal; (void)text; (void)length; } void trainlog_terminal_move(TrainlogTerminal *terminal, int row, int column) { (void)terminal; (void)row; (void)column; } void trainlog_terminal_cursor_yx(const TrainlogTerminal *terminal, int *row, int *column) { (void)terminal; if (row) *row = 0; if (column) *column = 40; } void trainlog_terminal_clear_to_end(TrainlogTerminal *terminal) { (void)terminal; } void trainlog_terminal_cursor_visible(TrainlogTerminal *terminal, bool visible) { (void)terminal; (void)visible; } void trainlog_terminal_draw(TrainlogTerminal *terminal, int row, int column, uint32_t codepoint) { (void)terminal; (void)row; (void)column; (void)codepoint; } void trainlog_terminal_box(TrainlogTerminal *terminal, int top, int left, int bottom, int right) { (void)terminal; (void)top; (void)left; (void)bottom; (void)right; } int trainlog_terminal_get_key(TrainlogTerminal *terminal) { return terminal->event_index < terminal->event_count ? terminal->events[terminal->event_index++] : TRAINLOG_KEY_NONE; } bool trainlog_terminal_read_unicode(TrainlogTerminal *terminal, int *codepoint, char utf8[5]) { int value = trainlog_terminal_get_key(terminal); if (value == TRAINLOG_KEY_NONE) return false; *codepoint = value; utf8[0] = value >= 0 && value < 128 ? (char)value : '\0'; utf8[1] = '\0'; return true; } bool trainlog_terminal_push_key(TrainlogTerminal *terminal, int key) { (void)terminal; (void)key; return false; } TrainlogPanel *tui_panel_create(TrainlogTerminal *terminal, int height, int width, int top, int left) { (void)terminal; (void)height; (void)width; (void)top; (void)left; return NULL; } void tui_panel_destroy(TrainlogPanel *panel) { (void)panel; } void tui_panel_box(TrainlogPanel *panel) { (void)panel; } void tui_panel_style_on(TrainlogPanel *panel, TrainlogTextStyle style) { (void)panel; (void)style; } void tui_panel_style_off(TrainlogPanel *panel, TrainlogTextStyle style) { (void)panel; (void)style; } void tui_panel_print(TrainlogPanel *panel, int row, int column, const char *format, ...) { (void)panel; (void)row; (void)column; (void)format; } void tui_panel_commit(TrainlogPanel *panel) { (void)panel; } static bool test_assistance_creation_labels(void) { TrainlogDatabase *database = NULL; TrainlogExercise created; TrainlogSessionExerciseInput input; TrainlogSessionDraftExercise draft; TrainlogSetInput sets[4]; TrainlogTerminal terminal; const int planning_events[] = { TRAINLOG_KEY_ENTER, TRAINLOG_KEY_ENTER, '3', '\n', '2', '0', '\n', '\n', '\n', '\n' }; const int table_events[] = { 'a', '5', '\n', TRAINLOG_KEY_RIGHT, TRAINLOG_KEY_ENTER, '1', '5', '\n', 'f' }; CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK); CHECK(trainlog_catalog_create_exercise_profiled(database, "Tractions assistées", TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, 0U, &created) == TRAINLOG_STATUS_OK); script(&terminal, planning_events, sizeof(planning_events) / sizeof(planning_events[0])); tui_terminal = &terminal; CHECK(build_session_exercise(database, TRAINLOG_SESSION_TRAINING, &input, sets, sizeof(sets) / sizeof(sets[0]))); CHECK(input.load_mode == TRAINLOG_LOAD_ASSISTANCE); CHECK(input.target_weight_kg == 20.0 && input.set_count == 0U); CHECK(strstr(terminal.output, "Assistance cible kg") != NULL); CHECK(strstr(terminal.output, "Séries réalisées") == NULL); CHECK(strstr(terminal.output, "Séries réellement faites") == NULL); CHECK(strstr(terminal.output, "Charge cible kg") == NULL); (void)memset(&draft, 0, sizeof(draft)); draft.input = input; draft.input.sets = draft.sets; draft.tracking_mode = TRAINLOG_TRACKING_REPS; (void)snprintf(draft.name, sizeof(draft.name), "%s", created.name); script(&terminal, table_events, sizeof(table_events) / sizeof(table_events[0])); draft_edit_sets(&draft); CHECK(draft.input.set_count == 1U); CHECK(draft.sets[0].reps == 5); CHECK(draft.sets[0].reps != draft.input.target_reps); CHECK(draft.sets[0].has_weight && draft.sets[0].weight_kg == 15.0); CHECK(strstr(terminal.output, "Assistance") != NULL); CHECK(strstr(terminal.output, "Charge kg (vide") == NULL); tui_terminal = NULL; trainlog_database_close(database); return true; } static bool test_duration_creation_starts_empty(void) { TrainlogDatabase *database = NULL; TrainlogExercise created; TrainlogSessionExerciseInput input; TrainlogSetInput sets[4]; TrainlogTerminal terminal; const int planning_events[] = { TRAINLOG_KEY_ENTER, TRAINLOG_KEY_ENTER, TRAINLOG_KEY_ENTER, TRAINLOG_KEY_ENTER, TRAINLOG_KEY_ENTER, TRAINLOG_KEY_ENTER }; CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK); CHECK(trainlog_catalog_create_exercise_profiled(database, "Gainage", TRAINLOG_TRACKING_DURATION, TRAINLOG_RECORDING_SETS, 0U, &created) == TRAINLOG_STATUS_OK); script(&terminal, planning_events, sizeof(planning_events) / sizeof(planning_events[0])); tui_terminal = &terminal; CHECK(build_session_exercise(database, TRAINLOG_SESSION_TRAINING, &input, sets, sizeof(sets) / sizeof(sets[0]))); CHECK(input.target_duration_seconds == 45); CHECK(input.set_count == 0U); CHECK(strstr(terminal.output, "Séries réellement faites") == NULL); CHECK(strstr(terminal.output, "Durée réalisée") == NULL); tui_terminal = NULL; trainlog_database_close(database); return true; } static bool test_append_requires_actual_and_rolls_back(void) { TrainlogSessionDraftExercise draft; TrainlogSessionDraftExercise duration_draft; TrainlogTerminal terminal; const int accepted[] = {'7', '\n'}; const int duration_accepted[] = {'1', ':', '3', '0', '\n'}; const int cancelled[] = {27}; (void)memset(&draft, 0, sizeof(draft)); draft.input.recording_mode = TRAINLOG_RECORDING_SETS; draft.tracking_mode = TRAINLOG_TRACKING_REPS; draft.input.target_reps = 12; draft.input.set_count = 1U; draft.sets[0].reps = 5; script(&terminal, accepted, sizeof(accepted) / sizeof(accepted[0])); tui_terminal = &terminal; CHECK(draft_append_incomplete_set(&draft)); CHECK(draft.input.set_count == 2U); CHECK(draft.sets[0].reps == 5); CHECK(draft.sets[1].reps == 7); CHECK(draft.sets[1].reps != draft.input.target_reps); script(&terminal, cancelled, sizeof(cancelled) / sizeof(cancelled[0])); CHECK(!draft_append_incomplete_set(&draft)); CHECK(draft.input.set_count == 2U); CHECK(draft.sets[0].reps == 5 && draft.sets[1].reps == 7); (void)memset(&duration_draft, 0, sizeof(duration_draft)); duration_draft.input.recording_mode = TRAINLOG_RECORDING_SETS; duration_draft.tracking_mode = TRAINLOG_TRACKING_DURATION; duration_draft.input.target_duration_seconds = 45; script(&terminal, duration_accepted, sizeof(duration_accepted) / sizeof(duration_accepted[0])); CHECK(draft_append_incomplete_set(&duration_draft)); CHECK(duration_draft.input.set_count == 1U); CHECK(duration_draft.sets[0].duration_seconds == 90); CHECK(duration_draft.sets[0].duration_seconds != duration_draft.input.target_duration_seconds); script(&terminal, cancelled, sizeof(cancelled) / sizeof(cancelled[0])); CHECK(!draft_append_incomplete_set(&duration_draft)); CHECK(duration_draft.input.set_count == 1U); CHECK(duration_draft.sets[0].duration_seconds == 90); tui_terminal = NULL; return true; } static bool test_empty_sets_cannot_finish(void) { TrainlogSessionDraftExercise draft; TrainlogTerminal terminal; size_t count = 1U; const int events[] = {'f', 'x', 'q'}; (void)memset(&draft, 0, sizeof(draft)); draft.input.recording_mode = TRAINLOG_RECORDING_SETS; draft.tracking_mode = TRAINLOG_TRACKING_REPS; (void)snprintf(draft.name, sizeof(draft.name), "Squat"); script(&terminal, events, sizeof(events) / sizeof(events[0])); tui_terminal = &terminal; CHECK(!edit_session_draft(NULL, &draft, &count, TRAINLOG_SESSION_TRAINING)); CHECK(count == 1U && draft.input.set_count == 0U); CHECK(strstr(terminal.output, "Ajoutez au moins une série réalisée pour Squat.") != NULL); tui_terminal = NULL; return true; } int main(void) { CHECK(test_assistance_creation_labels()); CHECK(test_duration_creation_starts_empty()); CHECK(test_append_requires_actual_and_rolls_back()); CHECK(test_empty_sets_cannot_finish()); (void)printf("PASS tui_workflows\n"); return 0; }