test: H1a near-miss matrix + friendlier add 1:n relationship label (ADR-0042)
Start the ADR-0042 §1 near-miss matrix: a table-driven
near_miss_matrix_simple_mode locking 26 simple-mode parse-error
renderings (every DSL entry word's bare/missing-clause cases plus the
mode arg-errors and the "this is SQL" rail), and an #[ignore]
baseline_dump capturing the full rendering for ongoing triage.
G1 fix: the bare `1` cardinality literal that opens `add 1:n
relationship …` rendered cryptically in expected-sets. Render it as
`1:n relationship` in error wording only (format_expectation) —
completion/hints still read the raw Expectation::Literal("1"), so the
candidate surface is unchanged. Updated the one anchor test.
Full suite green (lib 1578 / it 382 / typing_surface_matrix 192).
This commit is contained in:
@@ -13,6 +13,7 @@ use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||
use rdbms_playground::action::Action;
|
||||
use rdbms_playground::app::{App, OutputKind};
|
||||
use rdbms_playground::event::AppEvent;
|
||||
use rdbms_playground::mode::Mode;
|
||||
|
||||
const fn key(code: KeyCode) -> AppEvent {
|
||||
AppEvent::Key(KeyEvent {
|
||||
@@ -59,6 +60,149 @@ fn dump(input: &str, lines: &[String]) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
/// TEMP baseline-capture (ADR-0042 §1 step 1). Lenient: does not
|
||||
/// assert pass/fail — just dumps every output line so we can read
|
||||
/// the current rendering before writing assertions. Run with:
|
||||
/// cargo test -p ... --test it baseline_dump -- --nocapture --ignored
|
||||
/// Removed once the matrix assertions land.
|
||||
#[test]
|
||||
#[ignore = "baseline capture only; run with --ignored --nocapture"]
|
||||
fn baseline_dump() {
|
||||
// (input, advanced?) — salient near-misses across entry words.
|
||||
let cases: &[(&str, bool)] = &[
|
||||
// --- app-lifecycle (simple) ---
|
||||
("quit now", false),
|
||||
("import", false),
|
||||
("mode sideways", false),
|
||||
("messages louder", false),
|
||||
("copy everything", false),
|
||||
("save sideways", false),
|
||||
// --- DDL bare + missing-slot (simple) ---
|
||||
("create", false),
|
||||
("create table", false),
|
||||
("create table T", false),
|
||||
("drop", false),
|
||||
("drop table", false),
|
||||
("add", false),
|
||||
("add column", false),
|
||||
("rename", false),
|
||||
("rename column", false),
|
||||
("change", false),
|
||||
("change column", false),
|
||||
// --- data bare + missing-clause (simple) ---
|
||||
("show", false),
|
||||
("show data", false),
|
||||
("insert", false),
|
||||
("insert into", false),
|
||||
("insert into T", false),
|
||||
("insert into T ('Oli')", false),
|
||||
("update", false),
|
||||
("update T", false),
|
||||
("update T set x=1", false),
|
||||
("delete", false),
|
||||
("delete from", false),
|
||||
("delete from T", false),
|
||||
("replay", false),
|
||||
("explain", false),
|
||||
// --- advanced-only entry words ---
|
||||
("select", true),
|
||||
("select *", true),
|
||||
("select * from", true),
|
||||
("with", true),
|
||||
("alter", true),
|
||||
("alter table T", true),
|
||||
// advanced-only word typed in SIMPLE mode → "this is SQL" hint
|
||||
("alter table T add column c int", false),
|
||||
("select * from T", false),
|
||||
// --- advanced SQL variants + genuine gaps ---
|
||||
("insert into T", true),
|
||||
("update T", true),
|
||||
("delete from", true),
|
||||
("create", true),
|
||||
("create table", true),
|
||||
("create index", true),
|
||||
("drop", true),
|
||||
("drop index", true),
|
||||
];
|
||||
for (input, advanced) in cases {
|
||||
let mut app = App::new();
|
||||
if *advanced {
|
||||
app.mode = Mode::Advanced;
|
||||
}
|
||||
type_str(&mut app, input);
|
||||
let actions = submit(&mut app);
|
||||
let mode = if *advanced { "ADV" } else { "SIM" };
|
||||
eprintln!("\n=== [{mode}] {input:?} → actions: {actions:?}");
|
||||
for l in &app.output {
|
||||
eprintln!(" [{:?}] {}", l.kind, l.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The simple-mode near-miss matrix (ADR-0042 §1). Each row is a
|
||||
/// near-correct input plus substrings that MUST appear across its
|
||||
/// rendered error lines — the structural "name the missing
|
||||
/// keyword/clause" message and the per-command usage template.
|
||||
/// These are the cases triaged as already-good; the baseline dump
|
||||
/// (`baseline_dump`, #[ignore]) shows the full rendering. Cases
|
||||
/// flagged for wording fixes (bare `add` `1`, bare `select`
|
||||
/// first-set, mode-blind usage) are deliberately NOT locked here
|
||||
/// until their fixes land.
|
||||
#[test]
|
||||
fn near_miss_matrix_simple_mode() {
|
||||
// (input, required-substrings-across-error-lines)
|
||||
let matrix: &[(&str, &[&str])] = &[
|
||||
// app-lifecycle arg errors
|
||||
("quit now", &["after `quit`, expected end of input", " quit"]),
|
||||
("save sideways", &["after `save`, expected end of input", "save | save as"]),
|
||||
("mode sideways", &["unknown mode 'sideways'", "mode simple | mode advanced"]),
|
||||
("messages louder", &["unknown messages mode 'louder'", "messages short"]),
|
||||
("copy everything", &["unknown copy target 'everything'", "copy all"]),
|
||||
// DDL bare + missing-slot
|
||||
("create", &["after `create`, expected `table`", "create table <Name> with pk"]),
|
||||
("create table", &["after `create table`, expected identifier", "create table <Name> with pk"]),
|
||||
("create table T", &["with pk", "create table <Name> with pk"]),
|
||||
// G1: relationship cardinality reads as the named construct.
|
||||
("add", &["after `add`, expected `column`, `1:n relationship`", "add 1:n relationship"]),
|
||||
("drop table", &["after `drop table`, expected table name", "drop table <Name>"]),
|
||||
("add column", &["after `add column`, expected table name", "add column [to] [table]"]),
|
||||
("rename", &["after `rename`, expected `column`", "rename column [in] [table]"]),
|
||||
("rename column", &["after `rename column`, expected table name", "rename column [in] [table]"]),
|
||||
("change", &["after `change`, expected `column`", "change column [in] [table]"]),
|
||||
("change column", &["after `change column`, expected table name", "change column [in] [table]"]),
|
||||
// data bare + missing-clause
|
||||
("insert", &["after `insert`, expected `into`", "insert into <Table>"]),
|
||||
("insert into", &["after `insert into`, expected table name", "insert into <Table>"]),
|
||||
("insert into T", &["after `insert into T`, expected `values` or `(`", "insert into <Table>"]),
|
||||
("update", &["after `update`, expected table name", "update <Table> set"]),
|
||||
("update T", &["after `update T`, expected `set`", "update <Table> set"]),
|
||||
("update T set x=1", &["expected `where` or `--all-rows`", "update <Table> set"]),
|
||||
("delete", &["after `delete`, expected `from`", "delete from <Table>"]),
|
||||
("delete from", &["after `delete from`, expected table name", "delete from <Table>"]),
|
||||
("delete from T", &["expected `where` or `--all-rows`", "delete from <Table>"]),
|
||||
("replay", &["after `replay`, expected string literal or path", "replay <path>"]),
|
||||
("explain", &["after `explain`, expected `show`, `update`, or `delete`", "explain show data"]),
|
||||
// advanced-only entry word typed in simple mode → "this is SQL" rail
|
||||
("select * from T", &["`select` is SQL", "mode advanced"]),
|
||||
("alter table T add column c int", &["`alter` is SQL", "mode advanced"]),
|
||||
];
|
||||
for (input, needles) in matrix {
|
||||
let lines = error_lines_for(input);
|
||||
let dump_msg = dump(input, &lines);
|
||||
assert!(
|
||||
lines.iter().any(|l| l.starts_with("parse error")),
|
||||
"missing `parse error` line for {input:?}\n{dump_msg}",
|
||||
);
|
||||
let joined = lines.join("\n");
|
||||
for needle in *needles {
|
||||
assert!(
|
||||
joined.contains(needle),
|
||||
"near-miss {input:?} missing expected substring {needle:?}\n{dump_msg}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_alone_renders_create_table_usage() {
|
||||
let lines = error_lines_for("create");
|
||||
@@ -81,15 +225,18 @@ fn create_alone_renders_create_table_usage() {
|
||||
fn add_alone_renders_both_add_family_usages() {
|
||||
let lines = error_lines_for("add");
|
||||
let dump_msg = dump("add", &lines);
|
||||
// Aggregation across `choice` (ADR-0020): the structural
|
||||
// error line lists both add-family entries.
|
||||
// Aggregation across the top-level choice: the structural
|
||||
// error line lists every add-family branch. ADR-0042 G1: the
|
||||
// relationship branch renders as the friendly `1:n
|
||||
// relationship` rather than the cryptic bare `1` cardinality
|
||||
// literal.
|
||||
assert!(
|
||||
lines.iter().any(|l| {
|
||||
l.starts_with("parse error")
|
||||
&& l.contains("`1`")
|
||||
&& l.contains("`1:n relationship`")
|
||||
&& l.contains("`column`")
|
||||
}),
|
||||
"expected aggregated `1` or `column` in structural error\n{dump_msg}",
|
||||
"expected aggregated `1:n relationship` and `column` in structural error\n{dump_msg}",
|
||||
);
|
||||
// Usage block (ADR-0021): both add-* templates surface.
|
||||
assert!(
|
||||
|
||||
Reference in New Issue
Block a user