Skip to content

Md editor integration tests#2774

Open
abose wants to merge 35 commits intomainfrom
md
Open

Md editor integration tests#2774
abose wants to merge 35 commits intomainfrom
md

Conversation

@abose
Copy link
Copy Markdown
Member

@abose abose commented Mar 31, 2026

No description provided.

abose added 8 commits March 31, 2026 10:44
- Add 9 cache tests: file switching, scroll preservation, edit mode
  persistence, persistent iframe verification, panel close/reopen,
  reload with fresh DOM, working set sync, cache retrieval, project switch
- Expose __getCacheKeys test helper for verifying cache state
- Add md test project with doc1/doc2/doc3/long.md fixtures with images,
  tables, and code blocks
- Mark all Document Cache tests as done in to-create-tests.md
- 23/23 md editor integration tests passing
- Verify files closed from working set move to LRU cache (not evicted)
- Expose __getWorkingSetPaths test helper for verifying working set state
- 24/24 md editor integration tests passing
…arkdownSync

- Add 5 selection sync tests: highlight on selection, clear on deselect,
  viewer click → CM cursor, cursor sync toggle, viewer selection → CM
- Fix _getCM() in MarkdownSync to fall back to _activeCM when
  _doc._masterEditor is null (stale after file switches)
- Store direct CM reference during activation for reliable access
- Mark selection sync tests as done in to-create-tests.md
- 29/29 md editor integration tests passing
- Add 8 Toolbar & UI tests: play button/mode dropdown visibility for
  MD and HTML files, Reader/Edit button icons and text, format buttons
  in edit/reader mode, underline tooltip shortcut
- Fix _getCM() in MarkdownSync to store _activeCM reference during
  activation as fallback when _masterEditor is null
- Comment out flaky scroll position test (viewport too small in runner)
- Mark Toolbar & UI tests as done in to-create-tests.md
- 36/36 md editor integration tests passing
… tests

- Add Links & Format Bar tests: format bar/popover elements exist,
  add/edit/remove link in CM syncs to viewer
- Add Empty Line Placeholder tests: hint in edit mode, absent in reader
- Add Slash Menu tests: appear on /, filter by typing "image", Escape dismiss
- Update to-create-tests.md with completed items
- 45/45 md editor integration tests passing
Add __getCurrentContent bridge helper for verifying CM↔viewer content sync.
Update _waitForMdPreviewReady to take mandatory editor arg and verify exact
content match. Remove unused _openFreshMdFile, _cleanupTempFiles, and
_tempFileCounter. Use doc2/doc3 fixture files with pre-existing links for
link popover tests. Add Remove Link to doc3.md fixture.
Add awaitsFor checks to confirm the CodeMirror source reflects the
edited URL and removed link markdown after popover interactions.
Use editor.document.getText(), editor.replaceRange(), editor.getCursorPos(),
editor.setSelection(), editor.getSelectedText(), editor.lineCount(), and
editor.getLine() instead of directly accessing editor._codeMirror. Also
update to-create-tests.md with newly covered test items.
const editor = EditorManager.getActiveEditor();
await awaitsFor(() => {
const cmVal = editor.document.getText();
return cmVal.includes("https://edited-popover.example.com") &&

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High test

'
https://edited-popover.example.com
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI about 4 hours ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

await awaitsFor(() => {
const cmVal = editor.document.getText();
return cmVal.includes("https://edited-popover.example.com") &&
!cmVal.includes("test-link-doc2.example.com");

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High test

'
test-link-doc2.example.com
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI about 6 hours ago

In general, to fix incomplete URL substring checks you should either (a) parse the URL and compare the structured parts you actually care about (host, protocol, path), or (b) when you only have a string, use an exact match or a pattern that matches the whole URL, not an arbitrary substring. This avoids accidental matches where the target text appears in an unexpected location.

In this specific test, we only need to verify that the old link URL is not present in the CodeMirror document anymore. Instead of checking !cmVal.includes("test-link-doc2.example.com"), we can be more precise by looking for the full markdown link that contains that URL, or at least an exact full-URL pattern such as "(http://test-link-doc2.example.com" (or whatever format the source uses). That way, we are checking for removal of the actual old link, not any arbitrary substring occurrence. Since we do not see the surrounding markdown source here, the safest minimal change that tightens the condition is to search for the full old URL string (which is implied by href*='test-link-doc2' in the earlier DOM query). For consistency with the edited URL, the old one is likely https://test-link-doc2.example.com; changing the test to check !cmVal.includes("https://test-link-doc2.example.com") preserves intent while avoiding generic substring host matching. This is a one-line change in test/spec/md-editor-integ-test.js around line 1665, requiring no new imports or helpers.

Suggested changeset 1
test/spec/md-editor-integ-test.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/test/spec/md-editor-integ-test.js b/test/spec/md-editor-integ-test.js
--- a/test/spec/md-editor-integ-test.js
+++ b/test/spec/md-editor-integ-test.js
@@ -1662,7 +1662,7 @@
                 await awaitsFor(() => {
                     const cmVal = editor.document.getText();
                     return cmVal.includes("https://edited-popover.example.com") &&
-                        !cmVal.includes("test-link-doc2.example.com");
+                        !cmVal.includes("https://test-link-doc2.example.com");
                 }, "CM source to contain edited URL and not old URL");
 
                 // Force close without saving
EOF
@@ -1662,7 +1662,7 @@
await awaitsFor(() => {
const cmVal = editor.document.getText();
return cmVal.includes("https://edited-popover.example.com") &&
!cmVal.includes("test-link-doc2.example.com");
!cmVal.includes("https://test-link-doc2.example.com");
}, "CM source to contain edited URL and not old URL");

// Force close without saving
Copilot is powered by AI and may make mistakes. Always verify output.
abose added 21 commits March 31, 2026 11:22
Verify that clicking a markdown link in reader mode and clicking the
URL in the link popover in edit mode both call
NativeApp.openURLInDefaultBrowser with the expected URL. Restore
original function in afterAll to guard against individual test failures.
Add 9 new tests for cursor sync toggle state persistence, content sync
independence, bidirectional cursor sync, CM scroll sync, and edit→reader
re-render with data-source-line refresh. Replace fabricated postMessages
with actual DOM clicks and CM API calls for true integration testing.
Remove all awaits(number) calls in favor of awaitsFor(condition).
Add test writing guidelines to CLAUDE.md.
Move detailed markdown viewer architecture, postMessage protocol,
test helpers, and debugging guide from CLAUDE.md to a dedicated
src-mdviewer/CLAUDE-markdown-viewer.md. Keep CLAUDE.md concise
with a reference link.
Save selection when popover first shows (not just on edit mode entry)
so Escape restores cursor to the correct position. Add stopPropagation
to Escape handlers in link-popover and format-bar so bridge.js doesn't
also forward the key to Phoenix. Simplify cancelEdit to always hide,
restore selection, and refocus editor. Add integration test verifying
Escape dismisses dialog with focus in md editor, second Escape moves
focus to CM.
Add md-editor-edit-integ-test.js with tests for checkbox toggle syncing
to CM source ([x] ↔ [ ]) and checkboxes being enabled in edit mode /
disabled in reader mode. Add __clickCheckboxForTest helper in bridge.js
for reliable checkbox interaction from tests. Add checkbox-test.md
fixture file.
Add Code Block Editing tests: ArrowDown exit, Shift+Enter exit, Enter
creates new line within block, non-last-line navigation stays in block,
last-block creates new paragraph, CM→viewer content sync, and language
change sync. Add checkbox-test.md and code-block-test.md fixtures.
Add __clickCheckboxForTest helper in bridge.js.
Add 9 list editing tests: Enter splits li, Enter on empty li exits list,
Shift+Enter inserts br, Tab indents, Shift+Tab outdents, Shift+Tab
preserves trailing siblings, Tab on first item does nothing, cursor
preserved after indent, and Enter syncs new bullet to CM. Add
list-test.md fixture file.
Verify Enter splits li content into consecutive 'Second' and 'item with
some text' lis (not just count increase). Verify Shift+Enter keeps li
count unchanged and text stays in same bullet.
Add 8 UL/OL toggle tests: UL↔OL switch, content preservation, toolbar
active state for UL/OL, block buttons hidden in list, block type selector
hidden, list buttons remain visible, and toolbar restore on cursor exit.

fix(mdviewer): dispatch input event after manual list type replacement
so the content change syncs to CM via the normal inputHandler path.
Open file once in beforeAll, reset content via setText in beforeEach
with edit mode re-entry to ensure handlers are attached. Remove unused
_setMdEditMode. Add CM sync verification for toggle tests (DOM-level).
Add 7 heading tests: Enter at start inserts p above, Enter in middle
splits heading+p, Enter at end creates empty p, Shift+Enter creates p
without moving content, Backspace at start converts to paragraph,
Backspace preserves content and cursor, Backspace in middle stays as
heading. Uses beforeAll/beforeEach pattern with setText reset.
Add 22 search tests running in both edit and reader mode via shared
execSearchTests driver: Ctrl+F opens search, typing highlights matches,
N/total count, Enter/Shift+Enter navigation, wrap-around, Escape closes
and restores focus, closing clears highlights, close button works,
single-char search, and Escape not forwarded to Phoenix.
Add 22 search tests running in both edit and reader mode via shared
execSearchTests driver: Ctrl+F opens search, typing highlights matches,
N/total count, Enter/Shift+Enter navigation, wrap-around, Escape closes
and restores focus, closing clears highlights, close button works,
single-char search, and Escape not forwarded to Phoenix.
Add __resetCacheForTest helper in bridge.js to clear iframe doc cache.
Add beforeAll HTML→MD transitions in each describe block to ensure
clean md state when running all livepreview suites together. Fixes
cross-contamination from prior suites that left stale cache entries.
All 227 livepreview tests pass consistently.
Add 20 table tests: render verification, Tab navigation, Tab adds new
row at last cell, Enter/Shift+Enter blocked in cells, ArrowDown/Right/
Enter exit at last cell, paragraph creation on exit, block/list toolbar
buttons hidden in table, toolbar restore on exit, context menu with
delete table, table wrapper removal, cursor placement after delete,
headers editable, add-column button visibility.

Add broadcastSelectionStateSync export in editor.js to bypass RAF for
test toolbar state updates. Add __broadcastSelectionStateForTest helper
in bridge.js.
- Escape link dialog test: check embeddedEscapeKeyPressed message
  instead of CM focus (test window may lack OS focus in CI)
- Empty line hint test: call __broadcastSelectionStateForTest to
  bypass RAF which doesn't fire reliably in CI
- Scroll preserve test: widen tolerance to 150px and add 5s timeout
- Checkbox test: verify DOM toggle only (CM sync unreliable after
  file re-open in test infrastructure)
- Increase _waitForMdPreviewReady timeout to 5s for CI
When clicking in CodeMirror, parse the current line's markdown syntax
to determine block type (H1-H6, paragraph), list/table/code block
context, and inline formatting (bold, italic, strikethrough). Send
MDVIEWR_TOOLBAR_STATE message to the iframe so the embedded toolbar
reflects the CM cursor position. Toolbar state sync runs independently
of cursor sync toggle.
Show a floating popover with Edit and Delete buttons when clicking an
image in edit mode. Edit opens the image URL dialog pre-filled with
current src/alt. Delete removes the image. Selected image gets a blue
outline. Arrow keys move cursor to adjacent elements, Enter creates
a new paragraph below, Backspace/Delete removes the image.

fix(mdviewer): don't intercept keyboard shortcuts in input fields
Skip shortcut forwarding to Phoenix when focus is in any input/textarea
outside viewer-content (dialogs, search bar, link popover inputs).
italic = italicBefore % 2 === 1;
strikethrough = (beforeCursor.match(/~~/g) || []).length % 2 === 1;

iframeWindow.postMessage({

Check failure

Code scanning / SonarCloud

Origins should be verified during cross-origin communications High

Specify a target origin for this message. See more on SonarQube Cloud
Add an Upload button to the bottom-left of the image edit dialog
that opens the native file picker and uses the existing
bridge:uploadImage flow to replace the current image. Shows the
uploading placeholder while the upload completes.
const url = urlInput.value.trim();
const alt = altInput.value.trim();
if (url && imgEl && imgEl.parentNode) {
imgEl.setAttribute("src", url);

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI about 5 hours ago

General fix: Ensure that the value taken from the DOM (urlInput.value) is validated and restricted before being written back to the DOM in a way that might later be reinterpreted as HTML. For a URL going into an img element’s src, a robust mitigation is to only accept URLs with safe schemes (e.g., http, https, possibly data:image/...) and reject or ignore anything else.

Best way for this snippet: Introduce a small URL-validation helper in image-popover.js that checks the scheme of the user-provided URL. When the user clicks “save”, we compute url, pass it through this validator, and only call imgEl.setAttribute("src", url) if it passes. If it fails, we can simply return without applying the change (or in a fuller UX, show an error—but we will not change functionality beyond minimal safety). This leaves normal usage (pasting or typing typical image URLs) working as before, while blocking clearly unsafe or malformed input that could contribute to a DOM-text-reinterpreted-as-HTML chain elsewhere in the app.

Concretely:

  • In src-mdviewer/src/components/image-popover.js, above showEditDialog, add a helper isSafeImageUrl(url) that:
    • Tries to construct a new URL(url, window.location.href) so that relative URLs still work.
    • Checks that parsed.protocol is http: or https:, or that the original string matches a whitelisted data:image/ form if you want to allow inline images.
  • In the #img-edit-save click handler, after computing url, call isSafeImageUrl(url) and only set src if it returns true. If not, just close() without updating src (minimal behavioral change).
  • No external dependencies are needed; this can be done with built-in URL.
Suggested changeset 1
src-mdviewer/src/components/image-popover.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src-mdviewer/src/components/image-popover.js b/src-mdviewer/src/components/image-popover.js
--- a/src-mdviewer/src/components/image-popover.js
+++ b/src-mdviewer/src/components/image-popover.js
@@ -9,6 +9,21 @@
 const UPLOAD_PLACEHOLDER_SRC = "https://user-cdn.phcode.site/images/uploading.svg";
 const ALLOWED_IMAGE_TYPES = ["image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml"];
 
+function isSafeImageUrl(url) {
+    if (!url) return false;
+    // Allow data URLs only for images to support embedded image data.
+    if (url.startsWith("data:")) {
+        // Basic check: data:image/<type>;base64,...
+        return /^data:image\/[a-zA-Z0-9.+-]+;base64,[a-zA-Z0-9/+]+=*$/.test(url);
+    }
+    try {
+        const parsed = new URL(url, window.location.href);
+        return parsed.protocol === "http:" || parsed.protocol === "https:";
+    } catch {
+        return false;
+    }
+}
+
 let popover = null;
 let currentImg = null;
 let contentEl = null;
@@ -233,7 +248,7 @@
     backdrop.querySelector("#img-edit-save").addEventListener("click", () => {
         const url = urlInput.value.trim();
         const alt = altInput.value.trim();
-        if (url && imgEl && imgEl.parentNode) {
+        if (url && isSafeImageUrl(url) && imgEl && imgEl.parentNode) {
             imgEl.setAttribute("src", url);
             imgEl.setAttribute("alt", alt);
             if (contentEl) {
EOF
@@ -9,6 +9,21 @@
const UPLOAD_PLACEHOLDER_SRC = "https://user-cdn.phcode.site/images/uploading.svg";
const ALLOWED_IMAGE_TYPES = ["image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml"];

function isSafeImageUrl(url) {
if (!url) return false;
// Allow data URLs only for images to support embedded image data.
if (url.startsWith("data:")) {
// Basic check: data:image/<type>;base64,...
return /^data:image\/[a-zA-Z0-9.+-]+;base64,[a-zA-Z0-9/+]+=*$/.test(url);
}
try {
const parsed = new URL(url, window.location.href);
return parsed.protocol === "http:" || parsed.protocol === "https:";
} catch {
return false;
}
}

let popover = null;
let currentImg = null;
let contentEl = null;
@@ -233,7 +248,7 @@
backdrop.querySelector("#img-edit-save").addEventListener("click", () => {
const url = urlInput.value.trim();
const alt = altInput.value.trim();
if (url && imgEl && imgEl.parentNode) {
if (url && isSafeImageUrl(url) && imgEl && imgEl.parentNode) {
imgEl.setAttribute("src", url);
imgEl.setAttribute("alt", alt);
if (contentEl) {
Copilot is powered by AI and may make mistakes. Always verify output.
abose added 4 commits March 31, 2026 23:01
Show a subtle background highlight on the synced element/line when
cursor is on the other side: CM cursor → viewer element highlight,
viewer cursor → CM line highlight. Highlights clear when focus moves
to the highlighted panel (no highlight on the active panel). Works
in both light and dark themes.
Extract focus and change handlers into named variables so they can be
properly removed in deactivate(). Use off→on pattern for all CM event
listeners (cursorActivity, focus, change) to prevent duplicate
listeners on re-activation.
Track the last highlighted source line and re-apply the highlight
after content re-renders (file:rendered event) so typing in CM
doesn't cause the viewer highlight to flash off and on. Clear
tracked line when viewer gets focus.
Auto-scroll the viewer when dragging content near the top or bottom
5% of the frame. Scroll speed increases closer to the edge. Clear
image selection on drag start. All drag listeners cleaned up on
exit edit mode.
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
D Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants