/* * test_geom.c -- display-free unit tests for wWindowConstrainSize / wWindowCropSize * * Links only against geom.o; no display connection required. * * Phase 5: verifies that WNormalHints field mapping produces the same * geometry constraints that XSizeHints did before the abstraction. */ #include "wconfig.h" #include #include /* * Pull in the real headers. We compile without a display but still need * the complete type definitions for WWindow, WScreen, WNormalHints. */ #include "screen.h" /* WScreen — safe without a display connection */ #include "window.h" /* WWindow, WNormalHints */ #include "geom.h" /* * Define the extern globals that geom.c references. * The test links standalone so it must provide these itself. */ struct WPreferences wPreferences; /* zero-init; set .icon_size in main() */ /* ------------------------------------------------------------------ */ /* Test infrastructure */ /* ------------------------------------------------------------------ */ static int tests_run = 0; static int tests_failed = 0; #define EXPECT_EQ(got, want, label) \ do { \ tests_run++; \ int _g = (int)(got), _w = (int)(want); \ if (_g != _w) { \ tests_failed++; \ fprintf(stderr, "FAIL %s: got %d want %d\n", label, _g, _w); \ } \ } while (0) /* Build a zero-initialised WWindow pointing at a fake WScreen */ static WScreen fake_scr; static WNormalHints fake_hints; static WWindow make_wwin(void) { WWindow w; memset(&w, 0, sizeof(w)); w.screen_ptr = &fake_scr; w.normal_hints = NULL; return w; } /* ------------------------------------------------------------------ */ /* Tests */ /* ------------------------------------------------------------------ */ /* 1. No hints: size passes through (clamped only by screen size * 2) */ static void test_no_hints(void) { WWindow w = make_wwin(); unsigned int W = 500, H = 400; wWindowConstrainSize(&w, &W, &H); EXPECT_EQ(W, 500, "no_hints width"); EXPECT_EQ(H, 400, "no_hints height"); } /* 2. Min size: request below minimum is clamped up */ static void test_min_size(void) { WWindow w = make_wwin(); memset(&fake_hints, 0, sizeof(fake_hints)); fake_hints.min_width = 200; fake_hints.min_height = 150; fake_hints.max_width = 4000; fake_hints.max_height = 3000; fake_hints.width_inc = 1; fake_hints.height_inc = 1; w.normal_hints = &fake_hints; unsigned int W = 100, H = 80; wWindowConstrainSize(&w, &W, &H); EXPECT_EQ(W, 200, "min_size width clamped up"); EXPECT_EQ(H, 150, "min_size height clamped up"); } /* 3a. Max size: only width over max → 4/3 ratio fallback */ static void test_max_size_width_only(void) { WWindow w = make_wwin(); memset(&fake_hints, 0, sizeof(fake_hints)); fake_hints.min_width = 1; fake_hints.min_height = 1; fake_hints.max_width = 300; fake_hints.max_height = 10000; /* high enough not to trigger */ fake_hints.width_inc = 1; fake_hints.height_inc = 1; w.normal_hints = &fake_hints; unsigned int W = 400, H = 300; /* W > maxW, H < maxH */ wWindowConstrainSize(&w, &W, &H); /* Expect W = H * 4/3 = 300 * 4/3 = 400, then clamped to maxW=300 */ /* Actually: code sets width = height * 4/3 = 400, but then no further * clamp for width > maxW in this branch. Per the code as written, * the result is width = 400 still (4/3 of 300), which is > maxW. * This tests the code's actual documented behaviour. */ EXPECT_EQ(W, 400, "max_width only: 4/3 ratio applied"); EXPECT_EQ(H, 300, "max_width only: height unchanged"); } /* 3b. Max size: only height over max → 3/4 ratio fallback */ static void test_max_size_height_only(void) { WWindow w = make_wwin(); memset(&fake_hints, 0, sizeof(fake_hints)); fake_hints.min_width = 1; fake_hints.min_height = 1; fake_hints.max_width = 10000; fake_hints.max_height = 300; fake_hints.width_inc = 1; fake_hints.height_inc = 1; w.normal_hints = &fake_hints; unsigned int W = 400, H = 500; /* H > maxH, W < maxW */ wWindowConstrainSize(&w, &W, &H); EXPECT_EQ(H, 400*3/4, "max_height only: 3/4 ratio applied"); EXPECT_EQ(W, 400, "max_height only: width unchanged"); } /* 3c. Both dimensions over max → icon_size fallback */ static void test_max_size_both(void) { WWindow w = make_wwin(); memset(&fake_hints, 0, sizeof(fake_hints)); fake_hints.min_width = 1; fake_hints.min_height = 1; fake_hints.max_width = 200; fake_hints.max_height = 200; fake_hints.width_inc = 1; fake_hints.height_inc = 1; w.normal_hints = &fake_hints; wPreferences.icon_size = 32; unsigned int W = 500, H = 500; wWindowConstrainSize(&w, &W, &H); /* Both over max → use scr_width/scr_height - 2*icon_size */ int exp_w = fake_scr.scr_width - 2 * wPreferences.icon_size; int exp_h = fake_scr.scr_height - 2 * wPreferences.icon_size; EXPECT_EQ((int)W, exp_w, "both_over_max width"); EXPECT_EQ((int)H, exp_h, "both_over_max height"); } /* 4a. Size increment: snapping to increment grid without base size */ static void test_size_inc_no_base(void) { WWindow w = make_wwin(); memset(&fake_hints, 0, sizeof(fake_hints)); fake_hints.min_width = 80; fake_hints.min_height = 24; fake_hints.max_width = 4000; fake_hints.max_height = 3000; fake_hints.width_inc = 8; /* e.g. terminal columns */ fake_hints.height_inc = 13; /* e.g. terminal rows */ fake_hints.base_width = 0; fake_hints.base_height = 0; w.normal_hints = &fake_hints; /* Request 100×50: no base, so snaps to minW + k*inc */ /* width: (100 - 80) / 8 * 8 + 80 = 2*8 + 80 = 96 */ /* height: (50 - 24) / 13 * 13 + 24 = 2*13 + 24 = 50 */ unsigned int W = 100, H = 50; wWindowConstrainSize(&w, &W, &H); EXPECT_EQ(W, 96, "inc_no_base width snapped"); EXPECT_EQ(H, 50, "inc_no_base height snapped"); } /* 4b. Size increment: snapping to increment grid WITH base size */ static void test_size_inc_with_base(void) { WWindow w = make_wwin(); memset(&fake_hints, 0, sizeof(fake_hints)); fake_hints.min_width = 80; fake_hints.min_height = 24; fake_hints.max_width = 4000; fake_hints.max_height = 3000; fake_hints.width_inc = 7; fake_hints.height_inc = 14; fake_hints.base_width = 4; /* non-zero base */ fake_hints.base_height = 4; w.normal_hints = &fake_hints; /* Request 110×60 with base 4, inc 7/14: * width: (110 - 4) / 7 * 7 + 4 = 15*7 + 4 = 109 * height: (60 - 4) / 14 * 14 + 4 = 4*14 + 4 = 60 */ unsigned int W = 110, H = 60; wWindowConstrainSize(&w, &W, &H); EXPECT_EQ(W, 109, "inc_with_base width snapped"); EXPECT_EQ(H, 60, "inc_with_base height snapped"); } /* 5a. Aspect ratio: max aspect enforcement (too wide) */ static void test_aspect_max(void) { WWindow w = make_wwin(); memset(&fake_hints, 0, sizeof(fake_hints)); fake_hints.flags = WNH_P_ASPECT; fake_hints.min_width = 1; fake_hints.min_height = 1; fake_hints.max_width = 10000; fake_hints.max_height = 10000; fake_hints.width_inc = 1; fake_hints.height_inc = 1; /* max aspect 4:3 — window must not be wider than 4/3 its height */ fake_hints.min_aspect.x = 1; fake_hints.min_aspect.y = 1; fake_hints.max_aspect.x = 4; fake_hints.max_aspect.y = 3; w.normal_hints = &fake_hints; /* Request 800×300 (ratio 8:3, too wide for 4:3 max) */ /* Expected: height = width*maxAY/maxAX = 800*3/4 = 600 */ unsigned int W = 800, H = 300; wWindowConstrainSize(&w, &W, &H); EXPECT_EQ(W, 800, "max_aspect width unchanged"); EXPECT_EQ(H, 600, "max_aspect height adjusted"); } /* 5b. Aspect ratio: min aspect enforcement (too tall) */ static void test_aspect_min(void) { WWindow w = make_wwin(); memset(&fake_hints, 0, sizeof(fake_hints)); fake_hints.flags = WNH_P_ASPECT; fake_hints.min_width = 1; fake_hints.min_height = 1; fake_hints.max_width = 10000; fake_hints.max_height = 10000; fake_hints.width_inc = 1; fake_hints.height_inc = 1; /* min aspect 4:3 — window must not be taller than 3/4 its width */ fake_hints.min_aspect.x = 4; fake_hints.min_aspect.y = 3; fake_hints.max_aspect.x = 4; fake_hints.max_aspect.y = 3; w.normal_hints = &fake_hints; /* Request 300×800 (ratio 3:8, too tall for min 4:3) */ /* code path: minAX(4) > minAY(3), so * height = width*minAY/minAX = 300*3/4 = 225 */ unsigned int W = 300, H = 800; wWindowConstrainSize(&w, &W, &H); EXPECT_EQ(W, 300, "min_aspect width unchanged"); EXPECT_EQ(H, 225, "min_aspect height adjusted"); } /* 6. wWindowCropSize: crops each dimension independently */ static void test_crop_size(void) { WWindow w = make_wwin(); memset(&fake_hints, 0, sizeof(fake_hints)); fake_hints.width_inc = 8; fake_hints.height_inc = 13; fake_hints.base_width = 4; fake_hints.base_height = 4; w.normal_hints = &fake_hints; /* maxW=300, maxH=200 */ /* width: 300 - (300-4) % 8 = 300 - 296%8 = 300 - 0 = 300 */ /* height: 200 - (200-4) % 13 = 200 - 196%13 = 200 - 196%13 */ /* 196 / 13 = 15 rem 1, so 200 - 1 = 199 */ unsigned int W = 500, H = 400; wWindowCropSize(&w, 300, 200, &W, &H); EXPECT_EQ(W, 300, "crop width at boundary"); EXPECT_EQ(H, 199, "crop height snapped to inc"); } /* ------------------------------------------------------------------ */ /* main */ /* ------------------------------------------------------------------ */ int main(void) { /* Set screen size used as default max */ memset(&fake_scr, 0, sizeof(fake_scr)); fake_scr.scr_width = 1920; fake_scr.scr_height = 1080; wPreferences.icon_size = 64; /* default; overridden per test if needed */ test_no_hints(); test_min_size(); test_max_size_width_only(); test_max_size_height_only(); test_max_size_both(); test_size_inc_no_base(); test_size_inc_with_base(); test_aspect_max(); test_aspect_min(); test_crop_size(); if (tests_failed) fprintf(stderr, "FAILED %d/%d\n", tests_failed, tests_run); else printf("PASS (%d tests)\n", tests_run); return tests_failed ? 1 : 0; }