fix: small findings with regard to deletes from sidebar

This commit is contained in:
Bauer, Georg
2026-07-22 15:35:22 +02:00
parent cc520d517a
commit a3f2c451e3
13 changed files with 499 additions and 93 deletions

View File

@@ -382,6 +382,10 @@ pub enum Message {
CreateImport,
/// Per sidebar_views.allium ScriptListItemEntry: row-level delete affordance.
ScriptDeleteRequested(String),
/// Per sidebar_views.allium TemplateListItemEntry: row-level delete affordance.
TemplateDeleteRequested(String),
/// Per sidebar_views.allium ImportListItemEntry: row-level delete affordance.
ImportDeleteRequested(String),
// Conversational AI
ChatCreate,
@@ -1390,6 +1394,12 @@ impl BdsApp {
Message::ScriptDeleteRequested(script_id) => {
self.show_script_delete_confirmation(&script_id)
}
Message::TemplateDeleteRequested(template_id) => {
self.show_template_delete_confirmation(&template_id)
}
Message::ImportDeleteRequested(definition_id) => {
self.show_import_delete_confirmation(&definition_id)
}
Message::ChatCreate => self.create_chat_conversation(),
Message::ChatRenameInputChanged(value) => {
if let Some(state) = self.active_chat_state_mut() {
@@ -4677,17 +4687,7 @@ impl BdsApp {
ImportEditorMsg::Execute => self.start_import_execution(&definition_id),
ImportEditorMsg::AutoMapTaxonomy => self.start_import_auto_mapping(&definition_id),
ImportEditorMsg::DeleteRequested => {
let name = self
.import_editors
.get(&definition_id)
.map(|state| state.definition.name.clone())
.unwrap_or_default();
self.active_modal = Some(modal::ModalState::Confirm {
title: t(self.ui_locale, "import.deleteTitle"),
message: tw(self.ui_locale, "import.deleteMessage", &[("name", &name)]),
on_confirm: modal::ConfirmAction::DeleteImport(definition_id),
});
Task::none()
self.show_import_delete_confirmation(&definition_id)
}
ImportEditorMsg::SetResolution {
kind,
@@ -7046,18 +7046,20 @@ impl BdsApp {
("tags", &referencing_tags.to_string()),
],
);
return Task::done(Message::ShowModal(modal::ModalState::Confirm {
self.active_modal = Some(modal::ModalState::Confirm {
title,
message,
on_confirm: modal::ConfirmAction::ForceDeleteTemplate(template_id.to_string()),
}));
});
return Task::none();
}
Task::done(Message::ShowModal(modal::ModalState::ConfirmDelete {
self.active_modal = Some(modal::ModalState::ConfirmDelete {
entity_name: template.title,
references: Vec::new(),
on_confirm: modal::ConfirmAction::DeleteTemplate(template_id.to_string()),
}))
});
Task::none()
}
/// Per editor_script.allium ScriptDeleteAction / action_patterns.allium
@@ -7079,6 +7081,29 @@ impl BdsApp {
Task::none()
}
/// Per sidebar_views.allium ImportListItemEntry: confirming an import
/// deletion must not depend on an open editor tab, so the definition
/// name comes from the database.
fn show_import_delete_confirmation(&mut self, definition_id: &str) -> Task<Message> {
let Some(db) = &self.db else {
return Task::none();
};
let Ok(definition) = engine::wordpress_import::get_definition(db.conn(), definition_id)
else {
return Task::none();
};
self.active_modal = Some(modal::ModalState::Confirm {
title: t(self.ui_locale, "import.deleteTitle"),
message: tw(
self.ui_locale,
"import.deleteMessage",
&[("name", &definition.name)],
),
on_confirm: modal::ConfirmAction::DeleteImport(definition_id.to_string()),
});
Task::none()
}
fn delete_script_editor(&mut self, script_id: &str) -> Task<Message> {
let Some(db) = &self.db else {
return Task::none();
@@ -12619,6 +12644,166 @@ mod tests {
}
}
/// sidebar_views.allium TemplateListItemEntry provides
/// TemplateDeleteRequested(item.template_id): deleting from the sidebar
/// row routes through the same confirm modal as the editor delete button
/// and works without an open editor tab.
#[test]
fn sidebar_template_delete_requires_confirmation_and_works_without_open_tab() {
let (db, project, tmp) = setup();
let mut app = make_app(db, project, &tmp);
let _ = app.update(Message::CreateTemplate);
let template_id = app.sidebar_templates[0].id.clone();
// Close the editor tab: the sidebar row must not depend on one.
let _ = app.update(Message::CloseTab(template_id.clone()));
assert!(!app.tabs.iter().any(|tab| tab.id == template_id));
// Requesting deletion only opens the confirm modal; nothing is deleted.
let _ = app.update(Message::TemplateDeleteRequested(template_id.clone()));
assert!(matches!(
app.active_modal,
Some(modal::ModalState::ConfirmDelete {
on_confirm: modal::ConfirmAction::DeleteTemplate(ref id),
..
}) if id == &template_id
));
assert!(
bds_core::db::queries::template::get_template_by_id(
app.db.as_ref().unwrap().conn(),
&template_id
)
.is_ok()
);
let _ = app.update(Message::ConfirmModal(modal::ConfirmAction::DeleteTemplate(
template_id.clone(),
)));
assert!(
bds_core::db::queries::template::get_template_by_id(
app.db.as_ref().unwrap().conn(),
&template_id
)
.is_err()
);
assert!(!app.template_editors.contains_key(&template_id));
}
/// editor_template.allium TemplateDelete / TemplateListItemEntry
/// DeleteConfirmation: a referenced template prompts the force-delete
/// confirmation with reference counts instead of the plain delete modal.
#[test]
fn sidebar_template_delete_with_references_prompts_force_delete() {
let (db, project, tmp) = setup();
let mut app = make_app(db, project, &tmp);
let _ = app.update(Message::CreateTemplate);
let template = app.sidebar_templates[0].clone();
let referencing_post = post::create_post(
app.db.as_ref().unwrap().conn(),
tmp.path(),
&template.project_id,
"Uses Template",
None,
vec![],
vec![],
None,
Some("en"),
Some(&template.slug),
)
.unwrap();
let _ = app.update(Message::TemplateDeleteRequested(template.id.clone()));
assert!(matches!(
app.active_modal,
Some(modal::ModalState::Confirm {
on_confirm: modal::ConfirmAction::ForceDeleteTemplate(ref id),
..
}) if id == &template.id
));
// Force delete removes the template and clears the post reference.
let _ = app.update(Message::ConfirmModal(
modal::ConfirmAction::ForceDeleteTemplate(template.id.clone()),
));
assert!(
bds_core::db::queries::template::get_template_by_id(
app.db.as_ref().unwrap().conn(),
&template.id
)
.is_err()
);
assert_eq!(
bds_core::db::queries::post::get_post_by_id(
app.db.as_ref().unwrap().conn(),
&referencing_post.id
)
.unwrap()
.template_slug,
None
);
}
/// sidebar_views.allium ChatListItemEntry provides
/// ChatDeleteRequested(item.conversation_id); per DeleteBehaviour and
/// action_patterns.allium the delete is immediate (no confirmation) and
/// must not depend on an open tab.
#[test]
fn sidebar_chat_delete_works_without_open_tab() {
let (db, project, tmp) = setup();
let mut app = make_app(db, project, &tmp);
let _ = app.update(Message::ChatCreate);
let conversation_id = app.chat_conversations[0].id.clone();
let _ = app.update(Message::CloseTab(conversation_id.clone()));
assert!(!app.tabs.iter().any(|tab| tab.id == conversation_id));
let _ = app.update(Message::ChatDelete(conversation_id.clone()));
assert!(app.active_modal.is_none());
assert!(app.chat_conversations.is_empty());
assert!(
chat::list_conversations(app.db.as_ref().unwrap().conn())
.unwrap()
.is_empty()
);
}
/// sidebar_views.allium ImportListItemEntry provides
/// ImportDeleteRequested(item.definition_id): deleting from the sidebar
/// row confirms first and works without an open editor tab.
#[test]
fn sidebar_import_delete_requires_confirmation_and_works_without_open_tab() {
let (db, project, tmp) = setup();
let mut app = make_app(db, project, &tmp);
let _ = app.update(Message::CreateImport);
let definition_id = app.sidebar_imports[0].id.clone();
let _ = app.update(Message::CloseTab(definition_id.clone()));
assert!(!app.tabs.iter().any(|tab| tab.id == definition_id));
let _ = app.update(Message::ImportDeleteRequested(definition_id.clone()));
assert!(matches!(
app.active_modal,
Some(modal::ModalState::Confirm {
on_confirm: modal::ConfirmAction::DeleteImport(ref id),
..
}) if id == &definition_id
));
assert!(
wordpress_import::get_definition(app.db.as_ref().unwrap().conn(), &definition_id)
.is_ok()
);
let _ = app.update(Message::ConfirmModal(modal::ConfirmAction::DeleteImport(
definition_id.clone(),
)));
assert!(
wordpress_import::get_definition(app.db.as_ref().unwrap().conn(), &definition_id)
.is_err()
);
assert!(app.sidebar_imports.is_empty());
}
#[test]
fn rebuild_completion_refreshes_project_metadata_in_settings() {
let (db, project, tmp) = setup();