Mercurial
view infinite_canvas/canvas_test.c @ 280:49e9e591c9bb
Add persistent dictation, prewarmed WebRTC speech input, Copilot SDK routing, animated conversation lifecycle controls, parking, and architecture coverage.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 18 Aug 2026 19:14:53 -0700 |
| parents | 8d560f50ed4c |
| children | c57149ad216e |
line wrap: on
line source
#include "infinite_canvas/canvas.h" #include <assert.h> #include <math.h> #include <stdio.h> #include <string.h> static boolean Near(float left, float right) { return fabsf(left - right) < 0.001f ? TRUE : FALSE; } static Rectangle Entity_Bounds_For_Test(const Canvas_Entity *p_entity) { if (p_entity->type == CANVAS_ENTITY_CIRCLE) { return (Rectangle){ p_entity->position.x - p_entity->size.x, p_entity->position.y - p_entity->size.x, p_entity->size.x * 2.0f, p_entity->size.x * 2.0f, }; } if (p_entity->type == CANVAS_ENTITY_LINE) { float end_x = p_entity->position.x + p_entity->size.x; float end_y = p_entity->position.y + p_entity->size.y; return (Rectangle){ fminf(p_entity->position.x, end_x), fminf(p_entity->position.y, end_y), fabsf(p_entity->size.x), fabsf(p_entity->size.y), }; } return (Rectangle){ p_entity->position.x, p_entity->position.y, p_entity->size.x, p_entity->size.y, }; } static void Test_Camera_Round_Trip(void) { Canvas_Camera camera; Canvas_Camera_Init(&camera, 1200, 800); camera.target = (Vector2){340.0f, -125.0f}; camera.zoom = 2.5f; Vector2 world = {-22.0f, 718.0f}; Vector2 screen = Canvas_Camera_World_To_Screen(&camera, world); Vector2 result = Canvas_Camera_Screen_To_World(&camera, screen); assert(Near(result.x, world.x)); assert(Near(result.y, world.y)); } static void Test_Theme_Resolution(void) { Canvas_Theme light; Canvas_Theme dark; Canvas_Theme_Resolve(&light, CANVAS_THEME_LIGHT); Canvas_Theme_Resolve(&dark, CANVAS_THEME_DARK); assert(light.resolved_mode == CANVAS_THEME_LIGHT); assert(light.background.r == 255); assert(light.toast_surface.r == 255); assert(dark.resolved_mode == CANVAS_THEME_DARK); assert(dark.background.r == 15); assert(dark.toast_surface.r == 24); assert(light.switch_track_on.g > light.switch_track_on.b); assert(dark.switch_track_on.g > dark.switch_track_on.b); assert(light.switch_track_on.r != light.accent.r); assert(dark.switch_track_on.r != dark.accent.r); assert(light.pin.r > light.pin.b); assert(dark.pin.r > dark.pin.b); assert(light.pin.r != light.accent.r); assert(dark.pin.r != dark.accent.r); assert(light.button.r != dark.button.r); assert(light.selection.r != dark.selection.r); assert(light.danger.r != dark.danger.r); assert(Canvas_Theme_Mode_From_String("light") == CANVAS_THEME_LIGHT); assert(Canvas_Theme_Mode_From_String("dark") == CANVAS_THEME_DARK); assert(Canvas_Theme_Mode_From_String("invalid") == CANVAS_THEME_AUTO); Canvas_Theme_Toggle(&light); assert(light.resolved_mode == CANVAS_THEME_DARK); assert(light.requested_mode == CANVAS_THEME_DARK); Canvas_Theme_Toggle(&light); assert(light.resolved_mode == CANVAS_THEME_LIGHT); } static void Test_Anchored_Zoom(void) { Canvas_Camera camera; Canvas_Camera_Init(&camera, 1000, 600); camera.target = (Vector2){125.0f, -40.0f}; Vector2 cursor = {770.0f, 180.0f}; Vector2 before = Canvas_Camera_Screen_To_World(&camera, cursor); Canvas_Camera_Zoom_At(&camera, cursor, 3.0f); Vector2 after = Canvas_Camera_Screen_To_World(&camera, cursor); assert(Near(before.x, after.x)); assert(Near(before.y, after.y)); } static void Test_Viewport_Bounds(void) { Canvas_Camera camera; Canvas_Camera_Init(&camera, 200, 100); camera.zoom = 2.0f; Rectangle bounds = Canvas_Camera_Viewport_Bounds(&camera); assert(Near(bounds.x, -50.0f)); assert(Near(bounds.y, -25.0f)); assert(Near(bounds.width, 100.0f)); assert(Near(bounds.height, 50.0f)); } static void Test_Grid_Spacing(void) { assert(Near(Canvas_Grid_Spacing(1.0f), 64.0f)); assert(Near(Canvas_Grid_Spacing(0.25f), 256.0f)); assert(Near(Canvas_Grid_Spacing(4.0f), 16.0f)); } static void Test_Entity_Picking(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); assert(p_arena); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_RECTANGLE, (Vector2){10.0f, 20.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_CIRCLE, (Vector2){300.0f, 80.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_LINE, (Vector2){400.0f, 20.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_TEXT, (Vector2){650.0f, 40.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_BUTTON, (Vector2){850.0f, 40.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_TEXT_AREA, (Vector2){1050.0f, 40.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_DROPDOWN, (Vector2){1400.0f, 40.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_WEB_CONTENT, (Vector2){1700.0f, 40.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_ACCORDION, (Vector2){2500.0f, 40.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_CARD, (Vector2){2850.0f, 40.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_CALENDAR, (Vector2){3180.0f, 40.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_SWITCH, (Vector2){3510.0f, 40.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_TABLE, (Vector2){3650.0f, 40.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_NOTIFICATION, (Vector2){4120.0f, 40.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_SCROLL_AREA, (Vector2){4530.0f, 40.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_IMAGE, (Vector2){4900.0f, 40.0f})); assert(Canvas_Scene_Pick(&scene, (Vector2){30.0f, 40.0f}, 1.0f) == 0); assert(Canvas_Scene_Pick(&scene, (Vector2){300.0f, 80.0f}, 1.0f) == 1); assert(Canvas_Scene_Pick(&scene, (Vector2){490.0f, 60.0f}, 1.0f) == 2); assert(Canvas_Scene_Pick(&scene, (Vector2){700.0f, 55.0f}, 1.0f) == 3); assert(Canvas_Scene_Pick(&scene, (Vector2){900.0f, 60.0f}, 1.0f) == 4); assert(Canvas_Scene_Pick(&scene, (Vector2){1100.0f, 80.0f}, 1.0f) == 5); assert(Canvas_Scene_Pick(&scene, (Vector2){1450.0f, 60.0f}, 1.0f) == 6); scene.p_entities[6].active = TRUE; assert(Canvas_Scene_Pick(&scene, (Vector2){1450.0f, 120.0f}, 1.0f) == 6); assert(Canvas_Scene_Pick(&scene, (Vector2){1450.0f, 190.0f}, 1.0f) == 6); assert(Canvas_Scene_Pick(&scene, (Vector2){1704.0f, 44.0f}, 1.0f) == 7); assert(Canvas_Scene_Pick(&scene, (Vector2){1750.0f, 140.0f}, 1.0f) == -1); assert(Canvas_Scene_Pick(&scene, (Vector2){2550.0f, 60.0f}, 1.0f) == 8); assert(Canvas_Scene_Pick(&scene, (Vector2){2900.0f, 100.0f}, 1.0f) == 9); assert(Canvas_Scene_Pick(&scene, (Vector2){3230.0f, 100.0f}, 1.0f) == 10); assert(Canvas_Scene_Pick(&scene, (Vector2){3540.0f, 58.0f}, 1.0f) == 11); assert(Canvas_Scene_Pick(&scene, (Vector2){3700.0f, 100.0f}, 1.0f) == 12); assert(Canvas_Scene_Pick(&scene, (Vector2){4170.0f, 80.0f}, 1.0f) == 13); assert(Canvas_Scene_Pick(&scene, (Vector2){4580.0f, 100.0f}, 1.0f) == 14); assert(Canvas_Scene_Pick(&scene, (Vector2){4950.0f, 100.0f}, 1.0f) == 15); assert(Canvas_Scene_Pick(&scene, (Vector2){5500.0f, 500.0f}, 1.0f) == -1); Dowa_Arena_Free(p_arena); } static void Test_Overlapping_Entities_Use_Topmost_Z_Order(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(4 * ONE_MEGA_BYTE); assert(p_arena); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); Vector2 position = {0.0f, 0.0f}; Vector2 pointer = {40.0f, 30.0f}; assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_BUTTON, position)); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_BUTTON, position)); assert(Canvas_Scene_Pick(&scene, pointer, 1.0f) == 1); assert(Canvas_Scene_Topmost_Visual(&scene, pointer, 1.0f) == 1); scene.p_entities[1].removing = TRUE; assert(Canvas_Scene_Pick(&scene, pointer, 1.0f) == 0); assert(Canvas_Scene_Topmost_Visual(&scene, pointer, 1.0f) == 0); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_WEB_CONTENT, position)); assert(Canvas_Scene_Topmost_Visual(&scene, pointer, 1.0f) == 2); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_BUTTON, position)); assert(Canvas_Scene_Pick(&scene, pointer, 1.0f) == 3); assert(Canvas_Scene_Topmost_Visual(&scene, pointer, 1.0f) == 3); Dowa_Arena_Free(p_arena); } static void Test_New_Primitive_Defaults(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); assert(p_arena); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_ACCORDION, (Vector2){0.0f, 0.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_CARD, (Vector2){0.0f, 0.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_CALENDAR, (Vector2){0.0f, 0.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_SWITCH, (Vector2){0.0f, 0.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_TABLE, (Vector2){0.0f, 0.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_NOTIFICATION, (Vector2){0.0f, 0.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_SCROLL_AREA, (Vector2){0.0f, 0.0f})); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_IMAGE, (Vector2){0.0f, 0.0f})); assert(Near(scene.p_entities[0].size.x, 300.0f)); assert(Near(scene.p_entities[0].size.y, 56.0f)); assert(Near(scene.p_entities[1].size.x, 280.0f)); assert(Near(scene.p_entities[1].size.y, 220.0f)); assert(scene.p_entities[2].value == 17); assert(Near(scene.p_entities[3].size.x, 64.0f)); assert(Near(scene.p_entities[3].size.y, 36.0f)); assert(Near(scene.p_entities[4].size.x, 420.0f)); assert(scene.p_entities[4].value == 1); assert(scene.p_entities[5].active); assert(Near(scene.p_entities[6].size.y, 220.0f)); assert(strstr(scene.p_entities[7].text, "image-placeholder.svg")); assert(strcmp(Canvas_Entity_Type_Name(CANVAS_ENTITY_ACCORDION), "Accordion") == 0); assert(strcmp(Canvas_Entity_Type_Name(CANVAS_ENTITY_CARD), "Card") == 0); assert(strcmp(Canvas_Entity_Type_Name(CANVAS_ENTITY_CALENDAR), "Calendar") == 0); assert(strcmp(Canvas_Entity_Type_Name(CANVAS_ENTITY_SWITCH), "Switch") == 0); assert(strcmp(Canvas_Entity_Type_Name(CANVAS_ENTITY_TABLE), "Table") == 0); assert(strcmp(Canvas_Entity_Type_Name(CANVAS_ENTITY_NOTIFICATION), "Notification") == 0); assert(strcmp(Canvas_Entity_Type_Name(CANVAS_ENTITY_SCROLL_AREA), "Scrollable area") == 0); assert(strcmp(Canvas_Entity_Type_Name(CANVAS_ENTITY_IMAGE), "Image") == 0); Dowa_Arena_Free(p_arena); } static void Test_Text_Editing_Focus(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); assert(p_arena); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); assert(!Canvas_Scene_Is_Text_Editing(&scene)); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_TEXT_AREA, (Vector2){0.0f, 0.0f})); assert(sizeof(scene.p_entities[0].text) == CANVAS_ENTITY_TEXT_CAPACITY); assert(scene.p_entities[0].text_cursor == 0); assert(scene.p_entities[0].text_selection_anchor == 0); scene.selected_index = 0; assert(!Canvas_Scene_Is_Text_Editing(&scene)); scene.p_entities[0].active = TRUE; assert(Canvas_Scene_Is_Text_Editing(&scene)); scene.p_entities[0].active = FALSE; assert(!Canvas_Scene_Is_Text_Editing(&scene)); Dowa_Arena_Free(p_arena); } static void Test_Entity_Ids_Remain_Unique_After_Clear(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); assert(p_arena); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_WEB_CONTENT, (Vector2){0.0f, 0.0f})); uint32 first_id = scene.p_entities[0].id; Canvas_Scene_Clear(&scene); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_WEB_CONTENT, (Vector2){0.0f, 0.0f})); assert(scene.p_entities[0].id != first_id); Dowa_Arena_Free(p_arena); } static void Test_Entity_Fade_Lifecycle_Commands(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(4 * ONE_MEGA_BYTE); assert(p_arena); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); assert(Canvas_Scene_Add( &scene, CANVAS_ENTITY_RECTANGLE, (Vector2){0.0f, 0.0f})); assert(Canvas_Scene_Add( &scene, CANVAS_ENTITY_TEXT, (Vector2){200.0f, 0.0f})); assert(Near(scene.p_entities[0].visibility_amount, 0.0f)); assert(!scene.p_entities[0].removing); scene.selected_index = 0; Canvas_Scene_Fade_Out_Selected(&scene); assert(scene.p_entities[0].removing); assert(scene.selected_index == -1); assert(!scene.p_entities[1].removing); Canvas_Scene_Fade_Out_All(&scene); assert(scene.p_entities[0].removing); assert(scene.p_entities[1].removing); Dowa_Arena_Free(p_arena); } static void Test_Browser_Grid(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); assert(p_arena); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); Canvas_Scene_Add_Browser_Grid(&scene); assert(Dowa_Array_Length(scene.p_entities) == 10); for (size_t index = 0; index < Dowa_Array_Length(scene.p_entities); index++) { assert(scene.p_entities[index].type == CANVAS_ENTITY_WEB_CONTENT); assert(Near(scene.p_entities[index].size.x, 236.0f)); assert(Near(scene.p_entities[index].size.y, 176.0f)); } Dowa_Arena_Free(p_arena); } static void Test_Lucide_Gallery_Reuses_Entity_And_Fits_Camera(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); assert(p_arena); Canvas_Camera camera; Canvas_Camera_Init(&camera, 1920, 1200); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); assert(Canvas_Scene_Show_Lucide_Gallery(&scene, &camera)); assert(Dowa_Array_Length(scene.p_entities) == 1); assert(scene.selected_index == 0); Canvas_Entity *p_gallery = &scene.p_entities[0]; assert(p_gallery->type == CANVAS_ENTITY_LUCIDE_GALLERY); assert(strcmp(p_gallery->text, "") == 0); assert(Near(p_gallery->size.x, 1440.0f)); assert(Near(p_gallery->size.y, 1040.0f)); Vector2 screen_min = Canvas_Camera_World_To_Screen(&camera, p_gallery->position); Vector2 screen_max = Canvas_Camera_World_To_Screen( &camera, (Vector2){ p_gallery->position.x + p_gallery->size.x, p_gallery->position.y + p_gallery->size.y, }); assert(screen_min.x >= 319.0f); assert(screen_min.y >= 55.0f); assert(screen_max.x <= 1865.0f); assert(screen_max.y <= 1145.0f); assert(Canvas_Scene_Show_Lucide_Gallery(&scene, &camera)); assert(Dowa_Array_Length(scene.p_entities) == 1); Dowa_Arena_Free(p_arena); } static void Test_Lucide_Search_Is_Case_Insensitive(void) { assert(Canvas_Lucide_Count_Matches("") == 1767); assert(Canvas_Lucide_Count_Matches("pin") > 0); assert(Canvas_Lucide_Count_Matches("PIN") == Canvas_Lucide_Count_Matches("pin")); assert(Canvas_Lucide_Count_Matches("not-a-real-lucide-icon") == 0); } static void Test_Demo_Contains_Every_Entity_Type(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); assert(p_arena); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); Canvas_Scene_Add_Demo(&scene); assert(Dowa_Array_Length(scene.p_entities) == CANVAS_ENTITY_TYPE_COUNT + 1); int32 counts[CANVAS_ENTITY_TYPE_COUNT] = {0}; for (size_t index = 0; index < Dowa_Array_Length(scene.p_entities); index++) { counts[scene.p_entities[index].type]++; } for (int32 type = 0; type < CANVAS_ENTITY_TYPE_COUNT; type++) { if (type == CANVAS_ENTITY_LUCIDE_GALLERY) { assert(counts[type] == 0); continue; } int32 expected = type == CANVAS_ENTITY_NOTIFICATION ? 3 : 1; assert(counts[type] == expected); } for (size_t index = 0; index < Dowa_Array_Length(scene.p_entities); index++) { if (scene.p_entities[index].type == CANVAS_ENTITY_NOTIFICATION) continue; Rectangle bounds = Entity_Bounds_For_Test(&scene.p_entities[index]); assert(bounds.x >= -600.0f); assert(bounds.y >= -600.0f); assert(bounds.x + bounds.width <= 960.0f); assert(bounds.y + bounds.height <= 600.0f); for (size_t other = index + 1; other < Dowa_Array_Length(scene.p_entities); other++) { if (scene.p_entities[other].type == CANVAS_ENTITY_NOTIFICATION) continue; Rectangle other_bounds = Entity_Bounds_For_Test(&scene.p_entities[other]); assert(!CheckCollisionRecs(bounds, other_bounds)); } } Dowa_Arena_Free(p_arena); } static void Test_Pinned_Entity_Stays_In_Screen_Space(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); assert(p_arena); Canvas_Camera camera; Canvas_Camera_Init(&camera, 1000, 600); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_RECTANGLE, (Vector2){80.0f, 40.0f})); scene.selected_index = 0; Canvas_Scene_Toggle_Selected_Pin(&scene, &camera); assert(scene.p_entities[0].pinned); Vector2 original_screen = Canvas_Camera_World_To_Screen(&camera, scene.p_entities[0].position); Vector2 original_size = { scene.p_entities[0].size.x * camera.zoom, scene.p_entities[0].size.y * camera.zoom, }; camera.target = (Vector2){900.0f, -500.0f}; camera.zoom = 3.0f; Canvas_Scene_Sync_Pinned(&scene, &camera); Vector2 pinned_screen = Canvas_Camera_World_To_Screen(&camera, scene.p_entities[0].position); assert(Near(pinned_screen.x, original_screen.x)); assert(Near(pinned_screen.y, original_screen.y)); assert(Near(scene.p_entities[0].size.x * camera.zoom, original_size.x)); assert(Near(scene.p_entities[0].size.y * camera.zoom, original_size.y)); Canvas_Scene_Toggle_Selected_Pin(&scene, &camera); assert(!scene.p_entities[0].pinned); Dowa_Arena_Free(p_arena); } static void Test_Visible_Context_Tracks_Camera_And_Values(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); assert(p_arena); Canvas_Camera camera; Canvas_Camera_Init(&camera, 200, 100); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); assert(Canvas_Scene_Add( &scene, CANVAS_ENTITY_TEXT_AREA, (Vector2){-80.0f, -30.0f})); assert(Canvas_Scene_Add( &scene, CANVAS_ENTITY_DROPDOWN, (Vector2){250.0f, -20.0f})); assert(Canvas_Scene_Add( &scene, CANVAS_ENTITY_SWITCH, (Vector2){700.0f, 0.0f})); snprintf(scene.p_entities[0].text, sizeof(scene.p_entities[0].text), "Visible note"); scene.p_entities[0].active = TRUE; scene.p_entities[0].text_cursor = 7; scene.p_entities[0].text_selection_anchor = 2; scene.p_entities[1].value = 2; scene.p_entities[1].active = TRUE; scene.p_entities[2].active = TRUE; char context[4096]; Canvas_Context_Snapshot snapshot = Canvas_Scene_Build_Visible_Context( &scene, &camera, context, sizeof(context)); assert(snapshot.visible_count == 1); assert(!snapshot.truncated); assert(strstr(context, "Visible note")); assert(strstr(context, "editing=true")); assert(strstr(context, "cursor=7")); assert(strstr(context, "selection_anchor=2")); assert(!strstr(context, "Research")); assert(!strstr(context, "checked=true")); camera.zoom = 0.25f; snapshot = Canvas_Scene_Build_Visible_Context( &scene, &camera, context, sizeof(context)); assert(snapshot.visible_count == 2); assert(strstr(context, "selected=\"Research\"")); assert(strstr(context, "open=true")); assert(!strstr(context, "checked=true")); camera.target.x = 900.0f; snapshot = Canvas_Scene_Build_Visible_Context( &scene, &camera, context, sizeof(context)); assert(snapshot.visible_count == 1); assert(strstr(context, "checked=true")); assert(!strstr(context, "Visible note")); char small_context[24]; snapshot = Canvas_Scene_Build_Visible_Context( &scene, &camera, small_context, sizeof(small_context)); assert(snapshot.visible_count == 1); assert(snapshot.truncated); assert(small_context[sizeof(small_context) - 1] == '\0'); Dowa_Arena_Free(p_arena); } static void Test_Dictation_Scratchpad_Is_Not_A_Routing_Target(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); assert(p_arena); Canvas_Camera camera; Canvas_Camera_Init(&camera, 800, 600); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); assert(Canvas_Scene_Add( &scene, CANVAS_ENTITY_TEXT_AREA, (Vector2){-260.0f, -120.0f})); snprintf( scene.p_entities[0].label, sizeof(scene.p_entities[0].label), "Dictation"); snprintf( scene.p_entities[0].text, sizeof(scene.p_entities[0].text), "Route this thought"); uint32 conversation_id = Canvas_Scene_Add_Conversation( &scene, "Visible session", "Earlier thought", "Earlier response", (Vector2){40.0f, -120.0f}); assert(conversation_id != 0); char context[4096]; Canvas_Context_Snapshot snapshot = Canvas_Scene_Build_Visible_Context( &scene, &camera, context, sizeof(context)); assert(snapshot.visible_count == 1); assert(!strstr(context, "Route this thought")); assert(strstr(context, "Visible session")); assert(strstr(context, TextFormat("id=%u", conversation_id))); Dowa_Arena_Free(p_arena); } static void Test_Conversation_Create_And_Append(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); assert(p_arena); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); uint32 id = Canvas_Scene_Add_Conversation( &scene, "Rendering architecture", "How should images render?", "Use a rich entity with typed resources.", (Vector2){10.0f, 20.0f}); assert(id != 0); assert(Dowa_Array_Length(scene.p_entities) == 1); assert(scene.p_entities[0].type == CANVAS_ENTITY_CONVERSATION); assert(strcmp(scene.p_entities[0].label, "Rendering architecture") == 0); assert(strstr(scene.p_entities[0].text, "How should images render?")); assert(Canvas_Scene_Append_Conversation( &scene, id, "What about browser content?", "Keep the resource typed and composited by its backend.")); assert(strstr(scene.p_entities[0].text, "What about browser content?")); assert(strstr(scene.p_entities[0].text, "composited by its backend")); assert(!Canvas_Scene_Append_Conversation( &scene, id + 1, "Missing", "Missing")); Dowa_Arena_Free(p_arena); } static void Test_Conversation_Collapse_And_Parking(void) { Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); assert(p_arena); Canvas_Camera camera; Canvas_Camera_Init(&camera, 800, 600); Canvas_Scene scene; Canvas_Scene_Init(&scene, p_arena); Vector2 original = {20.0f, 30.0f}; uint32 id = Canvas_Scene_Add_Conversation( &scene, "Parkable session", "Question", "Answer", original); Canvas_Entity *p_conversation = &scene.p_entities[0]; assert(Near(p_conversation->animation_amount, 1.0f)); Canvas_Conversation_Set_Collapsed(p_conversation, TRUE); assert(p_conversation->active); Canvas_Conversation_Set_Collapsed(p_conversation, FALSE); assert(!p_conversation->active); assert(Canvas_Scene_Set_Conversation_Parked(&scene, id, TRUE)); assert(p_conversation->parked); assert(Canvas_Scene_Parked_Conversation_Count(&scene) == 1); assert(!Near(p_conversation->position.x, original.x)); char context[4096]; Canvas_Context_Snapshot snapshot = Canvas_Scene_Build_Visible_Context( &scene, &camera, context, sizeof(context)); assert(snapshot.visible_count == 0); assert(Canvas_Scene_Show_Parking_Lot(&scene, &camera)); snapshot = Canvas_Scene_Build_Visible_Context( &scene, &camera, context, sizeof(context)); assert(snapshot.visible_count == 0); assert(Canvas_Scene_Set_Conversation_Parked(&scene, id, FALSE)); assert(!p_conversation->parked); assert(Canvas_Scene_Parked_Conversation_Count(&scene) == 0); assert(Near(p_conversation->position.x, original.x)); assert(Near(p_conversation->position.y, original.y)); Dowa_Arena_Free(p_arena); } int main(void) { Test_Theme_Resolution(); Test_Camera_Round_Trip(); Test_Anchored_Zoom(); Test_Viewport_Bounds(); Test_Grid_Spacing(); Test_Entity_Picking(); Test_Overlapping_Entities_Use_Topmost_Z_Order(); Test_New_Primitive_Defaults(); Test_Text_Editing_Focus(); Test_Entity_Ids_Remain_Unique_After_Clear(); Test_Entity_Fade_Lifecycle_Commands(); Test_Browser_Grid(); Test_Lucide_Gallery_Reuses_Entity_And_Fits_Camera(); Test_Lucide_Search_Is_Case_Insensitive(); Test_Demo_Contains_Every_Entity_Type(); Test_Conversation_Create_And_Append(); Test_Pinned_Entity_Stays_In_Screen_Space(); Test_Visible_Context_Tracks_Camera_And_Values(); Test_Dictation_Scratchpad_Is_Not_A_Routing_Target(); Test_Conversation_Collapse_And_Parking(); printf("Infinite canvas tests passed.\n"); return 0; }