// tab_events — inyector de TableEvents sinteticos para auto-test del event sink. // Issue 0108 fase 2. // // Patron: se construye un vector LOCAL y se llama qa::consume_events // directamente — sin worker thread. Los counters de qa::counters() se incrementan // como si el usuario hubiera clickado. Ademas se renderiza la tabla real para que // eventos reales y sinteticos cuenten igual. // // Inspirado en altsnap_jitter_test: fakear eventos de bajo nivel directamente // sobre el estado interno del subsistema, sin depender de la UI. #include "tabs.h" #include "qa_state.h" #include "data_table/data_table.h" #include #include namespace tables_qa::tabs { namespace { qa::TabState g_st; int g_events_injected_total = 0; char g_burst_msg[128] = ""; void seed() { // 6 filas x 5 columnas: id, name, status (CategoricalChip), action (Button), value (Float) g_st.back = { // id name status action value "1", "task-alpha", "ok", "Activate", "1.25", "2", "task-beta", "error", "Activate", "0.50", "3", "task-gamma", "running", "Activate", "3.14", "4", "task-delta", "pending", "Activate", "2.71", "5", "task-epsilon", "ok", "Activate", "0.99", "6", "task-zeta", "error", "Activate", "4.20", }; qa::rebuild_ptrs(g_st); } // Helper: construye un ButtonClick sintetico y lo consume. void inject_button_click(const char* action_id, int row) { data_table::TableEvent ev; ev.kind = data_table::TableEventKind::ButtonClick; ev.row = row; ev.col = 3; ev.column_id = "action"; ev.action_id = action_id; ev.value = "Activate"; std::vector batch = {ev}; qa::consume_events(batch); g_events_injected_total++; } // Helper: construye un RowDoubleClick sintetico y lo consume. void inject_row_double_click(int row) { data_table::TableEvent ev; ev.kind = data_table::TableEventKind::RowDoubleClick; ev.row = row; ev.col = -1; std::vector batch = {ev}; qa::consume_events(batch); g_events_injected_total++; } // Helper: construye un RowRightClick sintetico y lo consume. void inject_row_right_click(int row) { data_table::TableEvent ev; ev.kind = data_table::TableEventKind::RowRightClick; ev.row = row; ev.col = -1; std::vector batch = {ev}; qa::consume_events(batch); g_events_injected_total++; } } // anon void render_events() { // 1. Seed si vacio if (g_st.back.empty()) seed(); const auto& c = qa::counters(); // 2. Panel de control — inyectores sinteticos ImGui::Text("Synthetic event injectors:"); ImGui::Spacing(); // Fila 1: ButtonClick injectors if (ImGui::Button("Inject ButtonClick (retry) row=0")) { inject_button_click("retry", 0); } ImGui::SameLine(); if (ImGui::Button("Inject ButtonClick (cancel) row=1")) { inject_button_click("cancel", 1); } // Fila 2: Row events if (ImGui::Button("Inject RowDoubleClick row=2")) { inject_row_double_click(2); } ImGui::SameLine(); if (ImGui::Button("Inject RowRightClick row=3")) { inject_row_right_click(3); } // Fila 3: Burst + reset if (ImGui::Button("Inject 100x ButtonClick burst")) { std::vector burst; burst.reserve(100); for (int i = 0; i < 100; i++) { data_table::TableEvent ev; ev.kind = data_table::TableEventKind::ButtonClick; ev.row = i % 6; ev.col = 3; ev.column_id = "action"; ev.action_id = "activate"; ev.value = "Activate"; burst.push_back(ev); } auto t0 = std::chrono::steady_clock::now(); qa::consume_events(burst); auto t1 = std::chrono::steady_clock::now(); long long us = std::chrono::duration_cast(t1 - t0).count(); g_events_injected_total += 100; snprintf(g_burst_msg, sizeof(g_burst_msg), "100 events processed in %lld us", us); } ImGui::SameLine(); if (ImGui::Button("Reset counters")) { auto& mut = qa::counters(); mut.button_clicks = 0; mut.row_double_click = 0; mut.row_right_click = 0; g_events_injected_total = 0; g_burst_msg[0] = '\0'; } // Fila 4: mensajes de estado if (g_burst_msg[0] != '\0') { ImGui::SameLine(); ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "%s", g_burst_msg); } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // 3. Counters vivos ImGui::Text("Global counters — button_clicks: %d row_double_click: %d row_right_click: %d", c.button_clicks, c.row_double_click, c.row_right_click); ImGui::Text("events_injected_total (esta tab): %d", g_events_injected_total); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // 4. Tabla real — eventos reales del usuario tambien cuentan data_table::TableInput tbl; tbl.name = "events"; tbl.headers = {"id", "name", "status", "action", "value"}; tbl.types = { data_table::ColumnType::Int, data_table::ColumnType::String, data_table::ColumnType::String, data_table::ColumnType::String, data_table::ColumnType::Float, }; tbl.cells = g_st.ptrs.data(); tbl.rows = 6; tbl.cols = 5; tbl.column_specs.resize(tbl.cols); for (int i = 0; i < tbl.cols; i++) tbl.column_specs[i].id = tbl.headers[i]; // status: CategoricalChip { auto& cs = tbl.column_specs[2]; cs.renderer = data_table::CellRenderer::CategoricalChip; cs.chips = { {"ok", "#22c55e"}, {"error", "#ef4444"}, {"running", "#f59e0b"}, {"pending", "#a3a3a3"}, }; } // action: Button { auto& cs = tbl.column_specs[3]; cs.renderer = data_table::CellRenderer::Button; cs.button_action = "activate"; cs.button_color_hex = "#3b82f6"; } ImGui::BeginChild("##events_host", ImVec2(-1, -1)); g_st.last_events.clear(); data_table::render("##events_tbl", {tbl}, g_st.dt, &g_st.last_events, qa::toggles().show_chrome); ImGui::EndChild(); // 5. Consumir eventos reales (incrementa los mismos counters globales) qa::consume_events(g_st.last_events); } } // namespace tables_qa::tabs