subtree pull sdk 来自主仓 pub/sdk(主仓 commit 68ce46a 等)。新增: - 布局引擎 meow_ui_box / 动画基座 meow_ui_anim / 滚动物理 meow_ui_scroll - §6.4 事件分发形式化 / W_LISTVIEW 虚拟化多行 / meow_str 字符串资源 i18n - sb_icons 动作图标扩充 + topbar 位图图标 / meow_img_downscale - tools/ 图标生成脚本与 SVG 源 - 对应宿主 test_*.c 与 demo_box_scroll.c build_sdk.sh 冲突解析:保留公开仓自包含版(vendored meow_font + -Iinclude -I.), OBJS 手工并入 4 个新模块(meow_ui_box/anim/scroll/str)。保留 vendored meow_font.c/ meow_ipc.h/meow_font.h/meow_notif.h,剔除宿主测试二进制与编译产物。
175 lines
9.1 KiB
C
175 lines
9.1 KiB
C
/*
|
||
* test_meow_ui_str.c — meow_str_* 宿主单测(2026-09-02,§13.i18n 雏形)。
|
||
*
|
||
* 编译(宿主,meow_ui_str 为纯 libc,无需 SDK 其它对象):
|
||
* gcc -Wall -Wextra -o test_meow_ui_str test_meow_ui_str.c meow_ui_str.c -I.
|
||
*
|
||
* 两种模式(覆盖表按 locale 懒加载,分两次跑):
|
||
* ./test_meow_ui_str tmp → unsetenv MEOW_DATA_DIR,仅验证内嵌双语表(无覆盖表)
|
||
* ./test_meow_ui_str env → 设 MEOW_DATA_DIR=<mkdtemp>/data,写 <mkdtemp>/strings/en_us.txt,
|
||
* 验证 TF 覆盖表优先级(覆盖>内嵌)+ locale 即时生效
|
||
*
|
||
* 覆盖矩阵:
|
||
* T1 内嵌回退(默认 zh_cn):common.ok/loading/no_data 等取 zh
|
||
* T2 切 locale 即时生效:set_locale(en_us) → 内嵌 en;set_locale(zh_cn) → 内嵌 zh(无需重初始化)
|
||
* T3 未知 id 回退:meow_str("nope.missing") 原样返回 id;meow_str(NULL) 返回 ""
|
||
* T4 TF 覆盖优先级:override > 内嵌当前语言;未覆盖 id 回落内嵌;自定义 id 命中
|
||
* T5 查找正确性:一批 id 的 zh/en 值逐一断言
|
||
* T6 meow_strf:取串并按 snprintf 语义写调用方缓冲(与 meow_str 一致)
|
||
*
|
||
* 注:set_locale 的 ui.locale 持久化目标是板载 /usr/data/meow/settings.conf,宿主无该目录
|
||
* → 静默失败(运行时仍切换,仅不跨启动);不影响本测试断言。
|
||
*/
|
||
#include "meow_ui.h"
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <sys/stat.h>
|
||
|
||
static int g_fail = 0;
|
||
#define CHECK(cond, msg) do { \
|
||
if (cond) { printf(" PASS: %s\n", msg); } \
|
||
else { printf(" FAIL: %s\n", msg); g_fail = 1; } \
|
||
} while (0)
|
||
|
||
/* 找内嵌某 id 的 zh/en 期望值(供 T5 逐项对照)。不在表内返回 NULL。 */
|
||
static const char *expect(const char *id, int en)
|
||
{
|
||
/* 与 meow_ui_str.c 的 g_embed[] 对齐(此处只列 T5 抽查的子集) */
|
||
static const struct { const char *id; const char *zh; const char *en; } t[] = {
|
||
{ "common.ok", "确定", "OK" },
|
||
{ "common.cancel", "取消", "Cancel" },
|
||
{ "common.back", "返回", "Back" },
|
||
{ "common.confirm", "确认", "Confirm" },
|
||
{ "common.loading", "加载中...", "Loading..." },
|
||
{ "common.no_data", "暂无数据", "No data" },
|
||
{ "common.retry_fail", "重试失败", "Retry failed" },
|
||
{ "common.tap_ok_edit", "点击确定编辑", "tap OK to edit..." },
|
||
{ "ime.space", "空格", "Space" },
|
||
{ "ime.enter", "确定", "OK" },
|
||
{ "ime.esc", "取消", "Cancel" },
|
||
{ "ime.clear", "清", "Clear" },
|
||
{ "ime.pinyin_mode", "拼音", "Pinyin" },
|
||
{ "ime.no_candidate", "(无候选)", "(none)" },
|
||
};
|
||
for (size_t i = 0; i < sizeof t / sizeof t[0]; i++)
|
||
if (strcmp(t[i].id, id) == 0) return en ? t[i].en : t[i].zh;
|
||
return NULL;
|
||
}
|
||
|
||
int main(int argc, char **argv)
|
||
{
|
||
const char *mode = (argc > 1) ? argv[1] : "tmp";
|
||
char *tmpdir = NULL;
|
||
|
||
if (strcmp(mode, "env") == 0) {
|
||
/* 建 <mkdtemp>/data + <mkdtemp>/strings/en_us.txt(meow_str 由 MEOW_DATA_DIR 去尾 /data
|
||
* 得 <mkdtemp>/,再拼 strings/<locale>.txt)。 */
|
||
tmpdir = strdup("/tmp/meowstrtestXXXXXX");
|
||
if (!mkdtemp(tmpdir)) { printf("FATAL: mkdtemp failed\n"); return 2; }
|
||
char datadir[512], strdir[512];
|
||
snprintf(datadir, sizeof datadir, "%s/data", tmpdir);
|
||
snprintf(strdir, sizeof strdir, "%s/strings", tmpdir);
|
||
mkdir(datadir, 0755);
|
||
mkdir(strdir, 0755);
|
||
char file[512];
|
||
snprintf(file, sizeof file, "%s/strings/en_us.txt", tmpdir);
|
||
FILE *f = fopen(file, "w");
|
||
if (!f) { printf("FATAL: cannot write override file\n"); free(tmpdir); return 2; }
|
||
/* 混合:正常行 / '=' 两侧带空格 / 空行 / 无 '=' 行(应被跳过) */
|
||
fprintf(f, "# comment line (no equals)\n");
|
||
fprintf(f, "common.ok=确认OK\n");
|
||
fprintf(f, "common.custom=自定义覆盖\n");
|
||
fprintf(f, "common.loading = 加载中覆盖\n");
|
||
fprintf(f, "badline-without-equals\n");
|
||
fprintf(f, "\n");
|
||
fclose(f);
|
||
setenv("MEOW_DATA_DIR", datadir, 1);
|
||
} else {
|
||
unsetenv("MEOW_DATA_DIR");
|
||
}
|
||
|
||
printf("== meow_str test [%s] ==\n", mode);
|
||
printf(" locale = %s\n", meow_str_locale());
|
||
|
||
/* --- T1 内嵌回退(默认 zh_cn,无覆盖表/无 zh_cn.txt)--- */
|
||
CHECK(strcmp(meow_str("common.ok"), "确定") == 0, "T1 zh common.ok = 确定");
|
||
CHECK(strcmp(meow_str("common.cancel"), "取消") == 0, "T1 zh common.cancel = 取消");
|
||
CHECK(strcmp(meow_str("common.loading"), "加载中...") == 0, "T1 zh common.loading = 加载中...");
|
||
CHECK(strcmp(meow_str("common.no_data"), "暂无数据") == 0, "T1 zh common.no_data = 暂无数据");
|
||
CHECK(strcmp(meow_str("ime.space"), "空格") == 0, "T1 zh ime.space = 空格");
|
||
|
||
/* --- T3 未知 id 回退(永不 NULL)--- */
|
||
CHECK(strcmp(meow_str("nope.missing"), "nope.missing") == 0, "T3 未知 id 原样返回 id");
|
||
CHECK(strcmp(meow_str(NULL), "") == 0, "T3 meow_str(NULL) = \"\"");
|
||
|
||
/* --- T6 meow_strf:与 meow_str 一致,snprintf 语义 --- */
|
||
{
|
||
char buf[128];
|
||
int r = meow_strf(buf, sizeof buf, "common.ok");
|
||
CHECK(r == (int)strlen("确定") && strcmp(buf, "确定") == 0, "T6 meow_strf zh common.ok = 确定");
|
||
meow_strf(buf, sizeof buf, "common.no_data");
|
||
CHECK(strcmp(buf, meow_str("common.no_data")) == 0, "T6 meow_strf == meow_str");
|
||
CHECK(meow_strf(NULL, 10, "common.ok") == -1, "T6 meow_strf(out=NULL) = -1");
|
||
CHECK(meow_strf(buf, 0, "common.ok") == -1, "T6 meow_strf(outsz=0) = -1");
|
||
}
|
||
|
||
/* --- T2 切 locale 即时生效(无需重初始化)---
|
||
* 用「未在覆盖表」的 id 证明 locale 即时切换(common.ok 等在 env 会被覆盖,留给 T4 验覆盖路径)。 */
|
||
meow_str_set_locale("en_us");
|
||
CHECK(strcmp(meow_str_locale(), "en_us") == 0, "T2 locale = en_us");
|
||
CHECK(strcmp(meow_str("common.cancel"), "Cancel") == 0, "T2 en common.cancel = Cancel");
|
||
CHECK(strcmp(meow_str("common.no_data"), "No data") == 0, "T2 en common.no_data = No data");
|
||
CHECK(strcmp(meow_str("ime.space"), "Space") == 0, "T2 en ime.space = Space");
|
||
|
||
/* --- T4 TF 覆盖优先级(仅 env 模式)--- */
|
||
if (strcmp(mode, "env") == 0) {
|
||
/* ① 覆盖内嵌当前语言(en_us*下 common.ok 应是覆盖值"确认OK"而非内嵌"OK") */
|
||
CHECK(strcmp(meow_str("common.ok"), "确认OK") == 0, "T4 @en override 覆盖 common.ok");
|
||
/* ② 覆盖自定义 id(内嵌无 → 覆盖命中) */
|
||
CHECK(strcmp(meow_str("common.custom"), "自定义覆盖") == 0, "T4 @en override 自定义 id");
|
||
/* ③ '=' 两侧带空格也可解析 */
|
||
CHECK(strcmp(meow_str("common.loading"), "加载中覆盖") == 0, "T4 @en override 带空格行");
|
||
/* ④ 未覆盖 id 回落内嵌当前语言 */
|
||
CHECK(strcmp(meow_str("common.cancel"), "Cancel") == 0, "T4 @en 未覆盖 → 内嵌 en");
|
||
CHECK(strcmp(meow_str("ime.clear"), "Clear") == 0, "T4 @en ime.clear 未覆盖 → 内嵌 en");
|
||
/* ⑤ 自定义 id 在 zh(无 zh_cn.txt,覆盖表清空)→ 内嵌无 → 原样返回 id */
|
||
meow_str_set_locale("zh_cn");
|
||
CHECK(strcmp(meow_str("common.ok"), "确定") == 0, "T4 @zh 覆盖清除 → 内嵌 zh");
|
||
CHECK(strcmp(meow_str("common.custom"), "common.custom") == 0, "T4 @zh 自定义 id 原样回退");
|
||
meow_str_set_locale("en_us"); /* 回到 en 供 T5 */
|
||
}
|
||
|
||
/* --- T5 查找正确性:一批 id 的 zh/en 逐一断言(仅 tmp 模式——无覆盖干扰,
|
||
* 才能验证内嵌表本身;env 模式的内嵌 vs 覆盖已由 T2/T4 覆盖,此处跳过多余断言)--- */
|
||
if (strcmp(mode, "tmp") == 0) {
|
||
const char *ids[] = { "common.ok", "common.cancel", "common.back", "common.confirm",
|
||
"common.loading", "common.no_data", "common.retry_fail",
|
||
"common.tap_ok_edit", "ime.space", "ime.enter", "ime.esc",
|
||
"ime.clear", "ime.pinyin_mode", "ime.no_candidate" };
|
||
meow_str_set_locale("zh_cn");
|
||
for (size_t i = 0; i < sizeof ids / sizeof ids[0]; i++) {
|
||
if (expect(ids[i], 0)) {
|
||
char msg[128];
|
||
snprintf(msg, sizeof msg, "T5 zh %s == 期望", ids[i]);
|
||
CHECK(strcmp(meow_str(ids[i]), expect(ids[i], 0)) == 0, msg);
|
||
}
|
||
}
|
||
meow_str_set_locale("en_us");
|
||
for (size_t i = 0; i < sizeof ids / sizeof ids[0]; i++) {
|
||
if (expect(ids[i], 1)) {
|
||
char msg[128];
|
||
snprintf(msg, sizeof msg, "T5 en %s == 期望", ids[i]);
|
||
CHECK(strcmp(meow_str(ids[i]), expect(ids[i], 1)) == 0, msg);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (tmpdir) free(tmpdir);
|
||
if (g_fail) { printf("\nRESULT: FAIL (%d)\n", g_fail); return 1; }
|
||
printf("\nRESULT: ALL PASS\n");
|
||
return 0;
|
||
}
|