import { test, expect } from './util/test';
import MainScreen from './models/MainScreen';
import SettingsScreen from './models/SettingsScreen';
import { _electron as electron } from '@playwright/test';
import { writeFile } from 'fs-extra';
import { join } from 'path';
import createStartupArgs from './util/createStartupArgs';
import firstNonDevToolsWindow from './util/firstNonDevToolsWindow';
import setFilePickerResponse from './util/setFilePickerResponse';
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);
const editor = await mainScreen.createNewNote('Test note');
// 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!');
});
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
await editor.codeMirrorEditor.click();
// 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(`HTML Link`);
// 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}`);
});
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();
await mainScreen.openSettings(electronApp);
// 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();
});
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(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);
});
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:
const startupArgs = createStartupArgs(profileDirectory);
const electronApp = await electron.launch({ args: startupArgs });
const mainWindow = await firstNonDevToolsWindow(electronApp);
const safeModeDisableLink = mainWindow.getByText('Disable safe mode and restart');
await safeModeDisableLink.waitFor();
await expect(safeModeDisableLink).toBeInViewport();
await electronApp.close();
});
});