/* test_webtoon_strip.c — 条漫排版/滚动窗口纯逻辑宿主测试(无 SDK/硬件)。 * * 覆盖: * W1 单页:高度=适宽、总高、max_offset=0、窗口只有本页、顶/底同时成立; * W2 多页:按页高累加 offsets;page_at 二分定位(页边界/末页/越界); * W3 可见窗口:视口±1 页;max_pages 截断;条底/条顶窗口不越界; * W4 尺寸兜底:未知 meta → 2:3 占位;横图适宽;极端长图夹到上限; * W5 参数/生命周期:空册、非法视口、重复 free、重建; * W6 跳页落位:offset_for_page 与 page_at 互逆(含末页 max 夹取)。 * * 编译(在 tuku/ 下): * gcc -Wall -Wextra -O1 -I. test/test_webtoon_strip.c logic/reader/nav.c \ * -o test/test_webtoon_strip && ./test/test_webtoon_strip * 或 test/run_webtoon_tests.sh。 */ #include "logic/reader/nav.h" #include #include static int g_pass, g_fail; #define CHECK(cond, ...) do { \ if (cond) { g_pass++; } \ else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \ } while (0) #define VW 480 #define VH 720 typedef struct fake_meta { int n; int w[16]; int h[16]; int known[16]; } fake_meta; static int meta_cb(void *ctx, int i, int *w, int *h) { fake_meta *m = (fake_meta *)ctx; if (!m || i < 0 || i >= m->n) return 0; *w = m->w[i]; *h = m->h[i]; return m->known[i]; } static void meta_page(fake_meta *m, int i, int w, int h, int known) { m->w[i] = w; m->h[i] = h; m->known[i] = known; if (i + 1 > m->n) m->n = i + 1; } static void test_single_page(void) { fake_meta m; memset(&m, 0, sizeof m); meta_page(&m, 0, 800, 1200, 1); mr_strip s; memset(&s, 0, sizeof s); CHECK(mr_strip_build(&s, 1, VW, VH, meta_cb, &m) == 0, "build single"); CHECK(s.count == 1, "count=%d", s.count); CHECK(mr_strip_page_h(&s, 0) == 720, "single h=%d", mr_strip_page_h(&s, 0)); CHECK(mr_strip_total(&s) == 720, "single total=%d", mr_strip_total(&s)); CHECK(mr_strip_max_offset(&s) == 0, "single max=%d", mr_strip_max_offset(&s)); CHECK(mr_strip_page_at(&s, 0) == 0, "single page at 0"); CHECK(mr_strip_page_at(&s, 719) == 0, "single page at 719"); CHECK(mr_strip_page_at(&s, 720) == 0, "single page at total clamps"); CHECK(mr_strip_at_end(&s, 0) == 1, "single at end"); CHECK(mr_strip_at_start(&s, 0) == 1, "single at start"); int f = -1, l = -1; CHECK(mr_strip_window(&s, 0, 4, &f, &l) == 1 && f == 0 && l == 0, "single window %d..%d", f, l); CHECK(mr_strip_offset_for_page(&s, 0) == 0, "single offset page0"); CHECK(mr_strip_offset_for_page(&s, 9) == 0, "single offset OOB clamps"); mr_strip_free(&s); } static void test_multi_pages(void) { fake_meta m; memset(&m, 0, sizeof m); /* 0: 800x1200 -> 720;1: 未知 -> 720 占位;2: 横图 1500x1000 -> 320; * 3: 方图 400x400 -> 480;4: 极端长图 1x2000 -> 夹到 200000。 */ meta_page(&m, 0, 800, 1200, 1); meta_page(&m, 1, 0, 0, 0); meta_page(&m, 2, 1500, 1000, 1); meta_page(&m, 3, 400, 400, 1); meta_page(&m, 4, 1, 2000, 1); mr_strip s; memset(&s, 0, sizeof s); CHECK(mr_strip_build(&s, 5, VW, VH, meta_cb, &m) == 0, "build multi"); CHECK(mr_strip_page_h(&s, 0) == 720, "h0=%d", mr_strip_page_h(&s, 0)); CHECK(mr_strip_page_h(&s, 1) == 720, "h1(unknown)=%d", mr_strip_page_h(&s, 1)); CHECK(mr_strip_page_h(&s, 2) == 320, "h2(landscape)=%d", mr_strip_page_h(&s, 2)); CHECK(mr_strip_page_h(&s, 3) == 480, "h3(square)=%d", mr_strip_page_h(&s, 3)); CHECK(mr_strip_page_h(&s, 4) == MR_STRIP_MAX_PAGE_H, "h4(extreme)=%d", mr_strip_page_h(&s, 4)); CHECK(mr_strip_top(&s, 0) == 0, "top0=%d", mr_strip_top(&s, 0)); CHECK(mr_strip_top(&s, 1) == 720, "top1=%d", mr_strip_top(&s, 1)); CHECK(mr_strip_top(&s, 2) == 1440, "top2=%d", mr_strip_top(&s, 2)); CHECK(mr_strip_top(&s, 3) == 1760, "top3=%d", mr_strip_top(&s, 3)); CHECK(mr_strip_top(&s, 4) == 2240, "top4=%d", mr_strip_top(&s, 4)); CHECK(mr_strip_top(&s, 5) == 202240, "top[count]=%d", mr_strip_top(&s, 5)); CHECK(mr_strip_total(&s) == 202240, "total=%d", mr_strip_total(&s)); CHECK(mr_strip_max_offset(&s) == 201520, "max=%d", mr_strip_max_offset(&s)); CHECK(mr_strip_page_at(&s, 0) == 0, "page_at 0"); CHECK(mr_strip_page_at(&s, 719) == 0, "page_at 719"); CHECK(mr_strip_page_at(&s, 720) == 1, "page_at 720"); CHECK(mr_strip_page_at(&s, 1439) == 1, "page_at 1439"); CHECK(mr_strip_page_at(&s, 1440) == 2, "page_at 1440"); CHECK(mr_strip_page_at(&s, 1759) == 2, "page_at 1759"); CHECK(mr_strip_page_at(&s, 1760) == 3, "page_at 1760"); CHECK(mr_strip_page_at(&s, 2239) == 3, "page_at 2239"); CHECK(mr_strip_page_at(&s, 2240) == 4, "page_at 2240"); CHECK(mr_strip_page_at(&s, 9999999) == 4, "page_at huge -> last"); CHECK(mr_strip_page_at(&s, -50) == 0, "page_at negative -> first"); CHECK(mr_strip_page_at(&s, 0) == 0 && mr_strip_page_at(NULL, 0) == -1, "page_at NULL"); /* 视口窗口:off=0 只看得到页 0(页 1 从 720 开始)→ ±1 = [0,1] */ int f = -1, l = -1; CHECK(mr_strip_window(&s, 0, 4, &f, &l) == 2 && f == 0 && l == 1, "window top %d..%d", f, l); /* off=1000:可见页 1..2 → 窗口 [0,3] */ CHECK(mr_strip_window(&s, 1000, 4, &f, &l) == 4 && f == 0 && l == 3, "window mid %d..%d", f, l); /* off=max:可见页 4 → 窗口 [3,4] */ CHECK(mr_strip_window(&s, 201520, 4, &f, &l) == 2 && f == 3 && l == 4, "window bottom %d..%d", f, l); /* 只用 1 个槽位:窗口收缩到焦点页 */ CHECK(mr_strip_window(&s, 1000, 1, &f, &l) == 1 && f >= 1 && f <= 2 && f == l, "window max_pages=1 %d..%d", f, l); CHECK(mr_strip_window(&s, -100, 4, &f, &l) == 2 && f == 0 && l == 1, "window negative off %d..%d", f, l); CHECK(mr_strip_at_end(&s, 0) == 0, "not at end at 0"); CHECK(mr_strip_at_end(&s, 201519) == 0, "not at end just before max"); CHECK(mr_strip_at_end(&s, 201520) == 1, "at end at max"); CHECK(mr_strip_at_start(&s, 0) == 1, "at start 0"); CHECK(mr_strip_at_start(&s, 1) == 0, "not at start 1"); /* 跳页落位:每页 offset 回读一致(末页夹到 max) */ for (int i = 0; i < 5; i++) { int off = mr_strip_offset_for_page(&s, i); int back = mr_strip_page_at(&s, off); CHECK(back == i, "roundtrip page %d -> off %d -> page %d", i, off, back); } CHECK(mr_strip_offset_for_page(&s, 4) == 2240, "last page offset = its top"); /* 短册(内容不足一屏):任何页都夹到 max=0 */ fake_meta m2; memset(&m2, 0, sizeof m2); meta_page(&m2, 0, 800, 300, 1); /* 高 180 */ meta_page(&m2, 1, 800, 300, 1); mr_strip s2; memset(&s2, 0, sizeof s2); CHECK(mr_strip_build(&s2, 2, VW, VH, meta_cb, &m2) == 0, "build short"); CHECK(mr_strip_total(&s2) == 360 && mr_strip_max_offset(&s2) == 0, "short totals"); CHECK(mr_strip_offset_for_page(&s2, 1) == 0, "short last page clamps to 0"); CHECK(mr_strip_at_end(&s2, 0) == 1 && mr_strip_at_start(&s2, 0) == 1, "short edges"); mr_strip_free(&s2); /* 越界参数 */ CHECK(mr_strip_page_h(&s, -1) == 0 && mr_strip_page_h(&s, 5) == 0, "page_h OOB"); CHECK(mr_strip_top(&s, -1) == 0 && mr_strip_top(&s, 6) == 0, "top OOB"); mr_strip_free(&s); CHECK(s.top == NULL && s.height == NULL && s.count == 0, "free clears"); mr_strip_free(&s); } static void test_build_contract(void) { fake_meta m; memset(&m, 0, sizeof m); meta_page(&m, 0, 800, 1200, 1); mr_strip s; memset(&s, 0, sizeof s); CHECK(mr_strip_build(NULL, 1, VW, VH, meta_cb, &m) == -1, "NULL strip"); CHECK(mr_strip_build(&s, 0, VW, VH, meta_cb, &m) == -1, "count 0 -> -1"); CHECK(s.top == NULL && s.count == 0, "failed build clears"); CHECK(mr_strip_build(&s, 1, 0, VH, meta_cb, &m) == -1, "vw 0 -> -1"); CHECK(mr_strip_build(&s, 1, VW, 0, meta_cb, &m) == -1, "vh 0 -> -1"); /* meta 回调为 NULL → 全部按 2:3 占位 */ CHECK(mr_strip_build(&s, 2, VW, VH, NULL, NULL) == 0, "NULL meta build"); CHECK(mr_strip_page_h(&s, 0) == 720 && mr_strip_page_h(&s, 1) == 720, "NULL meta fallback heights"); mr_strip_free(&s); /* meta 返回失败(0)也按占位处理 */ CHECK(mr_strip_build(&s, 1, VW, VH, meta_cb, &m) == 0, "rebuild after free"); CHECK(mr_strip_page_h(&s, 0) == 720, "rebuild height"); mr_strip_free(&s); /* 空排版查询全部安全 */ mr_strip e; memset(&e, 0, sizeof e); CHECK(mr_strip_total(&e) == 0 && mr_strip_max_offset(&e) == 0, "empty totals"); CHECK(mr_strip_page_at(&e, 0) == -1, "empty page_at"); CHECK(mr_strip_offset_for_page(&e, 0) == -1, "empty offset_for_page"); int f = 9, l = 9; CHECK(mr_strip_window(&e, 0, 4, &f, &l) == 0 && l == -1, "empty window"); CHECK(mr_strip_at_end(&e, 0) == 0 && mr_strip_at_start(&e, 0) == 0, "empty edges"); } int main(void) { test_single_page(); test_multi_pages(); test_build_contract(); printf("%s: %d passed, %d failed\n", g_fail ? "FAIL" : "PASS", g_pass, g_fail); return g_fail ? 1 : 0; }