/// /** * Test Pattern: Note Markdown Viewer * * Tests for the note-md pattern: * - Static properties: isHidden, isMentionable * - Wiki-link conversion in processedContent * - Checkbox toggle via checkboxToggle stream * - Note passthrough * - hasBacklinks computed * - Edge cases: regular markdown links, special characters, missing checkboxes, empty content * - sourceNoteRef and goToEdit action * * Run: deno task ct test packages/patterns/notes/note-md.test.tsx --verbose */ import { action, computed, NAME, pattern, Writable } from "commontools"; import NoteMd from "./note-md.tsx"; import Note from "./note.tsx"; export default pattern(() => { // Writable content cell for basic testing const contentCell = Writable.of("Hello world"); const md = NoteMd({ note: { title: "Test Note", content: "Simple content", backlinks: [], noteId: "test-note-1", }, content: contentCell, }); // Second instance with wiki-links in content const wikiContent = Writable.of( "See [[Alice (abc123)]] and [[Bob (def456)]] for details", ); const mdWiki = NoteMd({ note: { title: "Wiki Note", content: "Fallback content", backlinks: [], noteId: "test-note-2", }, content: wikiContent, }); // Third instance with no wiki-links (passthrough) const plainContent = Writable.of("No links here, just plain text."); const mdPlain = NoteMd({ note: { title: "Plain Note", content: "Fallback", backlinks: [], noteId: "test-note-3", }, content: plainContent, }); // Fourth instance with checkboxes for toggle testing const checkboxContent = Writable.of( "- [ ] Task one\n- [x] Task two\n- [ ] Task three", ); const mdCheckbox = NoteMd({ note: { title: "Checkbox Note", content: "Fallback", backlinks: [], noteId: "test-note-4", }, content: checkboxContent, }); // === Edge case - regular markdown links === const regularLinksContent = Writable.of( "Use [regular markdown](http://example.com) links", ); const mdRegularLinks = NoteMd({ note: { title: "Regular Links Note", content: "Fallback", backlinks: [], noteId: "test-note-5", }, content: regularLinksContent, }); // === Edge case - wiki-link with special characters === const specialCharsContent = Writable.of("See [[Alice & Bob (special-123)]]"); const mdSpecialChars = NoteMd({ note: { title: "Special Chars Note", content: "Fallback", backlinks: [], noteId: "test-note-6", }, content: specialCharsContent, }); // === Edge case - no checkboxes === const noCheckboxContent = Writable.of("Just regular text\nNo checkboxes"); const mdNoCheckbox = NoteMd({ note: { title: "No Checkbox Note", content: "Fallback", backlinks: [], noteId: "test-note-7", }, content: noCheckboxContent, }); // === Edge case - mixed markdown with checkboxes === const mixedContent = Writable.of( "# Title\n\n- [ ] First task\n\nSome text\n\n- [x] Second task", ); const mdMixed = NoteMd({ note: { title: "Mixed Note", content: "Fallback", backlinks: [], noteId: "test-note-8", }, content: mixedContent, }); // === Edge case - empty content === const emptyContent = Writable.of(""); const mdEmpty = NoteMd({ note: { title: "Empty Note", content: "Fallback", backlinks: [], noteId: "test-note-9", }, content: emptyContent, }); // === Edge case - whitespace only content === const whitespaceContent = Writable.of(" \n\n "); const mdWhitespace = NoteMd({ note: { title: "Whitespace Note", content: "Fallback", backlinks: [], noteId: "test-note-10", }, content: whitespaceContent, }); // === Instance with sourceNoteRef for Edit navigation === const sourceNote = Note({ title: "Source Note", content: "Original editable content", noteId: "source-note-1", isHidden: false, }); const sourceContent = Writable.of("Content from source"); const mdWithSource = NoteMd({ note: { title: "Source Note", content: "Original editable content", backlinks: [], noteId: "source-note-1", }, sourceNoteRef: sourceNote, content: sourceContent, }); // Instance WITHOUT sourceNoteRef (tests wish-based fallback path) const mdWithoutSource = NoteMd({ note: { title: "No Source Note", content: "Content without source ref", backlinks: [], noteId: "no-source-1", }, content: Writable.of("Direct content"), }); // ========================================================================== // Actions // ========================================================================== // Toggle first checkbox (index 0) to checked const action_check_first = action(() => { mdCheckbox.checkboxToggle.send({ detail: { index: 0, checked: true } }); }); // Toggle second checkbox (index 1) to unchecked const action_uncheck_second = action(() => { mdCheckbox.checkboxToggle.send({ detail: { index: 1, checked: false } }); }); // Toggle third checkbox (index 2) to checked const action_check_third = action(() => { mdCheckbox.checkboxToggle.send({ detail: { index: 2, checked: true } }); }); // Update wiki content to have multiple links const action_set_multi_wiki = action(() => { wikiContent.set( "Link to [[Charlie (ghi789)]] and back to [[Alice (abc123)]]", ); }); // === Toggle on content with no checkboxes === const action_toggle_no_checkbox = action(() => { mdNoCheckbox.checkboxToggle.send({ detail: { index: 0, checked: true } }); }); // === Toggle first checkbox in mixed content === const action_check_mixed_first = action(() => { mdMixed.checkboxToggle.send({ detail: { index: 0, checked: true } }); }); // === Toggle second checkbox in mixed content === const action_uncheck_mixed_second = action(() => { mdMixed.checkboxToggle.send({ detail: { index: 1, checked: false } }); }); // === Test goToEdit with sourceNoteRef === const action_go_to_edit_with_source = action(() => { mdWithSource.goToEdit.send(); }); // === Test goToEdit without sourceNoteRef (wish fallback) === const action_go_to_edit_without_source = action(() => { mdWithoutSource.goToEdit.send(); }); // ========================================================================== // Assertions - Static properties // ========================================================================== const assert_is_hidden = computed(() => md.isHidden === true); const assert_is_not_mentionable = computed(() => md.isMentionable === false); // ========================================================================== // Assertions - Note passthrough // ========================================================================== const assert_note_title = computed( () => md.note?.title === "Test Note", ); const assert_note_id = computed( () => md.note?.noteId === "test-note-1", ); // ========================================================================== // Assertions - NAME computed // ========================================================================== const assert_name = computed( () => md[NAME] === "📖 Test Note", ); // ========================================================================== // Assertions - Wiki-link conversion // ========================================================================== // Content with wiki-links should be converted const assert_wiki_links_converted = computed( () => mdWiki.processedContent === "See [Alice](/of:abc123) and [Bob](/of:def456) for details", ); // Content without wiki-links passes through unchanged const assert_plain_passthrough = computed( () => mdPlain.processedContent === "No links here, just plain text.", ); // After updating wiki content, new links should be converted const assert_multi_wiki_converted = computed( () => mdWiki.processedContent === "Link to [Charlie](/of:ghi789) and back to [Alice](/of:abc123)", ); // ========================================================================== // Assertions - Checkbox toggle // ========================================================================== const assert_initial_checkboxes = computed( () => mdCheckbox.processedContent === "- [ ] Task one\n- [x] Task two\n- [ ] Task three", ); // After checking first: "- [x] Task one\n- [x] Task two\n- [ ] Task three" const assert_first_checked = computed( () => mdCheckbox.processedContent === "- [x] Task one\n- [x] Task two\n- [ ] Task three", ); // After unchecking second: "- [x] Task one\n- [ ] Task two\n- [ ] Task three" const assert_second_unchecked = computed( () => mdCheckbox.processedContent === "- [x] Task one\n- [ ] Task two\n- [ ] Task three", ); // After checking third: "- [x] Task one\n- [ ] Task two\n- [x] Task three" const assert_third_checked = computed( () => mdCheckbox.processedContent === "- [x] Task one\n- [ ] Task two\n- [x] Task three", ); // ========================================================================== // Assertions - hasBacklinks computed // ========================================================================== const assert_no_backlinks_initially = computed( () => md.hasBacklinks === false, ); // ========================================================================== // Assertions - Edge cases - wiki-links // ========================================================================== const assert_regular_links_passthrough = computed( () => mdRegularLinks.processedContent === "Use [regular markdown](http://example.com) links", ); const assert_special_chars_converted = computed( () => mdSpecialChars.processedContent === "See [Alice & Bob](/of:special-123)", ); // ========================================================================== // Assertions - Edge cases - checkboxes // ========================================================================== const assert_no_checkbox_content = computed( () => mdNoCheckbox.processedContent === "Just regular text\nNo checkboxes", ); const assert_no_checkbox_unchanged = computed( () => mdNoCheckbox.processedContent === "Just regular text\nNo checkboxes", ); const assert_mixed_initial = computed( () => mdMixed.processedContent === "# Title\n\n- [ ] First task\n\nSome text\n\n- [x] Second task", ); const assert_mixed_first_checked = computed( () => mdMixed.processedContent === "# Title\n\n- [x] First task\n\nSome text\n\n- [x] Second task", ); const assert_mixed_second_unchecked = computed( () => mdMixed.processedContent === "# Title\n\n- [x] First task\n\nSome text\n\n- [ ] Second task", ); // ========================================================================== // Assertions - Edge cases - empty/missing content // ========================================================================== const assert_empty_content = computed( () => mdEmpty.processedContent === "", ); const assert_whitespace_preserved = computed( () => mdWhitespace.processedContent === " \n\n ", ); // ========================================================================== // Assertions - sourceNoteRef path // ========================================================================== const assert_source_name = computed( () => mdWithSource[NAME] === "📖 Source Note", ); const assert_source_content = computed( () => mdWithSource.processedContent === "Content from source", ); const assert_source_is_hidden = computed( () => mdWithSource.isHidden === true, ); // Verify state is stable after goToEdit with sourceNoteRef const assert_source_stable_after_edit = computed( () => mdWithSource.processedContent === "Content from source", ); // Verify state is stable after goToEdit without sourceNoteRef (wish fallback) const assert_no_source_stable_after_edit = computed( () => mdWithoutSource.processedContent === "Direct content", ); // ========================================================================== // Test Sequence // ========================================================================== return { tests: [ // === Static properties === { assertion: assert_is_hidden }, { assertion: assert_is_not_mentionable }, // === Note passthrough === { assertion: assert_note_title }, { assertion: assert_note_id }, // === NAME === { assertion: assert_name }, // === Wiki-link conversion === { assertion: assert_wiki_links_converted }, { assertion: assert_plain_passthrough }, // === Update wiki content and verify === { action: action_set_multi_wiki }, { assertion: assert_multi_wiki_converted }, // === Checkbox toggle === { assertion: assert_initial_checkboxes }, { action: action_check_first }, { assertion: assert_first_checked }, { action: action_uncheck_second }, { assertion: assert_second_unchecked }, { action: action_check_third }, { assertion: assert_third_checked }, // === hasBacklinks computed === { assertion: assert_no_backlinks_initially }, // === Edge cases - wiki-links === { assertion: assert_regular_links_passthrough }, { assertion: assert_special_chars_converted }, // === Edge cases - checkboxes === { assertion: assert_no_checkbox_content }, { action: action_toggle_no_checkbox }, { assertion: assert_no_checkbox_unchanged }, { assertion: assert_mixed_initial }, { action: action_check_mixed_first }, { assertion: assert_mixed_first_checked }, { action: action_uncheck_mixed_second }, { assertion: assert_mixed_second_unchecked }, // === Edge cases - empty/missing content === { assertion: assert_empty_content }, { assertion: assert_whitespace_preserved }, // === sourceNoteRef tests === { assertion: assert_source_name }, { assertion: assert_source_content }, { assertion: assert_source_is_hidden }, { action: action_go_to_edit_with_source }, { assertion: assert_source_stable_after_edit }, { action: action_go_to_edit_without_source }, { assertion: assert_no_source_stable_after_edit }, ], md, mdWiki, mdPlain, mdCheckbox, mdRegularLinks, mdSpecialChars, mdNoCheckbox, mdMixed, mdEmpty, mdWhitespace, mdWithSource, mdWithoutSource, }; });