Files
odysseus/tests/test_email_ai_reply_context_draft_static.py
T
Léo ab5a08a6f9 refactor(email): move emailLibrary.js into static/js/emailLibrary/
The email library was 11,375 lines in one file, the second-largest JS
module in the repo. `static/js/emailLibrary/` already held four extracted
helpers, so the package existed; the bulk of the code just was not in it.

The implementation moves to `emailLibrary/index.js` and the old path
becomes a re-export wrapper. Five call sites import that path, four of
them dynamically with a `?v=` string, and `sw.js` caches URLs verbatim,
so a wrapper is what makes the move need no coordinated edit to any of
them.

The test side is the part worth reviewing. 58 tests read
`static/js/emailLibrary.js` as text. Pointing them at
`emailLibrary/index.js` would buy one move and break again on the next
one, which is exactly what happened to the stylesheet tests (they now go
through `tests/helpers/stylesheets.py`). So the same shape:
`tests/helpers/js_modules.py` reads the whole package, and assertions
stop caring which module a function sits in.

`tests/test_email_library_module_graph_js.py` is new. This frontend has
no module-graph validation, and a package fails in ways a single file
cannot: a wrapper that drops an export is `undefined` at call time rather
than an error at load time, and a module that reads a `const` across an
import cycle throws only when that module is entered first. It pins the
wrapper's surface against the entry module's, evaluates every module on
its own in a browser, and requires both import paths to hand out one
instance.

It also caught a live gap while being written:
`emailLibrary/replyRecipients.js` is imported by `emailInbox.js`, an app
shell module, and was never in the `sw.js` precache.
2026-09-30 09:08:21 +02:00

59 lines
2.6 KiB
Python

from pathlib import Path
from tests.helpers.js_modules import email_library_source
ROOT = Path(__file__).resolve().parent.parent
def test_email_ai_reply_context_is_saved_and_restored_per_message():
source = email_library_source()
assert "_AI_REPLY_CONTEXT_DRAFT_PREFIX" in source
assert "data?.account_id || em?.account_id || state._libAccountId" in source
assert "data?.folder || em?.folder || state._libFolder" in source
assert "noteInput.value = _loadAiReplyContextDraft(contextDraftKey);" in source
assert "noteInput.addEventListener('input'" in source
assert "_saveAiReplyContextDraft(contextDraftKey, noteInput.value || '');" in source
assert "document.removeEventListener('click', _aiReplyChoiceOutsideClose, true);" in source
def test_email_ai_reply_context_only_clears_after_draft_opens():
library = email_library_source()
inbox = (ROOT / "static/js/emailInbox.js").read_text(encoding="utf-8")
assert "const draftOpened = await _runAiReplyFromButton" in library
assert "if (draftOpened === true) _clearAiReplyContextDraft(contextDraftKey);" in library
assert "return await _openEmail(opts.email" in inbox
assert "return replyDraftOpened;" in inbox
def test_document_ai_reply_does_not_overwrite_an_edited_draft():
source = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
start = source.index(" async function _aiReply(")
end = source.index(" async function _scheduleSend(", start)
function = source[start:end]
assert "let _emailAiReplyGeneration = 0;" in source
assert "Writing AI reply" in function
assert "const draftStillUnchanged = () =>" in function
assert "_emailAiReplyGeneration" in function
assert "_setEmailBodyText(textarea, newBody);" in function
assert "await _streamEmailBodyText(textarea, newBody);" not in function
assert "AI reply ready, but draft was edited" in function
inbox = (ROOT / "static/js/emailInbox.js").read_text(encoding="utf-8")
assert "showToast('Writing AI reply'" in inbox
assert "AI reply service returned HTTP" in inbox
def test_replacing_an_email_reply_checks_the_visible_draft_first():
source = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
start = source.index(" export async function replaceEmailReplyBody(")
end = source.index(" export async function ensureEmailDraftEnvelope(", start)
function = source[start:end]
assert "const initialBody = fields.body || '';" in function
assert "textarea.value !== initialBody" in function
assert "_setEmailBodyText(textarea, body);" in function
assert "await _streamEmailBodyText(textarea, body);" not in function