mirror of
https://github.com/abort-retry-ignore/joplock.git
synced 2026-08-11 09:45:13 +00:00
- Double-click an image or viewable attachment in desktop rendered/preview mode opens an in-app lightbox (images + pdf + text); Esc/Close dismiss. Desktop single-click open removed. - Dropped/pasted attachments (images AND documents) get a blank, deletable line before and after so stacked attachments stay individually removable; rendered mode uses the round-trip-safe md-blank-line marker. - Fix: blank line inside a fenced code block was deleted on markdown<->render switches (heading-gap collapse treated a C #include as a heading); now masked around fenced code (_applyHeadingSpacing). - Fix: switching render->markdown no longer marks an unchanged note "Edited" (reconcile save state after the content round-trip; genuine pending saves preserved). - Tests never leave data: teardownTestData() purges notebooks + their notes + orphaned resources; global-teardown safety net sweeps test-prefixed leftovers; GET /api/web/notes/headers now returns parentId. Verified zero DB accumulation across repeated runs. - Bump service worker cache to v43. - Stale Playwright assertions fixed (#note-body -> .cm-content, custom code modal instead of TinyMCE codesample dialog).
93 lines
3.7 KiB
JavaScript
93 lines
3.7 KiB
JavaScript
'use strict';
|
|
|
|
// Verifies the blank-line padding around inserted attachments in rendered
|
|
// (TinyMCE) mode: two images dropped one after another must end up separated
|
|
// by a blank line in the saved markdown, so each stays individually deletable.
|
|
// Desktop-only. Exercises _uploadFileToTinyMCE / _tinyMCEBlockAttachmentHtml
|
|
// (the drag-drop path).
|
|
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const { test, expect } = require('@playwright/test');
|
|
const {
|
|
acceptDialogs,
|
|
createDesktopNote,
|
|
createNotebook,
|
|
hasAdminCredentials,
|
|
login,
|
|
logout,
|
|
setNoteBody,
|
|
setNoteTitle,
|
|
teardownTestData,
|
|
waitForSaved,
|
|
} = require('./helpers');
|
|
|
|
const TEST_IMAGE = path.resolve(__dirname, '..', 'public', 'icon-192.png');
|
|
const IMG_B64 = fs.readFileSync(TEST_IMAGE).toString('base64');
|
|
|
|
// Drop an image file onto the TinyMCE iframe body (the drag-drop upload path).
|
|
async function dropImageOnTinyMCE(page, name) {
|
|
await page.evaluate(async ({ name, data }) => {
|
|
const bytes = Uint8Array.from(atob(data), c => c.charCodeAt(0));
|
|
const file = new File([bytes], name, { type: 'image/png' });
|
|
const dt = new DataTransfer();
|
|
dt.items.add(file);
|
|
const iframe = document.querySelector('iframe.tox-edit-area__iframe');
|
|
const body = iframe.contentDocument.body;
|
|
const event = new DragEvent('drop', { dataTransfer: dt, bubbles: true, cancelable: true });
|
|
body.dispatchEvent(event);
|
|
}, { name, data: IMG_B64 });
|
|
await page.waitForTimeout(2500);
|
|
}
|
|
|
|
test.describe('Attachment blank-line padding (rendered mode)', () => {
|
|
test.beforeEach(({ page }) => acceptDialogs(page));
|
|
|
|
test('two images dropped in rendered mode are separated by a blank line in markdown', async ({ page }, testInfo) => {
|
|
test.skip(testInfo.project.name !== 'desktop');
|
|
test.skip(!hasAdminCredentials(), 'Set JOPLOCK_ADMIN_EMAIL/JOPLOCK_ADMIN_PASSWORD (or PLAYWRIGHT_ADMIN_*) for this test');
|
|
|
|
const folder = `pw-padding-${Date.now()}`;
|
|
const noteTitle = `Padding note ${Date.now()}`;
|
|
|
|
try {
|
|
await login(page);
|
|
await createNotebook(page, folder);
|
|
await createDesktopNote(page, folder);
|
|
await setNoteTitle(page, noteTitle);
|
|
|
|
// Seed body, then switch to rich (TinyMCE) mode.
|
|
await page.locator('#editor-panel #markdown-toggle').click();
|
|
await expect(page.locator('#editor-panel .cm-content')).toBeVisible({ timeout: 15000 });
|
|
await setNoteBody(page, 'Intro text.');
|
|
await waitForSaved(page);
|
|
await page.locator('#editor-panel #preview-toggle').click();
|
|
await expect(page.locator('iframe.tox-edit-area__iframe')).toBeVisible({ timeout: 15000 });
|
|
|
|
// Drop two images back to back.
|
|
await dropImageOnTinyMCE(page, 'one.png');
|
|
await dropImageOnTinyMCE(page, 'two.png');
|
|
|
|
// Force a save round-trip and read the markdown body.
|
|
await page.locator('#editor-panel #markdown-toggle').click();
|
|
await expect(page.locator('#editor-panel .cm-content')).toBeVisible({ timeout: 15000 });
|
|
await page.waitForTimeout(500);
|
|
const body = await page.locator('#editor-panel #note-body').inputValue();
|
|
|
|
// Two resource refs present.
|
|
const refs = body.match(/!\[[^\]]*\]\(:\/[0-9a-fA-F]{32}\)/g) || [];
|
|
expect(refs.length, `expected two image refs, body was:\n${body}`).toBeGreaterThanOrEqual(2);
|
|
|
|
// A blank line (>=1 empty line == a deletable md-blank-line paragraph on
|
|
// re-render) must separate the two images — they are not glued together.
|
|
const between = body.slice(
|
|
body.indexOf(refs[0]) + refs[0].length,
|
|
body.lastIndexOf(refs[1]),
|
|
);
|
|
expect(/\n[ \t]*\n/.test(between), `images must be separated by a blank line, got between: ${JSON.stringify(between)}`).toBe(true);
|
|
} finally {
|
|
await teardownTestData(page, { folders: [folder] });
|
|
await logout(page).catch(() => {});
|
|
}
|
|
});
|
|
});
|