5974484bd4
PARTE A - CellRenderer::Dots (v1.3.0): - Add Dots=8 to CellRenderer enum (data_table_types.h) - Add dots_separator/dots_max/dots_show_count/dots_glyph_size fields to ColumnSpec - Implement draw_cell_custom case Dots in data_table.cpp - Parses comma-separated cell value into tokens - Looks up each token in badges for color + optional glyph override - Per-dot tooltip via tooltip_on_hover - tql_emit: serialize renderer="dots" + dots_max/dots_show_count/dots_glyph_size/dots_separator - tql_apply: deserialize all Dots fields - tql_emit_test: +6 assertions (58 total, 0 failed) - tql_apply_test: +8 assertions (114 total, 0 failed) - test_column_specs: +2 tests (10/10 pass) PARTE B - dag_engine_ui fix: 10 cols -> 6 cols (submodule commit 61314b7) PARTE C - docs/capabilities/data_table_renderers.md: - Update to v1.3.0 - Add decision tree for renderer selection - Add CellRenderer::Dots section with canonical example - Add Common pitfalls section (multiple columns, badge for free-text, etc.) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
417 lines
16 KiB
C++
417 lines
16 KiB
C++
// test_column_specs.cpp — Smoke / back-compat tests for declarative cell renderers.
|
|
// Issue 0081-N, v1.1.0. Phase 2 (issue 0081-O, v1.2.0).
|
|
// Phase 2.5 (issue 0081-O.5, v1.3.0): Dots renderer.
|
|
//
|
|
// 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).
|
|
// 9. Dots renderer: ColumnSpec with CellRenderer::Dots + badges constructs correctly.
|
|
//
|
|
// 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
|
|
// fn_table_viz link correctly.
|
|
//
|
|
// Build: cmake --build cpp/build/linux --target test_column_specs
|
|
// Run: ./cpp/build/linux/tests/test_column_specs
|
|
|
|
#include "core/data_table_types.h"
|
|
#include "viz/data_table.h"
|
|
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace data_table;
|
|
|
|
// Shared trivial dataset (3 rows x 4 cols).
|
|
static const char* g_cells[] = {
|
|
"ok", "0.75", "250", "fn",
|
|
"error", "0.20", "3500", "type",
|
|
"warn", "1.00", "12000", "fn",
|
|
};
|
|
static const std::vector<std::string> g_headers = {"status", "progress", "duration_ms", "kind"};
|
|
static const std::vector<ColumnType> g_types = {
|
|
ColumnType::String, ColumnType::Float, ColumnType::Float, ColumnType::String
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 1: back-compat — TableInput without column_specs.
|
|
// ---------------------------------------------------------------------------
|
|
static void test_no_column_specs() {
|
|
TableInput t;
|
|
t.name = "t1";
|
|
t.rows = 3;
|
|
t.cols = 4;
|
|
t.cells = g_cells;
|
|
t.headers = g_headers;
|
|
t.types = g_types;
|
|
// column_specs intentionally left empty (back-compat: default behavior).
|
|
|
|
assert(t.column_specs.empty() && "column_specs must be empty by default");
|
|
|
|
// Verify that render symbol is still linkable (no ImGui context needed
|
|
// to take the address; the linker verifies the symbol resolves).
|
|
// 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");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 2: Badge renderer — construct TableInput with Badge column_spec.
|
|
// ---------------------------------------------------------------------------
|
|
static void test_badge_column_spec() {
|
|
TableInput t;
|
|
t.name = "t2";
|
|
t.rows = 3;
|
|
t.cols = 4;
|
|
t.cells = g_cells;
|
|
t.headers = g_headers;
|
|
t.types = g_types;
|
|
|
|
// Column 0: Badge renderer mapping ok/error/warn to colors.
|
|
ColumnSpec cs_status;
|
|
cs_status.id = "status";
|
|
cs_status.renderer = CellRenderer::Badge;
|
|
cs_status.badges = {
|
|
BadgeRule{"ok", "#22c55e", "OK"},
|
|
BadgeRule{"error", "#ef4444", ""}, // label empty -> use value
|
|
BadgeRule{"warn", "#f59e0b", "WARN"},
|
|
};
|
|
|
|
// Remaining columns: default Text.
|
|
t.column_specs.resize(4); // default-initialized = CellRenderer::Text
|
|
t.column_specs[0] = cs_status;
|
|
|
|
assert(t.column_specs.size() == 4);
|
|
assert(t.column_specs[0].renderer == CellRenderer::Badge);
|
|
assert(t.column_specs[0].badges.size() == 3);
|
|
assert(t.column_specs[1].renderer == CellRenderer::Text);
|
|
|
|
std::printf("PASS: test_badge_column_spec (3 badge rules, remaining cols Text)\n");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 3: Progress renderer.
|
|
// ---------------------------------------------------------------------------
|
|
static void test_progress_column_spec() {
|
|
TableInput t;
|
|
t.name = "t3";
|
|
t.rows = 3;
|
|
t.cols = 4;
|
|
t.cells = g_cells;
|
|
t.headers = g_headers;
|
|
t.types = g_types;
|
|
|
|
ColumnSpec cs_progress;
|
|
cs_progress.id = "progress";
|
|
cs_progress.renderer = CellRenderer::Progress;
|
|
cs_progress.progress_scale_100 = false;
|
|
cs_progress.progress_color_hex = "#3b82f6";
|
|
|
|
t.column_specs.resize(4);
|
|
t.column_specs[1] = cs_progress;
|
|
|
|
assert(t.column_specs[1].renderer == CellRenderer::Progress);
|
|
assert(!t.column_specs[1].progress_scale_100);
|
|
assert(t.column_specs[1].progress_color_hex == "#3b82f6");
|
|
|
|
std::printf("PASS: test_progress_column_spec\n");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 4: Duration renderer.
|
|
// ---------------------------------------------------------------------------
|
|
static void test_duration_column_spec() {
|
|
TableInput t;
|
|
t.name = "t4";
|
|
t.rows = 3;
|
|
t.cols = 4;
|
|
t.cells = g_cells;
|
|
t.headers = g_headers;
|
|
t.types = g_types;
|
|
|
|
ColumnSpec cs_dur;
|
|
cs_dur.id = "duration_ms";
|
|
cs_dur.renderer = CellRenderer::Duration;
|
|
cs_dur.duration_warn_ms = 500.0f;
|
|
cs_dur.duration_error_ms = 2000.0f;
|
|
|
|
t.column_specs.resize(4);
|
|
t.column_specs[2] = cs_dur;
|
|
|
|
assert(t.column_specs[2].renderer == CellRenderer::Duration);
|
|
assert(t.column_specs[2].duration_warn_ms == 500.0f);
|
|
assert(t.column_specs[2].duration_error_ms == 2000.0f);
|
|
|
|
std::printf("PASS: test_duration_column_spec (warn=500ms error=2000ms)\n");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 5: Icon renderer.
|
|
// ---------------------------------------------------------------------------
|
|
static void test_icon_column_spec() {
|
|
TableInput t;
|
|
t.name = "t5";
|
|
t.rows = 3;
|
|
t.cols = 4;
|
|
t.cells = g_cells;
|
|
t.headers = g_headers;
|
|
t.types = g_types;
|
|
|
|
ColumnSpec cs_icon;
|
|
cs_icon.id = "kind";
|
|
cs_icon.renderer = CellRenderer::Icon;
|
|
cs_icon.icon_map = {
|
|
IconMapEntry{"fn", "TI_BOLT", "#3b82f6"},
|
|
IconMapEntry{"type", "TI_DATABASE", ""},
|
|
};
|
|
|
|
t.column_specs.resize(4);
|
|
t.column_specs[3] = cs_icon;
|
|
|
|
assert(t.column_specs[3].renderer == CellRenderer::Icon);
|
|
assert(t.column_specs[3].icon_map.size() == 2);
|
|
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 (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");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 9: Dots renderer — ColumnSpec with CellRenderer::Dots + badges.
|
|
// Cell value "ok,error,ok,running,ok" — 5 tokens, 5 badge rules, dots_max=5.
|
|
// ---------------------------------------------------------------------------
|
|
static void test_dots_column_spec() {
|
|
// Dataset with one column holding comma-separated run statuses.
|
|
static const char* cells_dots[] = {
|
|
"ok,error,ok,running,ok",
|
|
"ok,ok,ok",
|
|
"failed,failed",
|
|
};
|
|
std::vector<std::string> hdrs = {"recent"};
|
|
std::vector<ColumnType> types = {ColumnType::String};
|
|
|
|
TableInput t;
|
|
t.name = "t_dots";
|
|
t.rows = 3;
|
|
t.cols = 1;
|
|
t.cells = cells_dots;
|
|
t.headers = hdrs;
|
|
t.types = types;
|
|
|
|
ColumnSpec cs;
|
|
cs.id = "recent";
|
|
cs.renderer = CellRenderer::Dots;
|
|
cs.badges = {
|
|
BadgeRule{"ok", "#22c55e", ""}, // default glyph ●
|
|
BadgeRule{"error", "#ef4444", ""},
|
|
BadgeRule{"failed", "#ef4444", ""},
|
|
BadgeRule{"running", "#eab308", ""},
|
|
BadgeRule{"pending", "#94a3b8", ""},
|
|
};
|
|
cs.dots_max = 5;
|
|
cs.dots_show_count = false;
|
|
cs.tooltip_on_hover = true;
|
|
|
|
t.column_specs.resize(1);
|
|
t.column_specs[0] = cs;
|
|
|
|
assert(t.column_specs[0].renderer == CellRenderer::Dots);
|
|
assert(t.column_specs[0].dots_max == 5);
|
|
assert(t.column_specs[0].badges.size() == 5);
|
|
assert(t.column_specs[0].dots_show_count == false);
|
|
assert(t.column_specs[0].tooltip_on_hover == true);
|
|
assert(t.column_specs[0].dots_separator == ',');
|
|
|
|
// Verify that the struct is copyable and the enum value is distinct.
|
|
ColumnSpec cs2 = cs;
|
|
assert(cs2.renderer == CellRenderer::Dots);
|
|
assert(cs2.dots_max == 5);
|
|
|
|
std::printf("PASS: test_dots_column_spec "
|
|
"(5 badge rules, dots_max=5, tooltip_on_hover=true)\n");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 10: Dots TQL roundtrip — emit + apply preserves Dots fields.
|
|
// ---------------------------------------------------------------------------
|
|
static void test_dots_tql_roundtrip() {
|
|
// Import tql::emit / tql::apply via the smoke test include paths.
|
|
// This test checks that the new Dots fields survive a TQL roundtrip.
|
|
// We populate aux_column_specs[0] and verify apply reconstructs them.
|
|
|
|
// We only check struct construction + enum identity here (no Lua context
|
|
// available without the full fn_table_viz library; the roundtrip is covered
|
|
// by test_fn_table_viz_smoke.cpp test_tql_roundtrip).
|
|
ColumnSpec cs;
|
|
cs.id = "recent";
|
|
cs.renderer = CellRenderer::Dots;
|
|
cs.dots_separator = ',';
|
|
cs.dots_max = 5;
|
|
cs.dots_show_count = false;
|
|
cs.badges = {{"ok", "#22c55e", ""}, {"error", "#ef4444", ""}};
|
|
|
|
// Build a State with aux_column_specs to verify the container accepts Dots.
|
|
data_table::State st;
|
|
st.stages.push_back(data_table::Stage{});
|
|
st.aux_column_specs.push_back({cs});
|
|
|
|
assert(st.aux_column_specs.size() == 1);
|
|
assert(st.aux_column_specs[0].size() == 1);
|
|
assert(st.aux_column_specs[0][0].renderer == CellRenderer::Dots);
|
|
assert(st.aux_column_specs[0][0].dots_max == 5);
|
|
assert(st.aux_column_specs[0][0].badges.size() == 2);
|
|
|
|
std::printf("PASS: test_dots_tql_roundtrip "
|
|
"(State::aux_column_specs accepts Dots spec)\n");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// main
|
|
// ---------------------------------------------------------------------------
|
|
int main() {
|
|
std::printf("=== test_column_specs ===\n");
|
|
test_no_column_specs();
|
|
test_badge_column_spec();
|
|
test_progress_column_spec();
|
|
test_duration_column_spec();
|
|
test_icon_column_spec();
|
|
test_button_column_spec_and_event_struct();
|
|
test_tooltip_column_spec();
|
|
test_render_backcompat_overload();
|
|
test_dots_column_spec();
|
|
test_dots_tql_roundtrip();
|
|
std::printf("=== ALL TESTS PASSED (10/10) ===\n");
|
|
return 0;
|
|
}
|