Files
claude@clouddev1 d0c8f9d5d2 feat: copy the output panel to the system clipboard (#11)
New app-level `copy` / `copy all` / `copy last` command (ADR-0041).
Delivery is OSC 52 *and* a best-effort native write (arboard), always
both — OSC 52 acceptance is undetectable, so a true fallback can't be
built. Payload is the panel's plain text exactly as rendered (tags,
✓/✗, box-drawing), drift-locked to render_output_line. arboard added
--no-default-features (X11-only; OSC 52 covers Wayland).

Amends ADR-0003's command registry; requirements V6.
2026-06-02 14:23:21 +00:00

192 lines
5.8 KiB
Rust

//! Matrix coverage for app-lifecycle commands (ADR-0003):
//! quit, help, rebuild, save / save as, new, load, export,
//! import, mode, messages.
//!
//! App commands don't touch the schema, so the empty schema is
//! sufficient.
use crate::typing_surface::*;
use rdbms_playground::input_render::InputState;
#[test]
fn quit_parses() {
let a = assess_at_end("quit", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
assert_eq!(a.parse_result.as_deref(), Ok("App(Quit)"));
crate::snap!("quit", a);
}
#[test]
fn help_parses() {
let a = assess_at_end("help", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
assert_eq!(a.parse_result.as_deref(), Ok("App(Help)"));
crate::snap!("help", a);
}
#[test]
fn rebuild_parses() {
let a = assess_at_end("rebuild", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
crate::snap!("rebuild", a);
}
#[test]
fn save_parses() {
let a = assess_at_end("save", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
assert_eq!(a.parse_result.as_deref(), Ok("App(Save)"));
crate::snap!("save", a);
}
#[test]
fn save_space_offers_as_keyword() {
// Handoff-12 §3 smoke item: `save ` then Tab offers `as`.
let a = assess_at_end("save ", &schema_empty());
assert_candidate_present(&a, &["as"]);
crate::snap!("save_space", a);
}
#[test]
fn save_as_parses() {
let a = assess_at_end("save as", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
assert_eq!(a.parse_result.as_deref(), Ok("App(SaveAs)"));
crate::snap!("save_as", a);
}
#[test]
fn new_parses() {
let a = assess_at_end("new", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
crate::snap!("new", a);
}
#[test]
fn load_parses() {
let a = assess_at_end("load", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
crate::snap!("load", a);
}
#[test]
fn export_with_no_path_parses() {
// Export path is optional — `export` alone opens the modal.
let a = assess_at_end("export", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
crate::snap!("export_no_path", a);
}
#[test]
fn export_with_path_parses() {
let a = assess_at_end("export myproject.zip", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
crate::snap!("export_with_path", a);
}
#[test]
fn import_with_path_parses() {
let a = assess_at_end("import bundle.zip", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
crate::snap!("import_with_path", a);
}
#[test]
fn mode_with_value_parses() {
let a = assess_at_end("mode advanced", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
crate::snap!("mode_advanced", a);
}
#[test]
fn mode_space_offers_simple_and_advanced() {
let a = assess_at_end("mode ", &schema_empty());
assert!(matches!(a.state, InputState::IncompleteAtEof));
assert_candidate_present(&a, &["simple", "advanced"]);
crate::snap!("mode_space", a);
}
#[test]
fn messages_with_no_value_parses() {
// Messages value is optional — `messages` alone shows the
// current verbosity.
let a = assess_at_end("messages", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
crate::snap!("messages_no_value", a);
}
#[test]
fn messages_space_offers_short_and_verbose() {
let a = assess_at_end("messages ", &schema_empty());
assert_candidate_present(&a, &["short", "verbose"]);
crate::snap!("messages_space", a);
}
#[test]
fn messages_with_value_parses() {
let a = assess_at_end("messages verbose", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
crate::snap!("messages_verbose", a);
}
#[test]
fn copy_with_no_value_parses() {
// Copy scope is optional — bare `copy` copies the whole panel.
let a = assess_at_end("copy", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
assert_eq!(a.parse_result.as_deref(), Ok("App(Copy)"));
crate::snap!("copy_no_value", a);
}
#[test]
fn copy_all_parses() {
let a = assess_at_end("copy all", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
assert_eq!(a.parse_result.as_deref(), Ok("App(Copy)"));
crate::snap!("copy_all", a);
}
#[test]
fn copy_last_parses() {
let a = assess_at_end("copy last", &schema_empty());
assert!(matches!(a.state, InputState::Valid));
assert_eq!(a.parse_result.as_deref(), Ok("App(Copy)"));
crate::snap!("copy_last", a);
}
#[test]
fn copy_space_offers_all_and_last() {
let a = assess_at_end("copy ", &schema_empty());
assert_candidate_present(&a, &["all", "last"]);
crate::snap!("copy_space", a);
}
#[test]
fn partial_entry_word_classifies_as_definite_error_but_completes() {
// `qu` — mid-typing the `quit` entry word.
//
// KNOWN UX WRINKLE (handoff-13 finding): a partial entry
// word is currently classified `DefiniteErrorAt(0)` — the
// same as a genuinely unknown command (`frobulate`). The
// walker only engages once a *complete* registered entry
// word is present; until then `try_walker_route` returns
// None and the router emits the synthetic unknown-command
// error. So the input pane shows `qu` in error-red even
// though it is a valid prefix of `quit`.
//
// Completion still works (Tab at `qu` → `quit`), so the
// user can recover, but the red overlay while typing the
// first word is jarring. This test documents the current
// behaviour; flagged to the user for a decision rather
// than silently asserting it as correct.
let a = assess_at_end("qu", &schema_empty());
assert!(
matches!(a.state, InputState::DefiniteErrorAt(_)),
"documents current behaviour, got {:?}",
a.state,
);
// Completion recovers — `quit` is offered.
assert_candidate_present(&a, &["quit"]);
crate::snap!("partial_quit", a);
}