trainlog/tui/tests/test_tui_workflows.c

251 lines
11 KiB
C

/**
* @file test_tui_workflows.c
* @brief Scripted regressions through the production TUI workflow functions.
*/
#include <stdarg.h>
/* 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;
TrainlogDatabase *database = NULL;
size_t count = 1U;
const int events[] = {'f', 'x', 'q'};
CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK);
(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(database, &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;
trainlog_database_close(database);
return true;
}
int main(void)
{
if (!test_assistance_creation_labels() ||
!test_duration_creation_starts_empty() ||
!test_append_requires_actual_and_rolls_back() ||
!test_empty_sets_cannot_finish()) return 1;
(void)printf("PASS tui_workflows\n");
return 0;
}