diff --git a/docs/adr/0015-project-storage-runtime.md b/docs/adr/0015-project-storage-runtime.md index f1bc0ec..7730fc5 100644 --- a/docs/adr/0015-project-storage-runtime.md +++ b/docs/adr/0015-project-storage-runtime.md @@ -677,9 +677,13 @@ time"). (the teacher-export round-trip); `cli` `--mode` parse/precedence; `app::{mode_command_changes_mode_and_emits_persist_action, mode_command_via_one_shot_escape_persists_advanced, - project_switched_event_restores_the_stored_mode}`. The runtime's - unload call sites (quit + `handle_project_switch`) are thin - wiring over the tested `Database::set_mode`. + project_switched_event_restores_the_stored_mode}`; + `runtime::switch_persists_the_outgoing_projects_mode` — an + integration test driving the real `handle_project_switch` to + prove the unload wiring calls `set_mode` (red-first verified). + The quit unload site shares that `set_mode` call; the + `Action::Quit` emission is covered by `app`'s + `quit_command_returns_quit_action` / `ctrl_c_returns_quit_action`. ### Relationship to the Iteration 6 backlog diff --git a/src/runtime.rs b/src/runtime.rs index e82fcb0..6f80dfe 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -3434,4 +3434,59 @@ mod tests { "single-line FK echo when the child column already existed", ); } + + #[tokio::test] + async fn switch_persists_the_outgoing_projects_mode() { + // ADR-0015 mode-restore amendment (issue #14), persist on + // unload — exercised through the real `handle_project_switch` + // path, not just the db-level `set_mode`. Proves the wiring + // calls `set_mode` on the outgoing project before it is + // dropped. The outgoing project is *named* so it survives the + // switch (an unmodified temp would be cleaned up, taking its + // project.yaml with it). Without the unload persist the + // outgoing skeleton carries no `mode:` → `None`. + use super::{handle_project_switch, Session, SwitchRequest}; + use crate::db::Database; + use crate::mode::Mode; + use crate::persistence::Persistence; + use crate::project::{projects_dir, Project}; + use tokio::sync::mpsc; + + let data_root = tempfile::tempdir().unwrap(); + let projects = projects_dir(data_root.path()); + std::fs::create_dir_all(&projects).unwrap(); + let outgoing_path = projects.join("Outgoing"); + let outgoing = Project::create_named(&outgoing_path).unwrap(); + let db_path = outgoing.db_path(); + let persistence = + Persistence::new(outgoing.path().to_path_buf()).with_mode(Mode::Advanced); + let database = + Database::open_with_persistence_and_undo(&db_path, persistence, true).unwrap(); + let mut session = Session { + project: Some(outgoing), + database: Some(database), + data_root: data_root.path().to_path_buf(), + }; + // Keep the receiver alive so the switch's events aren't sent + // into a closed channel. + let (tx, _rx) = mpsc::channel(64); + + // Switch away to a fresh temp; the App's live mode here is + // Advanced (the outgoing project's mode). + handle_project_switch( + &mut session, + SwitchRequest::NewTemp, + "new".to_string(), + &tx, + true, + Mode::Advanced, + ) + .await; + + assert_eq!( + Persistence::read_stored_mode(&outgoing_path), + Some(Mode::Advanced), + "switching away must persist the outgoing project's mode", + ); + } }