from pathlib import Path import subprocess ROOT = Path(__file__).resolve().parents[1] def test_shared_action_menu_order_is_used_by_item_menus() -> None: expected_imports = { "static/js/documentLibrary.js": "orderActionMenuItems", "static/js/tasks.js": "orderActionMenuItems", "static/js/sessions.js": "orderActionMenuItems", "static/js/research/panel.js": "orderActionMenuItems", "static/js/emailLibrary.js": "orderActionMenuItems", "static/js/memory.js": "orderActionMenuItems", } for relative_path, helper in expected_imports.items(): source = (ROOT / relative_path).read_text(encoding="utf-8") assert "actionMenuOrder.js" in source assert helper in source def test_common_action_order_matches_product_convention() -> None: source = (ROOT / "static/js/actionMenuOrder.js").read_text(encoding="utf-8") for rank in (200, 400, 500, 550, 600, 650, 700, 900, 1000): assert f"{{ rank: {rank}" in source def test_callback_actions_are_sorted_by_their_labels() -> None: script = """ import { orderActionMenuItems } from './static/js/actionMenuOrder.js'; const callback = () => {}; const items = [ { label: 'Delete', action: callback }, { label: 'Archive', action: callback }, { label: 'Copy', action: callback }, { label: 'Favorite', action: callback }, { label: 'Select', action: callback }, { label: 'Rename', action: callback }, { label: 'Open', action: callback }, { label: 'Cancel', action: callback }, ]; process.stdout.write(orderActionMenuItems(items).map(item => item.label).join('|')); """ result = subprocess.run( ["node", "--input-type=module", "--eval", script], cwd=ROOT, check=True, capture_output=True, text=True, ) assert result.stdout == "Open|Rename|Favorite|Copy|Select|Archive|Delete|Cancel" def test_action_order_module_is_precached() -> None: service_worker = (ROOT / "static/sw.js").read_text(encoding="utf-8") assert "'/static/js/actionMenuOrder.js'" in service_worker def test_dropdown_select_actions_use_the_canonical_icon() -> None: source = (ROOT / "static/js/actionMenuOrder.js").read_text(encoding="utf-8") assert "export const SELECT_MENU_ICON" in source for relative_path in ( "static/js/documentLibrary.js", "static/js/memory.js", "static/js/sessions.js", "static/js/skills.js", "static/js/tasks.js", "static/js/emailLibrary.js", "static/js/research/panel.js", ): module = (ROOT / relative_path).read_text(encoding="utf-8") assert "SELECT_MENU_ICON" in module def test_email_filter_menu_has_context_title() -> None: source = (ROOT / "static/js/emailLibrary.js").read_text(encoding="utf-8") assert 'email-filter-menu-title">Filter by...' in source def test_email_setting_toggles_render_neutral_disabled_state() -> None: source = (ROOT / "static/js/emailLibrary.js").read_text(encoding="utf-8") style = (ROOT / "static/style.css").read_text(encoding="utf-8") assert 'email-settings-auto-reply-section' in source assert 'email-settings-display-enabled-state' in source assert 'stateLabel = section?.querySelector' in source assert '.email-settings-section.is-disabled .email-settings-enabled-state' in style def test_email_search_options_menu_has_context_title() -> None: source = (ROOT / "static/js/emailLibrary.js").read_text(encoding="utf-8") menu_start = source.index('id="email-search-options-menu"') menu_end = source.index("", menu_start) + len("") assert 'email-search-options-title">Filter by...' in source[menu_start:menu_end] def test_email_date_headers_mark_unexpected_timeline_gaps() -> None: source = (ROOT / "static/js/emailLibrary.js").read_text(encoding="utf-8") assert "function _emailTimelineGapThreshold(items)" in source assert "email-date-gap-break" in source assert "gapDays > 90 && gapDays > timelineGapThreshold" in source style = (ROOT / "static/style.css").read_text(encoding="utf-8") assert ".date-section-header.email-date-gap-break" in style def test_email_filters_and_card_favorite_toggle_are_wired() -> None: source = (ROOT / "static/js/emailLibrary.js").read_text(encoding="utf-8") assert '