Files
DTU-HMI/tests/test_p1_page_manager.c

293 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <string.h>
#include "test_common.h"
#include "../src/Drv/key.h"
#include "../src/Drv/pages/page_manager.h"
/* -------------------------------------------------------------------------
* 结构体名: test_page_ctx_t
* 功能:
* 测试页面私有上下文,用于统计各生命周期回调与事件处理的触发次数。
*
* 字段说明:
* createCount - on_create 被调用次数
* enterCount - on_enter 被调用次数
* exitCount - on_exit 被调用次数
* destroyCount - on_destroy 被调用次数
* loopCount - on_loop 被调用次数
* eventResult - on_event 预设返回值(用于控制是否触发全局兜底)
* ------------------------------------------------------------------------- */
typedef struct
{
int createCount;
int enterCount;
int exitCount;
int destroyCount;
int loopCount;
event_result_t eventResult;
} test_page_ctx_t;
static test_page_ctx_t g_ctx_a;
static test_page_ctx_t g_ctx_b;
/* A 页生命周期/事件回调桩函数:将调用痕迹写入 page->model 对应的计数器 */
static void on_create_a(page_t *page) { ((test_page_ctx_t *)page->model)->createCount++; }
static void on_enter_a(page_t *page) { ((test_page_ctx_t *)page->model)->enterCount++; }
static void on_exit_a(page_t *page) { ((test_page_ctx_t *)page->model)->exitCount++; }
static void on_destroy_a(page_t *page) { ((test_page_ctx_t *)page->model)->destroyCount++; }
static event_result_t on_event_a(page_t *page, input_event_t *event)
{
(void)event;
return ((test_page_ctx_t *)page->model)->eventResult;
}
static void on_loop_a(page_t *page) { ((test_page_ctx_t *)page->model)->loopCount++; }
/* B 页生命周期/事件回调桩函数:用于验证多页切换与销毁链路 */
static void on_create_b(page_t *page) { ((test_page_ctx_t *)page->model)->createCount++; }
static void on_enter_b(page_t *page) { ((test_page_ctx_t *)page->model)->enterCount++; }
static void on_exit_b(page_t *page) { ((test_page_ctx_t *)page->model)->exitCount++; }
static void on_destroy_b(page_t *page) { ((test_page_ctx_t *)page->model)->destroyCount++; }
static event_result_t on_event_b(page_t *page, input_event_t *event)
{
(void)event;
return ((test_page_ctx_t *)page->model)->eventResult;
}
static void on_loop_b(page_t *page) { ((test_page_ctx_t *)page->model)->loopCount++; }
static void setup_page(page_t *page,
page_id_t pageId,
uint8_t isCached,
test_page_ctx_t *ctx,
void (*on_create)(page_t *),
void (*on_enter)(page_t *),
void (*on_exit)(page_t *),
void (*on_destroy)(page_t *),
event_result_t (*on_event)(page_t *, input_event_t *),
void (*on_loop)(page_t *))
{
memset(page, 0, sizeof(*page));
page->page_id = pageId;
page->is_cached = isCached;
page->on_create = on_create;
page->on_enter = on_enter;
page->on_exit = on_exit;
page->on_destroy = on_destroy;
page->on_event = on_event;
page->on_loop = on_loop;
page->model = ctx;
}
/* -------------------------------------------------------------------------
* 函数名: main
* 功能:
* 覆盖 PageManager 的核心行为链路,验证注册、导航、事件分发(含 ESC 兜底回退)、
* 生命周期回调顺序以及循环驱动是否符合预期。
*
* 参数:
* 无
*
* 边界处理:
* - 通过 memset 将页面对象和上下文清零,避免脏数据影响断言。
* - 使用 ASSERT_TRUE 先校验 GetTop 非空,再访问 page_id防止空指针解引用。
*
* 说明:
* - pageA 设为缓存页is_cached = 1用于验证回退后可继续进入而不销毁。
* - pageB 设为非缓存页is_cached = 0用于验证 ESC 回退时触发 on_destroy。
* - 将 pageB.on_event 设为 EVENT_UNHANDLED确保 DispatchEvent 走全局兜底,
* 从而触发 KEY_ESC -> PageManager_Pop() 的系统行为。
* - 关键验证点:
* 1) Navigate 到 AA create/enter 各一次;
* 2) Navigate 到 BA exit 一次B create/enter 各一次;
* 3) ESC 事件B exit + destroy栈顶回到 AA enter 再次触发;
* 4) Loop仅驱动当前栈顶 A 的 on_loop。
*
* 返回值:
* - 0测试通过
* ------------------------------------------------------------------------- */
int main(void)
{
page_t pageA;
page_t pageB;
page_t pageA2;
page_t pageB2;
page_t pageA3;
input_event_t event;
page_t invalidNonePage;
page_t invalidMaxPage;
/* ===== Case 1: Init 后的默认行为与空栈边界 ===== */
memset(&g_ctx_a, 0, sizeof(g_ctx_a));
memset(&g_ctx_b, 0, sizeof(g_ctx_b));
PageManager_Init();
ASSERT_TRUE(PageManager_GetTop() == NULL);
ASSERT_TRUE(PageManager_Find(PAGE_ID_MENU) == NULL);
ASSERT_EQ_INT(PAGE_MANAGER_ERR_NOT_FOUND, PageManager_Navigate(PAGE_ID_MENU));
ASSERT_EQ_INT(PAGE_MANAGER_ERR_STACK_BOTTOM, PageManager_Pop());
ASSERT_EQ_INT(PAGE_MANAGER_ERR_NULL_PARAM, PageManager_Register(NULL));
ASSERT_EQ_INT(PAGE_MANAGER_ERR_NULL_PARAM, PageManager_Push(NULL));
ASSERT_EQ_INT(PAGE_MANAGER_ERR_NULL_PARAM, PageManager_DispatchEvent(NULL));
PageManager_Loop();
/* ===== Case 2: Register 参数边界与重复注册覆盖 ===== */
setup_page(&invalidNonePage,
PAGE_ID_NONE,
1,
&g_ctx_a,
on_create_a,
on_enter_a,
on_exit_a,
on_destroy_a,
on_event_a,
on_loop_a);
setup_page(&invalidMaxPage,
PAGE_ID_MAX,
1,
&g_ctx_b,
on_create_b,
on_enter_b,
on_exit_b,
on_destroy_b,
on_event_b,
on_loop_b);
ASSERT_EQ_INT(PAGE_MANAGER_ERR_INVALID_ID, PageManager_Register(&invalidNonePage));
ASSERT_EQ_INT(PAGE_MANAGER_ERR_INVALID_ID, PageManager_Register(&invalidMaxPage));
setup_page(&pageA,
PAGE_ID_MENU,
1,
&g_ctx_a,
on_create_a,
on_enter_a,
on_exit_a,
on_destroy_a,
on_event_a,
on_loop_a);
setup_page(&pageB,
PAGE_ID_APP_INFO,
0,
&g_ctx_b,
on_create_b,
on_enter_b,
on_exit_b,
on_destroy_b,
on_event_b,
on_loop_b);
setup_page(&pageA2,
PAGE_ID_MENU,
1,
&g_ctx_a,
on_create_a,
on_enter_a,
on_exit_a,
on_destroy_a,
on_event_a,
on_loop_a);
setup_page(&pageB2,
PAGE_ID_APP_INFO,
0,
&g_ctx_b,
on_create_b,
on_enter_b,
on_exit_b,
on_destroy_b,
on_event_b,
on_loop_b);
setup_page(&pageA3,
PAGE_ID_MENU,
1,
&g_ctx_a,
on_create_a,
on_enter_a,
on_exit_a,
on_destroy_a,
on_event_a,
on_loop_a);
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Register(&pageA));
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Register(&pageB));
ASSERT_TRUE(PageManager_Find(PAGE_ID_MENU) == &pageA);
ASSERT_TRUE(PageManager_Find(PAGE_ID_APP_INFO) == &pageB);
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Register(&pageA2)); /* 重复 ID 覆盖 */
ASSERT_TRUE(PageManager_Find(PAGE_ID_MENU) == &pageA2);
/* ===== Case 3: Navigate + 生命周期 + GetTop ===== */
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Navigate(PAGE_ID_MENU));
ASSERT_EQ_INT(1, g_ctx_a.createCount);
ASSERT_EQ_INT(1, g_ctx_a.enterCount);
ASSERT_TRUE(PageManager_GetTop() != NULL);
ASSERT_EQ_INT(PAGE_ID_MENU, PageManager_GetTop()->page_id);
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Navigate(PAGE_ID_MENU)); /* 幂等导航到当前页 */
ASSERT_EQ_INT(1, g_ctx_a.enterCount); /* 不应重复 enter */
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Navigate(PAGE_ID_APP_INFO));
ASSERT_EQ_INT(1, g_ctx_a.exitCount);
ASSERT_EQ_INT(1, g_ctx_b.createCount);
ASSERT_EQ_INT(1, g_ctx_b.enterCount);
ASSERT_TRUE(PageManager_GetTop() != NULL);
ASSERT_EQ_INT(PAGE_ID_APP_INFO, PageManager_GetTop()->page_id);
/* ===== Case 4: DispatchEvent handled/unhandled 分支 ===== */
g_ctx_b.eventResult = EVENT_HANDLED;
event.type = PAGE_EVENT_KEY;
event.keyVal = KEY_ESC;
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_DispatchEvent(&event));
ASSERT_TRUE(PageManager_GetTop() == &pageB); /* handled 时不走全局 Pop */
ASSERT_EQ_INT(0, g_ctx_b.exitCount);
ASSERT_EQ_INT(0, g_ctx_b.destroyCount);
g_ctx_b.eventResult = EVENT_UNHANDLED;
event.type = PAGE_EVENT_KEY;
event.keyVal = KEY_ESC;
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_DispatchEvent(&event));
ASSERT_TRUE(PageManager_GetTop() == &pageA2);
ASSERT_EQ_INT(PAGE_ID_MENU, PageManager_GetTop()->page_id);
ASSERT_EQ_INT(1, g_ctx_b.exitCount);
ASSERT_EQ_INT(1, g_ctx_b.destroyCount);
ASSERT_EQ_INT(2, g_ctx_a.enterCount);
/* 未处理但非键盘事件,不应触发全局行为 */
event.type = 0;
event.keyVal = KEY_ESC;
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_DispatchEvent(&event));
ASSERT_TRUE(PageManager_GetTop() == &pageA2);
/* ===== Case 5: Loop 仅驱动栈顶页 ===== */
PageManager_Loop();
ASSERT_EQ_INT(1, g_ctx_a.loopCount);
ASSERT_EQ_INT(0, g_ctx_b.loopCount);
/* ===== Case 6: Push/Pop 边界(含栈满) ===== */
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Push(&pageB2));
ASSERT_EQ_INT(2, g_ctx_b.createCount); /* 上次被 destroy重新 push 应再 create */
ASSERT_EQ_INT(2, g_ctx_b.enterCount);
ASSERT_TRUE(PageManager_GetTop() == &pageB2);
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Push(&pageA3));
ASSERT_TRUE(PageManager_GetTop() == &pageA3);
/* 当前栈深推进到 4A2 -> B2 -> A3 -> B */
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Push(&pageB));
ASSERT_TRUE(PageManager_GetTop() == &pageB);
/* 再 Push 一次达到栈深 5上限 */
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Push(&pageA3));
ASSERT_TRUE(PageManager_GetTop() == &pageA3);
/* 栈满后继续 Push 应失败 */
ASSERT_EQ_INT(PAGE_MANAGER_ERR_STACK_FULL, PageManager_Push(&pageA2));
/* 连续 Pop 到底页,最后一次应失败 */
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Pop());
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Pop());
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Pop());
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Pop());
ASSERT_EQ_INT(PAGE_MANAGER_ERR_STACK_BOTTOM, PageManager_Pop());
ASSERT_TRUE(PageManager_GetTop() == &pageA2);
/* ===== Case 7: Navigate 未注册页 ===== */
ASSERT_EQ_INT(PAGE_MANAGER_ERR_NOT_FOUND, PageManager_Navigate(PAGE_ID_NONE));
return 0;
}