data_table: Phase 2 — Button + events + tooltip + RightClick + TQL persist column_specs (issue 0081-O)
- CellRenderer::Button=5: renders SmallButton per cell; emits TableEvent::ButtonClick on click - TableEventKind enum (ButtonClick/RowDoubleClick/RowRightClick/CellEdit) + TableEvent struct - render() extended overload: adds events_out parameter (nullptr = back-compat, no events) - RowDoubleClick and RowRightClick detection in raw table loop (stage 0) - RowRightClick also detected in aggregated stage table (stage 1+) - Tooltip per cell: tooltip_on_hover + tooltip fields on ColumnSpec; "auto" = show cell value - State::aux_column_specs: TQL-persisted column specs sidecar per table - tql_emit: serializes aux_column_specs[0] as column_specs block (badge/progress/duration/icon/button/tooltip) - tql_apply: parses column_specs block back into state.aux_column_specs[0] - render() merges aux_column_specs into TableInput when caller passes empty column_specs - test_column_specs: 5->8 tests (Button struct, tooltip fields, both render() signatures link) - tql_emit_test: 3 new tests (column_specs badge/button/tooltip emit) — 52 passed - tql_apply_test: 3 new tests (column_specs badge/button/tooltip roundtrip) — 106 passed - Back-compat: existing apps (graph_explorer, registry_dashboard) unchanged - Version bump: data_table v1.1.0 -> v1.2.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
// test_column_specs.cpp — Smoke / back-compat tests for declarative cell renderers.
|
||||
// Issue 0081-N, v1.1.0.
|
||||
// Issue 0081-N, v1.1.0. Phase 2 (issue 0081-O, v1.2.0).
|
||||
//
|
||||
// These tests verify:
|
||||
// 1. TableInput without column_specs compiles and links (back-compat).
|
||||
// 2-5. TableInput with Badge/Progress/Duration/Icon column_specs compiles and links.
|
||||
// 6. Button renderer: TableEvent struct is constructible; events_out pointer accepted.
|
||||
// 7. Tooltip field: ColumnSpec with tooltip_on_hover=true compiles and links.
|
||||
// 8. render() overload with events_out=nullptr back-compat (symbol resolution only).
|
||||
//
|
||||
// None of these tests call data_table::render() (requires ImGui context).
|
||||
// They only verify that the new types are usable and that the symbols from
|
||||
@@ -50,7 +53,11 @@ static void test_no_column_specs() {
|
||||
|
||||
// Verify that render symbol is still linkable (no ImGui context needed
|
||||
// to take the address; the linker verifies the symbol resolves).
|
||||
auto* render_fn = &data_table::render;
|
||||
// Use the classic overload (without events_out) for the back-compat check.
|
||||
auto* render_fn = static_cast<void(*)(const char*,
|
||||
const std::vector<TableInput>&,
|
||||
State&,
|
||||
bool)>(&data_table::render);
|
||||
(void)render_fn;
|
||||
|
||||
std::printf("PASS: test_no_column_specs (back-compat, column_specs empty)\n");
|
||||
@@ -174,13 +181,130 @@ static void test_icon_column_spec() {
|
||||
assert(t.column_specs[3].icon_map[0].value == "fn");
|
||||
assert(t.column_specs[3].icon_map[0].icon_name == "TI_BOLT");
|
||||
|
||||
// Verify render symbol still links with column_specs populated.
|
||||
auto* render_fn = &data_table::render;
|
||||
// Verify render symbol still links with column_specs populated (classic overload).
|
||||
auto* render_fn = static_cast<void(*)(const char*,
|
||||
const std::vector<TableInput>&,
|
||||
State&,
|
||||
bool)>(&data_table::render);
|
||||
(void)render_fn;
|
||||
|
||||
std::printf("PASS: test_icon_column_spec (2 entries, render symbol links)\n");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 6: Button renderer — TableEvent struct is constructible; events_out ptr
|
||||
// is accepted by the new render() overload (symbol resolution only).
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_button_column_spec_and_event_struct() {
|
||||
TableInput t;
|
||||
t.name = "t6";
|
||||
t.rows = 3;
|
||||
t.cols = 4;
|
||||
t.cells = g_cells;
|
||||
t.headers = g_headers;
|
||||
t.types = g_types;
|
||||
|
||||
ColumnSpec cs_btn;
|
||||
cs_btn.id = "actions";
|
||||
cs_btn.renderer = CellRenderer::Button;
|
||||
cs_btn.button_action = "cancel";
|
||||
cs_btn.button_label = "Cancel";
|
||||
cs_btn.button_color_hex = "#ef4444";
|
||||
|
||||
t.column_specs.resize(4);
|
||||
t.column_specs[0] = cs_btn;
|
||||
|
||||
assert(t.column_specs[0].renderer == CellRenderer::Button);
|
||||
assert(t.column_specs[0].button_action == "cancel");
|
||||
assert(t.column_specs[0].button_label == "Cancel");
|
||||
assert(t.column_specs[0].button_color_hex == "#ef4444");
|
||||
|
||||
// Verify TableEvent struct can be constructed and holds expected fields.
|
||||
TableEvent ev;
|
||||
ev.kind = TableEventKind::ButtonClick;
|
||||
ev.row = 1;
|
||||
ev.col = 0;
|
||||
ev.column_id = "actions";
|
||||
ev.action_id = "cancel";
|
||||
ev.value = "ok";
|
||||
assert(ev.kind == TableEventKind::ButtonClick);
|
||||
assert(ev.row == 1);
|
||||
assert(ev.action_id == "cancel");
|
||||
|
||||
// Verify the render() overload with events_out is linkable.
|
||||
std::vector<TableEvent> events;
|
||||
auto* render_with_events = static_cast<void(*)(const char*,
|
||||
const std::vector<TableInput>&,
|
||||
State&,
|
||||
std::vector<TableEvent>*,
|
||||
bool)>(&data_table::render);
|
||||
(void)render_with_events;
|
||||
|
||||
std::printf("PASS: test_button_column_spec_and_event_struct "
|
||||
"(Button spec + TableEvent + render overload link)\n");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 7: Tooltip field — ColumnSpec with tooltip_on_hover=true.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_tooltip_column_spec() {
|
||||
TableInput t;
|
||||
t.name = "t7";
|
||||
t.rows = 3;
|
||||
t.cols = 4;
|
||||
t.cells = g_cells;
|
||||
t.headers = g_headers;
|
||||
t.types = g_types;
|
||||
|
||||
ColumnSpec cs_tip;
|
||||
cs_tip.id = "status";
|
||||
cs_tip.renderer = CellRenderer::Text; // tooltip works on any renderer
|
||||
cs_tip.tooltip = "auto"; // "auto" -> show cell value
|
||||
cs_tip.tooltip_on_hover = true;
|
||||
|
||||
t.column_specs.resize(4);
|
||||
t.column_specs[0] = cs_tip;
|
||||
|
||||
assert(t.column_specs[0].tooltip == "auto");
|
||||
assert(t.column_specs[0].tooltip_on_hover == true);
|
||||
|
||||
// Also test explicit tooltip text.
|
||||
ColumnSpec cs_tip2;
|
||||
cs_tip2.id = "progress";
|
||||
cs_tip2.renderer = CellRenderer::Progress;
|
||||
cs_tip2.tooltip = "Progress percentage (0..1)";
|
||||
cs_tip2.tooltip_on_hover = true;
|
||||
t.column_specs[1] = cs_tip2;
|
||||
|
||||
assert(t.column_specs[1].tooltip == "Progress percentage (0..1)");
|
||||
assert(t.column_specs[1].tooltip_on_hover == true);
|
||||
|
||||
std::printf("PASS: test_tooltip_column_spec (auto + explicit, tooltip_on_hover=true)\n");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 8: render() back-compat overload without events_out — symbol links.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_render_backcompat_overload() {
|
||||
// Verify both render() signatures are resolvable at link time.
|
||||
// Classic (no events_out):
|
||||
auto* render_classic = static_cast<void(*)(const char*,
|
||||
const std::vector<TableInput>&,
|
||||
State&,
|
||||
bool)>(&data_table::render);
|
||||
(void)render_classic;
|
||||
|
||||
// New (with events_out):
|
||||
auto* render_events = static_cast<void(*)(const char*,
|
||||
const std::vector<TableInput>&,
|
||||
State&,
|
||||
std::vector<TableEvent>*,
|
||||
bool)>(&data_table::render);
|
||||
(void)render_events;
|
||||
|
||||
std::printf("PASS: test_render_backcompat_overload (both render() signatures link)\n");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// main
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -191,6 +315,9 @@ int main() {
|
||||
test_progress_column_spec();
|
||||
test_duration_column_spec();
|
||||
test_icon_column_spec();
|
||||
std::printf("=== ALL TESTS PASSED (5/5) ===\n");
|
||||
test_button_column_spec_and_event_struct();
|
||||
test_tooltip_column_spec();
|
||||
test_render_backcompat_overload();
|
||||
std::printf("=== ALL TESTS PASSED (8/8) ===\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
// test_fn_table_viz_smoke.cpp — Linker smoke test for fn_table_viz static lib.
|
||||
// Issue 0081-I. Verifies that all 11 .cpp files in fn_table_viz resolve symbols
|
||||
// at link time. Does NOT call data_table::render (requires ImGui context).
|
||||
//
|
||||
// Build: cmake --build cpp/build/linux --target test_fn_table_viz_smoke
|
||||
// Run: ./cpp/build/linux/tests/test_fn_table_viz_smoke
|
||||
|
||||
#include "core/compute_stage.h"
|
||||
#include "core/compute_pipeline.h"
|
||||
#include "core/tql_emit.h"
|
||||
#include "core/tql_apply.h"
|
||||
#include "core/lua_engine.h"
|
||||
#include "core/join_tables.h"
|
||||
#include "core/auto_detect_type.h"
|
||||
#include "core/compute_column_stats.h"
|
||||
#include "viz/viz_render.h"
|
||||
#include "viz/data_table.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace data_table;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Minimal input: 3 rows x 2 cols (string + numeric).
|
||||
// ---------------------------------------------------------------------------
|
||||
static const char* g_cells[] = {
|
||||
"Alice", "10",
|
||||
"Bob", "20",
|
||||
"Carol", "30",
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 1: compute_stage with trivial Stage (no filters, no agg, no sort).
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_compute_stage_passthrough() {
|
||||
Stage s; // empty stage = passthrough
|
||||
std::vector<std::string> hdrs = {"Name", "Value"};
|
||||
std::vector<ColumnType> types = {ColumnType::String, ColumnType::Float};
|
||||
|
||||
StageOutput out = compute_stage(g_cells, 3, 2, hdrs, types, s);
|
||||
assert(out.rows == 3 && "compute_stage: rows must be 3");
|
||||
assert(out.cols == 2 && "compute_stage: cols must be 2");
|
||||
std::printf("PASS: compute_stage passthrough (rows=%d cols=%d)\n",
|
||||
out.rows, out.cols);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 2: auto_detect_type on the Value column (all numeric).
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_auto_detect_type() {
|
||||
std::vector<std::string> hdrs = {"Name", "Value"};
|
||||
std::vector<ColumnType> types = {ColumnType::String, ColumnType::Float};
|
||||
// Detect type for column 1 (Value: "10","20","30" -> Float or Int)
|
||||
ColumnType t = auto_detect_type(g_cells, 3, 2, /*col=*/1);
|
||||
assert((t == ColumnType::Float || t == ColumnType::Int) &&
|
||||
"auto_detect_type: Value col should be Float or Int");
|
||||
std::printf("PASS: auto_detect_type numeric (%s)\n",
|
||||
t == ColumnType::Float ? "Float" : "Int");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 3: compute_column_stats on the Value column.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_compute_column_stats() {
|
||||
ColStats s = compute_column_stats(g_cells, 3, 2, /*col=*/1);
|
||||
assert(s.numeric && "compute_column_stats: Value col should be numeric");
|
||||
assert(s.numeric_count == 3 && "compute_column_stats: 3 numeric values");
|
||||
assert(s.min < s.max && "compute_column_stats: min < max");
|
||||
std::printf("PASS: compute_column_stats (min=%.1f max=%.1f mean=%.1f)\n",
|
||||
s.min, s.max, s.mean);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 4: tql_emit -> tql_apply round-trip on a trivial State.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_tql_roundtrip() {
|
||||
State st;
|
||||
st.stages.push_back(Stage{});
|
||||
st.active_stage = 0;
|
||||
st.display = ViewMode::Table;
|
||||
|
||||
std::vector<std::string> hdrs = {"Name", "Value"};
|
||||
std::vector<ColumnType> types = {ColumnType::String, ColumnType::Float};
|
||||
|
||||
std::string lua_text = tql::emit(st, hdrs, types);
|
||||
assert(!lua_text.empty() && "tql_emit: must produce non-empty Lua text");
|
||||
|
||||
auto res = tql::apply(lua_text, hdrs);
|
||||
assert(res.ok && "tql_apply: round-trip must succeed");
|
||||
assert(!res.state.stages.empty() && "tql_apply: state must have stages");
|
||||
std::printf("PASS: tql_emit+tql_apply round-trip (ok=%s warnings=%zu)\n",
|
||||
res.ok ? "true" : "false", res.warnings.size());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 5: tql_apply extended overload (playground-compat bool signature).
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_tql_apply_extended() {
|
||||
State st;
|
||||
st.stages.push_back(Stage{});
|
||||
std::vector<std::string> hdrs = {"Name", "Value"};
|
||||
std::vector<ColumnType> types = {ColumnType::String, ColumnType::Float};
|
||||
std::string lua_text = tql::emit(st, hdrs, types);
|
||||
|
||||
std::string err;
|
||||
State out_st;
|
||||
bool ok = tql::apply(lua_text, out_st, hdrs, types, g_cells, 3, 2, &err);
|
||||
assert(ok && "tql_apply extended: must succeed");
|
||||
assert(!out_st.stages.empty() && "tql_apply extended: state must have stages");
|
||||
std::printf("PASS: tql_apply extended overload (ok=%s)\n",
|
||||
ok ? "true" : "false");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 6: lua_engine get + compile + release (verifies Lua 5.4 links).
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_lua_engine_compile() {
|
||||
lua_engine::Engine* e = lua_engine::get();
|
||||
assert(e && "lua_engine::get must return non-null");
|
||||
std::string err;
|
||||
int id = lua_engine::compile(e, "return row['Value'] * 2", &err);
|
||||
assert(id >= 0 && "lua_engine::compile must succeed");
|
||||
lua_engine::release(e, id);
|
||||
lua_engine::shutdown();
|
||||
std::printf("PASS: lua_engine compile + release (id=%d)\n", id);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 7: join_tables trivial self-join.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_join_tables_trivial() {
|
||||
std::vector<std::string> hdrs = {"Name", "Value"};
|
||||
std::vector<ColumnType> types = {ColumnType::String, ColumnType::Float};
|
||||
|
||||
TableInput right;
|
||||
right.name = "right";
|
||||
right.headers = hdrs;
|
||||
right.types = types;
|
||||
right.cells = g_cells;
|
||||
right.rows = 3;
|
||||
right.cols = 2;
|
||||
|
||||
Join j;
|
||||
j.alias = "r";
|
||||
j.source = "right";
|
||||
j.on = {{"Name", "Name"}};
|
||||
j.strategy = JoinStrategy::Left;
|
||||
|
||||
StageOutput out = join_tables(g_cells, 3, 2, hdrs, types, right, j);
|
||||
assert(out.rows == 3 && "join_tables: self-join must produce 3 rows");
|
||||
std::printf("PASS: join_tables trivial self-join (rows=%d cols=%d)\n",
|
||||
out.rows, out.cols);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 8: data_table::render symbol is linkable (Wave 3.5 — issue 0081-I).
|
||||
// Does NOT call render (requires ImGui context); just takes its address.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_data_table_render_links() {
|
||||
// Taking the address of render verifies the linker resolved the symbol
|
||||
// from data_table.cpp inside fn_table_viz. No ImGui context needed.
|
||||
// Use the events_out overload (Phase 2) as the canonical full-signature check.
|
||||
auto* render_fn = static_cast<void(*)(const char*,
|
||||
const std::vector<data_table::TableInput>&,
|
||||
data_table::State&,
|
||||
std::vector<data_table::TableEvent>*,
|
||||
bool)>(&data_table::render);
|
||||
(void)render_fn;
|
||||
std::printf("PASS: data_table::render symbol links (address=%p)\n",
|
||||
reinterpret_cast<void*>(render_fn));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// main
|
||||
// ---------------------------------------------------------------------------
|
||||
int main() {
|
||||
std::printf("=== test_fn_table_viz_smoke ===\n");
|
||||
test_compute_stage_passthrough();
|
||||
test_auto_detect_type();
|
||||
test_compute_column_stats();
|
||||
test_tql_roundtrip();
|
||||
test_tql_apply_extended();
|
||||
test_lua_engine_compile();
|
||||
test_join_tables_trivial();
|
||||
test_data_table_render_links();
|
||||
std::printf("=== ALL TESTS PASSED ===\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user