Files
odysseus/tests/test_notes_mobile_checklist_toggle_static.py
T
Alexandre TeixeiraandLéo f1a47f3812 refactor(notes): decompose Notes into feature-owned modules (#6713)
* refactor(notes): decompose Notes into feature-owned modules

* fix(notes): avoid HTML interpolation in reminder picker

* docs(notes): drop checkpoint archives, session logs and publication drafts

The N0-N6 tarballs and raw pytest/node logs are not read by any script or
test, and the logs carry absolute home-directory paths. Once squashed into
dev they would stay in history for good. notes-pr.md and notes-issue.md
were the drafts used to open #6712 and #6713, not documentation.

notes-validation.md keeps the counts, the checkpoint hashes and
implementation.sha256 as the record; local interpreter paths and the local
Docker image name are replaced with portable placeholders.

* fix(notes): keep the state store raw and stop masonry re-observing after close

- state: writes built from views (filter, spread, splice) stored nested
  read-only views in the store, so structuredClone on a stored note threw
  DataCloneError. Store writes now unwrap nested views.
- layout: a save that resolves after closePanel re-renders the leaving pane
  and re-created the masonry ResizeObserver that teardown had disposed.
  Skip creating it once Notes is closed.
- index.js: drop 30 imported or declared names that were never used and
  group the imports at the top. Import order is unchanged.
- Revert unrelated punctuation edits in .gitattributes and keep the header
  comment and docstring first in compare/vote.js and
  test_notes_dom_xss_helpers.py.

Both fixes have a node test that fails on the previous head.

* test(notes): make script rejection assertion case-insensitive

Address CodeQL js/bad-tag-filter alert #1150 in test-only assertion and refresh Notes evidence manifest checksum.

---------

Co-authored-by: Léo <leograndcontact@gmail.com>
2026-10-15 09:29:41 +01:00

102 lines
4.5 KiB
Python

from tests.helpers.notes_source import notes_source
from pathlib import Path
import re
from tests.helpers.stylesheets import app_css, stylesheet_cache_version
from tests.helpers.js_modules import email_library_source
ROOT = Path(__file__).resolve().parent.parent
def test_mobile_notes_checklist_rows_remain_tappable():
css = app_css()
notes = notes_source("ui/cardChecklist", "ui/checklist", "ui/editor", "ui/mobileEditor", "ui/mobileReorder", "ui/selection", "ui/drawing")
assert "body.notes-mobile-mode .note-card .note-checkbox {" in css
assert "pointer-events: auto;" in css
assert "touch-action: manipulation;" in css
assert "body.notes-mobile-mode .note-card .note-checkbox,\nbody.notes-mobile-mode .note-card .note-checkbox-rm" not in css
assert re.search(r"if \(state.selectMode\) return; // let card-level handler take over\n\s+e.preventDefault\(\);", notes)
def test_mobile_edit_checklist_toggle_marks_form_dirty():
notes = notes_source("ui/cardChecklist", "ui/checklist", "ui/editor", "ui/mobileEditor", "ui/mobileReorder", "ui/selection", "ui/drawing")
assert "container.dispatchEvent(new Event('input', { bubbles: true }));" in notes
def test_completed_mobile_todo_uses_finish_action():
notes = notes_source("ui/cardChecklist", "ui/checklist", "ui/editor", "ui/mobileEditor", "ui/mobileReorder", "ui/selection", "ui/drawing")
assert "const _isFinishedTodo = () =>" in notes
assert "const label = _isFinishedTodo() ? 'Finish' : 'Archive';" in notes
assert "if (_isFinishedTodo()) _enterArchive();" in notes
def test_mobile_long_press_enters_select_mode_for_that_note():
notes = notes_source("ui/cardChecklist", "ui/checklist", "ui/editor", "ui/mobileEditor", "ui/mobileReorder", "ui/selection", "ui/drawing")
assert "function _enterSelectMode(initialId = null)" in notes
assert "app.setSuppressNextNoteTapId(card.dataset.noteId);" in notes
assert "_enterSelectMode(card.dataset.noteId);" in notes
def test_mobile_select_mode_has_subtle_jiggle_feedback():
css = app_css()
assert "@keyframes notes-select-jiggle" in css
assert "body.notes-mobile-mode .note-card-selectmode" in css
assert "prefers-reduced-motion: reduce" in css
def test_notes_mobile_checklist_asset_versions_are_bumped():
app = (ROOT / "static" / "app.js").read_text(encoding="utf-8")
html = (ROOT / "static" / "index.html").read_text(encoding="utf-8")
assert re.search(r"notes\.js\?v=[A-Za-z0-9_-]+", app)
app_versions = re.findall(r"/static/app\.js\?v=([A-Za-z0-9_-]+)", html)
assert app_versions and len(set(app_versions)) == 1
assert stylesheet_cache_version() == app_versions[0]
def test_drawing_edits_mark_notes_dirty_and_keep_one_gallery_image():
notes = notes_source("ui/cardChecklist", "ui/checklist", "ui/editor", "ui/mobileEditor", "ui/mobileReorder", "ui/selection", "ui/drawing")
upload = (ROOT / "routes" / "upload_routes.py").read_text(encoding="utf-8")
note_routes = (ROOT / "routes" / "note" / "note_routes.py").read_text(encoding="utf-8")
assert "const _markCanvasDirty = () => container.dispatchEvent(new Event('input', { bubbles: true }));" in notes
assert "_uploadCanvasAsPng(canvas, note?.gallery_id || null)" in notes
assert "fd.append('gallery_id', galleryId)" in notes
assert "if gallery_id:" in upload
assert "note.gallery_id = body.gallery_id" in note_routes
def test_calendar_email_attachments_have_a_calendar_import_action():
email = email_library_source()
calendar = (ROOT / "static" / "js" / "calendar.js").read_text(encoding="utf-8")
assert "email-attachment-calendar-open" in email
assert "const _CALENDAR_RE = /\\.(calendar|ics|ical)$/i" in email
assert "fetch(`${API_BASE}/api/calendar/import`" in email
assert 'accept=".calendar,.ics,.ical"' in calendar
def test_mobile_bulk_select_long_press_is_shared_across_card_types():
helper = (ROOT / "static" / "js" / "mobileBulkSelect.js").read_text(encoding="utf-8")
css = app_css()
assert ".memory-item[data-memory-id]" in helper
assert ".skill-card[data-skill-name]" in helper
assert ".task-card[data-id]" in helper
assert "const HOLD_MS = 450" in helper
shared_buttons = (
"#memory-select-btn,", "#skills-select-btn,",
"#notes-select-btn,", "#tasks-select-btn,",
)
shared_css = css.split("/* Shared bulk-selection trigger.", 1)[1]
selectors = re.findall(r":is\(([^)]*)\)(?:\.active)?::before", shared_css)
assert len(selectors) == 2
for selector in selectors:
assert all(button in selector for button in shared_buttons)