Merge pull request #328 from fiddlerwoaroof/adr007/display
feat(core,protocol): a display beside the key, painted by the three panels (improvise-cq1q.3)
This commit is contained in:
@@ -1739,3 +1739,28 @@ must name both lines of a duplicate; `ClaimError` carries only the id, so the
|
|||||||
loader keeps the line numbers. The virtual views `_records` and `_drill` are
|
loader keeps the line numbers. The virtual views `_records` and `_drill` are
|
||||||
not yet in `VIRTUAL_IDS`. improvise-p003 item 1 asks for them, and they arrive
|
not yet in `VIRTUAL_IDS`. improvise-p003 item 1 asks for them, and they arrive
|
||||||
with the ADR text fix (improvise-cq1q.1) rather than ahead of it.
|
with the ADR text fix (improvise-cq1q.1) rather than ahead of it.
|
||||||
|
|
||||||
|
## 21. A display beside the key (ADR 007 clause 2, improvise-cq1q.3)
|
||||||
|
|
||||||
|
`Category`, `Item` and `View` each carry a private `display`. It starts equal to
|
||||||
|
the key and changes only through `set_display` / `set_item_display`, which move
|
||||||
|
no key. Until the re-key (improvise-cq1q.4), nothing in production calls those
|
||||||
|
setters, so `display == name` everywhere and behaviour does not change. The
|
||||||
|
split exists so that the re-key changes what goes *into* the key, and nothing a
|
||||||
|
person sees moves with it.
|
||||||
|
|
||||||
|
**The key travels beside the display, never in place of it.** `CategoryRow`,
|
||||||
|
`TileEntry` and `ViewSummary` each gain a `display` field. The key field
|
||||||
|
(`name`, `category`) stays exactly what it was, because both front ends compose
|
||||||
|
commands from it: `resolve.rs` builds `SetAxis`, `RemoveItem` and the rest out of
|
||||||
|
those strings. Carrying the display *in* the key field would make every
|
||||||
|
command name a key that does not exist once the two differ. That is the gap
|
||||||
|
improvise-cq1q.11 names, and cq1q.7 later turns these key fields into ids. The
|
||||||
|
active-view marker in both view panels still compares keys for the same reason.
|
||||||
|
|
||||||
|
**What this first slice covers:** the category panel's headers, the tile bar and
|
||||||
|
the view list, in both front ends. Item rows, the grid frame, the page bar,
|
||||||
|
records-mode labels, CSV export headers, status messages and the view title
|
||||||
|
still show keys. They follow in later slices of this step, because the grid's
|
||||||
|
label tuples are also cell coordinates (`cell_key`, `innermost_group`), so their
|
||||||
|
display has to be added at projection time rather than in place.
|
||||||
|
|||||||
@@ -1998,3 +1998,29 @@ Rival, from the same review: strip the leading `_` before the space step, so
|
|||||||
that ` Draft` becomes `_draft`. This test fails (`left: "_draft"`), and so does
|
that ` Draft` becomes `_draft`. This test fails (`left: "_draft"`), and so does
|
||||||
`generated_ids_are_distinct_well_formed_and_never_reserved`. Before this test,
|
`generated_ids_are_distinct_well_formed_and_never_reserved`. Before this test,
|
||||||
the proptest was the only thing that saw the rival.
|
the proptest was the only thing that saw the rival.
|
||||||
|
|
||||||
|
## Displays beside keys (ADR 007 clause 2, improvise-cq1q.3, first slice)
|
||||||
|
|
||||||
|
Each renderer test below builds a projection whose display differs from its key.
|
||||||
|
The test-only way to get one is `set_display`, since no production path makes
|
||||||
|
them differ yet. Every test failed before the change, with the key drawn where
|
||||||
|
the display should be: `left: "Region"` against `right: "Sales Area"` in the
|
||||||
|
projection and wasm tests, and a missing "Sales Area" or "Overview" in the TUI
|
||||||
|
buffers.
|
||||||
|
|
||||||
|
### ui::project::tests::the_category_panel_shows_a_display_and_keeps_the_key_commands_name
|
||||||
|
|
||||||
|
It finds the row by `name == "Region"` before asserting on the display. A rival
|
||||||
|
that writes the display into `name` fails the lookup, which covers the half that
|
||||||
|
keeps commands working. `_Measure` is checked to fall back to its own key.
|
||||||
|
|
||||||
|
### ui::view_panel::tests::a_view_row_draws_its_display_and_marks_the_active_view_by_its_key
|
||||||
|
|
||||||
|
Rival: the active marker compares `summary.display == active_view`. This test
|
||||||
|
fails on that rival (no `▶ Overview`), because the active view is a key
|
||||||
|
(`ViewState::active_view`) and its display differs from it.
|
||||||
|
|
||||||
|
### render::tests::the_panels_paint_displays_and_keep_keys_for_what_a_command_names
|
||||||
|
|
||||||
|
The browser half of the same three panels. It also asserts that the view row is
|
||||||
|
still `active` when found by key.
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ mod tests {
|
|||||||
fn cat_name_works_for_both_variants() {
|
fn cat_name_works_for_both_variants() {
|
||||||
let header = CatTreeEntry::Category {
|
let header = CatTreeEntry::Category {
|
||||||
name: "Region".into(),
|
name: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
item_count: 2,
|
item_count: 2,
|
||||||
expanded: false,
|
expanded: false,
|
||||||
axis: crate::view::Axis::None,
|
axis: crate::view::Axis::None,
|
||||||
|
|||||||
@@ -426,6 +426,11 @@ fn non_item(category: &str, item: &str) -> CellId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What a person sees for a category key; the key itself when no category holds it.
|
||||||
|
fn category_display<'a>(model: &'a Model, name: &'a str) -> &'a str {
|
||||||
|
model.category(name).map_or(name, |c| c.display())
|
||||||
|
}
|
||||||
|
|
||||||
/// The categories the category panel draws: **every** category, virtual ones
|
/// The categories the category panel draws: **every** category, virtual ones
|
||||||
/// included, with their items and their axis (improvise-vjms, gotcha 7).
|
/// included, with their items and their axis (improvise-vjms, gotcha 7).
|
||||||
pub fn category_rows(model: &Model, view: &View) -> Vec<CategoryRow> {
|
pub fn category_rows(model: &Model, view: &View) -> Vec<CategoryRow> {
|
||||||
@@ -434,6 +439,7 @@ pub fn category_rows(model: &Model, view: &View) -> Vec<CategoryRow> {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|name| CategoryRow {
|
.map(|name| CategoryRow {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
|
display: category_display(model, name).to_string(),
|
||||||
items: model.effective_item_names(name),
|
items: model.effective_item_names(name),
|
||||||
// `try_axis_of`, not `axis_of`: a view that has never seen a category
|
// `try_axis_of`, not `axis_of`: a view that has never seen a category
|
||||||
// has no entry and `axis_of` panics. Unseen shows as `None`.
|
// has no entry and `axis_of` panics. Unseen shows as `None`.
|
||||||
@@ -463,6 +469,7 @@ pub fn view_summaries(app: &App) -> Vec<ViewSummary> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|(name, view)| ViewSummary {
|
.map(|(name, view)| ViewSummary {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
|
display: view.display().to_string(),
|
||||||
axes: view
|
axes: view
|
||||||
.category_axes
|
.category_axes
|
||||||
.iter()
|
.iter()
|
||||||
@@ -484,6 +491,7 @@ pub fn tiles_of(model: &Model, view: &View) -> Vec<TileEntry> {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|cat| TileEntry {
|
.map(|cat| TileEntry {
|
||||||
category: cat.to_string(),
|
category: cat.to_string(),
|
||||||
|
display: category_display(model, cat).to_string(),
|
||||||
axis: view.try_axis_of(cat).unwrap_or(Axis::None),
|
axis: view.try_axis_of(cat).unwrap_or(Axis::None),
|
||||||
kind: model
|
kind: model
|
||||||
.category(cat)
|
.category(cat)
|
||||||
@@ -1793,6 +1801,46 @@ mod tests {
|
|||||||
app
|
app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One category and the active view each shown under a display that differs from their key.
|
||||||
|
fn app_with_displays_unlike_their_keys() -> App {
|
||||||
|
let mut app = app_with_data();
|
||||||
|
let wb = &mut app.model_state.workbook;
|
||||||
|
wb.model
|
||||||
|
.category_mut("Region")
|
||||||
|
.unwrap()
|
||||||
|
.set_display("Sales Area");
|
||||||
|
wb.active_view_mut().set_display("Overview");
|
||||||
|
app
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_category_panel_shows_a_display_and_keeps_the_key_commands_name() {
|
||||||
|
let app = app_with_displays_unlike_their_keys();
|
||||||
|
let rows = category_rows(&app.model_state.workbook.model, app.view());
|
||||||
|
let region = rows.iter().find(|r| r.name == "Region").unwrap();
|
||||||
|
assert_eq!(region.display, "Sales Area");
|
||||||
|
let measure = rows.iter().find(|r| r.name == "_Measure").unwrap();
|
||||||
|
assert_eq!(measure.display, "_Measure");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_tile_shows_a_display_and_keeps_the_key_commands_name() {
|
||||||
|
let app = app_with_displays_unlike_their_keys();
|
||||||
|
let tiles = tiles_of(&app.model_state.workbook.model, app.view());
|
||||||
|
let region = tiles.iter().find(|t| t.category == "Region").unwrap();
|
||||||
|
assert_eq!(region.display, "Sales Area");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_view_list_shows_a_display_and_keeps_the_key_commands_name() {
|
||||||
|
let app = app_with_displays_unlike_their_keys();
|
||||||
|
let active = app.model_state.workbook.active_view.clone();
|
||||||
|
let summaries = view_summaries(&app);
|
||||||
|
let summary = summaries.iter().find(|s| s.name == active).unwrap();
|
||||||
|
assert_eq!(summary.display, "Overview");
|
||||||
|
assert_ne!(summary.name, "Overview");
|
||||||
|
}
|
||||||
|
|
||||||
/// A pivot-shaped default view plus a records-shaped second one, so a
|
/// A pivot-shaped default view plus a records-shaped second one, so a
|
||||||
/// switch between them crosses the boundary `settled_mode` watches.
|
/// switch between them crosses the boundary `settled_mode` watches.
|
||||||
fn app_with_a_records_shaped_second_view() -> App {
|
fn app_with_a_records_shaped_second_view() -> App {
|
||||||
|
|||||||
@@ -8,19 +8,27 @@ pub type ItemId = usize;
|
|||||||
pub struct Item {
|
pub struct Item {
|
||||||
pub id: ItemId,
|
pub id: ItemId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// What a person sees; the key until a rename (ADR 007 clause 2).
|
||||||
|
display: String,
|
||||||
/// Parent group name, if any
|
/// Parent group name, if any
|
||||||
pub group: Option<String>,
|
pub group: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
pub fn new(id: ItemId, name: impl Into<String>) -> Self {
|
pub fn new(id: ItemId, name: impl Into<String>) -> Self {
|
||||||
|
let name = name.into();
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
name: name.into(),
|
display: name.clone(),
|
||||||
|
name,
|
||||||
group: None,
|
group: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn display(&self) -> &str {
|
||||||
|
&self.display
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_group(mut self, group: impl Into<String>) -> Self {
|
pub fn with_group(mut self, group: impl Into<String>) -> Self {
|
||||||
self.group = Some(group.into());
|
self.group = Some(group.into());
|
||||||
self
|
self
|
||||||
@@ -129,6 +137,8 @@ impl CategoryKind {
|
|||||||
pub struct Category {
|
pub struct Category {
|
||||||
pub id: CategoryId,
|
pub id: CategoryId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// What a person sees; the key until a rename (ADR 007 clause 2).
|
||||||
|
display: String,
|
||||||
/// Items in insertion order
|
/// Items in insertion order
|
||||||
pub items: IndexMap<String, Item>,
|
pub items: IndexMap<String, Item>,
|
||||||
/// Named groups (hierarchy nodes)
|
/// Named groups (hierarchy nodes)
|
||||||
@@ -141,9 +151,11 @@ pub struct Category {
|
|||||||
|
|
||||||
impl Category {
|
impl Category {
|
||||||
pub fn new(id: CategoryId, name: impl Into<String>) -> Self {
|
pub fn new(id: CategoryId, name: impl Into<String>) -> Self {
|
||||||
|
let name = name.into();
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
name: name.into(),
|
display: name.clone(),
|
||||||
|
name,
|
||||||
items: IndexMap::new(),
|
items: IndexMap::new(),
|
||||||
groups: Vec::new(),
|
groups: Vec::new(),
|
||||||
next_item_id: 0,
|
next_item_id: 0,
|
||||||
@@ -151,6 +163,23 @@ impl Category {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn display(&self) -> &str {
|
||||||
|
&self.display
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes what a person sees and nothing else: the key does not move.
|
||||||
|
pub fn set_display(&mut self, display: impl Into<String>) {
|
||||||
|
self.display = display.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes an item's display; false when the category has no such item.
|
||||||
|
pub fn set_item_display(&mut self, item: &str, display: impl Into<String>) -> bool {
|
||||||
|
self.items
|
||||||
|
.get_mut(item)
|
||||||
|
.map(|i| i.display = display.into())
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_kind(mut self, kind: CategoryKind) -> Self {
|
pub fn with_kind(mut self, kind: CategoryKind) -> Self {
|
||||||
self.kind = kind;
|
self.kind = kind;
|
||||||
self
|
self
|
||||||
@@ -264,6 +293,20 @@ mod tests {
|
|||||||
Category::new(0, "Region")
|
Category::new(0, "Region")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_display_starts_as_the_key_and_changing_it_moves_no_key() {
|
||||||
|
let mut c = cat();
|
||||||
|
c.add_item("East");
|
||||||
|
assert_eq!(c.display(), "Region");
|
||||||
|
assert_eq!(c.items["East"].display(), "East");
|
||||||
|
c.set_display("Sales Area");
|
||||||
|
assert!(c.set_item_display("East", "Eastern"));
|
||||||
|
assert_eq!((c.name.as_str(), c.display()), ("Region", "Sales Area"));
|
||||||
|
assert_eq!(c.items["East"].display(), "Eastern");
|
||||||
|
assert_eq!(c.items["East"].name, "East");
|
||||||
|
assert!(!c.set_item_display("West", "Western"), "no such item");
|
||||||
|
}
|
||||||
|
|
||||||
/// improvise-2lh: call sites ask `of_name` rather than compare literals,
|
/// improvise-2lh: call sites ask `of_name` rather than compare literals,
|
||||||
/// so both directions of the mapping must agree.
|
/// so both directions of the mapping must agree.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ pub enum GroupFold {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct View {
|
pub struct View {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// What a person sees; the key until a rename (ADR 007 clause 2).
|
||||||
|
display: String,
|
||||||
/// Axis assignment for each category
|
/// Axis assignment for each category
|
||||||
pub category_axes: IndexMap<String, Axis>,
|
pub category_axes: IndexMap<String, Axis>,
|
||||||
/// For page axis: selected item per category
|
/// For page axis: selected item per category
|
||||||
@@ -65,6 +67,7 @@ impl std::fmt::Debug for View {
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
let View {
|
let View {
|
||||||
name,
|
name,
|
||||||
|
display,
|
||||||
category_axes,
|
category_axes,
|
||||||
page_selections,
|
page_selections,
|
||||||
hidden_items,
|
hidden_items,
|
||||||
@@ -86,6 +89,7 @@ impl std::fmt::Debug for View {
|
|||||||
.collect();
|
.collect();
|
||||||
f.debug_struct("View")
|
f.debug_struct("View")
|
||||||
.field("name", name)
|
.field("name", name)
|
||||||
|
.field("display", display)
|
||||||
.field("category_axes", category_axes)
|
.field("category_axes", category_axes)
|
||||||
.field("page_selections", &pages)
|
.field("page_selections", &pages)
|
||||||
.field("hidden_items", &sorted(hidden_items))
|
.field("hidden_items", &sorted(hidden_items))
|
||||||
@@ -103,9 +107,20 @@ impl View {
|
|||||||
VIRTUAL_VIEW_NAMES.contains(&self.name.as_str())
|
VIRTUAL_VIEW_NAMES.contains(&self.name.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn display(&self) -> &str {
|
||||||
|
&self.display
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes what a person sees and nothing else: the key does not move.
|
||||||
|
pub fn set_display(&mut self, display: impl Into<String>) {
|
||||||
|
self.display = display.into();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(name: impl Into<String>) -> Self {
|
pub fn new(name: impl Into<String>) -> Self {
|
||||||
|
let name = name.into();
|
||||||
Self {
|
Self {
|
||||||
name: name.into(),
|
display: name.clone(),
|
||||||
|
name,
|
||||||
category_axes: IndexMap::new(),
|
category_axes: IndexMap::new(),
|
||||||
page_selections: HashMap::new(),
|
page_selections: HashMap::new(),
|
||||||
hidden_items: HashMap::new(),
|
hidden_items: HashMap::new(),
|
||||||
@@ -375,6 +390,14 @@ mod tests {
|
|||||||
use crate::view::Axis;
|
use crate::view::Axis;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_view_display_starts_as_the_key_and_changing_it_moves_no_key() {
|
||||||
|
let mut v = View::new("Default");
|
||||||
|
assert_eq!(v.display(), "Default");
|
||||||
|
v.set_display("Overview");
|
||||||
|
assert_eq!((v.name.as_str(), v.display()), ("Default", "Overview"));
|
||||||
|
}
|
||||||
|
|
||||||
/// improvise-vw47: the count is the starting guess, never the answer.
|
/// improvise-vw47: the count is the starting guess, never the answer.
|
||||||
/// What a taken name costs: `context/test-evidence/core.md`.
|
/// What a taken name costs: `context/test-evidence/core.md`.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -514,6 +514,7 @@ mod tests {
|
|||||||
live.fold(&Command::Project(Projection::CategoryTree(
|
live.fold(&Command::Project(Projection::CategoryTree(
|
||||||
CategoryTreeUpdate::Categories(vec![CategoryRow {
|
CategoryTreeUpdate::Categories(vec![CategoryRow {
|
||||||
name: "Region".into(),
|
name: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
items: vec!["North".into(), "South".into()],
|
items: vec!["North".into(), "South".into()],
|
||||||
axis: Axis::Row,
|
axis: Axis::Row,
|
||||||
}]),
|
}]),
|
||||||
@@ -523,11 +524,13 @@ mod tests {
|
|||||||
])));
|
])));
|
||||||
live.fold(&Command::Project(Projection::TileBar(vec![TileEntry {
|
live.fold(&Command::Project(Projection::TileBar(vec![TileEntry {
|
||||||
category: "Region".into(),
|
category: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
kind: crate::CategoryKind::Regular,
|
kind: crate::CategoryKind::Regular,
|
||||||
axis: Axis::Row,
|
axis: Axis::Row,
|
||||||
}])));
|
}])));
|
||||||
live.fold(&Command::Project(Projection::ViewList(vec![ViewSummary {
|
live.fold(&Command::Project(Projection::ViewList(vec![ViewSummary {
|
||||||
name: "Default".into(),
|
name: "Default".into(),
|
||||||
|
display: "Default".into(),
|
||||||
axes: vec![("Region".into(), Axis::Row)],
|
axes: vec![("Region".into(), Axis::Row)],
|
||||||
}])));
|
}])));
|
||||||
live.fold(&Command::SetSelected { row: 1, col: 1 });
|
live.fold(&Command::SetSelected { row: 1, col: 1 });
|
||||||
|
|||||||
@@ -556,10 +556,12 @@ mod tests {
|
|||||||
Command::Project(Projection::Formulas(vec!["Profit = Revenue - Cost".into()])),
|
Command::Project(Projection::Formulas(vec!["Profit = Revenue - Cost".into()])),
|
||||||
Command::Project(Projection::ViewList(vec![ViewSummary {
|
Command::Project(Projection::ViewList(vec![ViewSummary {
|
||||||
name: "Default".into(),
|
name: "Default".into(),
|
||||||
|
display: "Default".into(),
|
||||||
axes: vec![("Region".into(), Axis::Row)],
|
axes: vec![("Region".into(), Axis::Row)],
|
||||||
}])),
|
}])),
|
||||||
Command::Project(Projection::TileBar(vec![TileEntry {
|
Command::Project(Projection::TileBar(vec![TileEntry {
|
||||||
category: "Region".into(),
|
category: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
kind: crate::CategoryKind::Regular,
|
kind: crate::CategoryKind::Regular,
|
||||||
axis: Axis::Page,
|
axis: Axis::Page,
|
||||||
}])),
|
}])),
|
||||||
|
|||||||
@@ -759,6 +759,8 @@ impl WidgetCache for GridCache {
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct CategoryRow {
|
pub struct CategoryRow {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// What the panel shows; `name` stays what a command names (ADR 007).
|
||||||
|
pub display: String,
|
||||||
pub items: Vec<String>,
|
pub items: Vec<String>,
|
||||||
pub axis: Axis,
|
pub axis: Axis,
|
||||||
}
|
}
|
||||||
@@ -857,6 +859,8 @@ impl WidgetCache for FormulaCache {
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ViewSummary {
|
pub struct ViewSummary {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// What the view panel shows; `name` stays what a command names.
|
||||||
|
pub display: String,
|
||||||
/// `(category, axis)` for the categories this view displays.
|
/// `(category, axis)` for the categories this view displays.
|
||||||
pub axes: Vec<(String, Axis)>,
|
pub axes: Vec<(String, Axis)>,
|
||||||
}
|
}
|
||||||
@@ -881,6 +885,8 @@ impl WidgetCache for ViewListCache {
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct TileEntry {
|
pub struct TileEntry {
|
||||||
pub category: String,
|
pub category: String,
|
||||||
|
/// What the tile shows; `category` stays what a command names.
|
||||||
|
pub display: String,
|
||||||
pub axis: Axis,
|
pub axis: Axis,
|
||||||
pub kind: CategoryKind,
|
pub kind: CategoryKind,
|
||||||
}
|
}
|
||||||
@@ -1072,6 +1078,7 @@ mod tests {
|
|||||||
fn tile(category: &str, axis: Axis, kind: CategoryKind) -> TileEntry {
|
fn tile(category: &str, axis: Axis, kind: CategoryKind) -> TileEntry {
|
||||||
TileEntry {
|
TileEntry {
|
||||||
category: category.to_string(),
|
category: category.to_string(),
|
||||||
|
display: category.to_string(),
|
||||||
axis,
|
axis,
|
||||||
kind,
|
kind,
|
||||||
}
|
}
|
||||||
@@ -1156,6 +1163,7 @@ mod tests {
|
|||||||
fn cat(name: &str, items: &[&str], axis: Axis) -> CategoryRow {
|
fn cat(name: &str, items: &[&str], axis: Axis) -> CategoryRow {
|
||||||
CategoryRow {
|
CategoryRow {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
display: name.into(),
|
||||||
items: items.iter().map(|s| (*s).to_string()).collect(),
|
items: items.iter().map(|s| (*s).to_string()).collect(),
|
||||||
axis,
|
axis,
|
||||||
}
|
}
|
||||||
@@ -1801,12 +1809,14 @@ mod tests {
|
|||||||
|
|
||||||
let views = ViewListCache::folded(&[vec![ViewSummary {
|
let views = ViewListCache::folded(&[vec![ViewSummary {
|
||||||
name: "Default".into(),
|
name: "Default".into(),
|
||||||
|
display: "Default".into(),
|
||||||
axes: vec![("Region".into(), Axis::Row)],
|
axes: vec![("Region".into(), Axis::Row)],
|
||||||
}]]);
|
}]]);
|
||||||
assert_eq!(views.views[0].name, "Default");
|
assert_eq!(views.views[0].name, "Default");
|
||||||
|
|
||||||
let tiles = TileBarCache::folded(&[vec![TileEntry {
|
let tiles = TileBarCache::folded(&[vec![TileEntry {
|
||||||
category: "Region".into(),
|
category: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
axis: Axis::Page,
|
axis: Axis::Page,
|
||||||
kind: crate::CategoryKind::Regular,
|
kind: crate::CategoryKind::Regular,
|
||||||
}]]);
|
}]]);
|
||||||
@@ -2201,10 +2211,12 @@ mod tests {
|
|||||||
Projection::Formulas(vec!["Profit = Revenue - Cost".into()]),
|
Projection::Formulas(vec!["Profit = Revenue - Cost".into()]),
|
||||||
Projection::ViewList(vec![ViewSummary {
|
Projection::ViewList(vec![ViewSummary {
|
||||||
name: "Default".into(),
|
name: "Default".into(),
|
||||||
|
display: "Default".into(),
|
||||||
axes: vec![("Region".into(), Axis::None)],
|
axes: vec![("Region".into(), Axis::None)],
|
||||||
}]),
|
}]),
|
||||||
Projection::TileBar(vec![TileEntry {
|
Projection::TileBar(vec![TileEntry {
|
||||||
category: "Region".into(),
|
category: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
axis: Axis::Page,
|
axis: Axis::Page,
|
||||||
kind: crate::CategoryKind::Regular,
|
kind: crate::CategoryKind::Regular,
|
||||||
}]),
|
}]),
|
||||||
@@ -2242,6 +2254,7 @@ mod tests {
|
|||||||
Projection::Formulas(vec!["Profit = Revenue - Cost".into()]),
|
Projection::Formulas(vec!["Profit = Revenue - Cost".into()]),
|
||||||
Projection::TileBar(vec![TileEntry {
|
Projection::TileBar(vec![TileEntry {
|
||||||
category: "Region".into(),
|
category: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
axis: Axis::Page,
|
axis: Axis::Page,
|
||||||
kind: crate::CategoryKind::Regular,
|
kind: crate::CategoryKind::Regular,
|
||||||
}]),
|
}]),
|
||||||
|
|||||||
@@ -1892,11 +1892,13 @@ mod tests {
|
|||||||
crate::CategoryTreeUpdate::Categories(vec![
|
crate::CategoryTreeUpdate::Categories(vec![
|
||||||
crate::CategoryRow {
|
crate::CategoryRow {
|
||||||
name: "Region".into(),
|
name: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
items: vec!["North".into(), "South".into()],
|
items: vec!["North".into(), "South".into()],
|
||||||
axis: Axis::Row,
|
axis: Axis::Row,
|
||||||
},
|
},
|
||||||
crate::CategoryRow {
|
crate::CategoryRow {
|
||||||
name: "Product".into(),
|
name: "Product".into(),
|
||||||
|
display: "Product".into(),
|
||||||
items: vec!["Shirts".into()],
|
items: vec!["Shirts".into()],
|
||||||
axis: Axis::Column,
|
axis: Axis::Column,
|
||||||
},
|
},
|
||||||
@@ -1975,11 +1977,13 @@ mod tests {
|
|||||||
state.tiles.tiles = vec![
|
state.tiles.tiles = vec![
|
||||||
crate::TileEntry {
|
crate::TileEntry {
|
||||||
category: "Region".into(),
|
category: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
axis: Axis::Row,
|
axis: Axis::Row,
|
||||||
kind: crate::CategoryKind::Regular,
|
kind: crate::CategoryKind::Regular,
|
||||||
},
|
},
|
||||||
crate::TileEntry {
|
crate::TileEntry {
|
||||||
category: "Product".into(),
|
category: "Product".into(),
|
||||||
|
display: "Product".into(),
|
||||||
axis: Axis::Column,
|
axis: Axis::Column,
|
||||||
kind: crate::CategoryKind::Regular,
|
kind: crate::CategoryKind::Regular,
|
||||||
},
|
},
|
||||||
@@ -2334,10 +2338,12 @@ mod tests {
|
|||||||
state.fold(&Command::Project(Projection::ViewList(vec![
|
state.fold(&Command::Project(Projection::ViewList(vec![
|
||||||
crate::ViewSummary {
|
crate::ViewSummary {
|
||||||
name: "Default".into(),
|
name: "Default".into(),
|
||||||
|
display: "Default".into(),
|
||||||
axes: vec![],
|
axes: vec![],
|
||||||
},
|
},
|
||||||
crate::ViewSummary {
|
crate::ViewSummary {
|
||||||
name: "Plan".into(),
|
name: "Plan".into(),
|
||||||
|
display: "Plan".into(),
|
||||||
axes: vec![],
|
axes: vec![],
|
||||||
},
|
},
|
||||||
])));
|
])));
|
||||||
@@ -2617,6 +2623,7 @@ mod tests {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|name| crate::CategoryRow {
|
.map(|name| crate::CategoryRow {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
display: name.into(),
|
||||||
items: vec!["North".into(), "South".into()],
|
items: vec!["North".into(), "South".into()],
|
||||||
axis: Axis::Row,
|
axis: Axis::Row,
|
||||||
})
|
})
|
||||||
@@ -2635,6 +2642,7 @@ mod tests {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|name| crate::ViewSummary {
|
.map(|name| crate::ViewSummary {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
display: name.into(),
|
||||||
axes: Vec::new(),
|
axes: Vec::new(),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -2642,6 +2650,7 @@ mod tests {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|category| crate::TileEntry {
|
.map(|category| crate::TileEntry {
|
||||||
category: category.into(),
|
category: category.into(),
|
||||||
|
display: category.into(),
|
||||||
axis: Axis::Row,
|
axis: Axis::Row,
|
||||||
kind: crate::CategoryKind::Regular,
|
kind: crate::CategoryKind::Regular,
|
||||||
})
|
})
|
||||||
@@ -3747,6 +3756,7 @@ mod tests {
|
|||||||
let mut tiles = in_mode(AppMode::TileSelect);
|
let mut tiles = in_mode(AppMode::TileSelect);
|
||||||
tiles.tiles.tiles = vec![crate::TileEntry {
|
tiles.tiles.tiles = vec![crate::TileEntry {
|
||||||
category: "Region".into(),
|
category: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
axis: improvise_core::view::Axis::Row,
|
axis: improvise_core::view::Axis::Row,
|
||||||
kind: crate::CategoryKind::Regular,
|
kind: crate::CategoryKind::Regular,
|
||||||
}];
|
}];
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ use crate::render::{CategoryTreeCache, ViewSummary};
|
|||||||
/// so the entry list *is* what the panel draws.
|
/// so the entry list *is* what the panel draws.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum CatTreeEntry {
|
pub enum CatTreeEntry {
|
||||||
/// Category header row: name, item count, expanded?
|
/// Category header row: name (the key), what it shows, item count, expanded?
|
||||||
Category {
|
Category {
|
||||||
name: String,
|
name: String,
|
||||||
|
display: String,
|
||||||
item_count: usize,
|
item_count: usize,
|
||||||
expanded: bool,
|
expanded: bool,
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
@@ -49,6 +50,7 @@ pub fn flatten_cat_tree(
|
|||||||
let is_expanded = expanded.contains(&row.name);
|
let is_expanded = expanded.contains(&row.name);
|
||||||
entries.push(CatTreeEntry::Category {
|
entries.push(CatTreeEntry::Category {
|
||||||
name: row.name.clone(),
|
name: row.name.clone(),
|
||||||
|
display: row.display.clone(),
|
||||||
item_count: row.items.len(),
|
item_count: row.items.len(),
|
||||||
expanded: is_expanded,
|
expanded: is_expanded,
|
||||||
axis: row.axis,
|
axis: row.axis,
|
||||||
@@ -94,6 +96,7 @@ mod tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|(name, items)| CategoryRow {
|
.map(|(name, items)| CategoryRow {
|
||||||
name: (*name).to_string(),
|
name: (*name).to_string(),
|
||||||
|
display: (*name).to_string(),
|
||||||
items: items.iter().map(|i| i.to_string()).collect(),
|
items: items.iter().map(|i| i.to_string()).collect(),
|
||||||
axis: Axis::None,
|
axis: Axis::None,
|
||||||
})
|
})
|
||||||
@@ -112,6 +115,7 @@ mod tests {
|
|||||||
tree,
|
tree,
|
||||||
vec![CatTreeEntry::Category {
|
vec![CatTreeEntry::Category {
|
||||||
name: "Region".into(),
|
name: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
item_count: 2,
|
item_count: 2,
|
||||||
expanded: false,
|
expanded: false,
|
||||||
axis: Axis::None,
|
axis: Axis::None,
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ fn view_rows(state: &ClientState) -> Option<Vec<RenderViewRow>> {
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, summary)| RenderViewRow {
|
.map(|(i, summary)| RenderViewRow {
|
||||||
name: summary.name.clone(),
|
name: summary.display.clone(),
|
||||||
axes: improvise_protocol::viewmodel::view_axis_summary(summary),
|
axes: improvise_protocol::viewmodel::view_axis_summary(summary),
|
||||||
active: summary.name == state.view.active_view,
|
active: summary.name == state.view.active_view,
|
||||||
selected: i == state.view.view_panel_cursor,
|
selected: i == state.view.view_panel_cursor,
|
||||||
@@ -396,7 +396,7 @@ fn tile_row(state: &ClientState) -> Vec<RenderTile> {
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, tile)| RenderTile {
|
.map(|(i, tile)| RenderTile {
|
||||||
category: tile.category.clone(),
|
category: tile.display.clone(),
|
||||||
axis: tile.axis.plain_label(),
|
axis: tile.axis.plain_label(),
|
||||||
selected: selecting && i == state.view.tile_cat_idx,
|
selected: selecting && i == state.view.tile_cat_idx,
|
||||||
})
|
})
|
||||||
@@ -468,12 +468,13 @@ impl RenderCatRow {
|
|||||||
fn of(entry: &CatTreeEntry, selected: bool) -> Self {
|
fn of(entry: &CatTreeEntry, selected: bool) -> Self {
|
||||||
match entry {
|
match entry {
|
||||||
CatTreeEntry::Category {
|
CatTreeEntry::Category {
|
||||||
name,
|
display,
|
||||||
item_count,
|
item_count,
|
||||||
expanded,
|
expanded,
|
||||||
axis,
|
axis,
|
||||||
|
..
|
||||||
} => Self {
|
} => Self {
|
||||||
label: name.clone(),
|
label: display.clone(),
|
||||||
is_category: true,
|
is_category: true,
|
||||||
axis: axis.initial().to_string(),
|
axis: axis.initial().to_string(),
|
||||||
item_count: Some(*item_count),
|
item_count: Some(*item_count),
|
||||||
@@ -806,11 +807,13 @@ mod tests {
|
|||||||
CategoryTreeUpdate::Categories(vec![
|
CategoryTreeUpdate::Categories(vec![
|
||||||
CategoryRow {
|
CategoryRow {
|
||||||
name: "Region".into(),
|
name: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
items: vec!["North".into(), "South".into()],
|
items: vec!["North".into(), "South".into()],
|
||||||
axis: Axis::Row,
|
axis: Axis::Row,
|
||||||
},
|
},
|
||||||
CategoryRow {
|
CategoryRow {
|
||||||
name: "Measure".into(),
|
name: "Measure".into(),
|
||||||
|
display: "Measure".into(),
|
||||||
items: vec!["Revenue".into()],
|
items: vec!["Revenue".into()],
|
||||||
axis: Axis::Column,
|
axis: Axis::Column,
|
||||||
},
|
},
|
||||||
@@ -843,6 +846,39 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_panels_paint_displays_and_keep_keys_for_what_a_command_names() {
|
||||||
|
let mut client = init();
|
||||||
|
let state = client.state_mut();
|
||||||
|
state.fold(&Command::Project(Projection::CategoryTree(
|
||||||
|
CategoryTreeUpdate::Categories(vec![CategoryRow {
|
||||||
|
name: "Region".into(),
|
||||||
|
display: "Sales Area".into(),
|
||||||
|
items: vec!["North".into()],
|
||||||
|
axis: Axis::Row,
|
||||||
|
}]),
|
||||||
|
)));
|
||||||
|
state.fold(&Command::Project(Projection::ViewList(vec![ViewSummary {
|
||||||
|
name: "Default".into(),
|
||||||
|
display: "Overview".into(),
|
||||||
|
axes: vec![],
|
||||||
|
}])));
|
||||||
|
state.fold(&Command::Project(Projection::TileBar(vec![TileEntry {
|
||||||
|
category: "Region".into(),
|
||||||
|
display: "Sales Area".into(),
|
||||||
|
axis: Axis::Row,
|
||||||
|
kind: improvise_protocol::CategoryKind::Regular,
|
||||||
|
}])));
|
||||||
|
state.view.view_panel_open = true;
|
||||||
|
state.view.active_view = "Default".into();
|
||||||
|
let model = client.model();
|
||||||
|
assert_eq!(model.categories[0].label, "Sales Area");
|
||||||
|
assert_eq!(model.tiles[0].category, "Sales Area");
|
||||||
|
let views = model.views.expect("the view panel is open");
|
||||||
|
assert_eq!(views[0].name, "Overview");
|
||||||
|
assert!(views[0].active, "the active view is still found by its key");
|
||||||
|
}
|
||||||
|
|
||||||
fn client_with_panels() -> Client {
|
fn client_with_panels() -> Client {
|
||||||
let mut client = init();
|
let mut client = init();
|
||||||
client
|
client
|
||||||
@@ -850,10 +886,12 @@ mod tests {
|
|||||||
.fold(&Command::Project(Projection::ViewList(vec![
|
.fold(&Command::Project(Projection::ViewList(vec![
|
||||||
ViewSummary {
|
ViewSummary {
|
||||||
name: "Default".into(),
|
name: "Default".into(),
|
||||||
|
display: "Default".into(),
|
||||||
axes: vec![("Region".into(), Axis::Row), ("_Dim".into(), Axis::Column)],
|
axes: vec![("Region".into(), Axis::Row), ("_Dim".into(), Axis::Column)],
|
||||||
},
|
},
|
||||||
ViewSummary {
|
ViewSummary {
|
||||||
name: "Plan".into(),
|
name: "Plan".into(),
|
||||||
|
display: "Plan".into(),
|
||||||
axes: vec![],
|
axes: vec![],
|
||||||
},
|
},
|
||||||
])));
|
])));
|
||||||
@@ -867,11 +905,13 @@ mod tests {
|
|||||||
.fold(&Command::Project(Projection::TileBar(vec![
|
.fold(&Command::Project(Projection::TileBar(vec![
|
||||||
TileEntry {
|
TileEntry {
|
||||||
category: "Region".into(),
|
category: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
axis: Axis::Row,
|
axis: Axis::Row,
|
||||||
kind: improvise_protocol::CategoryKind::Regular,
|
kind: improvise_protocol::CategoryKind::Regular,
|
||||||
},
|
},
|
||||||
TileEntry {
|
TileEntry {
|
||||||
category: "Product".into(),
|
category: "Product".into(),
|
||||||
|
display: "Product".into(),
|
||||||
axis: Axis::None,
|
axis: Axis::None,
|
||||||
kind: improvise_protocol::CategoryKind::Regular,
|
kind: improvise_protocol::CategoryKind::Regular,
|
||||||
},
|
},
|
||||||
@@ -1192,11 +1232,13 @@ mod tests {
|
|||||||
.fold(&Command::Project(Projection::TileBar(vec![
|
.fold(&Command::Project(Projection::TileBar(vec![
|
||||||
TileEntry {
|
TileEntry {
|
||||||
category: "Quarter".into(),
|
category: "Quarter".into(),
|
||||||
|
display: "Quarter".into(),
|
||||||
axis: Axis::Page,
|
axis: Axis::Page,
|
||||||
kind: improvise_protocol::CategoryKind::Regular,
|
kind: improvise_protocol::CategoryKind::Regular,
|
||||||
},
|
},
|
||||||
TileEntry {
|
TileEntry {
|
||||||
category: "Region".into(),
|
category: "Region".into(),
|
||||||
|
display: "Region".into(),
|
||||||
axis: Axis::Column,
|
axis: Axis::Column,
|
||||||
kind: improvise_protocol::CategoryKind::Regular,
|
kind: improvise_protocol::CategoryKind::Regular,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -73,14 +73,15 @@ impl PanelContent for CategoryContent {
|
|||||||
|
|
||||||
match &self.tree[index] {
|
match &self.tree[index] {
|
||||||
CatTreeEntry::Category {
|
CatTreeEntry::Category {
|
||||||
name,
|
display,
|
||||||
item_count,
|
item_count,
|
||||||
expanded,
|
expanded,
|
||||||
axis,
|
axis,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let indicator = if *expanded { "▼" } else { "▶" };
|
let indicator = if *expanded { "▼" } else { "▶" };
|
||||||
let (axis_str, axis_style) = axis_display(*axis);
|
let (axis_str, axis_style) = axis_display(*axis);
|
||||||
let name_part = format!("{indicator} {name} ({item_count})");
|
let name_part = format!("{indicator} {display} ({item_count})");
|
||||||
let axis_part = format!(" [{axis_str}]");
|
let axis_part = format!(" [{axis_str}]");
|
||||||
|
|
||||||
buf.set_string(inner.x, y, &name_part, base_style);
|
buf.set_string(inner.x, y, &name_part, base_style);
|
||||||
@@ -114,6 +115,7 @@ mod tests {
|
|||||||
let cache = CategoryTreeCache {
|
let cache = CategoryTreeCache {
|
||||||
categories: vec![CategoryRow {
|
categories: vec![CategoryRow {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
|
display: name.to_string(),
|
||||||
axis: Axis::Row,
|
axis: Axis::Row,
|
||||||
items: vec!["x".to_string()],
|
items: vec!["x".to_string()],
|
||||||
}],
|
}],
|
||||||
@@ -129,6 +131,29 @@ mod tests {
|
|||||||
buf.content()[31..59].iter().map(|c| c.symbol()).collect()
|
buf.content()[31..59].iter().map(|c| c.symbol()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_category_header_draws_its_display_not_its_key() {
|
||||||
|
let cache = CategoryTreeCache {
|
||||||
|
categories: vec![CategoryRow {
|
||||||
|
name: "Region".to_string(),
|
||||||
|
display: "Sales Area".to_string(),
|
||||||
|
axis: Axis::Row,
|
||||||
|
items: vec!["x".to_string()],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
let area = Rect::new(0, 0, 30, 4);
|
||||||
|
let mut buf = Buffer::empty(area);
|
||||||
|
Panel::new(
|
||||||
|
CategoryContent::new(&cache, &Default::default()),
|
||||||
|
&AppMode::Normal,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.render(area, &mut buf);
|
||||||
|
let row: String = buf.content()[31..59].iter().map(|c| c.symbol()).collect();
|
||||||
|
assert!(row.contains("Sales Area"), "{row}");
|
||||||
|
assert!(!row.contains("Region"), "{row}");
|
||||||
|
}
|
||||||
|
|
||||||
/// improvise-8l0c — `context/test-evidence/tui.md`.
|
/// improvise-8l0c — `context/test-evidence/tui.md`.
|
||||||
#[test]
|
#[test]
|
||||||
fn the_axis_tag_sits_beside_a_multibyte_category_name() {
|
fn the_axis_tag_sits_beside_a_multibyte_category_name() {
|
||||||
|
|||||||
+18
-1
@@ -79,7 +79,7 @@ impl<'a> Widget for TileBar<'a> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|tile| {
|
.map(|tile| {
|
||||||
let (axis_symbol, _) = TileBar::axis_display(tile.axis);
|
let (axis_symbol, _) = TileBar::axis_display(tile.axis);
|
||||||
format!(" [{} {axis_symbol}] ", tile.category)
|
format!(" [{} {axis_symbol}] ", tile.display)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let widths: Vec<u16> = labels.iter().map(|l| l.width() as u16).collect();
|
let widths: Vec<u16> = labels.iter().map(|l| l.width() as u16).collect();
|
||||||
@@ -190,6 +190,23 @@ mod tests {
|
|||||||
assert_eq!(was, now);
|
assert_eq!(was, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_tile_draws_its_display_not_its_key() {
|
||||||
|
let mut app = app_with_two_categories();
|
||||||
|
let wb = &mut app.model_state.workbook;
|
||||||
|
wb.model
|
||||||
|
.category_mut("Region")
|
||||||
|
.unwrap()
|
||||||
|
.set_display("Sales Area");
|
||||||
|
let cache = project::tile_bar_of(&app);
|
||||||
|
let area = Rect::new(0, 0, 80, 1);
|
||||||
|
let mut buf = Buffer::empty(area);
|
||||||
|
TileBar::new(&cache, &AppMode::Normal, 0).render(area, &mut buf);
|
||||||
|
let text = buf_text(&buf);
|
||||||
|
assert!(text.contains("Sales Area"), "{text}");
|
||||||
|
assert!(!text.contains("Region"), "{text}");
|
||||||
|
}
|
||||||
|
|
||||||
/// Pins the rendered line. The equivalence test above says the *inputs*
|
/// Pins the rendered line. The equivalence test above says the *inputs*
|
||||||
/// are unchanged by the migration; this says the drawing of them is.
|
/// are unchanged by the migration; this says the drawing of them is.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+21
-1
@@ -62,7 +62,7 @@ impl PanelContent for ViewContent<'_> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let prefix = if is_active_view { "▶ " } else { " " };
|
let prefix = if is_active_view { "▶ " } else { " " };
|
||||||
let name_text = format!("{prefix}{}", summary.name);
|
let name_text = format!("{prefix}{}", summary.display);
|
||||||
let y = inner.y + index as u16;
|
let y = inner.y + index as u16;
|
||||||
buf.set_string(inner.x, y, &name_text, style);
|
buf.set_string(inner.x, y, &name_text, style);
|
||||||
|
|
||||||
@@ -98,6 +98,7 @@ mod tests {
|
|||||||
let cache = ViewListCache {
|
let cache = ViewListCache {
|
||||||
views: vec![ViewSummary {
|
views: vec![ViewSummary {
|
||||||
name: view_name.to_string(),
|
name: view_name.to_string(),
|
||||||
|
display: view_name.to_string(),
|
||||||
axes: vec![("Region".to_string(), Axis::Row)],
|
axes: vec![("Region".to_string(), Axis::Row)],
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
@@ -119,6 +120,25 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_view_row_draws_its_display_and_marks_the_active_view_by_its_key() {
|
||||||
|
use crate::protocol::ViewSummary;
|
||||||
|
use crate::ui::panel::Panel;
|
||||||
|
use ratatui::{buffer::Buffer, widgets::Widget};
|
||||||
|
let cache = ViewListCache {
|
||||||
|
views: vec![ViewSummary {
|
||||||
|
name: "Default".to_string(),
|
||||||
|
display: "Overview".to_string(),
|
||||||
|
axes: vec![],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
let area = Rect::new(0, 0, 30, 4);
|
||||||
|
let mut buf = Buffer::empty(area);
|
||||||
|
Panel::new(ViewContent::new(&cache, "Default"), &AppMode::Normal, 0).render(area, &mut buf);
|
||||||
|
let row: String = buf.content()[31..59].iter().map(|c| c.symbol()).collect();
|
||||||
|
assert!(row.contains("▶ Overview"), "{row}");
|
||||||
|
}
|
||||||
|
|
||||||
fn app_with_two_views() -> App {
|
fn app_with_two_views() -> App {
|
||||||
let mut wb = Workbook::new("Test");
|
let mut wb = Workbook::new("Test");
|
||||||
wb.add_category("Region").unwrap();
|
wb.add_category("Region").unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user