trainlog/tui/tests/test_presentation.c
2026-09-16 07:46:01 +02:00

226 lines
10 KiB
C

#include <assert.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "trainlog/app_shell.h"
#include "trainlog/presentation.h"
#include "trainlog/sync_screen_action.h"
static void test_default_and_catalog(void)
{
TrainlogShellLayout french;
TrainlogShellLayout english;
char date[9];
char dynamic[512];
trainlog_presentation_init();
assert(trainlog_presentation_language() == TRAINLOG_PRESENTATION_FRENCH);
assert(strcmp(trainlog_presentation_text("nav.settings"), "Paramètres") == 0);
assert(strcmp(trainlog_route_title(TRAINLOG_ROUTE_STATS_TRAINING),
"Statistiques / Entraînement") == 0);
assert(trainlog_presentation_compact_date("2026-09-15", true,
date, sizeof(date)));
assert(strcmp(date, "15/09/26") == 0);
trainlog_shell_layout_compute(72, 20, &french);
assert(french.usable);
assert(trainlog_presentation_set_language(TRAINLOG_PRESENTATION_ENGLISH));
assert(strcmp(trainlog_presentation_text("nav.settings"), "Settings") == 0);
assert(strcmp(trainlog_route_title(TRAINLOG_ROUTE_STATS_TRAINING),
"Statistics / Training") == 0);
assert(strcmp(trainlog_sync_screen_confirmation(TRAINLOG_SYNC_BIDIRECTIONAL),
"Synchronize Android and PC in both directions now?") == 0);
assert(strstr(trainlog_sync_screen_footer(100), "Refresh device") != NULL);
assert(strcmp(trainlog_presentation_source("Aucune donnée disponible."),
"No data available.") == 0);
assert(trainlog_presentation_format_key(dynamic, sizeof(dynamic),
"msg.actual.required", "Squat Français") > 0);
assert(strcmp(dynamic, "Enter an actual result for Squat Français.") == 0);
assert(trainlog_presentation_format_key(dynamic, sizeof(dynamic),
"sync.history.legacy", "2026-09-15T10:30:00Z",
trainlog_presentation_text("sync.state.success")) > 0);
assert(strstr(dynamic, "Historical PC↔Android synchronization") != NULL);
assert(strstr(dynamic, "Timestamp: 2026-09-15T10:30:00Z") != NULL);
assert(strstr(dynamic, "Status: successful") != NULL);
/* Representative generator/profile/body/MAX/search/merge/feedback UI
* formats must render English while preserving caller-provided values. */
assert(trainlog_presentation_format(dynamic, sizeof(dynamic),
"Objectif : %s", "Force") > 0);
assert(strcmp(dynamic, "Goal: Force") == 0);
assert(trainlog_presentation_format(dynamic, sizeof(dynamic),
"%zu exercice(s) · cible %d min · estimation %d min", (size_t)3,
45, 42) > 0);
assert(strcmp(dynamic, "3 exercise(s) · target 45 min · estimate 42 min") == 0);
assert(trainlog_presentation_format(dynamic, sizeof(dynamic),
"Tour de taille %.1f cm · variation %+.1f cm", 81.5, -1.0) > 0);
assert(strcmp(dynamic, "Waist circumference 81.5 cm · change -1.0 cm") == 0);
assert(trainlog_presentation_format(dynamic, sizeof(dynamic),
"Historique des MAX") > 0);
assert(strcmp(dynamic, "MAX history") == 0);
assert(trainlog_presentation_format(dynamic, sizeof(dynamic),
"Recherche cible : %s_", "Squat") > 0);
assert(strcmp(dynamic, "Search target: Squat_") == 0);
assert(trainlog_presentation_format(dynamic, sizeof(dynamic),
"Contexte : %s · %s · %zu point%s", "Squat", "MAX", (size_t)1,
"") > 0);
assert(strcmp(dynamic, "Context: Squat · MAX · 1 point") == 0);
assert(trainlog_presentation_compact_date("2026-09-15", true,
date, sizeof(date)));
assert(strcmp(date, "09/15/26") == 0);
/* Persisted and catalog names are not keys and must remain byte-exact. */
assert(strcmp(trainlog_presentation_source("Développé incliné — Alice"),
"Développé incliné — Alice") == 0);
trainlog_shell_layout_compute(72, 20, &english);
assert(memcmp(&french, &english, sizeof(french)) == 0);
assert(!trainlog_presentation_set_language(
(TrainlogPresentationLanguage)99));
assert(trainlog_presentation_set_language(TRAINLOG_PRESENTATION_FRENCH));
/* Decimal localization is scoped to the float conversion: persisted/user
* text is not parsed or mutated merely because it contains digits. */
assert(trainlog_presentation_format(dynamic, sizeof(dynamic),
"Valeur %.1f · modèle %s", 12.5, "Model 1.2") > 0);
assert(strcmp(dynamic, "Valeur 12,5 · modèle Model 1.2") == 0);
assert(strcmp(trainlog_presentation_source("Pendant la séance"),
"Pendant la séance") == 0);
assert(strcmp(trainlog_presentation_month_label("févr. 2026", dynamic,
sizeof(dynamic)), "févr. 2026") == 0);
assert(trainlog_presentation_set_language(TRAINLOG_PRESENTATION_ENGLISH));
assert(strcmp(trainlog_presentation_source("Pendant la séance"),
"During session") == 0);
assert(strcmp(trainlog_presentation_month_label("févr. 2026", dynamic,
sizeof(dynamic)), "Feb 2026") == 0);
assert(trainlog_presentation_set_language(TRAINLOG_PRESENTATION_FRENCH));
assert(trainlog_presentation_format_key(dynamic, sizeof(dynamic),
"msg.actual.required", "Squat Français") > 0);
assert(strcmp(dynamic, "Saisissez un résultat réel pour Squat Français.") == 0);
}
static void test_selected_language_owns_decimals(void)
{
char output[128];
char *original = setlocale(LC_NUMERIC, NULL);
char saved[128];
if (original != NULL) (void)snprintf(saved, sizeof(saved), "%s", original);
else saved[0] = '\0';
trainlog_presentation_init();
assert(trainlog_presentation_format(output, sizeof(output),
"Valeur %.2f · delta %+.1f", 12.5, 0.25) > 0);
assert(strcmp(output, "Valeur 12,50 · delta +0,2") == 0);
assert(trainlog_presentation_format(output, sizeof(output),
"%+.1f%%", 3.25) > 0);
assert(strcmp(output, "+3,2%") == 0);
assert(trainlog_presentation_format(output, sizeof(output),
"%2$*3$.*4$f · %1$s · %%", "Model 1.2", 12.5, 7, 2) > 0);
assert(strcmp(output, " 12,50 · Model 1.2 · %") == 0);
assert(trainlog_presentation_format(output, sizeof(output),
"%Lf · %zu", (long double)1.25L, (size_t)4) > 0);
assert(strcmp(output, "1,250000 · 4") == 0);
assert(trainlog_presentation_set_language(TRAINLOG_PRESENTATION_ENGLISH));
assert(trainlog_presentation_format(output, sizeof(output),
"Value %.2f", 12.5) > 0);
assert(strcmp(output, "Value 12.50") == 0);
/* Contrary-locale integration proof when the host supplies French. The
* assertions above remain the locale-independent fallback proof. */
if (setlocale(LC_NUMERIC, "fr_FR.UTF-8") != NULL ||
setlocale(LC_NUMERIC, "fr_FR.utf8") != NULL) {
assert(trainlog_presentation_format(output, sizeof(output),
"Value %.2f", 12.5) > 0);
assert(strcmp(output, "Value 12.50") == 0);
assert(trainlog_presentation_set_language(TRAINLOG_PRESENTATION_FRENCH));
assert(trainlog_presentation_format(output, sizeof(output),
"Valeur %.2f", 12.5) > 0);
assert(strcmp(output, "Valeur 12,50") == 0);
}
if (saved[0] != '\0') assert(setlocale(LC_NUMERIC, saved) != NULL);
}
static void test_persistence(void)
{
char root_template[] = "/tmp/trainlog-presentation-XXXXXX";
char *root = mkdtemp(root_template);
char path[4096];
char directory[4096];
char invalid[] = "version=1\nlanguage=de\n";
FILE *file;
assert(root != NULL);
assert(setenv("XDG_CONFIG_HOME", root, 1) == 0);
assert(trainlog_presentation_config_path(path, sizeof(path)));
assert(strstr(path, "/trainlog/presentation.conf") != NULL);
assert(unsetenv("XDG_CONFIG_HOME") == 0);
assert(setenv("HOME", root, 1) == 0);
assert(trainlog_presentation_config_path(path, sizeof(path)));
assert(strstr(path, "/.config/trainlog/presentation.conf") != NULL);
assert(setenv("XDG_CONFIG_HOME", root, 1) == 0);
assert(trainlog_presentation_config_path(path, sizeof(path)));
trainlog_presentation_init();
assert(trainlog_presentation_load());
assert(trainlog_presentation_language() == TRAINLOG_PRESENTATION_FRENCH);
assert(trainlog_presentation_set_language(TRAINLOG_PRESENTATION_ENGLISH));
(void)snprintf(directory, sizeof(directory), "%s/trainlog", root);
assert(mkdir(directory, 0700) == 0);
{
char stale[4128];
FILE *stale_file;
(void)snprintf(stale, sizeof(stale), "%s.tmp.%ld", path,
(long)getpid());
stale_file = fopen(stale, "wb");
assert(stale_file != NULL);
assert(fclose(stale_file) == 0);
assert(trainlog_presentation_save() ==
TRAINLOG_PRESENTATION_SAVE_COMMITTED_DURABLE);
assert(unlink(stale) == 0);
}
trainlog_presentation_init();
assert(trainlog_presentation_load());
assert(trainlog_presentation_language() == TRAINLOG_PRESENTATION_ENGLISH);
assert(trainlog_presentation_set_language(TRAINLOG_PRESENTATION_FRENCH));
assert(trainlog_presentation_save() ==
TRAINLOG_PRESENTATION_SAVE_COMMITTED_DURABLE);
assert(trainlog_presentation_set_language(TRAINLOG_PRESENTATION_ENGLISH));
assert(trainlog_presentation_load());
assert(trainlog_presentation_language() == TRAINLOG_PRESENTATION_FRENCH);
file = fopen(path, "wb");
assert(file != NULL);
assert(fwrite(invalid, 1U, strlen(invalid), file) == strlen(invalid));
assert(fclose(file) == 0);
assert(!trainlog_presentation_load());
assert(trainlog_presentation_language() == TRAINLOG_PRESENTATION_FRENCH);
assert(unlink(path) == 0);
(void)snprintf(directory, sizeof(directory), "%s/trainlog", root);
assert(rmdir(directory) == 0);
file = fopen(directory, "wb");
assert(file != NULL);
assert(fclose(file) == 0);
assert(trainlog_presentation_set_language(TRAINLOG_PRESENTATION_ENGLISH));
assert(trainlog_presentation_save() ==
TRAINLOG_PRESENTATION_SAVE_NOT_COMMITTED);
trainlog_presentation_resolve_save(TRAINLOG_PRESENTATION_FRENCH,
TRAINLOG_PRESENTATION_SAVE_NOT_COMMITTED);
assert(trainlog_presentation_language() == TRAINLOG_PRESENTATION_FRENCH);
assert(trainlog_presentation_set_language(TRAINLOG_PRESENTATION_ENGLISH));
trainlog_presentation_resolve_save(TRAINLOG_PRESENTATION_FRENCH,
TRAINLOG_PRESENTATION_SAVE_COMMITTED_DURABILITY_UNCERTAIN);
assert(trainlog_presentation_language() == TRAINLOG_PRESENTATION_ENGLISH);
assert(unlink(directory) == 0);
assert(rmdir(root) == 0);
}
int main(void)
{
test_default_and_catalog();
test_selected_language_owns_decimals();
test_persistence();
puts("presentation tests passed");
return 0;
}