Cleanup pass: --help, in-app help, post-rebuild message, unmodified-temp cleanup
Four post-Iteration-4 polish items surfaced by manual testing.
1. `--help` / `-h` CLI flag prints a usage banner (options +
app-level commands + DSL grammar reference) and exits. Parse
errors also print the banner to stderr.
2. `help` app-level command notes the same list of supported
commands to the output panel -- a simple stand-in for the
richer H3 help system, kept in sync with what's actually
wired up.
3. The silent rebuild that runs when playground.db is missing
now surfaces a system message in the output panel ("[ok]
rebuild -- N tables, M rows reconstructed; ...") via a new
initial_events plumbing. The user no longer wonders whether
the .db was magically restored or whether anything happened
on launch.
4. Unmodified empty temp projects (kind=Temp, project.yaml has
tables: [] and relationships: []) are now auto-deleted when
the user switches away (load / new / save as) or quits. This
addresses the "launch app, load existing project, quit"
pattern that was leaving an empty temp directory behind
every time. Modified temps (with any user-created tables or
relationships) are never auto-deleted; corrupted projects
are also never auto-deleted (defensive default-to-false on
yaml read/parse errors).
Tests: 338 passing (272 lib + 9 + 5 + 6 + 20 + 9 + 17),
0 failing, 0 skipped. Clippy clean.
This commit is contained in:
@@ -17,6 +17,9 @@ use rdbms_playground::app::{
|
||||
PathEntryPurpose,
|
||||
};
|
||||
use rdbms_playground::event::AppEvent;
|
||||
use rdbms_playground::db::Database;
|
||||
use rdbms_playground::dsl::{ColumnSpec, Type};
|
||||
use rdbms_playground::persistence::Persistence;
|
||||
use rdbms_playground::project::{self, Project, ProjectKind, copy_project};
|
||||
|
||||
const fn key(code: KeyCode) -> AppEvent {
|
||||
@@ -42,6 +45,26 @@ fn tempdir() -> tempfile::TempDir {
|
||||
tempfile::tempdir().expect("create tempdir")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_command_lists_supported_commands() {
|
||||
let mut app = App::new();
|
||||
type_str(&mut app, "help");
|
||||
let actions = submit(&mut app);
|
||||
assert!(actions.is_empty());
|
||||
let body = app
|
||||
.output
|
||||
.iter()
|
||||
.map(|l| l.text.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
for keyword in ["quit", "rebuild", "save", "load", "new", "create table"] {
|
||||
assert!(
|
||||
body.contains(keyword),
|
||||
"help output missing `{keyword}`:\n{body}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_on_temp_opens_path_entry_modal() {
|
||||
let mut app = App::new();
|
||||
@@ -308,6 +331,62 @@ fn project_kind_recovered_from_dirname_on_open() {
|
||||
assert_eq!(opened_named.display_name(), "My Project");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fresh_temp_is_unmodified() {
|
||||
let data = tempdir();
|
||||
let project = project::open_or_create(None, Some(data.path())).unwrap();
|
||||
assert!(project.is_unmodified_temp());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn temp_with_a_table_is_no_longer_unmodified() {
|
||||
let data = tempdir();
|
||||
let project = project::open_or_create(None, Some(data.path())).unwrap();
|
||||
let path = project.path().to_path_buf();
|
||||
let db = Database::open_with_persistence(
|
||||
project.db_path(),
|
||||
Persistence::new(path.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
rt.block_on(async {
|
||||
db.create_table(
|
||||
"T".to_string(),
|
||||
vec![ColumnSpec { name: "id".to_string(), ty: Type::Serial }],
|
||||
vec!["id".to_string()],
|
||||
Some("create".to_string()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
drop(db);
|
||||
drop(project);
|
||||
|
||||
let reopened = Project::open(&path).unwrap();
|
||||
assert!(
|
||||
!reopened.is_unmodified_temp(),
|
||||
"a temp with a table should not be considered unmodified",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named_project_is_never_unmodified_temp() {
|
||||
let data = tempdir();
|
||||
let temp = project::open_or_create(None, Some(data.path())).unwrap();
|
||||
let temp_path = temp.path().to_path_buf();
|
||||
drop(temp);
|
||||
|
||||
let named = data.path().join("MyOrders");
|
||||
copy_project(&temp_path, &named).unwrap();
|
||||
let opened = Project::open(&named).unwrap();
|
||||
// Even though the schema is empty, kind is Named.
|
||||
assert_eq!(opened.kind(), ProjectKind::Named);
|
||||
assert!(!opened.is_unmodified_temp());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_projects_sorts_by_mtime() {
|
||||
let data = tempdir();
|
||||
|
||||
Reference in New Issue
Block a user