diff --git a/static/js/documentLibrary.js b/static/js/documentLibrary.js index aabf7a9..da906b0 100644 --- a/static/js/documentLibrary.js +++ b/static/js/documentLibrary.js @@ -391,6 +391,27 @@ let _libraryArchivedView = false; // Documents tab showing archived docs? } } + function libraryRemoveDocumentFromState(docId) { + const removed = _libraryDocs.find(d => String(d.id) === String(docId)); + _libraryDocs = _libraryDocs.filter(d => String(d.id) !== String(docId)); + _librarySelectedIds.delete(docId); + _libraryTotal = Math.max(0, _libraryTotal - 1); + + const lang = removed && (removed.language || 'text'); + if (lang && Object.prototype.hasOwnProperty.call(_libraryLanguages, lang)) { + const next = Math.max(0, Number(_libraryLanguages[lang] || 0) - 1); + if (next > 0) { + _libraryLanguages[lang] = next; + } else { + delete _libraryLanguages[lang]; + } + } + + libraryRenderStats(); + libraryRenderLangChips(); + libraryUpdateBulkCount(); + } + function libraryRenderGrid() { const grid = document.getElementById('doclib-grid'); if (!grid) return; @@ -709,8 +730,7 @@ let _libraryArchivedView = false; // Documents tab showing archived docs? const res = await fetch(`${API_BASE}/api/document/${doc.id}/archive?archived=${toArchived}`, { method: 'POST', credentials: 'same-origin' }); if (!res.ok) throw new Error('failed'); // Drop it from the current view (it no longer belongs here) and refresh. - _libraryDocs = _libraryDocs.filter(d => d.id !== doc.id); - _libraryTotal = Math.max(0, _libraryTotal - 1); + libraryRemoveDocumentFromState(doc.id); libraryRenderGrid(); if (uiModule) uiModule.showToast(toArchived ? 'Archived' : 'Restored'); } catch { if (uiModule) uiModule.showError('Failed to ' + (toArchived ? 'archive' : 'restore')); } @@ -802,8 +822,7 @@ let _libraryArchivedView = false; // Documents tab showing archived docs? try { const res = await fetch(`${API_BASE}/api/document/${doc.id}/archive?archived=${toArchived}`, { method: 'POST', credentials: 'same-origin' }); if (!res.ok) throw new Error('failed'); - _libraryDocs = _libraryDocs.filter(d => d.id !== doc.id); - _libraryTotal = Math.max(0, _libraryTotal - 1); + libraryRemoveDocumentFromState(doc.id); libraryRenderGrid(); if (uiModule) uiModule.showToast(toArchived ? 'Archived' : 'Restored'); } catch { if (uiModule) uiModule.showError('Failed to ' + (toArchived ? 'archive' : 'restore')); } @@ -1170,9 +1189,7 @@ let _libraryArchivedView = false; // Documents tab showing archived docs? card.addEventListener('transitionend', () => card.remove(), { once: true }); setTimeout(() => { if (card.parentElement) card.remove(); }, 400); } - _libraryDocs = _libraryDocs.filter(d => d.id !== docId); - _libraryTotal = Math.max(0, _libraryTotal - 1); - libraryRenderStats(); + libraryRemoveDocumentFromState(docId); if (uiModule) uiModule.showToast('Document deleted'); } catch (e) { if (uiModule) uiModule.showError(`Failed to delete document: ${e.message || e}`); diff --git a/tests/test_document_library_delete_counters.py b/tests/test_document_library_delete_counters.py new file mode 100644 index 0000000..118d7c6 --- /dev/null +++ b/tests/test_document_library_delete_counters.py @@ -0,0 +1,43 @@ +"""Regression for #1809: document library counters must update after delete. + +documentLibrary.js is a browser module with several DOM-only imports, so this +guards the relevant wiring at the source level. A single-card delete used to +remove the card and decrement `_libraryTotal`, but the header/chips render from +`_libraryLanguages`, which stayed stale until a full library refetch. +""" + +from pathlib import Path + + +SRC = Path(__file__).resolve().parent.parent / "static/js/documentLibrary.js" + + +def _src() -> str: + return SRC.read_text(encoding="utf-8") + + +def _between(text: str, start: str, end: str) -> str: + begin = text.index(start) + finish = text.index(end, begin) + return text[begin:finish] + + +def test_single_delete_updates_language_counters_and_chips(): + text = _src() + + helper = _between( + text, + "function libraryRemoveDocumentFromState(docId)", + "function libraryRenderGrid()", + ) + assert "_libraryLanguages[lang]" in helper + assert "delete _libraryLanguages[lang]" in helper + assert "libraryRenderStats();" in helper + assert "libraryRenderLangChips();" in helper + + delete_body = _between( + text, + "async function libraryDeleteSingle(docId, card)", + "async function libraryBulkDelete()", + ) + assert "libraryRemoveDocumentFromState(docId);" in delete_body