diff options
| author | Paul Buetow <paul@buetow.org> | 2026-07-12 17:30:26 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-07-12 17:30:26 +0300 |
| commit | 547b810eea19b0c72731e30ca3e2e339593c1833 (patch) | |
| tree | e19fc9308d2f93ffd1d558ec705effc6d9ffb752 /tests | |
| parent | 1770b12d5d95476310c27ccf39a91b5ba4a3f119 (diff) | |
skeleton: GtkApplication, window, GSettings schema, desktop, tests (ft0)
M0 (task ft0): the app shell — no feature views yet (those are M1 large, M7
grid).
- src/main.c: g_application_run + --version via handle-local-options (--help
is GApplication's own); exits before activate so --version works headless.
- src/app.{c,h}: GgazeApp : AdwApplication : GtkApplication (libadwaita
styling, decision #29), single-instance (decision #32),
G_APPLICATION_HANDLES_OPEN; activate + open reuse the active window
(replace-on-new-open); ggaze_window_open remembers the GFile.
- src/window.{c,h}: GgazeWindow : GtkApplicationWindow with AdwHeaderBar and a
GtkStack (children 'grid'/'large', default 'grid'); dispose clears the file.
- data/org.buetow.ggaze.desktop (image/* MIME, validated) and
data/org.buetow.ggaze.gschema.xml (all keys + defaults, --strict clean).
- meson: libggaze static lib shared by the executable and tests; install to
bindir/applications/glib-2.0 schemas; gnome.post_install compiles schemas +
updates desktop db.
- tests: unit test_app (--version/--help/unknown via subprocess), integration
test_window (offscreen stack checks + open-titles), bootstrap. 3/3 green.
- docs/tech-stack.md: window-geometry shape corrected to (width,height,
fullscreen,maximized) to match the schema.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/meson.build | 26 | ||||
| -rw-r--r-- | tests/test_app.c | 80 | ||||
| -rw-r--r-- | tests/test_window.c | 80 |
3 files changed, 186 insertions, 0 deletions
diff --git a/tests/meson.build b/tests/meson.build index c0ec9fb..4c02ccd 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -10,6 +10,7 @@ # meson test -C build # ninja -C build coverage (needs lcov + genhtml) +# --- unit track --- test_bootstrap = executable( 'test_bootstrap', ['test_bootstrap.c', ggaze_conf_h], @@ -19,4 +20,29 @@ test_bootstrap = executable( ) test('bootstrap', test_bootstrap, suite : 'unit') +# test_app runs the ggaze binary as a subprocess; meson passes its path. +test_app = executable( + 'test_app', + ['test_app.c', ggaze_conf_h], + include_directories : inc, + dependencies : [glib_dep, gio_dep], + install : false, +) +test('app', test_app, + suite : 'unit', + args : [ggaze_exe.full_path()], + depends : ggaze_exe, +) + +# --- integration track (needs a display; CI uses xvfb-run) --- +test_window = executable( + 'test_window', + ['test_window.c', ggaze_conf_h], + include_directories : [inc, src_inc], + dependencies : ggaze_deps, + link_with : ggaze_lib, + install : false, +) +test('window', test_window, suite : 'integration') + subdir('integration')
\ No newline at end of file diff --git a/tests/test_app.c b/tests/test_app.c new file mode 100644 index 0000000..b1438a1 --- /dev/null +++ b/tests/test_app.c @@ -0,0 +1,80 @@ +/*:* + * ggaze — app CLI unit test + * + * Exercises the ggaze binary as a subprocess (so --help/--version, which call + * exit() inside g_application_run, do not terminate the test process itself). + * Verifies: `--version` prints "ggaze" and exits 0; `--help` exits 0; an + * unknown flag exits non-zero. No display is needed — all three exit before + * activate. The ggaze binary path is passed as argv[1] by meson. + * + * Copyright (c) 2026 ggaze contributors + * SPDX-License-Identifier: GPL-3.0-or-later + *:*/ + +#include "ggaze-config.h" + +#include <glib.h> +#include <gio/gio.h> + +static const char *GGAZE_BIN; + +/* Run GGAZE_BIN with c_arg, return the exit status (0 on success). If + * c_out is non-NULL, receive the combined stdout+stderr. */ +static gint +run_one(const char *c_arg, char **c_out) { + GError *p_err = NULL; + GSubprocess *p_sub = g_subprocess_new(G_SUBPROCESS_FLAGS_STDOUT_PIPE | + G_SUBPROCESS_FLAGS_STDERR_MERGE, + &p_err, GGAZE_BIN, c_arg, NULL); + g_assert_no_error(p_err); + + char *c_stdout = NULL; + g_subprocess_communicate_utf8(p_sub, NULL, NULL, &c_stdout, NULL, &p_err); + g_assert_no_error(p_err); + + g_assert_true(g_subprocess_get_if_exited(p_sub)); + gint i_status = g_subprocess_get_exit_status(p_sub); + + if (c_out != NULL) { + *c_out = c_stdout; + } else { + g_free(c_stdout); + } + g_object_unref(p_sub); + return (i_status); +} + +static void +test_version(void) { + char *c_out = NULL; + gint i_status = run_one("--version", &c_out); + g_assert_cmpint(i_status, ==, 0); + g_assert_nonnull(c_out); + g_assert_nonnull(g_strstr_len(c_out, -1, "ggaze")); + g_free(c_out); +} + +static void +test_help(void) { + /* GApplication provides --help itself; it prints and exits 0. */ + gint i_status = run_one("--help", NULL); + g_assert_cmpint(i_status, ==, 0); +} + +static void +test_unknown_flag(void) { + gint i_status = run_one("--definitely-not-a-ggaze-flag", NULL); + g_assert_cmpint(i_status, !=, 0); +} + +int +main(int i_argc, char **c_argv) { + g_assert_cmpint(i_argc, >=, 2); + GGAZE_BIN = c_argv[1]; + + g_test_init(&i_argc, &c_argv, NULL); + g_test_add_func("/app/version", test_version); + g_test_add_func("/app/help", test_help); + g_test_add_func("/app/unknown_flag", test_unknown_flag); + return (g_test_run()); +}
\ No newline at end of file diff --git a/tests/test_window.c b/tests/test_window.c new file mode 100644 index 0000000..0570397 --- /dev/null +++ b/tests/test_window.c @@ -0,0 +1,80 @@ +/*:* + * ggaze — window smoke test (integration) + * + * Constructs a GgazeWindow offscreen and asserts the two-view stack exists + * (children "grid" and "large", default visible = "grid"), and that + * ggaze_window_open titles the window with the file's basename. + * + * The window is built with g_object_new() (no "application" property): the + * stack/header are constructed in ggaze_window_init, independent of the app + * association. Setting GtkWindow:application requires the GApplication + * ::startup signal to have fired (it does in production via activate/open; + * not in this isolated smoke test). Needs a display, so this lives in the + * `integration` suite (CI runs it under xvfb); skipped cleanly when no + * display is available. + * + * Copyright (c) 2026 ggaze contributors + * SPDX-License-Identifier: GPL-3.0-or-later + *:*/ + +#include "window.h" + +#include <gio/gio.h> +#include <glib.h> +#include <gtk/gtk.h> + +static GgazeWindow * +new_window(void) { + return (GGAZE_WINDOW(g_object_new(GGAZE_TYPE_WINDOW, NULL))); +} + +static void +test_stack_has_two_views(void) { + GgazeWindow *p_win = new_window(); + g_assert_nonnull(p_win); + + GtkWidget *p_child = gtk_window_get_child(GTK_WINDOW(p_win)); + g_assert_nonnull(p_child); + g_assert_true(GTK_IS_STACK(p_child)); + + GtkStack *p_stack = GTK_STACK(p_child); + GListModel *p_pages = G_LIST_MODEL(gtk_stack_get_pages(p_stack)); + g_assert_cmpint(g_list_model_get_n_items(p_pages), ==, 2); + g_assert_nonnull(gtk_stack_get_child_by_name(p_stack, "grid")); + g_assert_nonnull(gtk_stack_get_child_by_name(p_stack, "large")); + g_assert_cmpstr(gtk_stack_get_visible_child_name(p_stack), ==, "grid"); + + g_object_unref(p_win); +} + +static void +test_open_titles_window(void) { + GgazeWindow *p_win = new_window(); + + GFile *p_file = g_file_new_for_path("/tmp/IMG_0001.jpg"); + ggaze_window_open(p_win, p_file); + + g_assert_cmpstr(gtk_window_get_title(GTK_WINDOW(p_win)), ==, "IMG_0001.jpg"); + + g_object_unref(p_file); + g_object_unref(p_win); +} + +int +main(int i_argc, char **c_argv) { + g_test_init(&i_argc, &c_argv, NULL); + + /* Tolerate host GTK WARNINGs (e.g. an unknown gtk-modules key delivered + * by the live GNOME session via XSettings) that g_test_init makes fatal. + * Keep CRITICALs fatal — those are real bugs. */ + g_log_set_always_fatal(G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); + + if (!gtk_init_check()) { + g_test_skip("no display available (run under xvfb)"); + return (g_test_run()); + } + + g_test_add_func("/window/stack_has_two_views", test_stack_has_two_views); + g_test_add_func("/window/open_titles_window", test_open_titles_window); + return (g_test_run()); +}
\ No newline at end of file |
