From 1a66d9c3a8007786ba217d707fc1e675cf1970ae Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 24 Aug 2026 08:26:50 -0400 Subject: [PATCH] tests: pin the export against writing every checklist twice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit M304 step 7. The code change landed with the server half — _note_markdown's `if items:` branch went, and the export payload stopped carrying an items array — but neither had a test, and the failure mode is quiet: every list appears twice in an export, then twice again when that export is imported back. Three cases, and the third is the one worth having. An export taken BEFORE this milestone has a body with no task lines and a separate items array, so importing one still has to fold the checklist in. That is the same fold the Keep importer does, and the reason _insert_note still accepts items at all — asymmetric on purpose: the export stopped writing them, the import did not stop reading them. --- tests/test_notes.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_notes.py b/tests/test_notes.py index 464fa4e..ef83f31 100644 --- a/tests/test_notes.py +++ b/tests/test_notes.py @@ -19,6 +19,7 @@ from thoughtsync.notes import ( _header_filename, _keep_spec, _native_spec, + _note_markdown, _safe_filename, _slugify, _usec_to_dt, @@ -533,3 +534,39 @@ def test_the_migration_folds_exactly_like_the_app(): ("prose\n", " padded ", True), ]: assert module._append_item(body, text, checked) == append_item(body, text, checked) + + +# --- import/export: the body already carries the list ------------------------ + + +def test_note_markdown_writes_a_checklist_once(): + """The export used to append the items after the body. The body IS them now + (M304), so the old branch would have doubled every checklist in an export — and + doubled it again on the next re-import.""" + note = Note( + display_title="shopping", + body="shopping\n\n- [ ] milk\n- [x] eggs", + color="default", + pinned=False, + archived=False, + ) + out = _note_markdown(note, []) + assert out.count("- [ ] milk") == 1 + assert out.count("- [x] eggs") == 1 + # And in place, under the note's own first line rather than in a block of its own. + assert out.rstrip().endswith("shopping\n\n- [ ] milk\n- [x] eggs") + + +def test_native_spec_of_a_current_export_folds_nothing(): + # Today's export carries no `items` key, because the body has the lines. An empty + # list is what stops _insert_note folding them in a second time. + assert _native_spec({"body": "a\n\n- [ ] milk"})["items"] == [] + + +def test_native_spec_of_a_pre_m304_export_still_carries_its_items(): + # An export taken BEFORE this milestone has a body with no task lines and a + # separate items array. Importing one has to put the checklist back — which is + # the same fold the Keep importer does, and the reason _insert_note still accepts + # items at all. + old = {"body": "shopping", "items": [{"text": "milk", "checked": True}]} + assert _native_spec(old)["items"] == [{"text": "milk", "checked": True}]