将菜单的架构改成 MVP,并且进一步优化视图层和模型层的逻辑
This commit is contained in:
292
tests/test_p1_page_manager.c
Normal file
292
tests/test_p1_page_manager.c
Normal file
@@ -0,0 +1,292 @@
|
||||
#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 到 A:A create/enter 各一次;
|
||||
* 2) Navigate 到 B:A exit 一次,B create/enter 各一次;
|
||||
* 3) ESC 事件:B exit + destroy,栈顶回到 A,A 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);
|
||||
|
||||
/* 当前栈深推进到 4(A2 -> 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;
|
||||
}
|
||||
Reference in New Issue
Block a user