2023-10-13 16:32:10 +02:00
|
|
|
import { test, expect } from './util/test';
|
|
|
|
import MainScreen from './models/MainScreen';
|
|
|
|
import SettingsScreen from './models/SettingsScreen';
|
2023-10-31 17:05:28 +02:00
|
|
|
import { _electron as electron } from '@playwright/test';
|
|
|
|
import { writeFile } from 'fs-extra';
|
|
|
|
import { join } from 'path';
|
2023-11-12 17:06:32 +02:00
|
|
|
import createStartupArgs from './util/createStartupArgs';
|
|
|
|
import firstNonDevToolsWindow from './util/firstNonDevToolsWindow';
|
2023-12-06 21:17:16 +02:00
|
|
|
import setFilePickerResponse from './util/setFilePickerResponse';
|
2024-02-02 19:58:27 +02:00
|
|
|
import setMessageBoxResponse from './util/setMessageBoxResponse';
|
2023-10-13 16:32:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
test.describe('main', () => {
|
|
|
|
test('app should launch', async ({ mainWindow }) => {
|
|
|
|
// A window should open with the correct title
|
|
|
|
expect(await mainWindow.title()).toMatch(/^Joplin/);
|
|
|
|
|
|
|
|
const mainPage = new MainScreen(mainWindow);
|
|
|
|
await mainPage.waitFor();
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should be able to create and edit a new note', async ({ mainWindow }) => {
|
|
|
|
const mainScreen = new MainScreen(mainWindow);
|
2023-12-06 21:17:16 +02:00
|
|
|
const editor = await mainScreen.createNewNote('Test note');
|
2023-10-13 16:32:10 +02:00
|
|
|
|
|
|
|
// Note list should contain the new note
|
|
|
|
await expect(mainScreen.noteListContainer.getByText('Test note')).toBeVisible();
|
|
|
|
|
|
|
|
// Focus the editor
|
|
|
|
await editor.codeMirrorEditor.click();
|
|
|
|
|
|
|
|
// Type some text
|
|
|
|
await mainWindow.keyboard.type('# Test note!');
|
|
|
|
await mainWindow.keyboard.press('Enter');
|
|
|
|
await mainWindow.keyboard.press('Enter');
|
|
|
|
await mainWindow.keyboard.type('New note content!');
|
|
|
|
|
|
|
|
// Should render
|
|
|
|
const viewerFrame = editor.getNoteViewerIframe();
|
|
|
|
await expect(viewerFrame.locator('h1')).toHaveText('Test note!');
|
|
|
|
});
|
|
|
|
|
2023-12-24 16:50:22 +02:00
|
|
|
test('mermaid and KaTeX should render', async ({ mainWindow }) => {
|
|
|
|
const mainScreen = new MainScreen(mainWindow);
|
|
|
|
const editor = await mainScreen.createNewNote('🚧 Test 🚧');
|
|
|
|
|
|
|
|
const testCommitId = 'bf59b2';
|
2024-02-02 19:58:27 +02:00
|
|
|
await editor.focusCodeMirrorEditor();
|
2023-12-24 16:50:22 +02:00
|
|
|
const noteText = [
|
|
|
|
'```mermaid',
|
|
|
|
'gitGraph',
|
|
|
|
' commit id: "973193"',
|
|
|
|
` commit id: "${testCommitId}"`,
|
|
|
|
' branch dev',
|
|
|
|
' checkout dev',
|
|
|
|
' commit id: "ceea77"',
|
|
|
|
'```',
|
|
|
|
'',
|
|
|
|
'',
|
|
|
|
'KaTeX:',
|
|
|
|
'$$ \\int_0^1 \\cos(2x + 3) $$',
|
|
|
|
'',
|
|
|
|
'Sum: $\\sum_{x=0}^{100} \\tan x$',
|
|
|
|
];
|
|
|
|
for (const line of noteText) {
|
|
|
|
if (line) {
|
|
|
|
await mainWindow.keyboard.press('Shift+Tab');
|
|
|
|
await mainWindow.keyboard.type(line);
|
|
|
|
}
|
|
|
|
await mainWindow.keyboard.press('Enter');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should render mermaid
|
|
|
|
const viewerFrame = editor.getNoteViewerIframe();
|
|
|
|
await expect(
|
|
|
|
viewerFrame.locator('pre.mermaid text', { hasText: testCommitId }),
|
|
|
|
).toBeVisible();
|
|
|
|
|
|
|
|
// Should render KaTeX (block)
|
2023-12-29 19:58:07 +02:00
|
|
|
// toBeAttached: To be added to the DOM.
|
|
|
|
await expect(viewerFrame.locator('.joplin-editable > .katex-display').first()).toBeAttached();
|
2023-12-24 16:50:22 +02:00
|
|
|
await expect(
|
|
|
|
viewerFrame.locator(
|
|
|
|
'.katex-display *', { hasText: 'cos' },
|
|
|
|
).last(),
|
|
|
|
).toBeVisible();
|
|
|
|
|
|
|
|
// Should render KaTeX (inline)
|
2023-12-29 19:58:07 +02:00
|
|
|
await expect(viewerFrame.locator('.joplin-editable > .katex').first()).toBeAttached();
|
2023-12-24 16:50:22 +02:00
|
|
|
});
|
|
|
|
|
2023-12-06 21:17:16 +02:00
|
|
|
test('HTML links should be preserved when editing a note in the WYSIWYG editor', async ({ electronApp, mainWindow }) => {
|
|
|
|
const mainScreen = new MainScreen(mainWindow);
|
|
|
|
await mainScreen.createNewNote('Testing!');
|
|
|
|
const editor = mainScreen.noteEditor;
|
|
|
|
|
|
|
|
// Set the note's content
|
2024-02-02 19:58:27 +02:00
|
|
|
await editor.focusCodeMirrorEditor();
|
2023-12-06 21:17:16 +02:00
|
|
|
|
|
|
|
// Attach this file to the note (create a resource ID)
|
|
|
|
await setFilePickerResponse(electronApp, [__filename]);
|
|
|
|
await editor.attachFileButton.click();
|
|
|
|
|
|
|
|
// Wait to render
|
|
|
|
const viewerFrame = editor.getNoteViewerIframe();
|
|
|
|
await viewerFrame.locator('a[data-from-md]').waitFor();
|
|
|
|
|
|
|
|
// Should have an attached resource
|
|
|
|
const codeMirrorContent = await editor.codeMirrorEditor.innerText();
|
|
|
|
|
|
|
|
const resourceUrlExpression = /\[.*\]\(:\/(\w+)\)/;
|
|
|
|
expect(codeMirrorContent).toMatch(resourceUrlExpression);
|
|
|
|
const resourceId = codeMirrorContent.match(resourceUrlExpression)[1];
|
|
|
|
|
|
|
|
// Create a new note with just an HTML link
|
|
|
|
await mainScreen.createNewNote('Another test');
|
|
|
|
await editor.codeMirrorEditor.click();
|
|
|
|
await mainWindow.keyboard.type(`<a href=":/${resourceId}">HTML Link</a>`);
|
|
|
|
|
|
|
|
// Switch to the RTE
|
|
|
|
await editor.toggleEditorsButton.click();
|
|
|
|
await editor.richTextEditor.waitFor();
|
|
|
|
|
|
|
|
// Edit the note to cause the original content to update
|
|
|
|
await editor.getTinyMCEFrameLocator().locator('a').click();
|
|
|
|
await mainWindow.keyboard.type('Test...');
|
|
|
|
|
|
|
|
await editor.toggleEditorsButton.click();
|
|
|
|
await editor.codeMirrorEditor.waitFor();
|
|
|
|
|
|
|
|
// Note should still contain the resource ID and note title
|
|
|
|
const finalCodeMirrorContent = await editor.codeMirrorEditor.innerText();
|
|
|
|
expect(finalCodeMirrorContent).toContain(`:/${resourceId}`);
|
|
|
|
});
|
|
|
|
|
2024-02-02 19:58:27 +02:00
|
|
|
test('should correctly resize large images', async ({ electronApp, mainWindow }) => {
|
|
|
|
const mainScreen = new MainScreen(mainWindow);
|
|
|
|
await mainScreen.createNewNote('Image resize test (part 1)');
|
|
|
|
const editor = mainScreen.noteEditor;
|
|
|
|
|
|
|
|
await editor.focusCodeMirrorEditor();
|
|
|
|
|
|
|
|
const filename = 'large-jpg-image-with-exif-rotation.jpg';
|
|
|
|
await setFilePickerResponse(electronApp, [join(__dirname, 'resources', filename)]);
|
|
|
|
|
|
|
|
// Should be possible to cancel attaching for large images
|
|
|
|
await setMessageBoxResponse(electronApp, /^Cancel/i);
|
|
|
|
await editor.attachFileButton.click();
|
|
|
|
await expect(editor.codeMirrorEditor).toHaveText('', { useInnerText: true });
|
|
|
|
|
|
|
|
// Clicking "No" should not resize
|
|
|
|
await setMessageBoxResponse(electronApp, /^No/i);
|
|
|
|
await editor.attachFileButton.click();
|
|
|
|
|
|
|
|
const getImageSize = async () => {
|
|
|
|
const viewerFrame = editor.getNoteViewerIframe();
|
|
|
|
const renderedImage = viewerFrame.getByAltText(filename);
|
2024-02-08 18:56:12 +02:00
|
|
|
|
|
|
|
// Use state: 'attached' -- we don't need the image to be on the screen (just present
|
|
|
|
// in the DOM).
|
|
|
|
await renderedImage.waitFor({ state: 'attached' });
|
2024-02-02 19:58:27 +02:00
|
|
|
|
|
|
|
// We load a copy of the image to avoid returning an overriden width set with
|
|
|
|
// .width = some_number
|
|
|
|
return await renderedImage.evaluate((originalImage: HTMLImageElement) => {
|
|
|
|
return new Promise<[number, number]>(resolve => {
|
|
|
|
const testImage = new Image();
|
|
|
|
testImage.onload = () => {
|
|
|
|
resolve([testImage.width, testImage.height]);
|
|
|
|
};
|
|
|
|
testImage.src = originalImage.src;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
const fullSize = await getImageSize();
|
|
|
|
|
|
|
|
// To make it easier to find the image (one image per note), we switch to a new, empty note.
|
|
|
|
await mainScreen.createNewNote('Image resize test (part 2)');
|
|
|
|
await editor.focusCodeMirrorEditor();
|
|
|
|
|
|
|
|
// Clicking "Yes" should resize
|
|
|
|
await setMessageBoxResponse(electronApp, /^Yes/i);
|
|
|
|
await editor.attachFileButton.click();
|
|
|
|
|
|
|
|
const resizedSize = await getImageSize();
|
|
|
|
expect(resizedSize[0]).toBeLessThan(fullSize[0]);
|
|
|
|
expect(resizedSize[1]).toBeLessThan(fullSize[1]);
|
|
|
|
|
|
|
|
// Should keep aspect ratio (regression test for #9597)
|
|
|
|
expect(fullSize[0] / resizedSize[0]).toBeCloseTo(fullSize[1] / resizedSize[1]);
|
|
|
|
});
|
|
|
|
|
2023-10-13 16:32:10 +02:00
|
|
|
test('should be possible to remove sort order buttons in settings', async ({ electronApp, mainWindow }) => {
|
|
|
|
const mainScreen = new MainScreen(mainWindow);
|
|
|
|
await mainScreen.waitFor();
|
|
|
|
|
|
|
|
// Sort order buttons should be visible by default
|
|
|
|
await expect(mainScreen.noteListContainer.locator('[title^="Toggle sort order"]')).toBeVisible();
|
|
|
|
|
2023-12-11 15:58:45 +02:00
|
|
|
await mainScreen.openSettings(electronApp);
|
2023-10-13 16:32:10 +02:00
|
|
|
|
|
|
|
// Should be on the settings screen
|
|
|
|
const settingsScreen = new SettingsScreen(mainWindow);
|
|
|
|
await settingsScreen.waitFor();
|
|
|
|
|
|
|
|
// Open the appearance tab
|
|
|
|
await settingsScreen.appearanceTabButton.click();
|
|
|
|
|
|
|
|
// Find the sort order visible checkbox
|
|
|
|
const sortOrderVisibleCheckbox = mainWindow.getByLabel(/^Show sort order/);
|
|
|
|
|
|
|
|
await expect(sortOrderVisibleCheckbox).toBeChecked();
|
|
|
|
await sortOrderVisibleCheckbox.click();
|
|
|
|
await expect(sortOrderVisibleCheckbox).not.toBeChecked();
|
|
|
|
|
|
|
|
// Save settings & close
|
|
|
|
await settingsScreen.okayButton.click();
|
|
|
|
await mainScreen.waitFor();
|
|
|
|
|
|
|
|
await expect(mainScreen.noteListContainer.locator('[title^="Toggle sort order"]')).not.toBeVisible();
|
|
|
|
});
|
2023-10-22 12:52:06 +02:00
|
|
|
|
|
|
|
test('clicking on an external link should try to launch a browser', async ({ electronApp, mainWindow }) => {
|
|
|
|
const mainScreen = new MainScreen(mainWindow);
|
|
|
|
await mainScreen.waitFor();
|
|
|
|
|
|
|
|
// Mock openExternal
|
|
|
|
const nextExternalUrlPromise = electronApp.evaluate(({ shell }) => {
|
|
|
|
return new Promise<string>(resolve => {
|
|
|
|
const openExternal = async (url: string) => {
|
|
|
|
resolve(url);
|
|
|
|
};
|
|
|
|
shell.openExternal = openExternal;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Create a test link
|
|
|
|
const testLinkTitle = 'This is a test link!';
|
|
|
|
const linkHref = 'https://joplinapp.org/';
|
|
|
|
|
|
|
|
await mainWindow.evaluate(({ testLinkTitle, linkHref }) => {
|
|
|
|
const testLink = document.createElement('a');
|
|
|
|
testLink.textContent = testLinkTitle;
|
|
|
|
testLink.onclick = () => {
|
|
|
|
// We need to navigate by setting location.href -- clicking on a link
|
|
|
|
// directly within the main window (i.e. not in a PDF viewer) doesn't
|
|
|
|
// navigate.
|
|
|
|
location.href = linkHref;
|
|
|
|
};
|
|
|
|
testLink.href = '#';
|
|
|
|
|
|
|
|
// Display on top of everything
|
|
|
|
testLink.style.zIndex = '99999';
|
|
|
|
testLink.style.position = 'fixed';
|
|
|
|
testLink.style.top = '0';
|
|
|
|
testLink.style.left = '0';
|
|
|
|
|
|
|
|
document.body.appendChild(testLink);
|
|
|
|
}, { testLinkTitle, linkHref });
|
|
|
|
|
|
|
|
const testLink = mainWindow.getByText(testLinkTitle);
|
|
|
|
await expect(testLink).toBeVisible();
|
|
|
|
await testLink.click({ noWaitAfter: true });
|
|
|
|
|
|
|
|
expect(await nextExternalUrlPromise).toBe(linkHref);
|
|
|
|
});
|
2023-10-31 17:05:28 +02:00
|
|
|
|
|
|
|
test('should start in safe mode if profile-dir/force-safe-mode-on-next-start exists', async ({ profileDirectory }) => {
|
|
|
|
await writeFile(join(profileDirectory, 'force-safe-mode-on-next-start'), 'true', 'utf8');
|
|
|
|
|
|
|
|
// We need to write to the force-safe-mode file before opening the Electron app.
|
|
|
|
// Open the app ourselves:
|
2023-11-12 17:06:32 +02:00
|
|
|
const startupArgs = createStartupArgs(profileDirectory);
|
2023-10-31 17:05:28 +02:00
|
|
|
const electronApp = await electron.launch({ args: startupArgs });
|
2023-11-12 17:06:32 +02:00
|
|
|
const mainWindow = await firstNonDevToolsWindow(electronApp);
|
2023-10-31 17:05:28 +02:00
|
|
|
|
|
|
|
const safeModeDisableLink = mainWindow.getByText('Disable safe mode and restart');
|
|
|
|
await safeModeDisableLink.waitFor();
|
|
|
|
await expect(safeModeDisableLink).toBeInViewport();
|
|
|
|
|
|
|
|
await electronApp.close();
|
|
|
|
});
|
2023-10-13 16:32:10 +02:00
|
|
|
});
|
2023-10-31 17:05:28 +02:00
|
|
|
|