feat: V5a show relationship/index <name> detail views

Fold the singular per-item forms into Command::ShowList { kind,
name: Option<String> } (name: Some = one item). Two grammar
branches reuse the relationship/index completion sources; worker
do_show_one renders a labelled detail block or a friendly
"No ... named X." line, reusing the V5 render path. Help +
parse-usage entries, two ADR-0042 near-miss rows, 5 integration
tests. Mark V5a [x] — V5's [<name>] clause now complete.
This commit is contained in:
claude@clouddev1
2026-06-07 14:04:00 +00:00
parent 757711f2bf
commit 1d898adf00
13 changed files with 272 additions and 32 deletions
+93 -10
View File
@@ -33,7 +33,8 @@ fn show_tables_parses_as_show_list_tables() {
assert_eq!(
parse_command("show tables").expect("parses"),
Command::ShowList {
kind: ShowListKind::Tables
kind: ShowListKind::Tables,
name: None,
},
);
}
@@ -43,7 +44,8 @@ fn show_relationships_parses_as_show_list_relationships() {
assert_eq!(
parse_command("show relationships").expect("parses"),
Command::ShowList {
kind: ShowListKind::Relationships
kind: ShowListKind::Relationships,
name: None,
},
);
}
@@ -53,7 +55,30 @@ fn show_indexes_parses_as_show_list_indexes() {
assert_eq!(
parse_command("show indexes").expect("parses"),
Command::ShowList {
kind: ShowListKind::Indexes
kind: ShowListKind::Indexes,
name: None,
},
);
}
#[test]
fn show_relationship_singular_parses_with_name() {
assert_eq!(
parse_command("show relationship orders_customer").expect("parses"),
Command::ShowList {
kind: ShowListKind::Relationships,
name: Some("orders_customer".to_string()),
},
);
}
#[test]
fn show_index_singular_parses_with_name() {
assert_eq!(
parse_command("show index idx_orders_customer").expect("parses"),
Command::ShowList {
kind: ShowListKind::Indexes,
name: Some("idx_orders_customer".to_string()),
},
);
}
@@ -145,7 +170,7 @@ fn show_tables_lists_all_user_tables_with_count_header() {
let rt = rt();
rt.block_on(seed_schema(&db));
let lines = rt
.block_on(db.show_list(ShowListKind::Tables))
.block_on(db.show_list(ShowListKind::Tables, None))
.expect("show tables");
assert_eq!(lines[0], "Tables (2):", "count header");
assert!(
@@ -164,7 +189,7 @@ fn show_relationships_lists_name_endpoints_and_nondefault_action() {
let rt = rt();
rt.block_on(seed_schema(&db));
let lines = rt
.block_on(db.show_list(ShowListKind::Relationships))
.block_on(db.show_list(ShowListKind::Relationships, None))
.expect("show relationships");
assert_eq!(lines[0], "Relationships (1):");
// Name, both endpoints, and the non-default ON DELETE CASCADE
@@ -182,7 +207,7 @@ fn show_indexes_lists_qualified_name_and_columns() {
let rt = rt();
rt.block_on(seed_schema(&db));
let lines = rt
.block_on(db.show_list(ShowListKind::Indexes))
.block_on(db.show_list(ShowListKind::Indexes, None))
.expect("show indexes");
assert_eq!(lines[0], "Indexes (1):");
assert_eq!(
@@ -197,20 +222,76 @@ fn show_lists_report_empty_collections_with_friendly_lines() {
let rt = rt();
// No schema seeded — every kind is empty.
assert_eq!(
rt.block_on(db.show_list(ShowListKind::Tables)).unwrap(),
rt.block_on(db.show_list(ShowListKind::Tables, None)).unwrap(),
vec!["No tables in this project yet.".to_string()],
);
assert_eq!(
rt.block_on(db.show_list(ShowListKind::Relationships))
rt.block_on(db.show_list(ShowListKind::Relationships, None))
.unwrap(),
vec!["No relationships in this project yet.".to_string()],
);
assert_eq!(
rt.block_on(db.show_list(ShowListKind::Indexes)).unwrap(),
rt.block_on(db.show_list(ShowListKind::Indexes, None)).unwrap(),
vec!["No indexes in this project yet.".to_string()],
);
}
// =================================================================
// V5a — singular per-item detail
// =================================================================
#[test]
fn show_one_relationship_renders_detail_block() {
let (_p, db, _dir) = open_project_db();
let rt = rt();
rt.block_on(seed_schema(&db));
let lines = rt
.block_on(db.show_list(ShowListKind::Relationships, Some("orders_customer".to_string())))
.expect("show relationship");
assert_eq!(lines[0], "Relationship `orders_customer`:");
assert_eq!(lines[1], " Customers.id → Orders.customer_id");
assert!(
lines.iter().any(|l| l == " on delete cascade"),
"on-delete shown: {lines:?}",
);
}
#[test]
fn show_one_index_renders_detail_block() {
let (_p, db, _dir) = open_project_db();
let rt = rt();
rt.block_on(seed_schema(&db));
let lines = rt
.block_on(db.show_list(ShowListKind::Indexes, Some("idx_orders_customer".to_string())))
.expect("show index");
assert_eq!(lines[0], "Index `idx_orders_customer` on Orders:");
assert!(
lines.iter().any(|l| l == " columns: customer_id"),
"columns shown: {lines:?}",
);
assert!(
lines.iter().any(|l| l == " unique: no"),
"uniqueness shown: {lines:?}",
);
}
#[test]
fn show_one_unknown_name_reports_friendly_not_found() {
let (_p, db, _dir) = open_project_db();
let rt = rt();
rt.block_on(seed_schema(&db));
assert_eq!(
rt.block_on(db.show_list(ShowListKind::Relationships, Some("nope".to_string())))
.unwrap(),
vec!["No relationship named `nope`.".to_string()],
);
assert_eq!(
rt.block_on(db.show_list(ShowListKind::Indexes, Some("nope".to_string())))
.unwrap(),
vec!["No index named `nope`.".to_string()],
);
}
// =================================================================
// App end-to-end
// =================================================================
@@ -241,7 +322,8 @@ fn app_show_tables_dispatches_show_list_command() {
a,
Action::ExecuteDsl {
command: Command::ShowList {
kind: ShowListKind::Tables
kind: ShowListKind::Tables,
name: None,
},
..
}
@@ -262,6 +344,7 @@ fn app_renders_show_list_lines_as_system_output() {
app.update(AppEvent::DslShowListSucceeded {
command: Command::ShowList {
kind: ShowListKind::Tables,
name: None,
},
lines: vec!["Tables (1):".to_string(), " Customers".to_string()],
});