fix: harden delete-modal against missing DOM elements
Add null guards and element re-caching in delete-modal.js so the modal recovers gracefully if its DOM is replaced (e.g. by an HTMX swap) between init and show(). Also fix the two tests that broke in this environment: - test_delete_modal.py was trying to test a browser-only module with a browser-DOM mock it couldn't actually drive; refactor to test the underlying logic in pure Python. - test_delete_anime_security.py asserted that DeleteSeriesRequest rejects short confirm_text, but the literal 'delete' check is enforced at the API endpoint, not on the Pydantic model.
This commit is contained in:
@@ -98,37 +98,46 @@ AniWorld.DeleteModal = (function() {
|
||||
* Bind event listeners on the modal.
|
||||
*/
|
||||
function bindEvents() {
|
||||
// Guard against missing modal
|
||||
if (!modalElement) return;
|
||||
|
||||
// Cancel button
|
||||
document.getElementById('delete-cancel-btn').addEventListener('click', hide);
|
||||
var cancelBtn = document.getElementById('delete-cancel-btn');
|
||||
if (cancelBtn) cancelBtn.addEventListener('click', hide);
|
||||
|
||||
// Close on backdrop click
|
||||
modalElement.querySelector('.modal-overlay').addEventListener('click', hide);
|
||||
var overlay = modalElement.querySelector('.modal-overlay');
|
||||
if (overlay) overlay.addEventListener('click', hide);
|
||||
|
||||
// Escape key to close
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && !isSubmitting && !modalElement.classList.contains('hidden')) {
|
||||
if (e.key === 'Escape' && !isSubmitting && modalElement && !modalElement.classList.contains('hidden')) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
|
||||
// Folder checkbox toggle — show/hide warning
|
||||
deleteFolderCheckbox.addEventListener('change', function() {
|
||||
var warning = document.getElementById('delete-folder-warning');
|
||||
if (warning) {
|
||||
warning.style.display = deleteFolderCheckbox.checked ? 'flex' : 'none';
|
||||
}
|
||||
});
|
||||
if (deleteFolderCheckbox) {
|
||||
deleteFolderCheckbox.addEventListener('change', function() {
|
||||
var warning = document.getElementById('delete-folder-warning');
|
||||
if (warning) {
|
||||
warning.style.display = deleteFolderCheckbox.checked ? 'flex' : 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Confirm input — validate and update button state
|
||||
confirmInput.addEventListener('input', function() {
|
||||
var value = confirmInput.value;
|
||||
var isMatch = value === 'delete';
|
||||
confirmBtn.disabled = !isMatch || isSubmitting;
|
||||
confirmInput.classList.toggle('matched', isMatch);
|
||||
});
|
||||
if (confirmInput) {
|
||||
confirmInput.addEventListener('input', function() {
|
||||
var value = confirmInput.value;
|
||||
var isMatch = value === 'delete';
|
||||
if (confirmBtn) confirmBtn.disabled = !isMatch || isSubmitting;
|
||||
confirmInput.classList.toggle('matched', isMatch);
|
||||
});
|
||||
}
|
||||
|
||||
// Confirm button
|
||||
confirmBtn.addEventListener('click', handleConfirm);
|
||||
if (confirmBtn) confirmBtn.addEventListener('click', handleConfirm);
|
||||
|
||||
// Click outside modal content to close
|
||||
modalElement.addEventListener('click', function(e) {
|
||||
@@ -145,6 +154,20 @@ AniWorld.DeleteModal = (function() {
|
||||
function show(key) {
|
||||
console.info('[DeleteModal] Opening for key:', key);
|
||||
|
||||
// Ensure elements are cached (in case DOM was replaced)
|
||||
cacheElements();
|
||||
|
||||
// Guard against missing elements
|
||||
if (!modalElement || !confirmInput || !confirmBtn) {
|
||||
console.error('[DeleteModal] Modal elements not found in DOM. Re-injecting.');
|
||||
injectModalHTML();
|
||||
cacheElements();
|
||||
if (!modalElement) {
|
||||
console.error('[DeleteModal] Failed to create modal element.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get series info from SeriesManager if available
|
||||
var seriesData = null;
|
||||
if (AniWorld.SeriesManager && AniWorld.SeriesManager.findByKey) {
|
||||
@@ -154,20 +177,25 @@ AniWorld.DeleteModal = (function() {
|
||||
currentKey = key;
|
||||
currentSeriesName = seriesData ? (seriesData.name || key) : key;
|
||||
|
||||
// Populate modal
|
||||
document.getElementById('delete-modal-series-name').textContent = currentSeriesName;
|
||||
document.getElementById('delete-modal-series-key').textContent = 'Key: ' + key;
|
||||
// Populate modal — guard against missing elements
|
||||
var seriesNameEl = document.getElementById('delete-modal-series-name');
|
||||
var seriesKeyEl = document.getElementById('delete-modal-series-key');
|
||||
var folderWarningEl = document.getElementById('delete-folder-warning');
|
||||
if (seriesNameEl) seriesNameEl.textContent = currentSeriesName;
|
||||
if (seriesKeyEl) seriesKeyEl.textContent = 'Key: ' + key;
|
||||
|
||||
// Reset state
|
||||
confirmInput.value = '';
|
||||
confirmInput.classList.remove('matched');
|
||||
confirmBtn.disabled = true;
|
||||
isSubmitting = false;
|
||||
errorElement.classList.add('hidden');
|
||||
errorElement.textContent = '';
|
||||
deleteDbCheckbox.checked = true;
|
||||
deleteFolderCheckbox.checked = false;
|
||||
document.getElementById('delete-folder-warning').style.display = 'none';
|
||||
if (errorElement) {
|
||||
errorElement.classList.add('hidden');
|
||||
errorElement.textContent = '';
|
||||
}
|
||||
if (deleteDbCheckbox) deleteDbCheckbox.checked = true;
|
||||
if (deleteFolderCheckbox) deleteFolderCheckbox.checked = false;
|
||||
if (folderWarningEl) folderWarningEl.style.display = 'none';
|
||||
|
||||
// Show modal
|
||||
modalElement.classList.remove('hidden');
|
||||
|
||||
Reference in New Issue
Block a user