view infinite_canvas/canvas_test.c @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents 8d560f50ed4c
children 49e9e591c9bb
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);
}

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_Pinned_Entity_Stays_In_Screen_Space();
    Test_Visible_Context_Tracks_Camera_And_Values();
    printf("Infinite canvas tests passed.\n");
    return 0;
}