Files
odysseus/tests/test_card_menu_style_contract.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

49 lines
1.5 KiB
Python

from pathlib import Path
from tests.helpers.js_modules import email_library_paths
ROOT = Path(__file__).resolve().parents[1]
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
LIBRARY = (ROOT / "static/js/documentLibrary.js").read_text(encoding="utf-8")
def test_card_kebab_menus_share_font_rows_and_hover_behavior():
marker = "/* One visual contract for card kebab menus."
contract = STYLE.split(marker, 1)[1].split(
"/* Email modal title unread badge", 1
)[0]
for selector in (
".session-dropdown-menu",
".email-card-dropdown",
".memory-item-dropdown",
".task-dropdown",
".skill-kebab-menu",
".doclib-card-dropdown",
):
assert selector in contract
assert "font-family: inherit !important" in contract
assert "font: inherit !important" in contract
assert "transform: none" in contract
def test_library_chat_card_menu_uses_standard_anchor_gap():
assert "dd.style.top = (rect.bottom + 4) + 'px'" in LIBRARY
def test_card_menus_use_the_same_anchor_gap():
modules = [
ROOT / relative_path
for relative_path in (
"static/js/sessions.js",
"static/js/documentLibrary.js",
"static/js/memory.js",
"static/js/tasks.js",
"static/js/skills.js",
)
] + email_library_paths(include_wrapper=True)
for module in modules:
source = module.read_text(encoding="utf-8")
assert "rect.bottom + 2" not in source, module
assert "r.bottom + 2" not in source, module