trainlog/tui/tests/test_tui_workflows.c

508 lines
23 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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;
int rows;
int columns;
bool coordinate_overflow;
bool text_overflow;
};
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;
terminal->rows = 24;
terminal->columns = 80;
}
TrainlogTerminal *trainlog_terminal_create(void) { return NULL; }
void trainlog_terminal_destroy(TrainlogTerminal *terminal) { (void)terminal; }
int trainlog_terminal_rows(const TrainlogTerminal *terminal) { return terminal->rows; }
int trainlog_terminal_columns(const TrainlogTerminal *terminal) { return terminal->columns; }
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;
if (row < 0 || row >= terminal->rows || column < 0 || column >= terminal->columns)
terminal->coordinate_overflow = true;
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) {
const char *text = terminal->output + terminal->output_used;
const char *at = text;
int cells = 0;
while (*at != '\0') {
utf8proc_int32_t codepoint;
utf8proc_ssize_t bytes = utf8proc_iterate((const utf8proc_uint8_t *)at,
-1, &codepoint);
int codepoint_cells;
if (bytes <= 0) { bytes = 1; codepoint = (unsigned char)*at; }
codepoint_cells = utf8proc_charwidth(codepoint);
cells += codepoint_cells < 0 ? 1 : codepoint_cells;
at += bytes;
}
if (column + cells >= terminal->columns) terminal->text_overflow = true;
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)
{
int key = terminal->event_index < terminal->event_count
? terminal->events[terminal->event_index++] : TRAINLOG_KEY_NONE;
if (key == TRAINLOG_KEY_RESIZE) {
terminal->rows = 20;
terminal->columns = 72;
}
return key;
}
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;
}
static bool test_knowledge_scrolls_long_lists_at_minimum_terminal(void)
{
TrainlogExercise exercise;
TrainlogTerminal terminal;
const int events[] = {
TRAINLOG_KEY_PAGE_DOWN, TRAINLOG_KEY_PAGE_DOWN, TRAINLOG_KEY_PAGE_DOWN,
TRAINLOG_KEY_PAGE_DOWN, 'b'
};
(void)memset(&exercise, 0, sizeof(exercise));
(void)snprintf(exercise.exercise_id, sizeof(exercise.exercise_id), "%s",
"ex_a72fa713-4b0e-431d-95e2-42d95beb77b1");
(void)snprintf(exercise.name, sizeof(exercise.name), "%s", "Lat pull");
script(&terminal, events, sizeof(events) / sizeof(events[0]));
terminal.rows = 20;
terminal.columns = 72;
tui_terminal = &terminal;
screen_exercise_knowledge(&exercise);
CHECK(!terminal.coordinate_overflow);
CHECK(!terminal.text_overflow);
CHECK(strstr(terminal.output, "Supra-épineux") != NULL);
CHECK(strstr(terminal.output, "Sources :") != NULL);
tui_terminal = NULL;
return true;
}
static size_t generator_zone_events(const char *zone_id, int *events)
{
size_t index;
for (index = 0U; index < trainlog_body_zone_catalog_count(); ++index) {
const TrainlogBodyZone *zone = trainlog_body_zone_catalog_at(index);
if (zone != NULL && strcmp(zone->zone_id, zone_id) == 0) {
size_t event;
for (event = 0U; event < index; ++event) events[event] = TRAINLOG_KEY_DOWN;
events[index] = TRAINLOG_KEY_ENTER;
return index + 1U;
}
}
return 0U;
}
static bool seed_generator_leg_press(TrainlogDatabase *database)
{
return trainlog_database_insert_exercise_profiled(database,
"ex_b432623f-bfe9-4daf-a653-60ec7fdffbde", "Leg press", "leg press",
TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, 0U) == TRAINLOG_STATUS_OK;
}
static bool test_generator_dashboard_entry_and_policy_choices(void)
{
TrainlogDatabase *database = NULL;
TrainlogTerminal terminal;
const TrainlogSessionGenerationGoalPolicy *goal = NULL;
int minutes = 0;
const int dashboard_events[] = {'g'};
const int goal_events[] = {TRAINLOG_KEY_DOWN, TRAINLOG_KEY_ENTER};
const int duration_events[] = {TRAINLOG_KEY_DOWN, TRAINLOG_KEY_ENTER};
CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK);
script(&terminal, dashboard_events, sizeof(dashboard_events) / sizeof(dashboard_events[0]));
terminal.rows = 20; terminal.columns = 72; tui_terminal = &terminal;
CHECK(screen_dashboard(database) == DASHBOARD_GENERATE_SESSION);
CHECK(strstr(terminal.output, "g générer une séance") != NULL);
CHECK(!terminal.coordinate_overflow && !terminal.text_overflow);
script(&terminal, goal_events, sizeof(goal_events) / sizeof(goal_events[0]));
CHECK(generator_choose_goal(&goal));
CHECK(goal == &trainlog_session_generation_policy_v1.goals[1]);
script(&terminal, duration_events, sizeof(duration_events) / sizeof(duration_events[0]));
CHECK(generator_choose_duration(&minutes));
CHECK(minutes == trainlog_session_generation_policy_v1.duration_presets_minutes[1]);
tui_terminal = NULL; trainlog_database_close(database); return true;
}
static bool test_generator_plan_drafts_have_no_actual_sets(void)
{
TrainlogDatabase *database = NULL;
TrainlogGeneratedSession generated;
TrainlogSessionDraftExercise drafts[1];
char summary[160];
CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK);
CHECK(seed_generator_leg_press(database));
(void)memset(&generated, 0, sizeof(generated));
generated.exercise_count = 1U;
(void)snprintf(generated.exercises[0].exercise_id,
sizeof(generated.exercises[0].exercise_id), "%s",
"ex_b432623f-bfe9-4daf-a653-60ec7fdffbde");
(void)snprintf(generated.exercises[0].equipment_id,
sizeof(generated.exercises[0].equipment_id), "%s", "leg_press");
generated.exercises[0].target_sets = 3;
generated.exercises[0].target_repetitions = 10;
generated.exercises[0].rest_seconds = 120;
generated.exercises[0].planned_load_mode = TRAINLOG_LOAD_NONE;
CHECK(generator_build_drafts(database, &generated, drafts));
CHECK(drafts[0].input.target_sets == 3 && drafts[0].input.target_reps == 10);
CHECK(drafts[0].input.rest_seconds == 120);
CHECK(drafts[0].input.set_count == 0U && drafts[0].input.sets == drafts[0].sets);
draft_set_summary(&drafts[0], summary, sizeof(summary));
CHECK(strstr(summary, "Plan 3×10") != NULL);
CHECK(strstr(summary, "0 réalisée") != NULL);
trainlog_database_close(database); return true;
}
static bool test_generator_preview_scrolls_at_minimum_terminal(void)
{
TrainlogGeneratedSession generated;
TrainlogGeneratorPreviewItem items[TRAINLOG_GENERATOR_MAX_SELECTED];
TrainlogTerminal terminal;
size_t index;
const int events[] = {TRAINLOG_KEY_RESIZE, TRAINLOG_KEY_DOWN, TRAINLOG_KEY_DOWN,
TRAINLOG_KEY_DOWN, TRAINLOG_KEY_DOWN, TRAINLOG_KEY_DOWN, 'q'};
(void)memset(&generated, 0, sizeof(generated));
(void)memset(items, 0, sizeof(items));
generated.exercise_count = TRAINLOG_GENERATOR_MAX_SELECTED;
generated.estimated_duration_seconds = 1800;
for (index = 0U; index < generated.exercise_count; ++index) {
generated.exercises[index].target_sets = 3;
generated.exercises[index].target_repetitions = 10;
generated.exercises[index].rest_seconds = 120;
(void)snprintf(items[index].exercise_name, sizeof(items[index].exercise_name), "Exercice %zu", index + 1U);
(void)snprintf(items[index].equipment_name, sizeof(items[index].equipment_name), "Machine %zu", index + 1U);
(void)snprintf(items[index].zone_name, sizeof(items[index].zone_name), "Cuisses");
(void)snprintf(items[index].movement_name, sizeof(items[index].movement_name), "Extension du genou");
}
script(&terminal, events, sizeof(events) / sizeof(events[0]));
terminal.rows = 30; terminal.columns = 100; tui_terminal = &terminal;
CHECK(!generator_preview(&generated, items));
CHECK(strstr(terminal.output, "Exercice 6") != NULL);
CHECK(strstr(terminal.output, "aucune prescription numérique") != NULL);
CHECK(!terminal.coordinate_overflow && !terminal.text_overflow);
tui_terminal = NULL; return true;
}
static bool test_generator_recency_continue_and_cancel(void)
{
TrainlogBodyZoneRecentExposure exposure;
TrainlogTerminal terminal;
const TrainlogBodyZone *zone = trainlog_body_zone_catalog_lookup("back");
const int continue_events[] = {'c'};
const int cancel_events[] = {'q'};
(void)memset(&exposure, 0, sizeof(exposure));
exposure.warning_level = TRAINLOG_GENERATION_WARNING_WARNING;
exposure.within_24h.primary_set_count = 2U;
exposure.within_72h.secondary_set_count = 4U;
exposure.has_latest = true;
(void)snprintf(exposure.latest_started_at, sizeof(exposure.latest_started_at),
"%s", "2026-09-10T08:00:00.250+02:00");
CHECK(zone != NULL);
script(&terminal, continue_events, sizeof(continue_events) / sizeof(continue_events[0]));
terminal.rows = 20; terminal.columns = 72; tui_terminal = &terminal;
CHECK(generator_confirm_exposure(zone, &exposure) == 1);
CHECK(strstr(terminal.output, "Dos travaillé récemment") != NULL);
CHECK(strstr(terminal.output, "2 séries principales") != NULL);
CHECK(!terminal.coordinate_overflow && !terminal.text_overflow);
script(&terminal, cancel_events, sizeof(cancel_events) / sizeof(cancel_events[0]));
CHECK(generator_confirm_exposure(zone, &exposure) == -1);
tui_terminal = NULL; return true;
}
static bool test_generator_cancel_preview_writes_nothing(void)
{
TrainlogDatabase *database = NULL;
TrainlogTerminal terminal;
int events[32];
size_t count = generator_zone_events("thighs", events);
size_t sessions = 99U;
CHECK(count > 0U);
events[count++] = TRAINLOG_KEY_ENTER;
events[count++] = TRAINLOG_KEY_ENTER;
events[count++] = 'q';
CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK);
CHECK(seed_generator_leg_press(database));
script(&terminal, events, count); tui_terminal = &terminal;
screen_session_generator(database);
CHECK(trainlog_database_session_count(database, &sessions) == TRAINLOG_STATUS_OK);
CHECK(sessions == 0U);
CHECK(strstr(terminal.output, "Aperçu de la séance générée") != NULL);
tui_terminal = NULL; trainlog_database_close(database); return true;
}
static bool test_generator_accept_requires_and_persists_actual(void)
{
TrainlogDatabase *database = NULL;
TrainlogTerminal terminal;
int events[48];
size_t count = generator_zone_events("thighs", events);
size_t sessions = 0U;
CHECK(count > 0U);
events[count++] = TRAINLOG_KEY_ENTER;
events[count++] = TRAINLOG_KEY_ENTER;
events[count++] = 'a';
events[count++] = TRAINLOG_KEY_ENTER;
events[count++] = 'a'; events[count++] = '8'; events[count++] = '\n';
events[count++] = 'f'; events[count++] = 'f'; events[count++] = 'x';
CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK);
CHECK(seed_generator_leg_press(database));
script(&terminal, events, count); tui_terminal = &terminal;
screen_session_generator(database);
CHECK(trainlog_database_session_count(database, &sessions) == TRAINLOG_STATUS_OK);
CHECK(sessions == 1U);
CHECK(strstr(terminal.output, "Valeurs réellement effectuées") != NULL);
CHECK(strstr(terminal.output, "Séance générée et réalisée enregistrée") != NULL);
tui_terminal = NULL; trainlog_database_close(database); return true;
}
static bool test_generator_empty_knowledge_cannot_save(void)
{
TrainlogDatabase *database = NULL;
TrainlogTerminal terminal;
const int events[] = {TRAINLOG_KEY_ENTER, TRAINLOG_KEY_ENTER,
TRAINLOG_KEY_ENTER, 'x'};
size_t sessions = 99U;
CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK);
script(&terminal, events, sizeof(events) / sizeof(events[0]));
tui_terminal = &terminal; screen_session_generator(database);
CHECK(trainlog_database_session_count(database, &sessions) == TRAINLOG_STATUS_OK);
CHECK(sessions == 0U);
CHECK(strstr(terminal.output, "Pas assez dexercices résolus") != NULL);
CHECK(strstr(terminal.output, "rien ne peut être enregistré") != 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() ||
!test_knowledge_scrolls_long_lists_at_minimum_terminal() ||
!test_generator_dashboard_entry_and_policy_choices() ||
!test_generator_plan_drafts_have_no_actual_sets() ||
!test_generator_preview_scrolls_at_minimum_terminal() ||
!test_generator_recency_continue_and_cancel() ||
!test_generator_cancel_preview_writes_nothing() ||
!test_generator_accept_requires_and_persists_actual() ||
!test_generator_empty_knowledge_cannot_save()) return 1;
(void)printf("PASS tui_workflows\n");
return 0;
}