summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/fixtures/.gitkeep0
-rw-r--r--tests/fixtures/gen.py93
-rw-r--r--tests/fixtures/plain.jpgbin0 -> 769 bytes
-rw-r--r--tests/fixtures/rgba.pngbin0 -> 108 bytes
-rw-r--r--tests/fixtures/rot6.jpgbin0 -> 768 bytes
-rw-r--r--tests/fixtures/small.pngbin0 -> 97 bytes
-rw-r--r--tests/meson.build43
-rw-r--r--tests/test_detect.c111
-rw-r--r--tests/test_loader_pixbuf.c163
-rw-r--r--tests/test_open_and_show.c138
-rw-r--r--tests/test_window.c1
11 files changed, 549 insertions, 0 deletions
diff --git a/tests/fixtures/.gitkeep b/tests/fixtures/.gitkeep
deleted file mode 100644
index e69de29..0000000
--- a/tests/fixtures/.gitkeep
+++ /dev/null
diff --git a/tests/fixtures/gen.py b/tests/fixtures/gen.py
new file mode 100644
index 0000000..8ead202
--- /dev/null
+++ b/tests/fixtures/gen.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+"""Generate ggaze loader test fixtures (run manually; outputs are committed).
+
+Produces, under the directory passed as argv[1] (or beside this script):
+ plain.jpg 6x3 JPEG, EXIF Orientation = 1 -> loader yields 6x3
+ rot6.jpg 8x4 JPEG, EXIF Orientation = 6 -> loader yields 4x8
+ small.png 5x2 PNG (no orientation) -> loader yields 5x2
+
+Uses only the Python stdlib + cjpeg + exiftool.
+"""
+import os
+import struct
+import subprocess
+import sys
+import zlib
+
+
+def write_ppm(path, w, h):
+ # Distinct-ish RGB so frames are not blank.
+ data = bytearray()
+ for y in range(h):
+ for x in range(w):
+ data += bytes((x * 30 % 256, y * 60 % 256, (x + y) * 17 % 256))
+ with open(path, "wb") as f:
+ f.write(b"P6\n%d %d\n255\n" % (w, h))
+ f.write(bytes(data))
+
+
+def write_png(path, w, h, alpha=False):
+ sig = b"\x89PNG\r\n\x1a\n"
+ color_type = 6 if alpha else 2 # 6 = RGBA, 2 = RGB
+ channels = 4 if alpha else 3
+
+ def chunk(typ, data):
+ return (struct.pack(">I", len(data)) + typ + data
+ + struct.pack(">I", zlib.crc32(typ + data) & 0xffffffff))
+
+ ihdr = struct.pack(">IIBBBBB", w, h, 8, color_type, 0, 0, 0)
+ raw = bytearray()
+ for y in range(h):
+ raw.append(0) # filter: none
+ for x in range(w):
+ r = (x * 50) % 256
+ g = (y * 90) % 256
+ b = (x * 7 + y * 11) % 256
+ if alpha:
+ a = (255 - (x + y) * 30) % 256 # varied alpha
+ raw += bytes((r, g, b, a))
+ else:
+ raw += bytes((r, g, b))
+ idat = zlib.compress(bytes(raw))
+ with open(path, "wb") as f:
+ f.write(sig + chunk(b"IHDR", ihdr) + chunk(b"IDAT", idat)
+ + chunk(b"IEND", b""))
+
+
+def main():
+ out = sys.argv[1] if len(sys.argv) > 1 else os.path.dirname(
+ os.path.abspath(__file__))
+
+ # plain.jpg: 6x3, orientation 1
+ write_ppm(os.path.join(out, "_plain.ppm"), 6, 3)
+ subprocess.run(["cjpeg", "-quality", "90", "-outfile",
+ os.path.join(out, "plain.jpg"),
+ os.path.join(out, "_plain.ppm")], check=True)
+ subprocess.run(["exiftool", "-overwrite_original", "-Orientation#=1",
+ os.path.join(out, "plain.jpg")], check=True)
+
+ # rot6.jpg: 8x4, orientation 6 (rotate 90 CW -> displayed 4x8)
+ write_ppm(os.path.join(out, "_rot6.ppm"), 8, 4)
+ subprocess.run(["cjpeg", "-quality", "90", "-outfile",
+ os.path.join(out, "rot6.jpg"),
+ os.path.join(out, "_rot6.ppm")], check=True)
+ subprocess.run(["exiftool", "-overwrite_original", "-Orientation#=6",
+ os.path.join(out, "rot6.jpg")], check=True)
+
+ # small.png: 5x2 RGB
+ write_png(os.path.join(out, "small.png"), 5, 2, alpha=False)
+
+ # rgba.png: 5x2 RGBA (exercises the has-alpha branch of texture_from_pixbuf)
+ write_png(os.path.join(out, "rgba.png"), 5, 2, alpha=True)
+
+ # tidy intermediates
+ for p in ("_plain.ppm", "_rot6.ppm"):
+ try:
+ os.remove(os.path.join(out, p))
+ except FileNotFoundError:
+ pass
+ print("fixtures generated in", out)
+
+
+if __name__ == "__main__":
+ main() \ No newline at end of file
diff --git a/tests/fixtures/plain.jpg b/tests/fixtures/plain.jpg
new file mode 100644
index 0000000..9f3022c
--- /dev/null
+++ b/tests/fixtures/plain.jpg
Binary files differ
diff --git a/tests/fixtures/rgba.png b/tests/fixtures/rgba.png
new file mode 100644
index 0000000..fe8a3f4
--- /dev/null
+++ b/tests/fixtures/rgba.png
Binary files differ
diff --git a/tests/fixtures/rot6.jpg b/tests/fixtures/rot6.jpg
new file mode 100644
index 0000000..be69ba6
--- /dev/null
+++ b/tests/fixtures/rot6.jpg
Binary files differ
diff --git a/tests/fixtures/small.png b/tests/fixtures/small.png
new file mode 100644
index 0000000..6c6656d
--- /dev/null
+++ b/tests/fixtures/small.png
Binary files differ
diff --git a/tests/meson.build b/tests/meson.build
index 4c02ccd..76d4962 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -9,6 +9,17 @@
# Coverage: meson setup -Db_coverage=true build
# meson test -C build
# ninja -C build coverage (needs lcov + genhtml)
+#
+# Fixture dirs are passed to tests via env vars:
+# GGAZE_FIXTURES_DIR committed tests/fixtures/ (CI-portable baseline)
+# GGAZE_SAMPLE_DIR local ./sample-images (not git-tracked; tests skip
+# cleanly when absent so CI stays green)
+
+fixtures_env = environment()
+fixtures_env.set('GGAZE_FIXTURES_DIR',
+ join_paths(meson.project_source_root(), 'tests', 'fixtures'))
+fixtures_env.set('GGAZE_SAMPLE_DIR',
+ join_paths(meson.project_source_root(), 'sample-images'))
# --- unit track ---
test_bootstrap = executable(
@@ -34,6 +45,26 @@ test('app', test_app,
depends : ggaze_exe,
)
+test_detect = executable(
+ 'test_detect',
+ ['test_detect.c', ggaze_conf_h],
+ include_directories : [inc, src_inc],
+ dependencies : [glib_dep],
+ link_with : ggaze_lib,
+ install : false,
+)
+test('detect', test_detect, suite : 'unit', env : fixtures_env)
+
+test_loader_pixbuf = executable(
+ 'test_loader_pixbuf',
+ ['test_loader_pixbuf.c', ggaze_conf_h],
+ include_directories : [inc, src_inc],
+ dependencies : [gdkpixbuf_dep, gtk4_dep, glib_dep, gio_dep],
+ link_with : ggaze_lib,
+ install : false,
+)
+test('loader_pixbuf', test_loader_pixbuf, suite : 'unit', env : fixtures_env)
+
# --- integration track (needs a display; CI uses xvfb-run) ---
test_window = executable(
'test_window',
@@ -45,4 +76,16 @@ test_window = executable(
)
test('window', test_window, suite : 'integration')
+test_open_and_show = executable(
+ 'test_open_and_show',
+ ['test_open_and_show.c', ggaze_conf_h],
+ include_directories : [inc, src_inc],
+ dependencies : ggaze_deps,
+ link_with : ggaze_lib,
+ install : false,
+)
+test('open_and_show', test_open_and_show,
+ suite : 'integration',
+ env : fixtures_env)
+
subdir('integration') \ No newline at end of file
diff --git a/tests/test_detect.c b/tests/test_detect.c
new file mode 100644
index 0000000..b12bf70
--- /dev/null
+++ b/tests/test_detect.c
@@ -0,0 +1,111 @@
+/*:*
+ * ggaze — format detection unit test
+ *
+ * Feeds magic-byte buffers to detect_format() and asserts the result. No I/O,
+ * no display. Covers every format plus edge cases (empty, too-short, garbage).
+ *
+ * Copyright (c) 2026 ggaze contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *:*/
+
+#include "loader/detect.h"
+
+#include <glib.h>
+
+static void
+test_jpeg(void) {
+ const guint8 h[] = {0xFF, 0xD8, 0xFF, 0xE0, 0x10, 0x00};
+ g_assert_cmpint(detect_format(h, G_N_ELEMENTS(h)), ==, GGAZE_FMT_JPEG);
+}
+
+static void
+test_png(void) {
+ const guint8 h[] = {0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A, 0, 0};
+ g_assert_cmpint(detect_format(h, G_N_ELEMENTS(h)), ==, GGAZE_FMT_PNG);
+}
+
+static void
+test_gif(void) {
+ const guint8 h[] = {'G', 'I', 'F', '8', '9', 'a'};
+ g_assert_cmpint(detect_format(h, G_N_ELEMENTS(h)), ==, GGAZE_FMT_GIF);
+}
+
+static void
+test_webp(void) {
+ const guint8 h[] = "RIFF\x00\x00\x00\x00WEBP";
+ g_assert_cmpint(detect_format(h, 12), ==, GGAZE_FMT_WEBP);
+}
+
+static void
+test_tiff_le(void) {
+ const guint8 h[] = {'I', 'I', 0x2A, 0x00};
+ g_assert_cmpint(detect_format(h, 4), ==, GGAZE_FMT_TIFF);
+}
+
+static void
+test_tiff_be(void) {
+ const guint8 h[] = {'M', 'M', 0x00, 0x2A};
+ g_assert_cmpint(detect_format(h, 4), ==, GGAZE_FMT_TIFF);
+}
+
+static void
+test_ico(void) {
+ const guint8 h[] = {0x00, 0x00, 0x01, 0x00};
+ g_assert_cmpint(detect_format(h, 4), ==, GGAZE_FMT_ICO);
+}
+
+static void
+test_jxl_codestream(void) {
+ const guint8 h[] = {0xFF, 0x0A, 0, 0};
+ g_assert_cmpint(detect_format(h, 2), ==, GGAZE_FMT_JXL);
+}
+
+static void
+test_jxl_container(void) {
+ const guint8 h[] = {0x00, 0x00, 0x00, 0x0C, 'J', 'X', 'L', ' ', 0, 0, 0, 0};
+ g_assert_cmpint(detect_format(h, 12), ==, GGAZE_FMT_JXL);
+}
+
+static void
+test_avif(void) {
+ const guint8 h[] = {0, 0, 0, 0, 'f', 't', 'y', 'p', 'a', 'v', 'i', 'f'};
+ g_assert_cmpint(detect_format(h, 12), ==, GGAZE_FMT_AVIF);
+}
+
+static void
+test_heif(void) {
+ const guint8 h[] = {0, 0, 0, 0, 'f', 't', 'y', 'p', 'h', 'e', 'i', 'c'};
+ g_assert_cmpint(detect_format(h, 12), ==, GGAZE_FMT_HEIF);
+}
+
+static void
+test_unknown_garbage(void) {
+ const guint8 h[] = {'h', 'e', 'l', 'l', 'o'};
+ g_assert_cmpint(detect_format(h, G_N_ELEMENTS(h)), ==, GGAZE_FMT_UNKNOWN);
+}
+
+static void
+test_empty_and_short(void) {
+ g_assert_cmpint(detect_format(NULL, 0), ==, GGAZE_FMT_UNKNOWN);
+ const guint8 h[] = {0xFF};
+ g_assert_cmpint(detect_format(h, 1), ==, GGAZE_FMT_UNKNOWN);
+}
+
+int
+main(int i_argc, char **c_argv) {
+ g_test_init(&i_argc, &c_argv, NULL);
+ g_test_add_func("/detect/jpeg", test_jpeg);
+ g_test_add_func("/detect/png", test_png);
+ g_test_add_func("/detect/gif", test_gif);
+ g_test_add_func("/detect/webp", test_webp);
+ g_test_add_func("/detect/tiff_le", test_tiff_le);
+ g_test_add_func("/detect/tiff_be", test_tiff_be);
+ g_test_add_func("/detect/ico", test_ico);
+ g_test_add_func("/detect/jxl_codestream", test_jxl_codestream);
+ g_test_add_func("/detect/jxl_container", test_jxl_container);
+ g_test_add_func("/detect/avif", test_avif);
+ g_test_add_func("/detect/heif", test_heif);
+ g_test_add_func("/detect/unknown_garbage", test_unknown_garbage);
+ g_test_add_func("/detect/empty_and_short", test_empty_and_short);
+ return (g_test_run());
+} \ No newline at end of file
diff --git a/tests/test_loader_pixbuf.c b/tests/test_loader_pixbuf.c
new file mode 100644
index 0000000..ba37522
--- /dev/null
+++ b/tests/test_loader_pixbuf.c
@@ -0,0 +1,163 @@
+/*:*
+ * ggaze — GdkPixbuf loader backend unit test
+ *
+ * Loads committed fixtures via loader_load() and asserts the resulting
+ * GdkTexture dimensions, including the rotated-EXIF case (decision #26): an
+ * 8x4 JPEG with Orientation=6 must load as 4x8 after
+ * gdk_pixbuf_apply_embedded_orientation. No display needed (texture creation
+ * from a pixbuf is headless). Fixture dir comes from $GGAZE_FIXTURES_DIR
+ * (set by meson). See ./sample-images for the optional realistic corpus.
+ *
+ * Copyright (c) 2026 ggaze contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *:*/
+
+#include "loader/loader.h"
+
+#include <gdk/gdk.h>
+#include <gio/gio.h>
+#include <glib.h>
+#include <unistd.h>
+
+static GdkTexture *
+load_fixture(const gchar *c_name) {
+ const gchar *c_dir = g_getenv("GGAZE_FIXTURES_DIR");
+ g_assert_nonnull(c_dir);
+ gchar *c_path = g_build_filename(c_dir, c_name, NULL);
+ GFile *p_file = g_file_new_for_path(c_path);
+ GError *p_err = NULL;
+ GdkTexture *p_tex = loader_load(p_file, NULL, &p_err);
+ g_assert_no_error(p_err);
+ g_object_unref(p_file);
+ g_free(c_path);
+ return (p_tex);
+}
+
+static void
+test_plain_jpeg(void) {
+ /* 6x3, Orientation = 1 -> 6x3 (no rotation applied). */
+ GdkTexture *p_tex = load_fixture("plain.jpg");
+ g_assert_cmpint(gdk_texture_get_width(p_tex), ==, 6);
+ g_assert_cmpint(gdk_texture_get_height(p_tex), ==, 3);
+ g_object_unref(p_tex);
+}
+
+static void
+test_rotated_exif_jpeg(void) {
+ /* 8x4, Orientation = 6 (rotate 90 CW) -> upright 4x8 (decision #26). */
+ GdkTexture *p_tex = load_fixture("rot6.jpg");
+ g_assert_cmpint(gdk_texture_get_width(p_tex), ==, 4);
+ g_assert_cmpint(gdk_texture_get_height(p_tex), ==, 8);
+ g_object_unref(p_tex);
+}
+
+static void
+test_png(void) {
+ /* 5x2 PNG, no orientation. */
+ GdkTexture *p_tex = load_fixture("small.png");
+ g_assert_cmpint(gdk_texture_get_width(p_tex), ==, 5);
+ g_assert_cmpint(gdk_texture_get_height(p_tex), ==, 2);
+ g_object_unref(p_tex);
+}
+
+static void
+test_missing_file_errors(void) {
+ const gchar *c_dir = g_getenv("GGAZE_FIXTURES_DIR");
+ g_assert_nonnull(c_dir);
+ gchar *c_path = g_build_filename(c_dir, "does-not-exist.jpg", NULL);
+ GFile *p_file = g_file_new_for_path(c_path);
+ GError *p_err = NULL;
+ GdkTexture *p_tex = loader_load(p_file, NULL, &p_err);
+ g_assert_null(p_tex);
+ g_assert_nonnull(p_err);
+ g_error_free(p_err);
+ g_object_unref(p_file);
+ g_free(c_path);
+}
+
+/* Write raw bytes to a temp file and load them (magic-byte / corrupt cases). */
+static GdkTexture *
+load_bytes(const guint8 *p_buf, gsize u_len, GError **p_err) {
+ gchar *c_path = NULL;
+ GError *p_sub = NULL;
+ gint i_fd = g_file_open_tmp("ggaze-XXXXXX", &c_path, &p_sub);
+ g_assert_no_error(p_sub);
+ g_assert_cmpint(i_fd, >=, 0);
+ gsize u_off = 0;
+ while (u_off < u_len) {
+ gssize n = write(i_fd, p_buf + u_off, u_len - u_off);
+ g_assert_cmpint(n, >, 0);
+ u_off += (gsize)n;
+ }
+ close(i_fd);
+ GFile *p_file = g_file_new_for_path(c_path);
+ GdkTexture *p_tex = loader_load(p_file, NULL, p_err);
+ g_object_unref(p_file);
+ unlink(c_path);
+ g_free(c_path);
+ return (p_tex);
+}
+
+static void
+assert_unsupported(const guint8 *p_buf, gsize u_len) {
+ GError *p_err = NULL;
+ GdkTexture *p_tex = load_bytes(p_buf, u_len, &p_err);
+ g_assert_null(p_tex);
+ g_assert_nonnull(p_err);
+ g_assert_cmpint(p_err->code, ==, G_IO_ERROR_NOT_SUPPORTED);
+ g_error_free(p_err);
+}
+
+static void
+test_unsupported_jxl(void) {
+ const guint8 h[] = {0xFF, 0x0A, 0x10, 0x00}; /* JXL codestream magic */
+ assert_unsupported(h, G_N_ELEMENTS(h));
+}
+
+static void
+test_unsupported_avif(void) {
+ const guint8 h[] = {0, 0, 0, 0, 'f', 't', 'y', 'p', 'a', 'v', 'i', 'f'};
+ assert_unsupported(h, G_N_ELEMENTS(h));
+}
+
+static void
+test_unsupported_heif(void) {
+ const guint8 h[] = {0, 0, 0, 0, 'f', 't', 'y', 'p', 'h', 'e', 'i', 'c'};
+ assert_unsupported(h, G_N_ELEMENTS(h));
+}
+
+static void
+test_corrupt_jpeg(void) {
+ /* Truncated JPEG: SOI + APP0 marker, no image data. GdkPixbuf fails. */
+ const guint8 h[] = {0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 'J', 'F',
+ 'I', 'F', 0, 1, 1, 0, 0, 0};
+ GError *p_err = NULL;
+ GdkTexture *p_tex = load_bytes(h, G_N_ELEMENTS(h), &p_err);
+ g_assert_null(p_tex);
+ g_assert_nonnull(p_err);
+ g_error_free(p_err);
+}
+
+static void
+test_rgba_png(void) {
+ /* 5x2 RGBA PNG -> exercises the has-alpha branch of texture_from_pixbuf. */
+ GdkTexture *p_tex = load_fixture("rgba.png");
+ g_assert_cmpint(gdk_texture_get_width(p_tex), ==, 5);
+ g_assert_cmpint(gdk_texture_get_height(p_tex), ==, 2);
+ g_object_unref(p_tex);
+}
+
+int
+main(int i_argc, char **c_argv) {
+ g_test_init(&i_argc, &c_argv, NULL);
+ g_test_add_func("/loader/pixbuf/plain_jpeg", test_plain_jpeg);
+ g_test_add_func("/loader/pixbuf/rotated_exif", test_rotated_exif_jpeg);
+ g_test_add_func("/loader/pixbuf/png", test_png);
+ g_test_add_func("/loader/pixbuf/missing_file", test_missing_file_errors);
+ g_test_add_func("/loader/pixbuf/unsupported_jxl", test_unsupported_jxl);
+ g_test_add_func("/loader/pixbuf/unsupported_avif", test_unsupported_avif);
+ g_test_add_func("/loader/pixbuf/unsupported_heif", test_unsupported_heif);
+ g_test_add_func("/loader/pixbuf/corrupt_jpeg", test_corrupt_jpeg);
+ g_test_add_func("/loader/pixbuf/rgba_png", test_rgba_png);
+ return (g_test_run());
+} \ No newline at end of file
diff --git a/tests/test_open_and_show.c b/tests/test_open_and_show.c
new file mode 100644
index 0000000..e707555
--- /dev/null
+++ b/tests/test_open_and_show.c
@@ -0,0 +1,138 @@
+/*:*
+ * ggaze — open-and-show integration test
+ *
+ * Exercises the window wiring: ggaze_window_open() loads a fixture via the
+ * loader, sets the viewer texture, and switches the stack to "large". Asserts
+ * the stack is on "large" and the viewer holds a texture of the right size.
+ *
+ * An optional second test opens an image from ./sample-images (the realistic
+ * local corpus, not git-tracked) and is skipped if $GGAZE_SAMPLE_DIR is unset
+ * or the directory is absent — so CI (which only has tests/fixtures/) stays
+ * green. Needs a display (integration suite; CI runs under xvfb).
+ *
+ * Copyright (c) 2026 ggaze contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *:*/
+
+#include "window.h"
+#include "viewer.h"
+
+#include <gdk/gdk.h>
+#include <gio/gio.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+
+static GFile *
+fixture_file(const gchar *c_name) {
+ const gchar *c_dir = g_getenv("GGAZE_FIXTURES_DIR");
+ g_assert_nonnull(c_dir);
+ gchar *c_path = g_build_filename(c_dir, c_name, NULL);
+ GFile *p_file = g_file_new_for_path(c_path);
+ g_free(c_path);
+ return (p_file);
+}
+
+static GgazeWindow *
+new_window(void) {
+ return (GGAZE_WINDOW(g_object_new(GGAZE_TYPE_WINDOW, NULL)));
+}
+
+static void
+assert_shown_large_with_dims(GgazeWindow *p_win, int i_w, int i_h) {
+ GtkWidget *p_child = gtk_window_get_child(GTK_WINDOW(p_win));
+ g_assert_true(GTK_IS_STACK(p_child));
+ g_assert_cmpstr(gtk_stack_get_visible_child_name(GTK_STACK(p_child)), ==,
+ "large");
+
+ GtkWidget *p_large =
+ gtk_stack_get_child_by_name(GTK_STACK(p_child), "large");
+ g_assert_true(GGAZE_IS_VIEWER(p_large));
+ GdkTexture *p_tex = ggaze_viewer_get_texture(GGAZE_VIEWER(p_large));
+ g_assert_nonnull(p_tex);
+ g_assert_cmpint(gdk_texture_get_width(p_tex), ==, i_w);
+ g_assert_cmpint(gdk_texture_get_height(p_tex), ==, i_h);
+}
+
+static void
+test_open_fixture_shows_large(void) {
+ GgazeWindow *p_win = new_window();
+ GFile *p_file = fixture_file("plain.jpg");
+ ggaze_window_open(p_win, p_file);
+ assert_shown_large_with_dims(p_win, 6, 3);
+ g_object_unref(p_file);
+ g_object_unref(p_win);
+}
+
+static void
+test_open_rotated_fixture(void) {
+ GgazeWindow *p_win = new_window();
+ GFile *p_file = fixture_file("rot6.jpg");
+ ggaze_window_open(p_win, p_file);
+ assert_shown_large_with_dims(p_win, 4, 8);
+ g_object_unref(p_file);
+ g_object_unref(p_win);
+}
+
+static void
+test_open_sample_image(void) {
+ const gchar *c_dir = g_getenv("GGAZE_SAMPLE_DIR");
+ if (c_dir == NULL || *c_dir == '\0') {
+ g_test_skip("GGAZE_SAMPLE_DIR unset (./sample-images not available)");
+ return;
+ }
+ if (!g_file_test(c_dir, G_FILE_TEST_IS_DIR)) {
+ g_test_skip("GGAZE_SAMPLE_DIR is not a directory");
+ return;
+ }
+ /* Pick the first JPEG in the corpus. */
+ GError *p_err = NULL;
+ GDir *p_dir = g_dir_open(c_dir, 0, &p_err);
+ g_assert_no_error(p_err);
+ const gchar *c_name = NULL;
+ while ((c_name = g_dir_read_name(p_dir)) != NULL) {
+ if (g_str_has_suffix(c_name, ".jpg") ||
+ g_str_has_suffix(c_name, ".JPG") ||
+ g_str_has_suffix(c_name, ".png")) {
+ break;
+ }
+ }
+ if (c_name == NULL) {
+ g_test_skip("no sample image found in corpus");
+ g_dir_close(p_dir);
+ return;
+ }
+ gchar *c_path = g_build_filename(c_dir, c_name, NULL);
+ g_dir_close(p_dir);
+
+ GgazeWindow *p_win = new_window();
+ GFile *p_file = g_file_new_for_path(c_path);
+ ggaze_window_open(p_win, p_file);
+ GtkWidget *p_child = gtk_window_get_child(GTK_WINDOW(p_win));
+ g_assert_cmpstr(gtk_stack_get_visible_child_name(GTK_STACK(p_child)), ==,
+ "large");
+ GtkWidget *p_large =
+ gtk_stack_get_child_by_name(GTK_STACK(p_child), "large");
+ GdkTexture *p_tex = ggaze_viewer_get_texture(GGAZE_VIEWER(p_large));
+ g_assert_nonnull(p_tex); /* loaded, whatever its dims */
+
+ g_object_unref(p_file);
+ g_object_unref(p_win);
+ g_free(c_path);
+}
+
+int
+main(int i_argc, char **c_argv) {
+ g_test_init(&i_argc, &c_argv, NULL);
+ /* Tolerate host GTK WARNINGs; keep CRITICALs fatal. */
+ 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("/open/fixture_shows_large", test_open_fixture_shows_large);
+ g_test_add_func("/open/rotated_fixture", test_open_rotated_fixture);
+ g_test_add_func("/open/sample_image", test_open_sample_image);
+ return (g_test_run());
+} \ No newline at end of file
diff --git a/tests/test_window.c b/tests/test_window.c
index 0570397..0f3fa74 100644
--- a/tests/test_window.c
+++ b/tests/test_window.c
@@ -43,6 +43,7 @@ test_stack_has_two_views(void) {
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_pages);
g_object_unref(p_win);
}