diff --git a/.eslintignore b/.eslintignore index 936cf5ec6f..1ac2aac47a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -51,6 +51,8 @@ ReactNativeClient/pluginAssets/ ReactNativeClient/lib/joplin-renderer/vendor/fountain.min.js ReactNativeClient/lib/joplin-renderer/assets/ ReactNativeClient/lib/rnInjectedJs/ +Clipper/popup/config/webpack.config.js +Clipper/popup/scripts/build.js # AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD ElectronClient/gui/editors/PlainEditor.js diff --git a/.eslintrc.js b/.eslintrc.js index 858f521293..47d4070bd7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -54,16 +54,21 @@ module.exports = { // This error is always a false positive so far since it detects // possible race conditions in contexts where we know it cannot happen. "require-atomic-updates": 0, + "prefer-const": ["error"], + "no-var": ["error"], // Checks rules of Hooks "react-hooks/rules-of-hooks": "error", // Checks effect dependencies - "react-hooks/exhaustive-deps": "warn", + // Disable because of this: https://github.com/facebook/react/issues/16265 + // "react-hooks/exhaustive-deps": "warn", // ------------------------------- // Formatting // ------------------------------- "space-in-parens": ["error", "never"], + "space-infix-ops": ["error"], + "curly": ["error", "multi-line", "consistent"], "semi": ["error", "always"], "eol-last": ["error", "always"], "quotes": ["error", "single"], @@ -92,7 +97,7 @@ module.exports = { "multiline-comment-style": ["error", "separate-lines"], "space-before-blocks": "error", "spaced-comment": ["error", "always"], - "keyword-spacing": ["error", { "before": true, "after": true }] + "keyword-spacing": ["error", { "before": true, "after": true }], }, "plugins": [ "react", diff --git a/CliClient/app/app-gui.js b/CliClient/app/app-gui.js index 23268ed16a..f12465250d 100644 --- a/CliClient/app/app-gui.js +++ b/CliClient/app/app-gui.js @@ -134,7 +134,7 @@ class AppGui { const item = folderList.currentItem; if (item === '-') { - let newIndex = event.currentIndex + (event.previousIndex < event.currentIndex ? +1 : -1); + const newIndex = event.currentIndex + (event.previousIndex < event.currentIndex ? +1 : -1); let nextItem = folderList.itemAt(newIndex); if (!nextItem) nextItem = folderList.itemAt(event.previousIndex); @@ -186,7 +186,7 @@ class AppGui { borderRightWidth: 1, }; noteList.on('currentItemChange', async () => { - let note = noteList.currentItem; + const note = noteList.currentItem; this.store_.dispatch({ type: 'NOTE_SELECT', id: note ? note.id : null, @@ -338,7 +338,7 @@ class AppGui { if (consoleWidget.isMaximized__ === doMaximize) return; - let constraints = { + const constraints = { type: 'stretch', factor: !doMaximize ? 1 : 4, }; @@ -415,10 +415,10 @@ class AppGui { async handleModelAction(action) { this.logger().info('Action:', action); - let state = Object.assign({}, defaultState); + const state = Object.assign({}, defaultState); state.notes = this.widget('noteList').items; - let newState = reducer(state, action); + const newState = reducer(state, action); if (newState !== state) { this.widget('noteList').items = newState.notes; @@ -485,9 +485,9 @@ class AppGui { // this.logger().debug('Got command: ' + cmd); try { - let note = this.widget('noteList').currentItem; - let folder = this.widget('folderList').currentItem; - let args = splitCommandString(cmd); + const note = this.widget('noteList').currentItem; + const folder = this.widget('folderList').currentItem; + const args = splitCommandString(cmd); for (let i = 0; i < args.length; i++) { if (args[i] == '$n') { @@ -548,7 +548,7 @@ class AppGui { stdout(text) { if (text === null || text === undefined) return; - let lines = text.split('\n'); + const lines = text.split('\n'); for (let i = 0; i < lines.length; i++) { const v = typeof lines[i] === 'object' ? JSON.stringify(lines[i]) : lines[i]; this.widget('console').addLine(v); @@ -626,7 +626,7 @@ class AppGui { if (link.type === 'item') { const itemId = link.id; - let item = await BaseItem.loadItemById(itemId); + const item = await BaseItem.loadItemById(itemId); if (!item) throw new Error(`No item with ID ${itemId}`); // Should be nearly impossible if (item.type_ === BaseModel.TYPE_RESOURCE) { @@ -750,7 +750,7 @@ class AppGui { // ------------------------------------------------------------------------- const shortcutKey = this.currentShortcutKeys_.join(''); - let keymapItem = this.keymapItemByKey(shortcutKey); + const keymapItem = this.keymapItemByKey(shortcutKey); // If this command is an alias to another command, resolve to the actual command @@ -766,7 +766,7 @@ class AppGui { if (keymapItem.type === 'function') { this.processFunctionCommand(keymapItem.command); } else if (keymapItem.type === 'prompt') { - let promptOptions = {}; + const promptOptions = {}; if ('cursorPosition' in keymapItem) promptOptions.cursorPosition = keymapItem.cursorPosition; const commandString = await statusBar.prompt(keymapItem.command ? keymapItem.command : '', null, promptOptions); this.addCommandToConsole(commandString); diff --git a/CliClient/app/app.js b/CliClient/app/app.js index 1a29842222..5dcc099a54 100644 --- a/CliClient/app/app.js +++ b/CliClient/app/app.js @@ -47,7 +47,7 @@ class Application extends BaseApplication { } async loadItem(type, pattern, options = null) { - let output = await this.loadItems(type, pattern, options); + const output = await this.loadItems(type, pattern, options); if (output.length > 1) { // output.sort((a, b) => { return a.user_updated_time < b.user_updated_time ? +1 : -1; }); @@ -144,7 +144,7 @@ class Application extends BaseApplication { if (options.type === 'boolean') { if (answer === null) return false; // Pressed ESCAPE if (!answer) answer = options.answers[0]; - let positiveIndex = options.booleanAnswerDefault == 'y' ? 0 : 1; + const positiveIndex = options.booleanAnswerDefault == 'y' ? 0 : 1; return answer.toLowerCase() === options.answers[positiveIndex].toLowerCase(); } else { return answer; @@ -181,7 +181,7 @@ class Application extends BaseApplication { const ext = fileExtension(path); if (ext != 'js') return; - let CommandClass = require(`./${path}`); + const CommandClass = require(`./${path}`); let cmd = new CommandClass(); if (!cmd.enabled()) return; cmd = this.setupCommand(cmd); @@ -192,8 +192,8 @@ class Application extends BaseApplication { } if (uiType !== null) { - let temp = []; - for (let n in this.commands_) { + const temp = []; + for (const n in this.commands_) { if (!this.commands_.hasOwnProperty(n)) continue; const c = this.commands_[n]; if (!c.supportsUi(uiType)) continue; @@ -207,8 +207,8 @@ class Application extends BaseApplication { async commandNames() { const metadata = await this.commandMetadata(); - let output = []; - for (let n in metadata) { + const output = []; + for (const n in metadata) { if (!metadata.hasOwnProperty(n)) continue; output.push(n); } @@ -227,7 +227,7 @@ class Application extends BaseApplication { const commands = this.commands(); output = {}; - for (let n in commands) { + for (const n in commands) { if (!commands.hasOwnProperty(n)) continue; const cmd = commands[n]; output[n] = cmd.metadata(); @@ -251,7 +251,7 @@ class Application extends BaseApplication { CommandClass = require(`${__dirname}/command-${name}.js`); } catch (error) { if (error.message && error.message.indexOf('Cannot find module') >= 0) { - let e = new Error(_('No such command: %s', name)); + const e = new Error(_('No such command: %s', name)); e.type = 'notFound'; throw e; } else { @@ -362,7 +362,7 @@ class Application extends BaseApplication { } const output = []; - for (let n in itemsByCommand) { + for (const n in itemsByCommand) { if (!itemsByCommand.hasOwnProperty(n)) continue; output.push(itemsByCommand[n]); } diff --git a/CliClient/app/autocompletion.js b/CliClient/app/autocompletion.js index 63835dee61..73836e090a 100644 --- a/CliClient/app/autocompletion.js +++ b/CliClient/app/autocompletion.js @@ -1,20 +1,20 @@ -var { app } = require('./app.js'); -var Note = require('lib/models/Note.js'); -var Folder = require('lib/models/Folder.js'); -var Tag = require('lib/models/Tag.js'); -var { cliUtils } = require('./cli-utils.js'); -var yargParser = require('yargs-parser'); -var fs = require('fs-extra'); +const { app } = require('./app.js'); +const Note = require('lib/models/Note.js'); +const Folder = require('lib/models/Folder.js'); +const Tag = require('lib/models/Tag.js'); +const { cliUtils } = require('./cli-utils.js'); +const yargParser = require('yargs-parser'); +const fs = require('fs-extra'); async function handleAutocompletionPromise(line) { // Auto-complete the command name const names = await app().commandNames(); - let words = getArguments(line); + const words = getArguments(line); // If there is only one word and it is not already a command name then you // should look for commands it could be if (words.length == 1) { if (names.indexOf(words[0]) === -1) { - let x = names.filter(n => n.indexOf(words[0]) === 0); + const x = names.filter(n => n.indexOf(words[0]) === 0); if (x.length === 1) { return `${x[0]} `; } @@ -36,8 +36,8 @@ async function handleAutocompletionPromise(line) { } // complete an option - let next = words.length > 1 ? words[words.length - 1] : ''; - let l = []; + const next = words.length > 1 ? words[words.length - 1] : ''; + const l = []; if (next[0] === '-') { for (let i = 0; i < metadata.options.length; i++) { const options = metadata.options[i][0].split(' '); @@ -60,7 +60,7 @@ async function handleAutocompletionPromise(line) { if (l.length === 0) { return line; } - let ret = l.map(a => toCommandLine(a)); + const ret = l.map(a => toCommandLine(a)); ret.prefix = `${toCommandLine(words.slice(0, -1))} `; return ret; } @@ -69,7 +69,7 @@ async function handleAutocompletionPromise(line) { // words that don't start with a - less one for the command name const positionalArgs = words.filter(a => a.indexOf('-') !== 0).length - 1; - let cmdUsage = yargParser(metadata.usage)['_']; + const cmdUsage = yargParser(metadata.usage)['_']; cmdUsage.splice(0, 1); if (cmdUsage.length >= positionalArgs) { @@ -95,29 +95,29 @@ async function handleAutocompletionPromise(line) { } if (argName == 'tag') { - let tags = await Tag.search({ titlePattern: `${next}*` }); + const tags = await Tag.search({ titlePattern: `${next}*` }); l.push(...tags.map(n => n.title)); } if (argName == 'file') { - let files = await fs.readdir('.'); + const files = await fs.readdir('.'); l.push(...files); } if (argName == 'tag-command') { - let c = filterList(['add', 'remove', 'list', 'notetags'], next); + const c = filterList(['add', 'remove', 'list', 'notetags'], next); l.push(...c); } if (argName == 'todo-command') { - let c = filterList(['toggle', 'clear'], next); + const c = filterList(['toggle', 'clear'], next); l.push(...c); } } if (l.length === 1) { return toCommandLine([...words.slice(0, -1), l[0]]); } else if (l.length > 1) { - let ret = l.map(a => toCommandLine(a)); + const ret = l.map(a => toCommandLine(a)); ret.prefix = `${toCommandLine(words.slice(0, -1))} `; return ret; } @@ -155,7 +155,7 @@ function getArguments(line) { let inSingleQuotes = false; let inDoubleQuotes = false; let currentWord = ''; - let parsed = []; + const parsed = []; for (let i = 0; i < line.length; i++) { if (line[i] === '"') { if (inDoubleQuotes) { @@ -192,7 +192,7 @@ function getArguments(line) { return parsed; } function filterList(list, next) { - let output = []; + const output = []; for (let i = 0; i < list.length; i++) { if (list[i].indexOf(next) !== 0) continue; output.push(list[i]); diff --git a/CliClient/app/base-command.js b/CliClient/app/base-command.js index 79e3df5858..1e445f287c 100644 --- a/CliClient/app/base-command.js +++ b/CliClient/app/base-command.js @@ -50,7 +50,7 @@ class BaseCommand { async cancel() {} name() { - let r = this.usage().split(' '); + const r = this.usage().split(' '); return r[0]; } diff --git a/CliClient/app/build-doc.js b/CliClient/app/build-doc.js index 877596a140..afdfe99246 100644 --- a/CliClient/app/build-doc.js +++ b/CliClient/app/build-doc.js @@ -15,11 +15,11 @@ function wrap(text, indent) { } function renderOptions(options) { - let output = []; + const output = []; const optionColWidth = getOptionColWidth(options); for (let i = 0; i < options.length; i++) { - let option = options[i]; + const option = options[i]; const flag = option[0]; const indent = INDENT + INDENT + ' '.repeat(optionColWidth + 2); @@ -33,7 +33,7 @@ function renderOptions(options) { } function renderCommand(cmd) { - let output = []; + const output = []; output.push(INDENT + cmd.usage()); output.push(''); output.push(wrap(cmd.description(), INDENT + INDENT)); @@ -48,14 +48,14 @@ function renderCommand(cmd) { } function getCommands() { - let output = []; + const output = []; fs.readdirSync(__dirname).forEach(path => { if (path.indexOf('command-') !== 0) return; const ext = fileExtension(path); if (ext != 'js') return; - let CommandClass = require(`./${path}`); - let cmd = new CommandClass(); + const CommandClass = require(`./${path}`); + const cmd = new CommandClass(); if (!cmd.enabled()) return; if (cmd.hidden()) return; output.push(cmd); @@ -73,7 +73,7 @@ function getOptionColWidth(options) { } function getHeader() { - let output = []; + const output = []; output.push('NAME'); output.push(''); @@ -84,7 +84,7 @@ function getHeader() { output.push('DESCRIPTION'); output.push(''); - let description = []; + const description = []; description.push('Joplin is a note taking and to-do application, which can handle a large number of notes organised into notebooks.'); description.push('The notes are searchable, can be copied, tagged and modified with your own text editor.'); description.push('\n\n'); @@ -98,7 +98,7 @@ function getHeader() { } function getFooter() { - let output = []; + const output = []; output.push('WEBSITE'); output.push(''); @@ -120,10 +120,10 @@ async function main() { // setLocale('fr_FR'); const commands = getCommands(); - let commandBlocks = []; + const commandBlocks = []; for (let i = 0; i < commands.length; i++) { - let cmd = commands[i]; + const cmd = commands[i]; commandBlocks.push(renderCommand(cmd)); } diff --git a/CliClient/app/cli-integration-tests.js b/CliClient/app/cli-integration-tests.js index 426118adcf..1e900cc32c 100644 --- a/CliClient/app/cli-integration-tests.js +++ b/CliClient/app/cli-integration-tests.js @@ -40,8 +40,8 @@ function createClient(id) { const client = createClient(1); function execCommand(client, command) { - let exePath = `node ${joplinAppPath}`; - let cmd = `${exePath} --update-geolocation-disabled --env dev --profile ${client.profileDir} ${command}`; + const exePath = `node ${joplinAppPath}`; + const cmd = `${exePath} --update-geolocation-disabled --env dev --profile ${client.profileDir} ${command}`; logger.info(`${client.id}: ${command}`); return new Promise((resolve, reject) => { @@ -129,8 +129,8 @@ testUnits.testCat = async () => { await execCommand(client, 'mkbook nb1'); await execCommand(client, 'mknote mynote'); - let folder = await Folder.loadByTitle('nb1'); - let note = await Note.loadFolderNoteByField(folder.id, 'title', 'mynote'); + const folder = await Folder.loadByTitle('nb1'); + const note = await Note.loadFolderNoteByField(folder.id, 'title', 'mynote'); let r = await execCommand(client, 'cat mynote'); assertTrue(r.indexOf('mynote') >= 0); @@ -149,7 +149,7 @@ testUnits.testConfig = async () => { await Setting.load(); assertEquals('subl', Setting.value('editor')); - let r = await execCommand(client, 'config'); + const r = await execCommand(client, 'config'); assertTrue(r.indexOf('editor') >= 0); assertTrue(r.indexOf('subl') >= 0); }; @@ -161,14 +161,14 @@ testUnits.testCp = async () => { await execCommand(client, 'cp n1'); - let f1 = await Folder.loadByTitle('nb1'); - let f2 = await Folder.loadByTitle('nb2'); + const f1 = await Folder.loadByTitle('nb1'); + const f2 = await Folder.loadByTitle('nb2'); let notes = await Note.previews(f1.id); assertEquals(2, notes.length); await execCommand(client, 'cp n1 nb2'); - let notesF1 = await Note.previews(f1.id); + const notesF1 = await Note.previews(f1.id); assertEquals(2, notesF1.length); notes = await Note.previews(f2.id); assertEquals(1, notes.length); @@ -179,7 +179,7 @@ testUnits.testLs = async () => { await execCommand(client, 'mkbook nb1'); await execCommand(client, 'mknote note1'); await execCommand(client, 'mknote note2'); - let r = await execCommand(client, 'ls'); + const r = await execCommand(client, 'ls'); assertTrue(r.indexOf('note1') >= 0); assertTrue(r.indexOf('note2') >= 0); @@ -191,8 +191,8 @@ testUnits.testMv = async () => { await execCommand(client, 'mknote n1'); await execCommand(client, 'mv n1 nb2'); - let f1 = await Folder.loadByTitle('nb1'); - let f2 = await Folder.loadByTitle('nb2'); + const f1 = await Folder.loadByTitle('nb1'); + const f2 = await Folder.loadByTitle('nb2'); let notes1 = await Note.previews(f1.id); let notes2 = await Note.previews(f2.id); @@ -224,12 +224,12 @@ async function main() { let onlyThisTest = 'testMv'; onlyThisTest = ''; - for (let n in testUnits) { + for (const n in testUnits) { if (!testUnits.hasOwnProperty(n)) continue; if (onlyThisTest && n != onlyThisTest) continue; await clearDatabase(); - let testName = n.substr(4).toLowerCase(); + const testName = n.substr(4).toLowerCase(); process.stdout.write(`${testName}: `); await testUnits[n](); console.info(''); diff --git a/CliClient/app/cli-utils.js b/CliClient/app/cli-utils.js index e850765367..40d072613f 100644 --- a/CliClient/app/cli-utils.js +++ b/CliClient/app/cli-utils.js @@ -11,27 +11,27 @@ cliUtils.printArray = function(logFunction, rows) { const ALIGN_LEFT = 0; const ALIGN_RIGHT = 1; - let colWidths = []; - let colAligns = []; + const colWidths = []; + const colAligns = []; for (let i = 0; i < rows.length; i++) { - let row = rows[i]; + const row = rows[i]; for (let j = 0; j < row.length; j++) { - let item = row[j]; - let width = item ? item.toString().length : 0; - let align = typeof item == 'number' ? ALIGN_RIGHT : ALIGN_LEFT; + const item = row[j]; + const width = item ? item.toString().length : 0; + const align = typeof item == 'number' ? ALIGN_RIGHT : ALIGN_LEFT; if (!colWidths[j] || colWidths[j] < width) colWidths[j] = width; if (colAligns.length <= j) colAligns[j] = align; } } for (let row = 0; row < rows.length; row++) { - let line = []; + const line = []; for (let col = 0; col < colWidths.length; col++) { - let item = rows[row][col]; - let width = colWidths[col]; - let dir = colAligns[col] == ALIGN_LEFT ? stringPadding.RIGHT : stringPadding.LEFT; + const item = rows[row][col]; + const width = colWidths[col]; + const dir = colAligns[col] == ALIGN_LEFT ? stringPadding.RIGHT : stringPadding.LEFT; line.push(stringPadding(item, width, ' ', dir)); } logFunction(line.join(' ')); @@ -39,7 +39,7 @@ cliUtils.printArray = function(logFunction, rows) { }; cliUtils.parseFlags = function(flags) { - let output = {}; + const output = {}; flags = flags.split(','); for (let i = 0; i < flags.length; i++) { let f = flags[i].trim(); @@ -76,11 +76,11 @@ cliUtils.parseCommandArg = function(arg) { cliUtils.makeCommandArgs = function(cmd, argv) { let cmdUsage = cmd.usage(); cmdUsage = yargParser(cmdUsage); - let output = {}; + const output = {}; - let options = cmd.options(); - let booleanFlags = []; - let aliases = {}; + const options = cmd.options(); + const booleanFlags = []; + const aliases = {}; for (let i = 0; i < options.length; i++) { if (options[i].length != 2) throw new Error(`Invalid options: ${options[i]}`); let flags = options[i][0]; @@ -97,7 +97,7 @@ cliUtils.makeCommandArgs = function(cmd, argv) { } } - let args = yargParser(argv, { + const args = yargParser(argv, { boolean: booleanFlags, alias: aliases, string: ['_'], @@ -113,8 +113,8 @@ cliUtils.makeCommandArgs = function(cmd, argv) { } } - let argOptions = {}; - for (let key in args) { + const argOptions = {}; + for (const key in args) { if (!args.hasOwnProperty(key)) continue; if (key == '_') continue; argOptions[key] = args[key]; @@ -134,7 +134,7 @@ cliUtils.promptMcq = function(message, answers) { }); message += '\n\n'; - for (let n in answers) { + for (const n in answers) { if (!answers.hasOwnProperty(n)) continue; message += `${_('%s: %s', n, answers[n])}\n`; } diff --git a/CliClient/app/command-apidoc.js b/CliClient/app/command-apidoc.js index 246f90a14d..528f79a76a 100644 --- a/CliClient/app/command-apidoc.js +++ b/CliClient/app/command-apidoc.js @@ -56,7 +56,6 @@ class Command extends BaseCommand { lines.push('# Joplin API'); lines.push(''); - lines.push('When the Web Clipper service is enabled, Joplin exposes a [REST API](https://en.wikipedia.org/wiki/Representational_state_transfer) which allows third-party applications to access Joplin\'s data and to create, modify or delete notes, notebooks, resources or tags.'); lines.push(''); lines.push('In order to use it, you\'ll first need to find on which port the service is running. To do so, open the Web Clipper Options in Joplin and if the service is running it should tell you on which port. Normally it runs on port **41184**. If you want to find it programmatically, you may follow this kind of algorithm:'); lines.push(''); diff --git a/CliClient/app/command-attach.js b/CliClient/app/command-attach.js index 7d92737bf3..c7ee88f7c7 100644 --- a/CliClient/app/command-attach.js +++ b/CliClient/app/command-attach.js @@ -14,9 +14,9 @@ class Command extends BaseCommand { } async action(args) { - let title = args['note']; + const title = args['note']; - let note = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() }); + const note = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() }); this.encryptionCheck(note); if (!note) throw new Error(_('Cannot find "%s".', title)); diff --git a/CliClient/app/command-cat.js b/CliClient/app/command-cat.js index fd64a54350..3a66acc211 100644 --- a/CliClient/app/command-cat.js +++ b/CliClient/app/command-cat.js @@ -18,9 +18,9 @@ class Command extends BaseCommand { } async action(args) { - let title = args['note']; + const title = args['note']; - let item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() }); + const item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() }); if (!item) throw new Error(_('Cannot find "%s".', title)); const content = args.options.verbose ? await Note.serialize(item) : await Note.serializeForEdit(item); diff --git a/CliClient/app/command-config.js b/CliClient/app/command-config.js index 7490a7b331..e4af92ff3a 100644 --- a/CliClient/app/command-config.js +++ b/CliClient/app/command-config.js @@ -35,7 +35,7 @@ class Command extends BaseCommand { }); inputStream.on('end', () => { - let json = chunks.join(''); + const json = chunks.join(''); let settingsObj; try { settingsObj = JSON.parse(json); @@ -83,7 +83,7 @@ class Command extends BaseCommand { }; if (isExport || (!isImport && !args.value)) { - let keys = Setting.keys(!verbose, 'cli'); + const keys = Setting.keys(!verbose, 'cli'); keys.sort(); if (isExport) { diff --git a/CliClient/app/command-dump.js b/CliClient/app/command-dump.js index b8939de14b..3847ccbade 100644 --- a/CliClient/app/command-dump.js +++ b/CliClient/app/command-dump.js @@ -18,15 +18,15 @@ class Command extends BaseCommand { async action() { let items = []; - let folders = await Folder.all(); + const folders = await Folder.all(); for (let i = 0; i < folders.length; i++) { - let folder = folders[i]; - let notes = await Note.previews(folder.id); + const folder = folders[i]; + const notes = await Note.previews(folder.id); items.push(folder); items = items.concat(notes); } - let tags = await Tag.all(); + const tags = await Tag.all(); for (let i = 0; i < tags.length; i++) { tags[i].notes_ = await Tag.noteIds(tags[i].id); } diff --git a/CliClient/app/command-e2ee.js b/CliClient/app/command-e2ee.js index f00984ef7d..08845f581f 100644 --- a/CliClient/app/command-e2ee.js +++ b/CliClient/app/command-e2ee.js @@ -138,7 +138,7 @@ class Command extends BaseCommand { if (!targetPath) throw new Error('Please specify the sync target path.'); const dirPaths = function(targetPath) { - let paths = []; + const paths = []; fs.readdirSync(targetPath).forEach(path => { paths.push(path); }); @@ -151,10 +151,10 @@ class Command extends BaseCommand { let encryptedResourceCount = 0; let otherItemCount = 0; - let encryptedPaths = []; - let decryptedPaths = []; + const encryptedPaths = []; + const decryptedPaths = []; - let paths = dirPaths(targetPath); + const paths = dirPaths(targetPath); for (let i = 0; i < paths.length; i++) { const path = paths[i]; @@ -164,7 +164,7 @@ class Command extends BaseCommand { // this.stdout(fullPath); if (path === '.resource') { - let resourcePaths = dirPaths(fullPath); + const resourcePaths = dirPaths(fullPath); for (let j = 0; j < resourcePaths.length; j++) { const resourcePath = resourcePaths[j]; resourceCount++; diff --git a/CliClient/app/command-edit.js b/CliClient/app/command-edit.js index 7eb8e58467..d13897c38f 100644 --- a/CliClient/app/command-edit.js +++ b/CliClient/app/command-edit.js @@ -35,7 +35,7 @@ class Command extends BaseCommand { // Load note or create it if it doesn't exist // ------------------------------------------------------------------------- - let title = args['note']; + const title = args['note']; if (!app().currentFolder()) throw new Error(_('No active notebook.')); let note = await app().loadItem(BaseModel.TYPE_NOTE, title); @@ -91,7 +91,7 @@ class Command extends BaseCommand { const updatedContent = await fs.readFile(tempFilePath, 'utf8'); if (updatedContent !== originalContent) { - let updatedNote = await Note.unserializeForEdit(updatedContent); + const updatedNote = await Note.unserializeForEdit(updatedContent); updatedNote.id = note.id; await Note.save(updatedNote); this.stdout(_('Note has been saved.')); diff --git a/CliClient/app/command-export.js b/CliClient/app/command-export.js index 9532936abb..cd582f0eda 100644 --- a/CliClient/app/command-export.js +++ b/CliClient/app/command-export.js @@ -24,7 +24,7 @@ class Command extends BaseCommand { } async action(args) { - let exportOptions = {}; + const exportOptions = {}; exportOptions.path = args.path; exportOptions.format = args.options.format ? args.options.format : 'jex'; diff --git a/CliClient/app/command-geoloc.js b/CliClient/app/command-geoloc.js index aaf248054c..8b8220a951 100644 --- a/CliClient/app/command-geoloc.js +++ b/CliClient/app/command-geoloc.js @@ -14,9 +14,9 @@ class Command extends BaseCommand { } async action(args) { - let title = args['note']; + const title = args['note']; - let item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() }); + const item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() }); if (!item) throw new Error(_('Cannot find "%s".', title)); const url = Note.geolocationUrl(item); this.stdout(url); diff --git a/CliClient/app/command-help.js b/CliClient/app/command-help.js index e26a88b1b9..afce0ed471 100644 --- a/CliClient/app/command-help.js +++ b/CliClient/app/command-help.js @@ -15,8 +15,8 @@ class Command extends BaseCommand { allCommands() { const commands = app().commands(app().uiType()); - let output = []; - for (let n in commands) { + const output = []; + for (const n in commands) { if (!commands.hasOwnProperty(n)) continue; const command = commands[n]; if (command.hidden()) continue; @@ -48,7 +48,7 @@ class Command extends BaseCommand { .gui() .keymap(); - let rows = []; + const rows = []; for (let i = 0; i < keymap.length; i++) { const item = keymap[i]; diff --git a/CliClient/app/command-import.js b/CliClient/app/command-import.js index 4b559e8972..eac6fc2489 100644 --- a/CliClient/app/command-import.js +++ b/CliClient/app/command-import.js @@ -25,7 +25,7 @@ class Command extends BaseCommand { } async action(args) { - let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook); + const folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook); if (args.notebook && !folder) throw new Error(_('Cannot find "%s".', args.notebook)); @@ -39,7 +39,7 @@ class Command extends BaseCommand { // onProgress/onError supported by Enex import only importOptions.onProgress = progressState => { - let line = []; + const line = []; line.push(_('Found: %d.', progressState.loaded)); line.push(_('Created: %d.', progressState.created)); if (progressState.updated) line.push(_('Updated: %d.', progressState.updated)); @@ -51,7 +51,7 @@ class Command extends BaseCommand { }; importOptions.onError = error => { - let s = error.trace ? error.trace : error.toString(); + const s = error.trace ? error.trace : error.toString(); this.stdout(s); }; diff --git a/CliClient/app/command-ls.js b/CliClient/app/command-ls.js index 852b025b4f..cf0bf734a1 100644 --- a/CliClient/app/command-ls.js +++ b/CliClient/app/command-ls.js @@ -34,11 +34,11 @@ class Command extends BaseCommand { } async action(args) { - let pattern = args['note-pattern']; + const pattern = args['note-pattern']; let items = []; - let options = args.options; + const options = args.options; - let queryOptions = {}; + const queryOptions = {}; if (options.limit) queryOptions.limit = options.limit; if (options.sort) { queryOptions.orderBy = options.sort; @@ -70,19 +70,19 @@ class Command extends BaseCommand { } else { let hasTodos = false; for (let i = 0; i < items.length; i++) { - let item = items[i]; + const item = items[i]; if (item.is_todo) { hasTodos = true; break; } } - let seenTitles = []; - let rows = []; + const seenTitles = []; + const rows = []; let shortIdShown = false; for (let i = 0; i < items.length; i++) { - let item = items[i]; - let row = []; + const item = items[i]; + const row = []; if (options.long) { row.push(BaseModel.shortId(item.id)); diff --git a/CliClient/app/command-mkbook.js b/CliClient/app/command-mkbook.js index 032a892857..1faf3cb44c 100644 --- a/CliClient/app/command-mkbook.js +++ b/CliClient/app/command-mkbook.js @@ -13,7 +13,7 @@ class Command extends BaseCommand { } async action(args) { - let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true }); + const folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true }); app().switchCurrentFolder(folder); } } diff --git a/CliClient/app/command-rmnote.js b/CliClient/app/command-rmnote.js index 9d8c18d11d..c832404e8d 100644 --- a/CliClient/app/command-rmnote.js +++ b/CliClient/app/command-rmnote.js @@ -26,7 +26,7 @@ class Command extends BaseCommand { const ok = force ? true : await this.prompt(notes.length > 1 ? _('%d notes match this pattern. Delete them?', notes.length) : _('Delete note?'), { booleanAnswerDefault: 'n' }); if (!ok) return; - let ids = notes.map(n => n.id); + const ids = notes.map(n => n.id); await Note.batchDelete(ids); } } diff --git a/CliClient/app/command-search.js b/CliClient/app/command-search.js index fc1385f3f2..9953a3e283 100644 --- a/CliClient/app/command-search.js +++ b/CliClient/app/command-search.js @@ -18,8 +18,8 @@ class Command extends BaseCommand { } async action(args) { - let pattern = args['pattern']; - let folderTitle = args['notebook']; + const pattern = args['pattern']; + const folderTitle = args['notebook']; let folder = null; if (folderTitle) { diff --git a/CliClient/app/command-set.js b/CliClient/app/command-set.js index 402899acb6..a70329f6ac 100644 --- a/CliClient/app/command-set.js +++ b/CliClient/app/command-set.js @@ -23,18 +23,18 @@ class Command extends BaseCommand { } async action(args) { - let title = args['note']; - let propName = args['name']; + const title = args['note']; + const propName = args['name']; let propValue = args['value']; if (!propValue) propValue = ''; - let notes = await app().loadItems(BaseModel.TYPE_NOTE, title); + const notes = await app().loadItems(BaseModel.TYPE_NOTE, title); if (!notes.length) throw new Error(_('Cannot find "%s".', title)); for (let i = 0; i < notes.length; i++) { this.encryptionCheck(notes[i]); - let newNote = { + const newNote = { id: notes[i].id, type_: notes[i].type_, }; diff --git a/CliClient/app/command-status.js b/CliClient/app/command-status.js index aad423dd63..0f0322ba9f 100644 --- a/CliClient/app/command-status.js +++ b/CliClient/app/command-status.js @@ -14,20 +14,20 @@ class Command extends BaseCommand { } async action() { - let service = new ReportService(); - let report = await service.status(Setting.value('sync.target')); + const service = new ReportService(); + const report = await service.status(Setting.value('sync.target')); for (let i = 0; i < report.length; i++) { - let section = report[i]; + const section = report[i]; if (i > 0) this.stdout(''); this.stdout(`# ${section.title}`); this.stdout(''); - for (let n in section.body) { + for (const n in section.body) { if (!section.body.hasOwnProperty(n)) continue; - let line = section.body[n]; + const line = section.body[n]; this.stdout(line); } } diff --git a/CliClient/app/command-sync.js b/CliClient/app/command-sync.js index 8c6515da15..2425a4fd1f 100644 --- a/CliClient/app/command-sync.js +++ b/CliClient/app/command-sync.js @@ -161,9 +161,9 @@ class Command extends BaseCommand { const sync = await syncTarget.synchronizer(); - let options = { + const options = { onProgress: report => { - let lines = Synchronizer.reportToLines(report); + const lines = Synchronizer.reportToLines(report); if (lines.length) cliUtils.redraw(lines.join(' ')); }, onMessage: msg => { @@ -185,7 +185,7 @@ class Command extends BaseCommand { options.context = context; try { - let newContext = await sync.start(options); + const newContext = await sync.start(options); Setting.setValue(contextKey, JSON.stringify(newContext)); } catch (error) { if (error.code == 'alreadyStarted') { diff --git a/CliClient/app/command-tag.js b/CliClient/app/command-tag.js index a0c36d3108..d199ff52da 100644 --- a/CliClient/app/command-tag.js +++ b/CliClient/app/command-tag.js @@ -20,7 +20,7 @@ class Command extends BaseCommand { async action(args) { let tag = null; - let options = args.options; + const options = args.options; if (args.tag) tag = await app().loadItem(BaseModel.TYPE_TAG, args.tag); let notes = []; @@ -46,7 +46,7 @@ class Command extends BaseCommand { } } else if (command == 'list') { if (tag) { - let notes = await Tag.notes(tag.id); + const notes = await Tag.notes(tag.id); notes.map(note => { let line = ''; if (options.long) { @@ -70,7 +70,7 @@ class Command extends BaseCommand { this.stdout(line); }); } else { - let tags = await Tag.all(); + const tags = await Tag.all(); tags.map(tag => { this.stdout(tag.title); }); diff --git a/CliClient/app/command-use.js b/CliClient/app/command-use.js index bcdfc3a94f..067fae375f 100644 --- a/CliClient/app/command-use.js +++ b/CliClient/app/command-use.js @@ -17,7 +17,7 @@ class Command extends BaseCommand { } async action(args) { - let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']); + const folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']); if (!folder) throw new Error(_('Cannot find "%s".', args['notebook'])); app().switchCurrentFolder(folder); } diff --git a/CliClient/app/fuzzing.js b/CliClient/app/fuzzing.js index 6905fc4323..e45a070205 100644 --- a/CliClient/app/fuzzing.js +++ b/CliClient/app/fuzzing.js @@ -12,7 +12,7 @@ const fs = require('fs-extra'); const baseDir = `${dirname(__dirname)}/tests/fuzzing`; const syncDir = `${baseDir}/sync`; const joplinAppPath = `${__dirname}/main.js`; -let syncDurations = []; +const syncDurations = []; const fsDriver = new FsDriverNode(); Logger.fsDriver_ = fsDriver; @@ -34,10 +34,10 @@ function createClient(id) { } async function createClients() { - let output = []; - let promises = []; + const output = []; + const promises = []; for (let clientId = 0; clientId < 2; clientId++) { - let client = createClient(clientId); + const client = createClient(clientId); promises.push(fs.remove(client.profileDir)); promises.push( execCommand(client, 'config sync.target 2').then(() => { @@ -2064,8 +2064,8 @@ function randomWord() { } function execCommand(client, command, options = {}) { - let exePath = `node ${joplinAppPath}`; - let cmd = `${exePath} --update-geolocation-disabled --env dev --log-level debug --profile ${client.profileDir} ${command}`; + const exePath = `node ${joplinAppPath}`; + const cmd = `${exePath} --update-geolocation-disabled --env dev --log-level debug --profile ${client.profileDir} ${command}`; logger.info(`${client.id}: ${command}`); if (options.killAfter) { @@ -2073,7 +2073,7 @@ function execCommand(client, command, options = {}) { } return new Promise((resolve, reject) => { - let childProcess = exec(cmd, (error, stdout, stderr) => { + const childProcess = exec(cmd, (error, stdout, stderr) => { if (error) { if (error.signal == 'SIGTERM') { resolve('Process was killed'); @@ -2096,7 +2096,7 @@ function execCommand(client, command, options = {}) { } async function clientItems(client) { - let itemsJson = await execCommand(client, 'dump'); + const itemsJson = await execCommand(client, 'dump'); try { return JSON.parse(itemsJson); } catch (error) { @@ -2105,7 +2105,7 @@ async function clientItems(client) { } function randomTag(items) { - let tags = []; + const tags = []; for (let i = 0; i < items.length; i++) { if (items[i].type_ != 5) continue; tags.push(items[i]); @@ -2115,7 +2115,7 @@ function randomTag(items) { } function randomNote(items) { - let notes = []; + const notes = []; for (let i = 0; i < items.length; i++) { if (items[i].type_ != 1) continue; notes.push(items[i]); @@ -2125,14 +2125,14 @@ function randomNote(items) { } async function execRandomCommand(client) { - let possibleCommands = [ + const possibleCommands = [ ['mkbook {word}', 40], // CREATE FOLDER ['mknote {word}', 70], // CREATE NOTE [ async () => { // DELETE RANDOM ITEM - let items = await clientItems(client); - let item = randomElement(items); + const items = await clientItems(client); + const item = randomElement(items); if (!item) return; if (item.type_ == 1) { @@ -2150,8 +2150,8 @@ async function execRandomCommand(client) { [ async () => { // SYNC - let avgSyncDuration = averageSyncDuration(); - let options = {}; + const avgSyncDuration = averageSyncDuration(); + const options = {}; if (!isNaN(avgSyncDuration)) { if (Math.random() >= 0.5) { options.killAfter = avgSyncDuration * Math.random(); @@ -2164,8 +2164,8 @@ async function execRandomCommand(client) { [ async () => { // UPDATE RANDOM ITEM - let items = await clientItems(client); - let item = randomNote(items); + const items = await clientItems(client); + const item = randomNote(items); if (!item) return; return execCommand(client, `set ${item.id} title "${randomWord()}"`); @@ -2175,12 +2175,12 @@ async function execRandomCommand(client) { [ async () => { // ADD TAG - let items = await clientItems(client); - let note = randomNote(items); + const items = await clientItems(client); + const note = randomNote(items); if (!note) return; - let tag = randomTag(items); - let tagTitle = !tag || Math.random() >= 0.9 ? `tag-${randomWord()}` : tag.title; + const tag = randomTag(items); + const tagTitle = !tag || Math.random() >= 0.9 ? `tag-${randomWord()}` : tag.title; return execCommand(client, `tag add ${tagTitle} ${note.id}`); }, @@ -2191,7 +2191,7 @@ async function execRandomCommand(client) { let cmd = null; while (true) { cmd = randomElement(possibleCommands); - let r = 1 + Math.floor(Math.random() * 100); + const r = 1 + Math.floor(Math.random() * 100); if (r <= cmd[1]) break; } @@ -2210,7 +2210,7 @@ function averageSyncDuration() { } function randomNextCheckTime() { - let output = time.unixMs() + 1000 + Math.random() * 1000 * 120; + const output = time.unixMs() + 1000 + Math.random() * 1000 * 120; logger.info(`Next sync check: ${time.unixMsToIso(output)} (${Math.round((output - time.unixMs()) / 1000)} sec.)`); return output; } @@ -2223,11 +2223,11 @@ function findItem(items, itemId) { } function compareItems(item1, item2) { - let output = []; - for (let n in item1) { + const output = []; + for (const n in item1) { if (!item1.hasOwnProperty(n)) continue; - let p1 = item1[n]; - let p2 = item2[n]; + const p1 = item1[n]; + const p2 = item2[n]; if (n == 'notes_') { p1.sort(); @@ -2243,13 +2243,13 @@ function compareItems(item1, item2) { } function findMissingItems_(items1, items2) { - let output = []; + const output = []; for (let i = 0; i < items1.length; i++) { - let item1 = items1[i]; + const item1 = items1[i]; let found = false; for (let j = 0; j < items2.length; j++) { - let item2 = items2[j]; + const item2 = items2[j]; if (item1.id == item2.id) { found = true; break; @@ -2269,33 +2269,33 @@ function findMissingItems(items1, items2) { } async function compareClientItems(clientItems) { - let itemCounts = []; + const itemCounts = []; for (let i = 0; i < clientItems.length; i++) { - let items = clientItems[i]; + const items = clientItems[i]; itemCounts.push(items.length); } logger.info(`Item count: ${itemCounts.join(', ')}`); - let missingItems = findMissingItems(clientItems[0], clientItems[1]); + const missingItems = findMissingItems(clientItems[0], clientItems[1]); if (missingItems[0].length || missingItems[1].length) { logger.error('Items are different'); logger.error(missingItems); process.exit(1); } - let differences = []; - let items = clientItems[0]; + const differences = []; + const items = clientItems[0]; for (let i = 0; i < items.length; i++) { - let item1 = items[i]; + const item1 = items[i]; for (let clientId = 1; clientId < clientItems.length; clientId++) { - let item2 = findItem(clientItems[clientId], item1.id); + const item2 = findItem(clientItems[clientId], item1.id); if (!item2) { logger.error(`Item not found on client ${clientId}:`); logger.error(item1); process.exit(1); } - let diff = compareItems(item1, item2); + const diff = compareItems(item1, item2); if (diff.length) { differences.push({ item1: JSON.stringify(item1), @@ -2315,7 +2315,7 @@ async function compareClientItems(clientItems) { async function main() { await fs.remove(syncDir); - let clients = await createClients(); + const clients = await createClients(); let clientId = 0; for (let i = 0; i < clients.length; i++) { @@ -2348,7 +2348,7 @@ async function main() { if (state == 'syncCheck') { state = 'waitForSyncCheck'; - let clientItems = []; + const clientItems = []; // Up to 3 sync operations must be performed by each clients in order for them // to be perfectly in sync - in order for each items to send their changes // and get those from the other clients, and to also get changes that are @@ -2356,12 +2356,12 @@ async function main() { // with another one). for (let loopCount = 0; loopCount < 3; loopCount++) { for (let i = 0; i < clients.length; i++) { - let beforeTime = time.unixMs(); + const beforeTime = time.unixMs(); await execCommand(clients[i], 'sync'); syncDurations.push(time.unixMs() - beforeTime); if (syncDurations.length > 20) syncDurations.splice(0, 1); if (loopCount === 2) { - let dump = await execCommand(clients[i], 'dump'); + const dump = await execCommand(clients[i], 'dump'); clientItems[i] = JSON.parse(dump); } } diff --git a/CliClient/app/gui/FolderListWidget.js b/CliClient/app/gui/FolderListWidget.js index 92054993de..4042916213 100644 --- a/CliClient/app/gui/FolderListWidget.js +++ b/CliClient/app/gui/FolderListWidget.js @@ -20,7 +20,7 @@ class FolderListWidget extends ListWidget { this.trimItemTitle = false; this.itemRenderer = item => { - let output = []; + const output = []; if (item === '-') { output.push('-'.repeat(this.innerWidth)); } else if (item.type_ === Folder.modelType()) { @@ -121,7 +121,7 @@ class FolderListWidget extends ListWidget { folderHasChildren_(folders, folderId) { for (let i = 0; i < folders.length; i++) { - let folder = folders[i]; + const folder = folders[i]; if (folder.parent_id === folderId) return true; } return false; diff --git a/CliClient/app/gui/StatusBarWidget.js b/CliClient/app/gui/StatusBarWidget.js index c79e14f73c..70e6788f23 100644 --- a/CliClient/app/gui/StatusBarWidget.js +++ b/CliClient/app/gui/StatusBarWidget.js @@ -106,7 +106,7 @@ class StatusBarWidget extends BaseWidget { const isSecurePrompt = !!this.promptState_.secure; - let options = { + const options = { cancelable: true, history: this.history, default: this.promptState_.initialText, diff --git a/CliClient/app/help-utils.js b/CliClient/app/help-utils.js index c9483a97ad..b60f4e9c65 100644 --- a/CliClient/app/help-utils.js +++ b/CliClient/app/help-utils.js @@ -6,11 +6,11 @@ const MAX_WIDTH = 78; const INDENT = ' '; function renderTwoColumnData(options, baseIndent, width) { - let output = []; + const output = []; const optionColWidth = getOptionColWidth(options); for (let i = 0; i < options.length; i++) { - let option = options[i]; + const option = options[i]; const flag = option[0]; const indent = baseIndent + INDENT + ' '.repeat(optionColWidth + 2); @@ -28,7 +28,7 @@ function renderCommandHelp(cmd, width = null) { const baseIndent = ''; - let output = []; + const output = []; output.push(baseIndent + cmd.usage()); output.push(''); output.push(wrap(cmd.description(), baseIndent + INDENT, width)); @@ -42,7 +42,7 @@ function renderCommandHelp(cmd, width = null) { if (cmd.name() === 'config') { const renderMetadata = md => { - let desc = []; + const desc = []; if (md.label) { let label = md.label(); @@ -77,7 +77,7 @@ function renderCommandHelp(cmd, width = null) { output.push(_('Possible keys/values:')); output.push(''); - let keysValues = []; + const keysValues = []; const keys = Setting.keys(true, 'cli'); for (let i = 0; i < keys.length; i++) { if (keysValues.length) keysValues.push(['', '']); diff --git a/CliClient/app/main.js b/CliClient/app/main.js index 9530e140d8..b798ede8d1 100644 --- a/CliClient/app/main.js +++ b/CliClient/app/main.js @@ -54,7 +54,7 @@ shimInit(); const application = app(); if (process.platform === 'win32') { - var rl = require('readline').createInterface({ + const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout, }); diff --git a/CliClient/locales/sr_RS.po b/CliClient/locales/sr_RS.po index 83b10c9325..c208b14050 100644 --- a/CliClient/locales/sr_RS.po +++ b/CliClient/locales/sr_RS.po @@ -1048,11 +1048,11 @@ msgstr "" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/app.js:1028 msgid "Zoom In" -msgstr "" +msgstr "Зумирај" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/app.js:1034 msgid "Zoom Out" -msgstr "" +msgstr "Одзумирај" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/app.js:1042 msgid "&Tools" @@ -1245,7 +1245,7 @@ msgstr "" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/ConfigScreen.min.js:82 msgid "This will open a new screen. Save your current changes?" -msgstr "" +msgstr "Ово ће отворити нови екран. Сачувај своје промене?" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/ConfigScreen.min.js:139 #, javascript-format @@ -1286,7 +1286,7 @@ msgstr "Прикажи све" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/ConfigScreen.min.js:219 msgid "Joplin Nextcloud App status:" -msgstr "" +msgstr "Статус Joplin Nextcloud апликације" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/ConfigScreen.min.js:233 #, fuzzy @@ -1452,23 +1452,23 @@ msgstr "Шифровање је:" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/ExtensionBadge.min.js:10 msgid "Firefox Extension" -msgstr "" +msgstr "Firefox екстензија" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/ExtensionBadge.min.js:17 msgid "Chrome Web Store" -msgstr "" +msgstr "Chrome Web продавница" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/ExtensionBadge.min.js:44 msgid "Get it now:" -msgstr "" +msgstr "Набави их сада" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/FolderPropertiesDialog.min.js:22 msgid "Name" -msgstr "" +msgstr "Име" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/FolderPropertiesDialog.min.js:23 msgid "Icon" -msgstr "" +msgstr "Икона" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/FolderPropertiesDialog.min.js:272 #, fuzzy @@ -1516,7 +1516,7 @@ msgstr "Подеси аларм:" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/MainScreen.min.js:368 msgid "Template file:" -msgstr "" +msgstr "Шаблонска датотека:" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/MainScreen.min.js:547 #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/NoteText.min.js:1043 @@ -1545,19 +1545,19 @@ msgstr "Постави лозинку" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/NoteContentPropertiesDialog.js:31 msgid "Words" -msgstr "" +msgstr "Речи" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/NoteContentPropertiesDialog.js:32 msgid "Characters" -msgstr "" +msgstr "Карактери" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/NoteContentPropertiesDialog.js:33 msgid "Characters excluding spaces" -msgstr "" +msgstr "Карактери искључујући празне" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/NoteContentPropertiesDialog.js:34 msgid "Lines" -msgstr "" +msgstr "Линије" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/NoteContentPropertiesDialog.js:56 #, fuzzy @@ -1567,7 +1567,7 @@ msgstr "Својства белешке" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/NoteContentPropertiesDialog.js:58 #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/ShareNoteDialog.js:180 msgid "Close" -msgstr "" +msgstr "Затвори" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/NoteList.min.js:451 msgid "No notes in here. Create one by clicking on \"New note\"." @@ -1599,7 +1599,7 @@ msgstr "Историја о белешци" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/NotePropertiesDialog.min.js:33 msgid "Markup" -msgstr "" +msgstr "Означавање" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/NotePropertiesDialog.min.js:304 msgid "Previous versions of this note" @@ -1661,7 +1661,7 @@ msgstr "Копирај адресу везе" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/NoteText.min.js:818 msgid "There was an error downloading this attachment:" -msgstr "" +msgstr "Дошло је до грешке приликом преузимања овог прилога" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/NoteText.min.js:820 #, fuzzy @@ -1778,7 +1778,7 @@ msgstr "назив" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/ResourceScreen.js:32 msgid "Size" -msgstr "" +msgstr "Величина" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/ResourceScreen.js:36 #, fuzzy @@ -1802,12 +1802,12 @@ msgstr "Проверавам... Молимо вас да сачекате." #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/ResourceScreen.js:128 msgid "No resources!" -msgstr "" +msgstr "Без ресурса" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/ResourceScreen.js:130 #, javascript-format msgid "Warning: not all resources shown for performance reasons (limit: %s)." -msgstr "" +msgstr "Упозорење: нису сви ресурси приказани због перформанси (лимит: %s)." #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/Root.min.js:89 msgid "OneDrive Login" @@ -1842,6 +1842,7 @@ msgstr[2] "Токен је копиран у клипборд!" msgid "" "Note: When a note is shared, it will no longer be encrypted on the server." msgstr "" +"Белешка: Када је белешка дељена, не може се више шифровати на серверу." #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/ShareNoteDialog.js:175 #, fuzzy @@ -1858,7 +1859,7 @@ msgstr[2] "Подели" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/SideBar.min.js:282 msgid "Remove" -msgstr "" +msgstr "Уклони" #: /Users/tessus/data/work/joplin/Tools/../ElectronClient/gui/SideBar.min.js:285 #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/components/side-menu-content.js:148 @@ -2211,7 +2212,7 @@ msgstr "Не могу да преместим бележницу у \"%s\" бе #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Resource.js:286 msgid "Not downloaded" -msgstr "" +msgstr "Није преузето" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Resource.js:287 #, fuzzy @@ -2220,7 +2221,7 @@ msgstr "Преузимам ресурсе..." #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Resource.js:288 msgid "Downloaded" -msgstr "" +msgstr "Преузето" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:27 #, javascript-format @@ -2236,7 +2237,7 @@ msgstr "" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:45 msgid "Keyboard Mode" -msgstr "" +msgstr "Режим тастатуре" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:48 #, fuzzy @@ -2245,11 +2246,11 @@ msgstr "Подразумевано: %s" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:49 msgid "Emacs" -msgstr "" +msgstr "Емакс" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:50 msgid "Vim" -msgstr "" +msgstr "Вим" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:60 msgid "Synchronisation target" @@ -2348,19 +2349,19 @@ msgstr "Тамна" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:246 msgid "Dracula" -msgstr "" +msgstr "Дракула" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:247 msgid "Solarised Light" -msgstr "" +msgstr "Соларизовано светло" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:248 msgid "Solarised Dark" -msgstr "" +msgstr "Соларизовано тамно" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:249 msgid "Nord" -msgstr "" +msgstr "Норд" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:251 #, fuzzy @@ -2390,19 +2391,19 @@ msgstr "&Приказ" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:266 #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:267 msgid "Split View" -msgstr "" +msgstr "Раздвојени преглед" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:264 #, javascript-format msgid "%s / %s / %s" -msgstr "" +msgstr "%s / %s / %s" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:265 #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:266 #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:267 #, javascript-format msgid "%s / %s" -msgstr "" +msgstr "%s / %s" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:270 msgid "Uncompleted to-dos on top" @@ -2418,7 +2419,7 @@ msgstr "Сортирај белешке по" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:296 msgid "Auto-pair braces, parenthesis, quotations, etc." -msgstr "" +msgstr "Ауто-упари заграде, цитате, итд." #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:298 #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:316 @@ -2559,7 +2560,7 @@ msgstr "" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:471 msgid "Custom stylesheet for Joplin-wide app styles" -msgstr "" +msgstr "Прилагођена таблица стилова за стилове Џоплин програма" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:477 msgid "Automatically update the application" @@ -2611,43 +2612,43 @@ msgstr "" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:505 msgid "Page size for PDF export" -msgstr "" +msgstr "Величина странице за извоз у ПДФ формат" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:507 msgid "A4" -msgstr "" +msgstr "А4" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:508 msgid "Letter" -msgstr "" +msgstr "Писмо" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:509 msgid "A3" -msgstr "" +msgstr "А3" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:510 msgid "A5" -msgstr "" +msgstr "А5" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:511 msgid "Tabloid" -msgstr "" +msgstr "Таблоид" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:512 msgid "Legal" -msgstr "" +msgstr "Правно" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:515 msgid "Page orientation for PDF export" -msgstr "" +msgstr "Орјентација странице за извоз у ПДФ" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:517 msgid "Portrait" -msgstr "" +msgstr "Усправно" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:518 msgid "Landscape" -msgstr "" +msgstr "Положено" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:533 msgid "Custom TLS certificates" @@ -2866,7 +2867,7 @@ msgstr "Приложи фајл" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/services/report.js:172 msgid "Downloaded and decrypted" -msgstr "" +msgstr "Преузето и дешифровано" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/services/report.js:172 #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/services/report.js:173 @@ -2877,7 +2878,7 @@ msgstr "Укупно: %d/%d" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/services/report.js:173 msgid "Downloaded and encrypted" -msgstr "" +msgstr "Преузето и шифровано" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/services/report.js:186 #, fuzzy @@ -3063,7 +3064,7 @@ msgstr "Унесите нове ознаке или одаберите са ли #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/components/screens/config.js:51 msgid "Warning" -msgstr "" +msgstr "Упозорење" #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/components/screens/config.js:51 #: /Users/tessus/data/work/joplin/Tools/../ReactNativeClient/lib/components/screens/config.js:152 diff --git a/CliClient/package-lock.json b/CliClient/package-lock.json index 8e62b4decd..0f0f800c15 100644 --- a/CliClient/package-lock.json +++ b/CliClient/package-lock.json @@ -2121,8 +2121,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -2143,14 +2142,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2165,20 +2162,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -2295,8 +2289,7 @@ "inherits": { "version": "2.0.4", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -2308,7 +2301,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2323,7 +2315,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2331,14 +2322,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.9.0", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2357,7 +2346,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -2447,8 +2435,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -2460,7 +2447,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -2546,8 +2532,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -2583,7 +2568,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2603,7 +2587,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2647,14 +2630,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -6595,7 +6576,7 @@ "requires": { "chalk": "^2.1.0", "emphasize": "^1.5.0", - "node-emoji": "git+https://github.com/laurent22/node-emoji.git", + "node-emoji": "git+https://github.com/laurent22/node-emoji.git#9fa01eac463e94dde1316ef8c53089eeef4973b5", "slice-ansi": "^1.0.0", "string-width": "^2.1.1", "terminal-kit": "^1.13.11", diff --git a/CliClient/tests/MdToMd.js b/CliClient/tests/MdToMd.js index 9afe16815f..186b7f40c4 100644 --- a/CliClient/tests/MdToMd.js +++ b/CliClient/tests/MdToMd.js @@ -14,14 +14,14 @@ describe('InteropService_Importer_Md: importLocalImages', function() { it('should import linked files and modify tags appropriately', async function() { const tagNonExistentFile = '![does not exist](does_not_exist.png)'; const note = await importer.importFile(`${__dirname}/md_to_md/sample.md`, 'notebook'); - let items = await Note.linkedItems(note.body); + const items = await Note.linkedItems(note.body); expect(items.length).toBe(2); const inexistentLinkUnchanged = note.body.includes(tagNonExistentFile); expect(inexistentLinkUnchanged).toBe(true); }); it('should only create 1 resource for duplicate links, all tags should be updated', async function() { const note = await importer.importFile(`${__dirname}/md_to_md/sample-duplicate-links.md`, 'notebook'); - let items = await Note.linkedItems(note.body); + const items = await Note.linkedItems(note.body); expect(items.length).toBe(1); const reg = new RegExp(items[0].id, 'g'); const matched = note.body.match(reg); @@ -29,12 +29,12 @@ describe('InteropService_Importer_Md: importLocalImages', function() { }); it('should import linked files and modify tags appropriately when link is also in alt text', async function() { const note = await importer.importFile(`${__dirname}/md_to_md/sample-link-in-alt-text.md`, 'notebook'); - let items = await Note.linkedItems(note.body); + const items = await Note.linkedItems(note.body); expect(items.length).toBe(1); }); it('should passthrough unchanged if no links present', async function() { const note = await importer.importFile(`${__dirname}/md_to_md/sample-no-links.md`, 'notebook'); - let items = await Note.linkedItems(note.body); + const items = await Note.linkedItems(note.body); expect(items.length).toBe(0); expect(note.body).toContain('Unidentified vessel travelling at sub warp speed, bearing 235.7. Fluctuations in energy readings from it, Captain. All transporters off.'); }); diff --git a/CliClient/tests/integration_ShowAllNotes.js b/CliClient/tests/integration_ShowAllNotes.js index 0c5d22b454..22b04a387b 100644 --- a/CliClient/tests/integration_ShowAllNotes.js +++ b/CliClient/tests/integration_ShowAllNotes.js @@ -44,19 +44,19 @@ describe('integration_ShowAllNotes', function() { it('should show all notes', asyncTest(async () => { // setup - let folders = await createNTestFolders(3); + const folders = await createNTestFolders(3); Folder.moveToFolder(id(folders[2]), id(folders[1])); // subfolder await time.msleep(100); - let notes0 = await createNTestNotes(3, folders[0]); - let notes1 = await createNTestNotes(3, folders[1]); - let notes2 = await createNTestNotes(3, folders[2]); + const notes0 = await createNTestNotes(3, folders[0]); + const notes1 = await createNTestNotes(3, folders[1]); + const notes2 = await createNTestNotes(3, folders[2]); // TEST ACTION: View all-notes testApp.dispatch({ type: 'SMART_FILTER_SELECT', id: ALL_NOTES_FILTER_ID }); await time.msleep(100); // check: all the notes are shown - let state = testApp.store().getState(); + const state = testApp.store().getState(); expect(state.notesParentType).toEqual('SmartFilter'); expect(state.selectedSmartFilterId).toEqual(ALL_NOTES_FILTER_ID); expect(sortedIds(state.notes)).toEqual(sortedIds(notes0.concat(notes1).concat(notes2))); @@ -64,9 +64,9 @@ describe('integration_ShowAllNotes', function() { it('should show retain note selection when going from a folder to all-notes', asyncTest(async () => { // setup - let folders = await createNTestFolders(2); - let notes0 = await createNTestNotes(3, folders[0]); - let notes1 = await createNTestNotes(3, folders[1]); + const folders = await createNTestFolders(2); + const notes0 = await createNTestNotes(3, folders[0]); + const notes1 = await createNTestNotes(3, folders[1]); testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[1]) }); await time.msleep(100); testApp.dispatch({ type: 'NOTE_SELECT', id: id(notes1[1]) }); diff --git a/CliClient/tests/integration_TagList.js b/CliClient/tests/integration_TagList.js index e8ee883629..6210144422 100644 --- a/CliClient/tests/integration_TagList.js +++ b/CliClient/tests/integration_TagList.js @@ -8,27 +8,27 @@ const Tag = require('lib/models/Tag.js'); const { time } = require('lib/time-utils.js'); async function createNTestFolders(n) { - let folders = []; + const folders = []; for (let i = 0; i < n; i++) { - let folder = await Folder.save({ title: 'folder' }); + const folder = await Folder.save({ title: 'folder' }); folders.push(folder); } return folders; } async function createNTestNotes(n, folder) { - let notes = []; + const notes = []; for (let i = 0; i < n; i++) { - let note = await Note.save({ title: 'note', parent_id: folder.id, is_conflict: 0 }); + const note = await Note.save({ title: 'note', parent_id: folder.id, is_conflict: 0 }); notes.push(note); } return notes; } async function createNTestTags(n) { - let tags = []; + const tags = []; for (let i = 0; i < n; i++) { - let tag = await Tag.save({ title: 'tag' }); + const tag = await Tag.save({ title: 'tag' }); tags.push(tag); } return tags; @@ -58,9 +58,9 @@ describe('integration_TagList', function() { // the tag list should be cleared if the next note has no tags it('should clear tag list when a note is deleted', asyncTest(async () => { // setup and select the note - let folders = await createNTestFolders(1); - let notes = await createNTestNotes(5, folders[0]); - let tags = await createNTestTags(3); + const folders = await createNTestFolders(1); + const notes = await createNTestNotes(5, folders[0]); + const tags = await createNTestTags(3); await Tag.addNote(tags[2].id, notes[2].id); @@ -96,9 +96,9 @@ describe('integration_TagList', function() { // the tag list should be updated if the next note has tags it('should update tag list when a note is deleted', asyncTest(async () => { // set up and select the note - let folders = await createNTestFolders(1); - let notes = await createNTestNotes(5, folders[0]); - let tags = await createNTestTags(3); + const folders = await createNTestFolders(1); + const notes = await createNTestNotes(5, folders[0]); + const tags = await createNTestTags(3); await Tag.addNote(tags[1].id, notes[1].id); await Tag.addNote(tags[0].id, notes[0].id); @@ -130,8 +130,8 @@ describe('integration_TagList', function() { // check the tag list is updated state = testApp.store().getState(); - let tagIds = state.selectedNoteTags.map(n => n.id).sort(); - let expectedTagIds = [tags[0].id, tags[2].id].sort(); + const tagIds = state.selectedNoteTags.map(n => n.id).sort(); + const expectedTagIds = [tags[0].id, tags[2].id].sort(); expect(state.selectedNoteTags.length).toEqual(2); expect(tagIds).toEqual(expectedTagIds); })); diff --git a/CliClient/tests/models_BaseItem.js b/CliClient/tests/models_BaseItem.js index b138b62a89..cd0181b8c0 100644 --- a/CliClient/tests/models_BaseItem.js +++ b/CliClient/tests/models_BaseItem.js @@ -16,8 +16,8 @@ process.on('unhandledRejection', (reason, p) => { }); async function allItems() { - let folders = await Folder.all(); - let notes = await Note.all(); + const folders = await Folder.all(); + const notes = await Note.all(); return folders.concat(notes); } @@ -32,27 +32,27 @@ describe('models_BaseItem', function() { // This is to handle the case where a property is removed from a BaseItem table - in that case files in // the sync target will still have the old property but we don't need it locally. it('should ignore properties that are present in sync file but not in database when serialising', asyncTest(async () => { - let folder = await Folder.save({ title: 'folder1' }); + const folder = await Folder.save({ title: 'folder1' }); let serialized = await Folder.serialize(folder); serialized += '\nignore_me: true'; - let unserialized = await Folder.unserialize(serialized); + const unserialized = await Folder.unserialize(serialized); expect('ignore_me' in unserialized).toBe(false); })); it('should not modify title when unserializing', asyncTest(async () => { - let folder1 = await Folder.save({ title: '' }); - let folder2 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: '' }); + const folder2 = await Folder.save({ title: 'folder1' }); - let serialized1 = await Folder.serialize(folder1); - let unserialized1 = await Folder.unserialize(serialized1); + const serialized1 = await Folder.serialize(folder1); + const unserialized1 = await Folder.unserialize(serialized1); expect(unserialized1.title).toBe(folder1.title); - let serialized2 = await Folder.serialize(folder2); - let unserialized2 = await Folder.unserialize(serialized2); + const serialized2 = await Folder.serialize(folder2); + const unserialized2 = await Folder.unserialize(serialized2); expect(unserialized2.title).toBe(folder2.title); })); diff --git a/CliClient/tests/models_Folder.js b/CliClient/tests/models_Folder.js index 16be5b1480..a96b861704 100644 --- a/CliClient/tests/models_Folder.js +++ b/CliClient/tests/models_Folder.js @@ -3,7 +3,7 @@ require('app-module-path').addPath(__dirname); const { time } = require('lib/time-utils.js'); -const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js'); +const { createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js'); const Folder = require('lib/models/Folder.js'); const Note = require('lib/models/Note.js'); const BaseModel = require('lib/BaseModel.js'); @@ -14,8 +14,8 @@ process.on('unhandledRejection', (reason, p) => { }); async function allItems() { - let folders = await Folder.all(); - let notes = await Note.all(); + const folders = await Folder.all(); + const notes = await Note.all(); return folders.concat(notes); } @@ -28,10 +28,10 @@ describe('models_Folder', function() { }); it('should tell if a notebook can be nested under another one', asyncTest(async () => { - let f1 = await Folder.save({ title: 'folder1' }); - let f2 = await Folder.save({ title: 'folder2', parent_id: f1.id }); - let f3 = await Folder.save({ title: 'folder3', parent_id: f2.id }); - let f4 = await Folder.save({ title: 'folder4' }); + const f1 = await Folder.save({ title: 'folder1' }); + const f2 = await Folder.save({ title: 'folder2', parent_id: f1.id }); + const f3 = await Folder.save({ title: 'folder3', parent_id: f2.id }); + const f4 = await Folder.save({ title: 'folder4' }); expect(await Folder.canNestUnder(f1.id, f2.id)).toBe(false); expect(await Folder.canNestUnder(f2.id, f2.id)).toBe(false); @@ -44,9 +44,16 @@ describe('models_Folder', function() { })); it('should recursively delete notes and sub-notebooks', asyncTest(async () => { - let f1 = await Folder.save({ title: 'folder1' }); - let f2 = await Folder.save({ title: 'folder2', parent_id: f1.id }); - let n1 = await Note.save({ title: 'note1', parent_id: f2.id }); + const f1 = await Folder.save({ title: 'folder1' }); + const f2 = await Folder.save({ title: 'folder2', parent_id: f1.id }); + const f3 = await Folder.save({ title: 'folder3', parent_id: f2.id }); + const f4 = await Folder.save({ title: 'folder4', parent_id: f1.id }); + + const noOfNotes = 20; + await createNTestNotes(noOfNotes, f1, null, 'note1'); + await createNTestNotes(noOfNotes, f2, null, 'note2'); + await createNTestNotes(noOfNotes, f3, null, 'note3'); + await createNTestNotes(noOfNotes, f4, null, 'note4'); await Folder.delete(f1.id); @@ -57,10 +64,10 @@ describe('models_Folder', function() { it('should sort by last modified, based on content', asyncTest(async () => { let folders; - let f1 = await Folder.save({ title: 'folder1' }); await sleep(0.1); - let f2 = await Folder.save({ title: 'folder2' }); await sleep(0.1); - let f3 = await Folder.save({ title: 'folder3' }); await sleep(0.1); - let n1 = await Note.save({ title: 'note1', parent_id: f2.id }); + const f1 = await Folder.save({ title: 'folder1' }); await sleep(0.1); + const f2 = await Folder.save({ title: 'folder2' }); await sleep(0.1); + const f3 = await Folder.save({ title: 'folder3' }); await sleep(0.1); + const n1 = await Note.save({ title: 'note1', parent_id: f2.id }); folders = await Folder.orderByLastModified(await Folder.all(), 'desc'); expect(folders.length).toBe(3); @@ -68,7 +75,7 @@ describe('models_Folder', function() { expect(folders[1].id).toBe(f3.id); expect(folders[2].id).toBe(f1.id); - let n2 = await Note.save({ title: 'note1', parent_id: f1.id }); + const n2 = await Note.save({ title: 'note1', parent_id: f1.id }); folders = await Folder.orderByLastModified(await Folder.all(), 'desc'); expect(folders[0].id).toBe(f1.id); @@ -91,10 +98,10 @@ describe('models_Folder', function() { it('should sort by last modified, based on content (sub-folders too)', asyncTest(async () => { let folders; - let f1 = await Folder.save({ title: 'folder1' }); await sleep(0.1); - let f2 = await Folder.save({ title: 'folder2' }); await sleep(0.1); - let f3 = await Folder.save({ title: 'folder3', parent_id: f1.id }); await sleep(0.1); - let n1 = await Note.save({ title: 'note1', parent_id: f3.id }); + const f1 = await Folder.save({ title: 'folder1' }); await sleep(0.1); + const f2 = await Folder.save({ title: 'folder2' }); await sleep(0.1); + const f3 = await Folder.save({ title: 'folder3', parent_id: f1.id }); await sleep(0.1); + const n1 = await Note.save({ title: 'note1', parent_id: f3.id }); folders = await Folder.orderByLastModified(await Folder.all(), 'desc'); expect(folders.length).toBe(3); @@ -102,7 +109,7 @@ describe('models_Folder', function() { expect(folders[1].id).toBe(f3.id); expect(folders[2].id).toBe(f2.id); - let n2 = await Note.save({ title: 'note2', parent_id: f2.id }); + const n2 = await Note.save({ title: 'note2', parent_id: f2.id }); folders = await Folder.orderByLastModified(await Folder.all(), 'desc'); expect(folders[0].id).toBe(f2.id); @@ -116,8 +123,8 @@ describe('models_Folder', function() { expect(folders[1].id).toBe(f3.id); expect(folders[2].id).toBe(f2.id); - let f4 = await Folder.save({ title: 'folder4', parent_id: f1.id }); await sleep(0.1); - let n3 = await Note.save({ title: 'note3', parent_id: f4.id }); + const f4 = await Folder.save({ title: 'folder4', parent_id: f1.id }); await sleep(0.1); + const n3 = await Note.save({ title: 'note3', parent_id: f4.id }); folders = await Folder.orderByLastModified(await Folder.all(), 'desc'); expect(folders.length).toBe(4); @@ -128,14 +135,14 @@ describe('models_Folder', function() { })); it('should add node counts', asyncTest(async () => { - let f1 = await Folder.save({ title: 'folder1' }); - let f2 = await Folder.save({ title: 'folder2', parent_id: f1.id }); - let f3 = await Folder.save({ title: 'folder3', parent_id: f2.id }); - let f4 = await Folder.save({ title: 'folder4' }); + const f1 = await Folder.save({ title: 'folder1' }); + const f2 = await Folder.save({ title: 'folder2', parent_id: f1.id }); + const f3 = await Folder.save({ title: 'folder3', parent_id: f2.id }); + const f4 = await Folder.save({ title: 'folder4' }); - let n1 = await Note.save({ title: 'note1', parent_id: f3.id }); - let n2 = await Note.save({ title: 'note1', parent_id: f3.id }); - let n3 = await Note.save({ title: 'note1', parent_id: f1.id }); + const n1 = await Note.save({ title: 'note1', parent_id: f3.id }); + const n2 = await Note.save({ title: 'note1', parent_id: f3.id }); + const n3 = await Note.save({ title: 'note1', parent_id: f1.id }); const folders = await Folder.all(); await Folder.addNoteCounts(folders); @@ -152,17 +159,17 @@ describe('models_Folder', function() { it('should not count completed to-dos', asyncTest(async () => { - let f1 = await Folder.save({ title: 'folder1' }); - let f2 = await Folder.save({ title: 'folder2', parent_id: f1.id }); - let f3 = await Folder.save({ title: 'folder3', parent_id: f2.id }); - let f4 = await Folder.save({ title: 'folder4' }); + const f1 = await Folder.save({ title: 'folder1' }); + const f2 = await Folder.save({ title: 'folder2', parent_id: f1.id }); + const f3 = await Folder.save({ title: 'folder3', parent_id: f2.id }); + const f4 = await Folder.save({ title: 'folder4' }); - let n1 = await Note.save({ title: 'note1', parent_id: f3.id }); - let n2 = await Note.save({ title: 'note2', parent_id: f3.id }); - let n3 = await Note.save({ title: 'note3', parent_id: f1.id }); - let n4 = await Note.save({ title: 'note4', parent_id: f3.id, is_todo: true, todo_completed: 0 }); - let n5 = await Note.save({ title: 'note5', parent_id: f3.id, is_todo: true, todo_completed: 999 }); - let n6 = await Note.save({ title: 'note6', parent_id: f3.id, is_todo: true, todo_completed: 999 }); + const n1 = await Note.save({ title: 'note1', parent_id: f3.id }); + const n2 = await Note.save({ title: 'note2', parent_id: f3.id }); + const n3 = await Note.save({ title: 'note3', parent_id: f1.id }); + const n4 = await Note.save({ title: 'note4', parent_id: f3.id, is_todo: true, todo_completed: 0 }); + const n5 = await Note.save({ title: 'note5', parent_id: f3.id, is_todo: true, todo_completed: 999 }); + const n6 = await Note.save({ title: 'note6', parent_id: f3.id, is_todo: true, todo_completed: 999 }); const folders = await Folder.all(); await Folder.addNoteCounts(folders, false); @@ -177,4 +184,19 @@ describe('models_Folder', function() { expect(foldersById[f4.id].note_count).toBe(0); })); + it('should recursively find folder path', asyncTest(async () => { + + const f1 = await Folder.save({ title: 'folder1' }); + const f2 = await Folder.save({ title: 'folder2', parent_id: f1.id }); + const f3 = await Folder.save({ title: 'folder3', parent_id: f2.id }); + + const folders = await Folder.all(); + const folderPath = await Folder.folderPath(folders, f3.id); + + expect(folderPath.length).toBe(3); + expect(folderPath[0].id).toBe(f1.id); + expect(folderPath[1].id).toBe(f2.id); + expect(folderPath[2].id).toBe(f3.id); + })); + }); diff --git a/CliClient/tests/models_Note.js b/CliClient/tests/models_Note.js index afab6fecbb..55435e24af 100644 --- a/CliClient/tests/models_Note.js +++ b/CliClient/tests/models_Note.js @@ -3,7 +3,7 @@ require('app-module-path').addPath(__dirname); const { time } = require('lib/time-utils.js'); -const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js'); +const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js'); const Folder = require('lib/models/Folder.js'); const Note = require('lib/models/Note.js'); const BaseModel = require('lib/BaseModel.js'); @@ -14,8 +14,13 @@ process.on('unhandledRejection', (reason, p) => { console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); }); -describe('models_Note', function() { +async function allItems() { + const folders = await Folder.all(); + const notes = await Note.all(); + return folders.concat(notes); +} +describe('models_Note', function() { beforeEach(async (done) => { await setupDatabaseAndSynchronizer(1); await switchClient(1); @@ -23,8 +28,8 @@ describe('models_Note', function() { }); it('should find resource and note IDs', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); let note2 = await Note.save({ title: 'ma deuxième note', body: `Lien vers première note : ${Note.markdownTag(note1)}`, parent_id: folder1.id }); let items = await Note.linkedItems(note2.body); @@ -69,7 +74,7 @@ describe('models_Note', function() { })); it('should change the type of notes', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); note1 = await Note.load(note1.id); @@ -90,7 +95,7 @@ describe('models_Note', function() { })); it('should serialize and unserialize without modifying data', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); const testCases = [ [{ title: '', body: 'Body and no title\nSecond line\nThird Line', parent_id: folder1.id }, '', 'Body and no title\nSecond line\nThird Line'], @@ -107,9 +112,9 @@ describe('models_Note', function() { const expectedTitle = t[1]; const expectedBody = t[1]; - let note1 = await Note.save(input); - let serialized = await Note.serialize(note1); - let unserialized = await Note.unserialize(serialized); + const note1 = await Note.save(input); + const serialized = await Note.serialize(note1); + const unserialized = await Note.unserialize(serialized); expect(unserialized.title).toBe(input.title); expect(unserialized.body).toBe(input.body); @@ -117,10 +122,10 @@ describe('models_Note', function() { })); it('should reset fields for a duplicate', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'note', parent_id: folder1.id }); - let duplicatedNote = await Note.duplicate(note1.id); + const duplicatedNote = await Note.duplicate(note1.id); expect(duplicatedNote !== note1).toBe(true); expect(duplicatedNote.created_time !== note1.created_time).toBe(true); @@ -129,4 +134,65 @@ describe('models_Note', function() { expect(duplicatedNote.user_updated_time !== note1.user_updated_time).toBe(true); })); + it('should delete a set of notes', asyncTest(async () => { + const folder1 = await Folder.save({ title: 'folder1' }); + const noOfNotes = 20; + await createNTestNotes(noOfNotes, folder1); + + const noteIds = await Folder.noteIds(folder1.id); + await Note.batchDelete(noteIds); + + const all = await allItems(); + expect(all.length).toBe(1); + expect(all[0].id).toBe(folder1.id); + })); + + it('should delete only the selected notes', asyncTest(async () => { + const f1 = await Folder.save({ title: 'folder1' }); + const f2 = await Folder.save({ title: 'folder2', parent_id: f1.id }); + + const noOfNotes = 20; + await createNTestNotes(noOfNotes, f1, null, 'note1'); + await createNTestNotes(noOfNotes, f2, null, 'note1'); + + const allBeforeDelete = await allItems(); + + const notesInFolder1IDs = await Folder.noteIds(f1.id); + const notesInFolder2IDs = await Folder.noteIds(f2.id); + + const notesToRemoveFromFolder1 = notesInFolder1IDs.slice(0, 6); + const notesToRemoveFromFolder2 = notesInFolder2IDs.slice(11, 14); + + await Note.batchDelete(notesToRemoveFromFolder1); + await Note.batchDelete(notesToRemoveFromFolder2); + + const allAfterDelete = await allItems(); + + const expectedLength = allBeforeDelete.length - notesToRemoveFromFolder1.length - notesToRemoveFromFolder2.length; + expect(allAfterDelete.length).toBe(expectedLength); + + // Common elements between the to-be-deleted notes and the notes and folders remaining after the delete + const intersection = [...notesToRemoveFromFolder1, ...notesToRemoveFromFolder2].filter(x => allAfterDelete.includes(x)); + // Should be empty + expect(intersection.length).toBe(0); + })); + + it('should delete nothing', asyncTest(async () => { + const f1 = await Folder.save({ title: 'folder1' }); + const f2 = await Folder.save({ title: 'folder2', parent_id: f1.id }); + const f3 = await Folder.save({ title: 'folder3', parent_id: f2.id }); + const f4 = await Folder.save({ title: 'folder4', parent_id: f1.id }); + + const noOfNotes = 20; + await createNTestNotes(noOfNotes, f1, null, 'note1'); + await createNTestNotes(noOfNotes, f2, null, 'note2'); + await createNTestNotes(noOfNotes, f3, null, 'note3'); + await createNTestNotes(noOfNotes, f4, null, 'note4'); + + const beforeDelete = await allItems(); + await Note.batchDelete([]); + const afterDelete = await allItems(); + + expect(sortedIds(afterDelete)).toEqual(sortedIds(beforeDelete)); + })); }); diff --git a/CliClient/tests/models_Resource.js b/CliClient/tests/models_Resource.js index 22ff4d9dae..3c693bb4d3 100644 --- a/CliClient/tests/models_Resource.js +++ b/CliClient/tests/models_Resource.js @@ -27,30 +27,30 @@ describe('models_Resource', function() { }); it('should have a "done" fetch_status when created locally', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, testImagePath); - let resource1 = (await Resource.all())[0]; - let ls = await Resource.localState(resource1); + const resource1 = (await Resource.all())[0]; + const ls = await Resource.localState(resource1); expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_DONE); })); it('should have a default local state', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, testImagePath); - let resource1 = (await Resource.all())[0]; - let ls = await Resource.localState(resource1); + const resource1 = (await Resource.all())[0]; + const ls = await Resource.localState(resource1); expect(!ls.id).toBe(true); expect(ls.resource_id).toBe(resource1.id); expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_DONE); })); it('should save and delete local state', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, testImagePath); - let resource1 = (await Resource.all())[0]; + const resource1 = (await Resource.all())[0]; await Resource.setLocalState(resource1, { fetch_status: Resource.FETCH_STATUS_IDLE }); let ls = await Resource.localState(resource1); @@ -63,13 +63,13 @@ describe('models_Resource', function() { })); it('should resize the resource if the image is below the required dimensions', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); const previousMax = Resource.IMAGE_MAX_DIMENSION; Resource.IMAGE_MAX_DIMENSION = 5; await shim.attachFileToNote(note1, testImagePath); Resource.IMAGE_MAX_DIMENSION = previousMax; - let resource1 = (await Resource.all())[0]; + const resource1 = (await Resource.all())[0]; const originalStat = await shim.fsDriver().stat(testImagePath); const newStat = await shim.fsDriver().stat(Resource.fullPath(resource1)); @@ -78,10 +78,10 @@ describe('models_Resource', function() { })); it('should not resize the resource if the image is below the required dimensions', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, testImagePath); - let resource1 = (await Resource.all())[0]; + const resource1 = (await Resource.all())[0]; const originalStat = await shim.fsDriver().stat(testImagePath); const newStat = await shim.fsDriver().stat(Resource.fullPath(resource1)); diff --git a/CliClient/tests/models_Revision.js b/CliClient/tests/models_Revision.js index 7bb2937653..58303f3509 100644 --- a/CliClient/tests/models_Revision.js +++ b/CliClient/tests/models_Revision.js @@ -92,7 +92,7 @@ describe('models_Revision', function() { - How to view a note history%0A%0AWhile all the apps +%C2%A0How does it work?%0A%0AAll the apps save a version of the modified notes every 10 minutes. %0A%0A# `, - expected: [-(19+27+2), 17+67+4], + expected: [-(19 + 27 + 2), 17 + 67 + 4], }, ]; diff --git a/CliClient/tests/models_Tag.js b/CliClient/tests/models_Tag.js index 8107c16bd7..843144f990 100644 --- a/CliClient/tests/models_Tag.js +++ b/CliClient/tests/models_Tag.js @@ -24,8 +24,8 @@ describe('models_Tag', function() { }); it('should add tags by title', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await Tag.setNoteTagsByTitles(note1.id, ['un', 'deux']); @@ -34,8 +34,8 @@ describe('models_Tag', function() { })); it('should not allow renaming tag to existing tag names', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await Tag.setNoteTagsByTitles(note1.id, ['un', 'deux']); @@ -46,8 +46,8 @@ describe('models_Tag', function() { })); it('should not return tags without notes', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await Tag.setNoteTagsByTitles(note1.id, ['un']); let tags = await Tag.allWithNotes(); @@ -60,9 +60,9 @@ describe('models_Tag', function() { })); it('should return tags with note counts', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); - let note2 = await Note.save({ title: 'ma 2nd note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const note2 = await Note.save({ title: 'ma 2nd note', parent_id: folder1.id }); await Tag.setNoteTagsByTitles(note1.id, ['un']); await Tag.setNoteTagsByTitles(note2.id, ['un']); @@ -83,10 +83,10 @@ describe('models_Tag', function() { })); it('should load individual tags with note count', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); - let note2 = await Note.save({ title: 'ma 2nd note', parent_id: folder1.id }); - let tag = await Tag.save({ title: 'mytag' }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const note2 = await Note.save({ title: 'ma 2nd note', parent_id: folder1.id }); + const tag = await Tag.save({ title: 'mytag' }); await Tag.addNote(tag.id, note1.id); let tagWithCount = await Tag.loadWithCount(tag.id); @@ -98,16 +98,16 @@ describe('models_Tag', function() { })); it('should get common tags for set of notes', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let taga = await Tag.save({ title: 'mytaga' }); - let tagb = await Tag.save({ title: 'mytagb' }); - let tagc = await Tag.save({ title: 'mytagc' }); - let tagd = await Tag.save({ title: 'mytagd' }); + const folder1 = await Folder.save({ title: 'folder1' }); + const taga = await Tag.save({ title: 'mytaga' }); + const tagb = await Tag.save({ title: 'mytagb' }); + const tagc = await Tag.save({ title: 'mytagc' }); + const tagd = await Tag.save({ title: 'mytagd' }); - let note0 = await Note.save({ title: 'ma note 0', parent_id: folder1.id }); - let note1 = await Note.save({ title: 'ma note 1', parent_id: folder1.id }); - let note2 = await Note.save({ title: 'ma note 2', parent_id: folder1.id }); - let note3 = await Note.save({ title: 'ma note 3', parent_id: folder1.id }); + const note0 = await Note.save({ title: 'ma note 0', parent_id: folder1.id }); + const note1 = await Note.save({ title: 'ma note 1', parent_id: folder1.id }); + const note2 = await Note.save({ title: 'ma note 2', parent_id: folder1.id }); + const note3 = await Note.save({ title: 'ma note 3', parent_id: folder1.id }); await Tag.addNote(taga.id, note1.id); diff --git a/CliClient/tests/reducer.js b/CliClient/tests/reducer.js index d5443917c5..b17ff93f46 100644 --- a/CliClient/tests/reducer.js +++ b/CliClient/tests/reducer.js @@ -7,7 +7,7 @@ const Note = require('lib/models/Note.js'); const Tag = require('lib/models/Tag.js'); const { reducer, defaultState, stateUtils } = require('lib/reducer.js'); -function initTestState(folders, selectedFolderIndex, notes, selectedNoteIndexes, tags=null, selectedTagIndex=null) { +function initTestState(folders, selectedFolderIndex, notes, selectedNoteIndexes, tags = null, selectedTagIndex = null) { let state = defaultState; if (selectedFolderIndex != null) { @@ -20,7 +20,7 @@ function initTestState(folders, selectedFolderIndex, notes, selectedNoteIndexes, state = reducer(state, { type: 'NOTE_UPDATE_ALL', notes: notes, noteSource: 'test' }); } if (selectedNoteIndexes != null) { - let selectedIds = []; + const selectedIds = []; for (let i = 0; i < selectedNoteIndexes.length; i++) { selectedIds.push(notes[selectedNoteIndexes[i]].id); } @@ -37,7 +37,7 @@ function initTestState(folders, selectedFolderIndex, notes, selectedNoteIndexes, } function createExpectedState(items, keepIndexes, selectedIndexes) { - let expected = { items: [], selectedIds: [] }; + const expected = { items: [], selectedIds: [] }; for (let i = 0; i < selectedIndexes.length; i++) { expected.selectedIds.push(items[selectedIndexes[i]].id); @@ -48,8 +48,8 @@ function createExpectedState(items, keepIndexes, selectedIndexes) { return expected; } -function getIds(items, indexes=null) { - let ids = []; +function getIds(items, indexes = null) { + const ids = []; for (let i = 0; i < items.length; i++) { if (indexes == null || i in indexes) { ids.push(items[i].id); @@ -76,9 +76,9 @@ describe('Reducer', function() { // tests for NOTE_DELETE it('should delete selected note', asyncTest(async () => { // create 1 folder - let folders = await createNTestFolders(1); + const folders = await createNTestFolders(1); // create 5 notes - let notes = await createNTestNotes(5, folders[0]); + const notes = await createNTestNotes(5, folders[0]); // select the 1st folder and the 3rd note let state = initTestState(folders, 0, notes, [2]); @@ -87,7 +87,7 @@ describe('Reducer', function() { state = reducer(state, { type: 'NOTE_DELETE', id: notes[2].id }); // expect that the third note is missing, and the 4th note is now selected - let expected = createExpectedState(notes, [0,1,3,4], [3]); + const expected = createExpectedState(notes, [0,1,3,4], [3]); // check the ids of all the remaining notes expect(getIds(state.notes)).toEqual(getIds(expected.items)); @@ -96,136 +96,136 @@ describe('Reducer', function() { })); it('should delete selected note at top', asyncTest(async () => { - let folders = await createNTestFolders(1); - let notes = await createNTestNotes(5, folders[0]); + const folders = await createNTestFolders(1); + const notes = await createNTestNotes(5, folders[0]); let state = initTestState(folders, 0, notes, [1]); // test action state = reducer(state, { type: 'NOTE_DELETE', id: notes[0].id }); - let expected = createExpectedState(notes, [1,2,3,4], [1]); + const expected = createExpectedState(notes, [1,2,3,4], [1]); expect(getIds(state.notes)).toEqual(getIds(expected.items)); expect(state.selectedNoteIds).toEqual(expected.selectedIds); })); it('should delete last remaining note', asyncTest(async () => { - let folders = await createNTestFolders(1); - let notes = await createNTestNotes(1, folders[0]); + const folders = await createNTestFolders(1); + const notes = await createNTestNotes(1, folders[0]); let state = initTestState(folders, 0, notes, [0]); // test action state = reducer(state, { type: 'NOTE_DELETE', id: notes[0].id }); - let expected = createExpectedState(notes, [], []); + const expected = createExpectedState(notes, [], []); expect(getIds(state.notes)).toEqual(getIds(expected.items)); expect(state.selectedNoteIds).toEqual(expected.selectedIds); })); it('should delete selected note at bottom', asyncTest(async () => { - let folders = await createNTestFolders(1); - let notes = await createNTestNotes(5, folders[0]); + const folders = await createNTestFolders(1); + const notes = await createNTestNotes(5, folders[0]); let state = initTestState(folders, 0, notes, [4]); // test action state = reducer(state, { type: 'NOTE_DELETE', id: notes[4].id }); - let expected = createExpectedState(notes, [0,1,2,3], [3]); + const expected = createExpectedState(notes, [0,1,2,3], [3]); expect(getIds(state.notes)).toEqual(getIds(expected.items)); expect(state.selectedNoteIds).toEqual(expected.selectedIds); })); it('should delete note when a note below is selected', asyncTest(async () => { - let folders = await createNTestFolders(1); - let notes = await createNTestNotes(5, folders[0]); + const folders = await createNTestFolders(1); + const notes = await createNTestNotes(5, folders[0]); let state = initTestState(folders, 0, notes, [3]); // test action state = reducer(state, { type: 'NOTE_DELETE', id: notes[1].id }); - let expected = createExpectedState(notes, [0,2,3,4], [3]); + const expected = createExpectedState(notes, [0,2,3,4], [3]); expect(getIds(state.notes)).toEqual(getIds(expected.items)); expect(state.selectedNoteIds).toEqual(expected.selectedIds); })); it('should delete note when a note above is selected', asyncTest(async () => { - let folders = await createNTestFolders(1); - let notes = await createNTestNotes(5, folders[0]); + const folders = await createNTestFolders(1); + const notes = await createNTestNotes(5, folders[0]); let state = initTestState(folders, 0, notes, [1]); // test action state = reducer(state, { type: 'NOTE_DELETE', id: notes[3].id }); - let expected = createExpectedState(notes, [0,1,2,4], [1]); + const expected = createExpectedState(notes, [0,1,2,4], [1]); expect(getIds(state.notes)).toEqual(getIds(expected.items)); expect(state.selectedNoteIds).toEqual(expected.selectedIds); })); it('should delete selected notes', asyncTest(async () => { - let folders = await createNTestFolders(1); - let notes = await createNTestNotes(5, folders[0]); + const folders = await createNTestFolders(1); + const notes = await createNTestNotes(5, folders[0]); let state = initTestState(folders, 0, notes, [1,2]); // test action state = reducer(state, { type: 'NOTE_DELETE', id: notes[1].id }); state = reducer(state, { type: 'NOTE_DELETE', id: notes[2].id }); - let expected = createExpectedState(notes, [0,3,4], [3]); + const expected = createExpectedState(notes, [0,3,4], [3]); expect(getIds(state.notes)).toEqual(getIds(expected.items)); expect(state.selectedNoteIds).toEqual(expected.selectedIds); })); it('should delete note when a notes below it are selected', asyncTest(async () => { - let folders = await createNTestFolders(1); - let notes = await createNTestNotes(5, folders[0]); + const folders = await createNTestFolders(1); + const notes = await createNTestNotes(5, folders[0]); let state = initTestState(folders, 0, notes, [3,4]); // test action state = reducer(state, { type: 'NOTE_DELETE', id: notes[1].id }); - let expected = createExpectedState(notes, [0,2,3,4], [3,4]); + const expected = createExpectedState(notes, [0,2,3,4], [3,4]); expect(getIds(state.notes)).toEqual(getIds(expected.items)); expect(state.selectedNoteIds).toEqual(expected.selectedIds); })); it('should delete note when a notes above it are selected', asyncTest(async () => { - let folders = await createNTestFolders(1); - let notes = await createNTestNotes(5, folders[0]); + const folders = await createNTestFolders(1); + const notes = await createNTestNotes(5, folders[0]); let state = initTestState(folders, 0, notes, [1,2]); // test action state = reducer(state, { type: 'NOTE_DELETE', id: notes[3].id }); - let expected = createExpectedState(notes, [0,1,2,4], [1,2]); + const expected = createExpectedState(notes, [0,1,2,4], [1,2]); expect(getIds(state.notes)).toEqual(getIds(expected.items)); expect(state.selectedNoteIds).toEqual(expected.selectedIds); })); it('should delete notes at end', asyncTest(async () => { - let folders = await createNTestFolders(1); - let notes = await createNTestNotes(5, folders[0]); + const folders = await createNTestFolders(1); + const notes = await createNTestNotes(5, folders[0]); let state = initTestState(folders, 0, notes, [3,4]); // test action state = reducer(state, { type: 'NOTE_DELETE', id: notes[3].id }); state = reducer(state, { type: 'NOTE_DELETE', id: notes[4].id }); - let expected = createExpectedState(notes, [0,1,2], [2]); + const expected = createExpectedState(notes, [0,1,2], [2]); expect(getIds(state.notes)).toEqual(getIds(expected.items)); expect(state.selectedNoteIds).toEqual(expected.selectedIds); })); it('should delete notes when non-contiguous selection', asyncTest(async () => { - let folders = await createNTestFolders(1); - let notes = await createNTestNotes(5, folders[0]); + const folders = await createNTestFolders(1); + const notes = await createNTestNotes(5, folders[0]); let state = initTestState(folders, 0, notes, [0,2,4]); // test action @@ -233,7 +233,7 @@ describe('Reducer', function() { state = reducer(state, { type: 'NOTE_DELETE', id: notes[2].id }); state = reducer(state, { type: 'NOTE_DELETE', id: notes[4].id }); - let expected = createExpectedState(notes, [1,3], [1]); + const expected = createExpectedState(notes, [1,3], [1]); expect(getIds(state.notes)).toEqual(getIds(expected.items)); expect(state.selectedNoteIds).toEqual(expected.selectedIds); @@ -241,42 +241,42 @@ describe('Reducer', function() { // tests for FOLDER_DELETE it('should delete selected notebook', asyncTest(async () => { - let folders = await createNTestFolders(5); - let notes = await createNTestNotes(5, folders[0]); + const folders = await createNTestFolders(5); + const notes = await createNTestNotes(5, folders[0]); let state = initTestState(folders, 2, notes, [2]); // test action state = reducer(state, { type: 'FOLDER_DELETE', id: folders[2].id }); - let expected = createExpectedState(folders, [0,1,3,4], [3]); + const expected = createExpectedState(folders, [0,1,3,4], [3]); expect(getIds(state.folders)).toEqual(getIds(expected.items)); expect(state.selectedFolderId).toEqual(expected.selectedIds[0]); })); it('should delete notebook when a book above is selected', asyncTest(async () => { - let folders = await createNTestFolders(5); - let notes = await createNTestNotes(5, folders[0]); + const folders = await createNTestFolders(5); + const notes = await createNTestNotes(5, folders[0]); let state = initTestState(folders, 1, notes, [2]); // test action state = reducer(state, { type: 'FOLDER_DELETE', id: folders[2].id }); - let expected = createExpectedState(folders, [0,1,3,4], [1]); + const expected = createExpectedState(folders, [0,1,3,4], [1]); expect(getIds(state.folders)).toEqual(getIds(expected.items)); expect(state.selectedFolderId).toEqual(expected.selectedIds[0]); })); it('should delete notebook when a book below is selected', asyncTest(async () => { - let folders = await createNTestFolders(5); - let notes = await createNTestNotes(5, folders[0]); + const folders = await createNTestFolders(5); + const notes = await createNTestNotes(5, folders[0]); let state = initTestState(folders, 4, notes, [2]); // test action state = reducer(state, { type: 'FOLDER_DELETE', id: folders[2].id }); - let expected = createExpectedState(folders, [0,1,3,4], [4]); + const expected = createExpectedState(folders, [0,1,3,4], [4]); expect(getIds(state.folders)).toEqual(getIds(expected.items)); expect(state.selectedFolderId).toEqual(expected.selectedIds[0]); @@ -284,47 +284,47 @@ describe('Reducer', function() { // tests for TAG_DELETE it('should delete selected tag', asyncTest(async () => { - let tags = await createNTestTags(5); + const tags = await createNTestTags(5); let state = initTestState(null, null, null, null, tags, [2]); // test action state = reducer(state, { type: 'TAG_DELETE', id: tags[2].id }); - let expected = createExpectedState(tags, [0,1,3,4], [3]); + const expected = createExpectedState(tags, [0,1,3,4], [3]); expect(getIds(state.tags)).toEqual(getIds(expected.items)); expect(state.selectedTagId).toEqual(expected.selectedIds[0]); })); it('should delete tag when a tag above is selected', asyncTest(async () => { - let tags = await createNTestTags(5); + const tags = await createNTestTags(5); let state = initTestState(null, null, null, null, tags, [2]); // test action state = reducer(state, { type: 'TAG_DELETE', id: tags[4].id }); - let expected = createExpectedState(tags, [0,1,2,3], [2]); + const expected = createExpectedState(tags, [0,1,2,3], [2]); expect(getIds(state.tags)).toEqual(getIds(expected.items)); expect(state.selectedTagId).toEqual(expected.selectedIds[0]); })); it('should delete tag when a tag below is selected', asyncTest(async () => { - let tags = await createNTestTags(5); + const tags = await createNTestTags(5); let state = initTestState(null, null, null, null, tags, [2]); // test action state = reducer(state, { type: 'TAG_DELETE', id: tags[0].id }); - let expected = createExpectedState(tags, [1,2,3,4], [2]); + const expected = createExpectedState(tags, [1,2,3,4], [2]); expect(getIds(state.tags)).toEqual(getIds(expected.items)); expect(state.selectedTagId).toEqual(expected.selectedIds[0]); })); it('should select all notes', asyncTest(async () => { - let folders = await createNTestFolders(2); - let notes = []; + const folders = await createNTestFolders(2); + const notes = []; for (let i = 0; i < folders.length; i++) { notes.push(...await createNTestNotes(3, folders[i])); } diff --git a/CliClient/tests/encryption.js b/CliClient/tests/services_EncryptionService.js similarity index 69% rename from CliClient/tests/encryption.js rename to CliClient/tests/services_EncryptionService.js index bbe8d760a0..6871b10692 100644 --- a/CliClient/tests/encryption.js +++ b/CliClient/tests/services_EncryptionService.js @@ -23,7 +23,7 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; // The first test is slow because the let service = null; -describe('Encryption', function() { +describe('services_EncryptionService', function() { beforeEach(async (done) => { await setupDatabaseAndSynchronizer(1); @@ -49,7 +49,6 @@ describe('Encryption', function() { it('should generate and decrypt a master key', asyncTest(async () => { const masterKey = await service.generateMasterKey('123456'); - expect(!!masterKey.checksum).toBe(true); expect(!!masterKey.content).toBe(true); let hasThrown = false; @@ -65,6 +64,91 @@ describe('Encryption', function() { expect(decryptedMasterKey.length).toBe(512); })); + it('should upgrade a master key', asyncTest(async () => { + // Create an old style master key + let masterKey = await service.generateMasterKey('123456', { + encryptionMethod: EncryptionService.METHOD_SJCL_2, + }); + masterKey = await MasterKey.save(masterKey); + + let upgradedMasterKey = await service.upgradeMasterKey(masterKey, '123456'); + upgradedMasterKey = await MasterKey.save(upgradedMasterKey); + + // Check that master key has been upgraded (different ciphertext) + expect(masterKey.content).not.toBe(upgradedMasterKey.content); + + // Check that master key plain text is still the same + const plainTextOld = await service.decryptMasterKey_(masterKey, '123456'); + const plainTextNew = await service.decryptMasterKey_(upgradedMasterKey, '123456'); + expect(plainTextOld.content).toBe(plainTextNew.content); + + // Check that old content can be decrypted with new master key + await service.loadMasterKey_(masterKey, '123456', true); + const cipherText = await service.encryptString('some secret'); + const plainTextFromOld = await service.decryptString(cipherText); + + await service.loadMasterKey_(upgradedMasterKey, '123456', true); + const plainTextFromNew = await service.decryptString(cipherText); + + expect(plainTextFromOld).toBe(plainTextFromNew); + })); + + it('should not upgrade master key if invalid password', asyncTest(async () => { + const masterKey = await service.generateMasterKey('123456', { + encryptionMethod: EncryptionService.METHOD_SJCL_2, + }); + + const hasThrown = await checkThrowAsync(async () => await service.upgradeMasterKey(masterKey, '777')); + })); + + it('should require a checksum only for old master keys', asyncTest(async () => { + const masterKey = await service.generateMasterKey('123456', { + encryptionMethod: EncryptionService.METHOD_SJCL_2, + }); + + expect(!!masterKey.checksum).toBe(true); + expect(!!masterKey.content).toBe(true); + })); + + it('should not require a checksum for new master keys', asyncTest(async () => { + const masterKey = await service.generateMasterKey('123456', { + encryptionMethod: EncryptionService.METHOD_SJCL_4, + }); + + expect(!masterKey.checksum).toBe(true); + expect(!!masterKey.content).toBe(true); + + const decryptedMasterKey = await service.decryptMasterKey_(masterKey, '123456'); + expect(decryptedMasterKey.length).toBe(512); + })); + + it('should throw an error if master key decryption fails', asyncTest(async () => { + const masterKey = await service.generateMasterKey('123456', { + encryptionMethod: EncryptionService.METHOD_SJCL_4, + }); + + const hasThrown = await checkThrowAsync(async () => await service.decryptMasterKey_(masterKey, 'wrong')); + + expect(hasThrown).toBe(true); + })); + + it('should return the master keys that need an upgrade', asyncTest(async () => { + const masterKey1 = await MasterKey.save(await service.generateMasterKey('123456', { + encryptionMethod: EncryptionService.METHOD_SJCL_2, + })); + + const masterKey2 = await MasterKey.save(await service.generateMasterKey('123456', { + encryptionMethod: EncryptionService.METHOD_SJCL, + })); + + const masterKey3 = await MasterKey.save(await service.generateMasterKey('123456')); + + const needUpgrade = service.masterKeysThatNeedUpgrading(await MasterKey.all()); + + expect(needUpgrade.length).toBe(2); + expect(needUpgrade.map(k => k.id).sort()).toEqual([masterKey1.id, masterKey2.id].sort()); + })); + it('should encrypt and decrypt with a master key', asyncTest(async () => { let masterKey = await service.generateMasterKey('123456'); masterKey = await MasterKey.save(masterKey); @@ -123,7 +207,7 @@ describe('Encryption', function() { await service.unloadMasterKey(masterKey); - let hasThrown = await checkThrowAsync(async () => await service.decryptString(cipherText)); + const hasThrown = await checkThrowAsync(async () => await service.decryptString(cipherText)); expect(hasThrown).toBe(true); })); @@ -138,7 +222,7 @@ describe('Encryption', function() { let cipherText = await service.encryptString('some secret'); cipherText += 'ABCDEFGHIJ'; - let hasThrown = await checkThrowAsync(async () => await service.decryptString(cipherText)); + const hasThrown = await checkThrowAsync(async () => await service.decryptString(cipherText)); expect(hasThrown).toBe(true); })); @@ -148,10 +232,10 @@ describe('Encryption', function() { masterKey = await MasterKey.save(masterKey); await service.loadMasterKey_(masterKey, '123456', true); - let folder = await Folder.save({ title: 'folder' }); - let note = await Note.save({ title: 'encrypted note', body: 'something', parent_id: folder.id }); - let serialized = await Note.serializeForSync(note); - let deserialized = Note.filter(await Note.unserialize(serialized)); + const folder = await Folder.save({ title: 'folder' }); + const note = await Note.save({ title: 'encrypted note', body: 'something', parent_id: folder.id }); + const serialized = await Note.serializeForSync(note); + const deserialized = Note.filter(await Note.unserialize(serialized)); // Check that required properties are not encrypted expect(deserialized.id).toBe(note.id); diff --git a/CliClient/tests/services_InteropService.js b/CliClient/tests/services_InteropService.js index 9ced4d46a5..2b030c7718 100644 --- a/CliClient/tests/services_InteropService.js +++ b/CliClient/tests/services_InteropService.js @@ -59,7 +59,7 @@ describe('services_InteropService', function() { // Check that a new folder, with a new ID, has been created expect(await Folder.count()).toBe(1); - let folder2 = (await Folder.all())[0]; + const folder2 = (await Folder.all())[0]; expect(folder2.id).not.toBe(folder1.id); expect(folder2.title).toBe(folder1.title); @@ -68,7 +68,7 @@ describe('services_InteropService', function() { // As there was already a folder with the same title, check that the new one has been renamed await Folder.delete(folder2.id); - let folder3 = (await Folder.all())[0]; + const folder3 = (await Folder.all())[0]; expect(await Folder.count()).toBe(1); expect(folder3.title).not.toBe(folder2.title); @@ -81,7 +81,7 @@ describe('services_InteropService', function() { it('should export and import folders and notes', asyncTest(async () => { const service = new InteropService(); - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); note1 = await Note.load(note1.id); const filePath = `${exportDir()}/test.jex`; @@ -95,7 +95,7 @@ describe('services_InteropService', function() { expect(await Note.count()).toBe(1); let note2 = (await Note.all())[0]; - let folder2 = (await Folder.all())[0]; + const folder2 = (await Folder.all())[0]; expect(note1.parent_id).not.toBe(note2.parent_id); expect(note1.id).not.toBe(note2.id); @@ -110,7 +110,7 @@ describe('services_InteropService', function() { await service.import({ path: filePath }); note2 = (await Note.all())[0]; - let note3 = (await Note.all())[1]; + const note3 = (await Note.all())[1]; expect(note2.id).not.toBe(note3.id); expect(note2.parent_id).not.toBe(note3.parent_id); @@ -120,7 +120,7 @@ describe('services_InteropService', function() { it('should export and import notes to specific folder', asyncTest(async () => { const service = new InteropService(); - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); note1 = await Note.load(note1.id); const filePath = `${exportDir()}/test.jex`; @@ -140,8 +140,8 @@ describe('services_InteropService', function() { it('should export and import tags', asyncTest(async () => { const service = new InteropService(); const filePath = `${exportDir()}/test.jex`; - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); let tag1 = await Tag.save({ title: 'mon tag' }); tag1 = await Tag.load(tag1.id); await Tag.addNote(tag1.id, note1.id); @@ -155,8 +155,8 @@ describe('services_InteropService', function() { await service.import({ path: filePath }); expect(await Tag.count()).toBe(1); - let tag2 = (await Tag.all())[0]; - let note2 = (await Note.all())[0]; + const tag2 = (await Tag.all())[0]; + const note2 = (await Note.all())[0]; expect(tag1.id).not.toBe(tag2.id); let fieldNames = Note.fieldNames(); @@ -180,12 +180,12 @@ describe('services_InteropService', function() { it('should export and import resources', asyncTest(async () => { const service = new InteropService(); const filePath = `${exportDir()}/test.jex`; - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); note1 = await Note.load(note1.id); let resourceIds = await Note.linkedResourceIds(note1.body); - let resource1 = await Resource.load(resourceIds[0]); + const resource1 = await Resource.load(resourceIds[0]); await service.export({ path: filePath }); @@ -195,11 +195,11 @@ describe('services_InteropService', function() { expect(await Resource.count()).toBe(2); - let note2 = (await Note.all())[0]; + const note2 = (await Note.all())[0]; expect(note2.body).not.toBe(note1.body); resourceIds = await Note.linkedResourceIds(note2.body); expect(resourceIds.length).toBe(1); - let resource2 = await Resource.load(resourceIds[0]); + const resource2 = await Resource.load(resourceIds[0]); expect(resource2.id).not.toBe(resource1.id); let fieldNames = Note.fieldNames(); @@ -216,8 +216,8 @@ describe('services_InteropService', function() { it('should export and import single notes', asyncTest(async () => { const service = new InteropService(); const filePath = `${exportDir()}/test.jex`; - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await service.export({ path: filePath, sourceNoteIds: [note1.id] }); @@ -229,15 +229,15 @@ describe('services_InteropService', function() { expect(await Note.count()).toBe(1); expect(await Folder.count()).toBe(1); - let folder2 = (await Folder.all())[0]; + const folder2 = (await Folder.all())[0]; expect(folder2.title).toBe('test'); })); it('should export and import single folders', asyncTest(async () => { const service = new InteropService(); const filePath = `${exportDir()}/test.jex`; - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await service.export({ path: filePath, sourceFolderIds: [folder1.id] }); @@ -249,7 +249,7 @@ describe('services_InteropService', function() { expect(await Note.count()).toBe(1); expect(await Folder.count()).toBe(1); - let folder2 = (await Folder.all())[0]; + const folder2 = (await Folder.all())[0]; expect(folder2.title).toBe('folder1'); })); @@ -257,11 +257,11 @@ describe('services_InteropService', function() { const service = new InteropService(); const filePath = `${exportDir()}/test.jex`; - let folder1 = await Folder.save({ title: 'folder1' }); - let folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id }); - let folder3 = await Folder.save({ title: 'folder3', parent_id: folder2.id }); - let folder4 = await Folder.save({ title: 'folder4', parent_id: folder2.id }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder4.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id }); + const folder3 = await Folder.save({ title: 'folder3', parent_id: folder2.id }); + const folder4 = await Folder.save({ title: 'folder4', parent_id: folder2.id }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder4.id }); await service.export({ path: filePath, sourceFolderIds: [folder1.id] }); @@ -276,11 +276,11 @@ describe('services_InteropService', function() { expect(await Note.count()).toBe(1); expect(await Folder.count()).toBe(4); - let folder1_2 = await Folder.loadByTitle('folder1'); - let folder2_2 = await Folder.loadByTitle('folder2'); - let folder3_2 = await Folder.loadByTitle('folder3'); - let folder4_2 = await Folder.loadByTitle('folder4'); - let note1_2 = await Note.loadByTitle('ma note'); + const folder1_2 = await Folder.loadByTitle('folder1'); + const folder2_2 = await Folder.loadByTitle('folder2'); + const folder3_2 = await Folder.loadByTitle('folder3'); + const folder4_2 = await Folder.loadByTitle('folder4'); + const note1_2 = await Note.loadByTitle('ma note'); expect(folder2_2.parent_id).toBe(folder1_2.id); expect(folder3_2.parent_id).toBe(folder2_2.id); @@ -291,9 +291,9 @@ describe('services_InteropService', function() { it('should export and import links to notes', asyncTest(async () => { const service = new InteropService(); const filePath = `${exportDir()}/test.jex`; - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); - let note2 = await Note.save({ title: 'ma deuxième note', body: `Lien vers première note : ${Note.markdownTag(note1)}`, parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const note2 = await Note.save({ title: 'ma deuxième note', body: `Lien vers première note : ${Note.markdownTag(note1)}`, parent_id: folder1.id }); await service.export({ path: filePath, sourceFolderIds: [folder1.id] }); @@ -306,15 +306,15 @@ describe('services_InteropService', function() { expect(await Note.count()).toBe(2); expect(await Folder.count()).toBe(1); - let note1_2 = await Note.loadByTitle('ma note'); - let note2_2 = await Note.loadByTitle('ma deuxième note'); + const note1_2 = await Note.loadByTitle('ma note'); + const note2_2 = await Note.loadByTitle('ma deuxième note'); expect(note2_2.body.indexOf(note1_2.id) >= 0).toBe(true); })); it('should export into json format', asyncTest(async () => { const service = new InteropService(); - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); note1 = await Note.load(note1.id); const filePath = exportDir(); @@ -325,8 +325,8 @@ describe('services_InteropService', function() { const items = [folder1, note1]; for (let i = 0; i < items.length; i++) { const jsonFile = `${filePath}/${items[i].id}.json`; - let json = await fs.readFile(jsonFile, 'utf-8'); - let obj = JSON.parse(json); + const json = await fs.readFile(jsonFile, 'utf-8'); + const obj = JSON.parse(json); expect(obj.id).toBe(items[i].id); expect(obj.type_).toBe(items[i].type_); expect(obj.title).toBe(items[i].title); @@ -336,7 +336,7 @@ describe('services_InteropService', function() { it('should export selected notes in md format', asyncTest(async () => { const service = new InteropService(); - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note11 = await Note.save({ title: 'title note11', parent_id: folder1.id }); note11 = await Note.load(note11.id); let note12 = await Note.save({ title: 'title note12', parent_id: folder1.id }); @@ -365,15 +365,15 @@ describe('services_InteropService', function() { it('should export MD with unicode filenames', asyncTest(async () => { const service = new InteropService(); - let folder1 = await Folder.save({ title: 'folder1' }); - let folder2 = await Folder.save({ title: 'ジョプリン' }); - let note1 = await Note.save({ title: '生活', parent_id: folder1.id }); - let note2 = await Note.save({ title: '生活', parent_id: folder1.id }); - let note2b = await Note.save({ title: '生活', parent_id: folder1.id }); - let note3 = await Note.save({ title: '', parent_id: folder1.id }); - let note4 = await Note.save({ title: '', parent_id: folder1.id }); - let note5 = await Note.save({ title: 'salut, ça roule ?', parent_id: folder1.id }); - let note6 = await Note.save({ title: 'ジョプリン', parent_id: folder2.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const folder2 = await Folder.save({ title: 'ジョプリン' }); + const note1 = await Note.save({ title: '生活', parent_id: folder1.id }); + const note2 = await Note.save({ title: '生活', parent_id: folder1.id }); + const note2b = await Note.save({ title: '生活', parent_id: folder1.id }); + const note3 = await Note.save({ title: '', parent_id: folder1.id }); + const note4 = await Note.save({ title: '', parent_id: folder1.id }); + const note5 = await Note.save({ title: 'salut, ça roule ?', parent_id: folder1.id }); + const note6 = await Note.save({ title: 'ジョプリン', parent_id: folder2.id }); const outDir = exportDir(); diff --git a/CliClient/tests/services_InteropService_Exporter_Md.js b/CliClient/tests/services_InteropService_Exporter_Md.js index 2ba5e2caf9..5b4ca14d1a 100644 --- a/CliClient/tests/services_InteropService_Exporter_Md.js +++ b/CliClient/tests/services_InteropService_Exporter_Md.js @@ -49,9 +49,9 @@ describe('services_InteropService_Exporter_Md', function() { }); }; - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'note1', parent_id: folder1.id }); - let note2 = await Note.save({ title: 'note2', parent_id: folder1.id }); + const note2 = await Note.save({ title: 'note2', parent_id: folder1.id }); await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); note1 = await Note.load(note1.id); queueExportItem(BaseModel.TYPE_FOLDER, folder1.id); @@ -59,7 +59,7 @@ describe('services_InteropService_Exporter_Md', function() { queueExportItem(BaseModel.TYPE_NOTE, note2); queueExportItem(BaseModel.TYPE_RESOURCE, (await Note.linkedResourceIds(note1.body))[0]); - let folder2 = await Folder.save({ title: 'folder2' }); + const folder2 = await Folder.save({ title: 'folder2' }); let note3 = await Note.save({ title: 'note3', parent_id: folder2.id }); await shim.attachFileToNote(note3, `${__dirname}/../tests/support/photo.jpg`); note3 = await Note.load(note3.id); @@ -91,9 +91,9 @@ describe('services_InteropService_Exporter_Md', function() { }); }; - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'note1', parent_id: folder1.id }); - let note1_2 = await Note.save({ title: 'note1', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'note1', parent_id: folder1.id }); + const note1_2 = await Note.save({ title: 'note1', parent_id: folder1.id }); queueExportItem(BaseModel.TYPE_FOLDER, folder1.id); queueExportItem(BaseModel.TYPE_NOTE, note1); queueExportItem(BaseModel.TYPE_NOTE, note1_2); @@ -118,8 +118,8 @@ describe('services_InteropService_Exporter_Md', function() { }); }; - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'note1', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'note1', parent_id: folder1.id }); queueExportItem(BaseModel.TYPE_FOLDER, folder1.id); queueExportItem(BaseModel.TYPE_NOTE, note1); @@ -145,23 +145,23 @@ describe('services_InteropService_Exporter_Md', function() { }); }; - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'note1', parent_id: folder1.id }); await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); note1 = await Note.load(note1.id); queueExportItem(BaseModel.TYPE_FOLDER, folder1.id); queueExportItem(BaseModel.TYPE_NOTE, note1); queueExportItem(BaseModel.TYPE_RESOURCE, (await Note.linkedResourceIds(note1.body))[0]); - let resource1 = await Resource.load(itemsToExport[2].itemOrId); + const resource1 = await Resource.load(itemsToExport[2].itemOrId); - let folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id }); + const folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id }); let note2 = await Note.save({ title: 'note2', parent_id: folder2.id }); await shim.attachFileToNote(note2, `${__dirname}/../tests/support/photo.jpg`); note2 = await Note.load(note2.id); queueExportItem(BaseModel.TYPE_FOLDER, folder2.id); queueExportItem(BaseModel.TYPE_NOTE, note2); queueExportItem(BaseModel.TYPE_RESOURCE, (await Note.linkedResourceIds(note2.body))[0]); - let resource2 = await Resource.load(itemsToExport[5].itemOrId); + const resource2 = await Resource.load(itemsToExport[5].itemOrId); await exporter.processResource(resource1, Resource.fullPath(resource1)); await exporter.processResource(resource2, Resource.fullPath(resource2)); @@ -182,13 +182,13 @@ describe('services_InteropService_Exporter_Md', function() { }); }; - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); - let folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id }); - let note2 = await Note.save({ title: 'note2', parent_id: folder2.id }); + const folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id }); + const note2 = await Note.save({ title: 'note2', parent_id: folder2.id }); queueExportItem(BaseModel.TYPE_NOTE, note2); - let folder3 = await Folder.save({ title: 'folder3', parent_id: folder1.id }); + const folder3 = await Folder.save({ title: 'folder3', parent_id: folder1.id }); queueExportItem(BaseModel.TYPE_FOLDER, folder3.id); await exporter.processItem(Folder, folder2); @@ -213,18 +213,18 @@ describe('services_InteropService_Exporter_Md', function() { }); }; - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'note1', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'note1', parent_id: folder1.id }); queueExportItem(BaseModel.TYPE_FOLDER, folder1.id); queueExportItem(BaseModel.TYPE_NOTE, note1); - let folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id }); - let note2 = await Note.save({ title: 'note2', parent_id: folder2.id }); + const folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id }); + const note2 = await Note.save({ title: 'note2', parent_id: folder2.id }); queueExportItem(BaseModel.TYPE_FOLDER, folder2.id); queueExportItem(BaseModel.TYPE_NOTE, note2); - let folder3 = await Folder.save({ title: 'folder3' }); - let note3 = await Note.save({ title: 'note3', parent_id: folder3.id }); + const folder3 = await Folder.save({ title: 'folder3' }); + const note3 = await Note.save({ title: 'note3', parent_id: folder3.id }); queueExportItem(BaseModel.TYPE_FOLDER, folder3.id); queueExportItem(BaseModel.TYPE_NOTE, note3); @@ -250,24 +250,24 @@ describe('services_InteropService_Exporter_Md', function() { }); }; - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'note1', parent_id: folder1.id }); await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); note1 = await Note.load(note1.id); queueExportItem(BaseModel.TYPE_NOTE, note1); - let resource1 = await Resource.load((await Note.linkedResourceIds(note1.body))[0]); + const resource1 = await Resource.load((await Note.linkedResourceIds(note1.body))[0]); - let folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id }); + const folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id }); let note2 = await Note.save({ title: 'note2', parent_id: folder2.id }); await shim.attachFileToNote(note2, `${__dirname}/../tests/support/photo.jpg`); note2 = await Note.load(note2.id); queueExportItem(BaseModel.TYPE_NOTE, note2); - let resource2 = await Resource.load((await Note.linkedResourceIds(note2.body))[0]); + const resource2 = await Resource.load((await Note.linkedResourceIds(note2.body))[0]); await exporter.processItem(Folder, folder1); await exporter.processItem(Folder, folder2); await exporter.prepareForProcessingItemType(BaseModel.TYPE_NOTE, itemsToExport); - let context = { + const context = { resourcePaths: {}, }; context.resourcePaths[resource1.id] = 'resource1.jpg'; @@ -276,8 +276,8 @@ describe('services_InteropService_Exporter_Md', function() { await exporter.processItem(Note, note1); await exporter.processItem(Note, note2); - let note1_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note1.id]}`); - let note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`); + const note1_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note1.id]}`); + const note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`); expect(note1_body).toContain('](../_resources/resource1.jpg)', 'Resource id should be replaced with a relative path.'); expect(note2_body).toContain('](../../_resources/resource2.jpg)', 'Resource id should be replaced with a relative path.'); @@ -301,13 +301,13 @@ describe('services_InteropService_Exporter_Md', function() { return await Note.load(note.id); }; - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'note1', parent_id: folder1.id }); - let folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id }); + const folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id }); let note2 = await Note.save({ title: 'note2', parent_id: folder2.id }); - let folder3 = await Folder.save({ title: 'folder3' }); + const folder3 = await Folder.save({ title: 'folder3' }); let note3 = await Note.save({ title: 'note3', parent_id: folder3.id }); note1 = await changeNoteBodyAndReload(note1, `# Some text \n\n [A link to note3](:/${note3.id})`); @@ -325,9 +325,9 @@ describe('services_InteropService_Exporter_Md', function() { await exporter.processItem(Note, note2); await exporter.processItem(Note, note3); - let note1_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note1.id]}`); - let note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`); - let note3_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note3.id]}`); + const note1_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note1.id]}`); + const note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`); + const note3_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note3.id]}`); expect(note1_body).toContain('](../folder3/note3.md)', 'Note id should be replaced with a relative path.'); expect(note2_body).toContain('](../../folder3/note3.md)', 'Resource id should be replaced with a relative path.'); @@ -347,9 +347,9 @@ describe('services_InteropService_Exporter_Md', function() { }); }; - let folder1 = await Folder.save({ title: 'folder with space1' }); - let note1 = await Note.save({ title: 'note1 name with space', parent_id: folder1.id }); - let note2 = await Note.save({ title: 'note2', parent_id: folder1.id, body: `[link](:/${note1.id})` }); + const folder1 = await Folder.save({ title: 'folder with space1' }); + const note1 = await Note.save({ title: 'note1 name with space', parent_id: folder1.id }); + const note2 = await Note.save({ title: 'note2', parent_id: folder1.id, body: `[link](:/${note1.id})` }); queueExportItem(BaseModel.TYPE_NOTE, note1); queueExportItem(BaseModel.TYPE_NOTE, note2); @@ -358,7 +358,7 @@ describe('services_InteropService_Exporter_Md', function() { await exporter.processItem(Note, note1); await exporter.processItem(Note, note2); - let note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`); + const note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`); expect(note2_body).toContain('[link](../folder%20with%20space1/note1%20name%20with%20space.md)', 'Whitespace in URL should be encoded'); })); }); diff --git a/CliClient/tests/services_ResourceService.js b/CliClient/tests/services_ResourceService.js index 94b5d5e6e6..b9ed02b762 100644 --- a/CliClient/tests/services_ResourceService.js +++ b/CliClient/tests/services_ResourceService.js @@ -48,10 +48,10 @@ describe('services_ResourceService', function() { it('should delete orphaned resources', asyncTest(async () => { const service = new ResourceService(); - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); note1 = await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); - let resource1 = (await Resource.all())[0]; + const resource1 = (await Resource.all())[0]; const resourcePath = Resource.fullPath(resource1); await service.indexNoteResources(); @@ -79,11 +79,11 @@ describe('services_ResourceService', function() { it('should not delete resource if still associated with at least one note', asyncTest(async () => { const service = new ResourceService(); - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); - let note2 = await Note.save({ title: 'ma deuxième note', parent_id: folder1.id }); + const note2 = await Note.save({ title: 'ma deuxième note', parent_id: folder1.id }); note1 = await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); - let resource1 = (await Resource.all())[0]; + const resource1 = (await Resource.all())[0]; await service.indexNoteResources(); @@ -113,10 +113,10 @@ describe('services_ResourceService', function() { it('should not delete resource if it is used in an IMG tag', asyncTest(async () => { const service = new ResourceService(); - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); note1 = await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); - let resource1 = (await Resource.all())[0]; + const resource1 = (await Resource.all())[0]; await service.indexNoteResources(); @@ -132,10 +132,10 @@ describe('services_ResourceService', function() { it('should not process twice the same change', asyncTest(async () => { const service = new ResourceService(); - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); note1 = await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); - let resource1 = (await Resource.all())[0]; + const resource1 = (await Resource.all())[0]; await service.indexNoteResources(); @@ -169,8 +169,8 @@ describe('services_ResourceService', function() { const masterKey = await loadEncryptionMasterKey(); await encryptionService().enableEncryption(masterKey, '123456'); await encryptionService().loadMasterKeysFromSettings(); - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); // R1 await resourceService().indexNoteResources(); await synchronizer().start(); @@ -199,7 +199,7 @@ describe('services_ResourceService', function() { it('should double-check if the resource is still linked before deleting it', asyncTest(async () => { SearchEngine.instance().setDb(db()); // /!\ Note that we use the global search engine here, which we shouldn't but will work for now - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); note1 = await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); await resourceService().indexNoteResources(); diff --git a/CliClient/tests/services_SearchEngine.js b/CliClient/tests/services_SearchEngine.js index ec3da92221..5d2fa62564 100644 --- a/CliClient/tests/services_SearchEngine.js +++ b/CliClient/tests/services_SearchEngine.js @@ -1,4 +1,5 @@ /* eslint-disable no-unused-vars */ +/* eslint prefer-const: 0*/ require('app-module-path').addPath(__dirname); diff --git a/CliClient/tests/services_rest_Api.js b/CliClient/tests/services_rest_Api.js index 867c1b63ab..8a16ccc932 100644 --- a/CliClient/tests/services_rest_Api.js +++ b/CliClient/tests/services_rest_Api.js @@ -8,6 +8,7 @@ const Folder = require('lib/models/Folder'); const Resource = require('lib/models/Resource'); const Note = require('lib/models/Note'); const Tag = require('lib/models/Tag'); +const NoteTag = require('lib/models/NoteTag'); const { shim } = require('lib/shim'); jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; @@ -36,26 +37,26 @@ describe('services_rest_Api', function() { })); it('should get folders', asyncTest(async () => { - let f1 = await Folder.save({ title: 'mon carnet' }); + const f1 = await Folder.save({ title: 'mon carnet' }); const response = await api.route('GET', 'folders'); expect(response.length).toBe(1); })); it('should update folders', asyncTest(async () => { - let f1 = await Folder.save({ title: 'mon carnet' }); + const f1 = await Folder.save({ title: 'mon carnet' }); const response = await api.route('PUT', `folders/${f1.id}`, null, JSON.stringify({ title: 'modifié', })); - let f1b = await Folder.load(f1.id); + const f1b = await Folder.load(f1.id); expect(f1b.title).toBe('modifié'); })); it('should delete folders', asyncTest(async () => { - let f1 = await Folder.save({ title: 'mon carnet' }); + const f1 = await Folder.save({ title: 'mon carnet' }); await api.route('DELETE', `folders/${f1.id}`); - let f1b = await Folder.load(f1.id); + const f1b = await Folder.load(f1.id); expect(!f1b).toBe(true); })); @@ -66,13 +67,13 @@ describe('services_rest_Api', function() { expect(!!response.id).toBe(true); - let f = await Folder.all(); + const f = await Folder.all(); expect(f.length).toBe(1); expect(f[0].title).toBe('from api'); })); it('should get one folder', asyncTest(async () => { - let f1 = await Folder.save({ title: 'mon carnet' }); + const f1 = await Folder.save({ title: 'mon carnet' }); const response = await api.route('GET', `folders/${f1.id}`); expect(response.id).toBe(f1.id); @@ -81,7 +82,7 @@ describe('services_rest_Api', function() { })); it('should get the folder notes', asyncTest(async () => { - let f1 = await Folder.save({ title: 'mon carnet' }); + const f1 = await Folder.save({ title: 'mon carnet' }); const response2 = await api.route('GET', `folders/${f1.id}/notes`); expect(response2.length).toBe(0); @@ -326,4 +327,84 @@ describe('services_rest_Api', function() { expect(response3.length).toBe(2); })); + it('should update tags when updating notes', asyncTest(async () => { + const tag1 = await Tag.save({ title: 'mon étiquette 1' }); + const tag2 = await Tag.save({ title: 'mon étiquette 2' }); + const tag3 = await Tag.save({ title: 'mon étiquette 3' }); + + const note = await Note.save({ + title: 'ma note un', + }); + Tag.addNote(tag1.id, note.id); + Tag.addNote(tag2.id, note.id); + + const response = await api.route('PUT', `notes/${note.id}`, null, JSON.stringify({ + tags: `${tag1.title},${tag3.title}`, + })); + const tagIds = await NoteTag.tagIdsByNoteId(note.id); + expect(response.tags === `${tag1.title},${tag3.title}`).toBe(true); + expect(tagIds.length === 2).toBe(true); + expect(tagIds.includes(tag1.id)).toBe(true); + expect(tagIds.includes(tag3.id)).toBe(true); + })); + + it('should create and update tags when updating notes', asyncTest(async () => { + const tag1 = await Tag.save({ title: 'mon étiquette 1' }); + const tag2 = await Tag.save({ title: 'mon étiquette 2' }); + const newTagTitle = 'mon étiquette 3'; + + const note = await Note.save({ + title: 'ma note un', + }); + Tag.addNote(tag1.id, note.id); + Tag.addNote(tag2.id, note.id); + + const response = await api.route('PUT', `notes/${note.id}`, null, JSON.stringify({ + tags: `${tag1.title},${newTagTitle}`, + })); + const newTag = await Tag.loadByTitle(newTagTitle); + const tagIds = await NoteTag.tagIdsByNoteId(note.id); + expect(response.tags === `${tag1.title},${newTag.title}`).toBe(true); + expect(tagIds.length === 2).toBe(true); + expect(tagIds.includes(tag1.id)).toBe(true); + expect(tagIds.includes(newTag.id)).toBe(true); + })); + + it('should not update tags if tags is not mentioned when updating', asyncTest(async () => { + const tag1 = await Tag.save({ title: 'mon étiquette 1' }); + const tag2 = await Tag.save({ title: 'mon étiquette 2' }); + + const note = await Note.save({ + title: 'ma note un', + }); + Tag.addNote(tag1.id, note.id); + Tag.addNote(tag2.id, note.id); + + const response = await api.route('PUT', `notes/${note.id}`, null, JSON.stringify({ + title: 'Some other title', + })); + const tagIds = await NoteTag.tagIdsByNoteId(note.id); + expect(response.tags === undefined).toBe(true); + expect(tagIds.length === 2).toBe(true); + expect(tagIds.includes(tag1.id)).toBe(true); + expect(tagIds.includes(tag2.id)).toBe(true); + })); + + it('should remove tags from note if tags is set to empty string when updating', asyncTest(async () => { + const tag1 = await Tag.save({ title: 'mon étiquette 1' }); + const tag2 = await Tag.save({ title: 'mon étiquette 2' }); + + const note = await Note.save({ + title: 'ma note un', + }); + Tag.addNote(tag1.id, note.id); + Tag.addNote(tag2.id, note.id); + + const response = await api.route('PUT', `notes/${note.id}`, null, JSON.stringify({ + tags: '', + })); + const tagIds = await NoteTag.tagIdsByNoteId(note.id); + expect(response.tags === '').toBe(true); + expect(tagIds.length === 0).toBe(true); + })); }); diff --git a/CliClient/tests/synchronizer.js b/CliClient/tests/synchronizer.js index 7ff36e2637..a39e4e8ca1 100644 --- a/CliClient/tests/synchronizer.js +++ b/CliClient/tests/synchronizer.js @@ -27,8 +27,8 @@ process.on('unhandledRejection', (reason, p) => { jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000 + 30000; // The first test is slow because the database needs to be built async function allNotesFolders() { - let folders = await Folder.all(); - let notes = await Note.all(); + const folders = await Folder.all(); + const notes = await Note.all(); return folders.concat(notes); } @@ -66,9 +66,9 @@ async function localNotesFoldersSameAsRemote(locals, expect) { expect(locals.length).toBe(nf.length); for (let i = 0; i < locals.length; i++) { - let dbItem = locals[i]; - let path = BaseItem.systemPath(dbItem); - let remote = await fileApi().stat(path); + const dbItem = locals[i]; + const path = BaseItem.systemPath(dbItem); + const remote = await fileApi().stat(path); expect(!!remote).toBe(true); if (!remote) continue; @@ -101,10 +101,10 @@ describe('synchronizer', function() { }); it('should create remote items', asyncTest(async () => { - let folder = await Folder.save({ title: 'folder1' }); + const folder = await Folder.save({ title: 'folder1' }); await Note.save({ title: 'un', parent_id: folder.id }); - let all = await allNotesFolders(); + const all = await allNotesFolders(); await synchronizer().start(); @@ -112,20 +112,20 @@ describe('synchronizer', function() { })); it('should update remote items', asyncTest(async () => { - let folder = await Folder.save({ title: 'folder1' }); - let note = await Note.save({ title: 'un', parent_id: folder.id }); + const folder = await Folder.save({ title: 'folder1' }); + const note = await Note.save({ title: 'un', parent_id: folder.id }); await synchronizer().start(); await Note.save({ title: 'un UPDATE', id: note.id }); - let all = await allNotesFolders(); + const all = await allNotesFolders(); await synchronizer().start(); await localNotesFoldersSameAsRemote(all, expect); })); it('should create local items', asyncTest(async () => { - let folder = await Folder.save({ title: 'folder1' }); + const folder = await Folder.save({ title: 'folder1' }); await Note.save({ title: 'un', parent_id: folder.id }); await synchronizer().start(); @@ -133,14 +133,14 @@ describe('synchronizer', function() { await synchronizer().start(); - let all = await allNotesFolders(); + const all = await allNotesFolders(); await localNotesFoldersSameAsRemote(all, expect); })); it('should update local items', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'un', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'un', parent_id: folder1.id }); await synchronizer().start(); await switchClient(2); @@ -160,14 +160,14 @@ describe('synchronizer', function() { await synchronizer().start(); - let all = await allNotesFolders(); + const all = await allNotesFolders(); await localNotesFoldersSameAsRemote(all, expect); })); it('should resolve note conflicts', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'un', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'un', parent_id: folder1.id }); await synchronizer().start(); await switchClient(2); @@ -186,29 +186,29 @@ describe('synchronizer', function() { await Note.save(note2conf); note2conf = await Note.load(note1.id); await synchronizer().start(); - let conflictedNotes = await Note.conflictedNotes(); + const conflictedNotes = await Note.conflictedNotes(); expect(conflictedNotes.length).toBe(1); // Other than the id (since the conflicted note is a duplicate), and the is_conflict property // the conflicted and original note must be the same in every way, to make sure no data has been lost. - let conflictedNote = conflictedNotes[0]; + const conflictedNote = conflictedNotes[0]; expect(conflictedNote.id == note2conf.id).toBe(false); - for (let n in conflictedNote) { + for (const n in conflictedNote) { if (!conflictedNote.hasOwnProperty(n)) continue; if (n == 'id' || n == 'is_conflict') continue; expect(conflictedNote[n]).toBe(note2conf[n], `Property: ${n}`); } - let noteUpdatedFromRemote = await Note.load(note1.id); - for (let n in noteUpdatedFromRemote) { + const noteUpdatedFromRemote = await Note.load(note1.id); + for (const n in noteUpdatedFromRemote) { if (!noteUpdatedFromRemote.hasOwnProperty(n)) continue; expect(noteUpdatedFromRemote[n]).toBe(note2[n], `Property: ${n}`); } })); it('should resolve folders conflicts', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'un', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'un', parent_id: folder1.id }); await synchronizer().start(); await switchClient(2); // ---------------------------------- @@ -235,13 +235,13 @@ describe('synchronizer', function() { await synchronizer().start(); - let folder1_final = await Folder.load(folder1.id); + const folder1_final = await Folder.load(folder1.id); expect(folder1_final.title).toBe(folder1_modRemote.title); })); it('should delete remote notes', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'un', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'un', parent_id: folder1.id }); await synchronizer().start(); await switchClient(2); @@ -258,13 +258,13 @@ describe('synchronizer', function() { expect(remotes.length).toBe(1); expect(remotes[0].id).toBe(folder1.id); - let deletedItems = await BaseItem.deletedItems(syncTargetId()); + const deletedItems = await BaseItem.deletedItems(syncTargetId()); expect(deletedItems.length).toBe(0); })); it('should not created deleted_items entries for items deleted via sync', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'un', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'un', parent_id: folder1.id }); await synchronizer().start(); await switchClient(2); @@ -276,7 +276,7 @@ describe('synchronizer', function() { await switchClient(1); await synchronizer().start(); - let deletedItems = await BaseItem.deletedItems(syncTargetId()); + const deletedItems = await BaseItem.deletedItems(syncTargetId()); expect(deletedItems.length).toBe(0); })); @@ -285,9 +285,9 @@ describe('synchronizer', function() { // property of the basicDelta() function is cleared properly at the end of a sync operation. If it is not cleared // it means items will no longer be deleted locally via sync. - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'un', parent_id: folder1.id }); - let note2 = await Note.save({ title: 'deux', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'un', parent_id: folder1.id }); + const note2 = await Note.save({ title: 'deux', parent_id: folder1.id }); let context1 = await synchronizer().start(); await switchClient(2); @@ -299,17 +299,17 @@ describe('synchronizer', function() { await switchClient(1); context1 = await synchronizer().start({ context: context1 }); - let items = await allNotesFolders(); + const items = await allNotesFolders(); expect(items.length).toBe(2); - let deletedItems = await BaseItem.deletedItems(syncTargetId()); + const deletedItems = await BaseItem.deletedItems(syncTargetId()); expect(deletedItems.length).toBe(0); await Note.delete(note2.id); context1 = await synchronizer().start({ context: context1 }); })); it('should delete remote folder', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let folder2 = await Folder.save({ title: 'folder2' }); + const folder1 = await Folder.save({ title: 'folder1' }); + const folder2 = await Folder.save({ title: 'folder2' }); await synchronizer().start(); await switchClient(2); @@ -322,30 +322,30 @@ describe('synchronizer', function() { await synchronizer().start(); - let all = await allNotesFolders(); + const all = await allNotesFolders(); await localNotesFoldersSameAsRemote(all, expect); })); it('should delete local folder', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let folder2 = await Folder.save({ title: 'folder2' }); - let context1 = await synchronizer().start(); + const folder1 = await Folder.save({ title: 'folder1' }); + const folder2 = await Folder.save({ title: 'folder2' }); + const context1 = await synchronizer().start(); await switchClient(2); - let context2 = await synchronizer().start(); + const context2 = await synchronizer().start(); await Folder.delete(folder2.id); await synchronizer().start({ context: context2 }); await switchClient(1); await synchronizer().start({ context: context1 }); - let items = await allNotesFolders(); + const items = await allNotesFolders(); await localNotesFoldersSameAsRemote(items, expect); })); it('should resolve conflict if remote folder has been deleted, but note has been added to folder locally', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); await synchronizer().start(); await switchClient(2); @@ -356,17 +356,17 @@ describe('synchronizer', function() { await switchClient(1); - let note = await Note.save({ title: 'note1', parent_id: folder1.id }); + const note = await Note.save({ title: 'note1', parent_id: folder1.id }); await synchronizer().start(); - let items = await allNotesFolders(); + const items = await allNotesFolders(); expect(items.length).toBe(1); expect(items[0].title).toBe('note1'); expect(items[0].is_conflict).toBe(1); })); it('should resolve conflict if note has been deleted remotely and locally', asyncTest(async () => { - let folder = await Folder.save({ title: 'folder' }); - let note = await Note.save({ title: 'note', parent_id: folder.title }); + const folder = await Folder.save({ title: 'folder' }); + const note = await Note.save({ title: 'note', parent_id: folder.title }); await synchronizer().start(); await switchClient(2); @@ -380,7 +380,7 @@ describe('synchronizer', function() { await Note.delete(note.id); await synchronizer().start(); - let items = await allNotesFolders(); + const items = await allNotesFolders(); expect(items.length).toBe(1); expect(items[0].title).toBe('folder'); @@ -391,8 +391,8 @@ describe('synchronizer', function() { // If client1 and 2 have two folders, client 1 deletes item 1 and client // 2 deletes item 2, they should both end up with no items after sync. - let folder1 = await Folder.save({ title: 'folder1' }); - let folder2 = await Folder.save({ title: 'folder2' }); + const folder1 = await Folder.save({ title: 'folder1' }); + const folder2 = await Folder.save({ title: 'folder2' }); await synchronizer().start(); await switchClient(2); @@ -413,21 +413,21 @@ describe('synchronizer', function() { await synchronizer().start(); - let items2 = await allNotesFolders(); + const items2 = await allNotesFolders(); await switchClient(1); await synchronizer().start(); - let items1 = await allNotesFolders(); + const items1 = await allNotesFolders(); expect(items1.length).toBe(0); expect(items1.length).toBe(items2.length); })); it('should handle conflict when remote note is deleted then local note is modified', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'un', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'un', parent_id: folder1.id }); await synchronizer().start(); await switchClient(2); @@ -442,25 +442,25 @@ describe('synchronizer', function() { await switchClient(1); - let newTitle = 'Modified after having been deleted'; + const newTitle = 'Modified after having been deleted'; await Note.save({ id: note1.id, title: newTitle }); await synchronizer().start(); - let conflictedNotes = await Note.conflictedNotes(); + const conflictedNotes = await Note.conflictedNotes(); expect(conflictedNotes.length).toBe(1); expect(conflictedNotes[0].title).toBe(newTitle); - let unconflictedNotes = await Note.unconflictedNotes(); + const unconflictedNotes = await Note.unconflictedNotes(); expect(unconflictedNotes.length).toBe(0); })); it('should handle conflict when remote folder is deleted then local folder is renamed', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let folder2 = await Folder.save({ title: 'folder2' }); - let note1 = await Note.save({ title: 'un', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const folder2 = await Folder.save({ title: 'folder2' }); + const note1 = await Note.save({ title: 'un', parent_id: folder1.id }); await synchronizer().start(); await switchClient(2); @@ -477,18 +477,18 @@ describe('synchronizer', function() { await sleep(0.1); - let newTitle = 'Modified after having been deleted'; + const newTitle = 'Modified after having been deleted'; await Folder.save({ id: folder1.id, title: newTitle }); await synchronizer().start(); - let items = await allNotesFolders(); + const items = await allNotesFolders(); expect(items.length).toBe(1); })); it('should allow duplicate folder titles', asyncTest(async () => { - let localF1 = await Folder.save({ title: 'folder' }); + const localF1 = await Folder.save({ title: 'folder' }); await switchClient(2); @@ -501,7 +501,7 @@ describe('synchronizer', function() { await synchronizer().start(); - let localF2 = await Folder.load(remoteF2.id); + const localF2 = await Folder.load(remoteF2.id); expect(localF2.title == remoteF2.title).toBe(true); @@ -528,10 +528,10 @@ describe('synchronizer', function() { masterKey = await loadEncryptionMasterKey(); } - let f1 = await Folder.save({ title: 'folder' }); - let n1 = await Note.save({ title: 'mynote' }); - let n2 = await Note.save({ title: 'mynote2' }); - let tag = await Tag.save({ title: 'mytag' }); + const f1 = await Folder.save({ title: 'folder' }); + const n1 = await Note.save({ title: 'mynote' }); + const n2 = await Note.save({ title: 'mynote2' }); + const tag = await Tag.save({ title: 'mytag' }); let context1 = await synchronizer().start(); await switchClient(2); @@ -540,10 +540,10 @@ describe('synchronizer', function() { if (withEncryption) { const masterKey_2 = await MasterKey.load(masterKey.id); await encryptionService().loadMasterKey_(masterKey_2, '123456', true); - let t = await Tag.load(tag.id); + const t = await Tag.load(tag.id); await Tag.decrypt(t); } - let remoteTag = await Tag.loadByTitle(tag.title); + const remoteTag = await Tag.loadByTitle(tag.title); expect(!!remoteTag).toBe(true); expect(remoteTag.id).toBe(tag.id); await Tag.addNote(remoteTag.id, n1.id); @@ -579,22 +579,22 @@ describe('synchronizer', function() { })); it('should not sync notes with conflicts', asyncTest(async () => { - let f1 = await Folder.save({ title: 'folder' }); - let n1 = await Note.save({ title: 'mynote', parent_id: f1.id, is_conflict: 1 }); + const f1 = await Folder.save({ title: 'folder' }); + const n1 = await Note.save({ title: 'mynote', parent_id: f1.id, is_conflict: 1 }); await synchronizer().start(); await switchClient(2); await synchronizer().start(); - let notes = await Note.all(); - let folders = await Folder.all(); + const notes = await Note.all(); + const folders = await Folder.all(); expect(notes.length).toBe(0); expect(folders.length).toBe(1); })); it('should not try to delete on remote conflicted notes that have been deleted', asyncTest(async () => { - let f1 = await Folder.save({ title: 'folder' }); - let n1 = await Note.save({ title: 'mynote', parent_id: f1.id }); + const f1 = await Folder.save({ title: 'folder' }); + const n1 = await Note.save({ title: 'mynote', parent_id: f1.id }); await synchronizer().start(); await switchClient(2); @@ -613,8 +613,8 @@ describe('synchronizer', function() { await loadEncryptionMasterKey(); } - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'un', is_todo: 1, parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'un', is_todo: 1, parent_id: folder1.id }); await synchronizer().start(); await switchClient(2); @@ -625,7 +625,7 @@ describe('synchronizer', function() { await decryptionWorker().start(); } let note2 = await Note.load(note1.id); - note2.todo_completed = time.unixMs()-1; + note2.todo_completed = time.unixMs() - 1; await Note.save(note2); note2 = await Note.load(note2.id); await synchronizer().start(); @@ -646,10 +646,10 @@ describe('synchronizer', function() { // but in practice it doesn't matter, we can just take the date when the // todo was marked as "done" the first time. - let conflictedNotes = await Note.conflictedNotes(); + const conflictedNotes = await Note.conflictedNotes(); expect(conflictedNotes.length).toBe(0); - let notes = await Note.all(); + const notes = await Note.all(); expect(notes.length).toBe(1); expect(notes[0].id).toBe(note1.id); expect(notes[0].todo_completed).toBe(note2.todo_completed); @@ -658,10 +658,10 @@ describe('synchronizer', function() { // smart conflict resolving since we don't know the content, so in that // case it's handled as a regular conflict. - let conflictedNotes = await Note.conflictedNotes(); + const conflictedNotes = await Note.conflictedNotes(); expect(conflictedNotes.length).toBe(1); - let notes = await Note.all(); + const notes = await Note.all(); expect(notes.length).toBe(2); } } @@ -675,14 +675,14 @@ describe('synchronizer', function() { })); it('items should be downloaded again when user cancels in the middle of delta operation', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'un', is_todo: 1, parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'un', is_todo: 1, parent_id: folder1.id }); await synchronizer().start(); await switchClient(2); synchronizer().testingHooks_ = ['cancelDeltaLoop2']; - let context = await synchronizer().start(); + const context = await synchronizer().start(); let notes = await Note.all(); expect(notes.length).toBe(0); @@ -693,8 +693,8 @@ describe('synchronizer', function() { })); it('should skip items that cannot be synced', asyncTest(async () => { - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'un', is_todo: 1, parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'un', is_todo: 1, parent_id: folder1.id }); const noteId = note1.id; await synchronizer().start(); let disabledItems = await BaseItem.syncDisabledItems(syncTargetId()); @@ -708,7 +708,7 @@ describe('synchronizer', function() { await switchClient(2); await synchronizer().start(); - let notes = await Note.all(); + const notes = await Note.all(); expect(notes.length).toBe(1); expect(notes[0].title).toBe('un'); @@ -721,7 +721,7 @@ describe('synchronizer', function() { it('notes and folders should get encrypted when encryption is enabled', asyncTest(async () => { Setting.setValue('encryption.enabled', true); const masterKey = await loadEncryptionMasterKey(); - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); let note1 = await Note.save({ title: 'un', body: 'to be encrypted', parent_id: folder1.id }); await synchronizer().start(); // After synchronisation, remote items should be encrypted but local ones remain plain text @@ -733,7 +733,7 @@ describe('synchronizer', function() { await synchronizer().start(); let folder1_2 = await Folder.load(folder1.id); let note1_2 = await Note.load(note1.id); - let masterKey_2 = await MasterKey.load(masterKey.id); + const masterKey_2 = await MasterKey.load(masterKey.id); // On this side however it should be received encrypted expect(!note1_2.title).toBe(true); expect(!folder1_2.title).toBe(true); @@ -820,7 +820,7 @@ describe('synchronizer', function() { it('should encrypt existing notes too when enabling E2EE', asyncTest(async () => { // First create a folder, without encryption enabled, and sync it - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); await synchronizer().start(); let files = await fileApi().list(); let content = await fileApi().get(files.items[0].path); @@ -848,18 +848,18 @@ describe('synchronizer', function() { it('should sync resources', asyncTest(async () => { while (insideBeforeEach) await time.msleep(500); - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); - let resource1 = (await Resource.all())[0]; - let resourcePath1 = Resource.fullPath(resource1); + const resource1 = (await Resource.all())[0]; + const resourcePath1 = Resource.fullPath(resource1); await synchronizer().start(); expect((await remoteNotesFoldersResources()).length).toBe(3); await switchClient(2); await synchronizer().start(); - let allResources = await Resource.all(); + const allResources = await Resource.all(); expect(allResources.length).toBe(1); let resource1_2 = allResources[0]; let ls = await Resource.localState(resource1_2); @@ -874,18 +874,18 @@ describe('synchronizer', function() { ls = await Resource.localState(resource1_2); expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_DONE); - let resourcePath1_2 = Resource.fullPath(resource1_2); + const resourcePath1_2 = Resource.fullPath(resource1_2); expect(fileContentEqual(resourcePath1, resourcePath1_2)).toBe(true); })); it('should handle resource download errors', asyncTest(async () => { while (insideBeforeEach) await time.msleep(500); - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); let resource1 = (await Resource.all())[0]; - let resourcePath1 = Resource.fullPath(resource1); + const resourcePath1 = Resource.fullPath(resource1); await synchronizer().start(); await switchClient(2); @@ -902,7 +902,7 @@ describe('synchronizer', function() { await fetcher.waitForAllFinished(); resource1 = await Resource.load(resource1.id); - let ls = await Resource.localState(resource1); + const ls = await Resource.localState(resource1); expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_ERROR); expect(ls.fetch_error).toBe('did not work'); })); @@ -910,8 +910,8 @@ describe('synchronizer', function() { it('should set the resource file size if it is missing', asyncTest(async () => { while (insideBeforeEach) await time.msleep(500); - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); await synchronizer().start(); @@ -933,11 +933,11 @@ describe('synchronizer', function() { it('should delete resources', asyncTest(async () => { while (insideBeforeEach) await time.msleep(500); - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); - let resource1 = (await Resource.all())[0]; - let resourcePath1 = Resource.fullPath(resource1); + const resource1 = (await Resource.all())[0]; + const resourcePath1 = Resource.fullPath(resource1); await synchronizer().start(); await switchClient(2); @@ -945,7 +945,7 @@ describe('synchronizer', function() { await synchronizer().start(); let allResources = await Resource.all(); expect(allResources.length).toBe(1); - let all = await fileApi().list(); + const all = await fileApi().list(); expect((await remoteNotesFoldersResources()).length).toBe(3); await Resource.delete(resource1.id); await synchronizer().start(); @@ -967,11 +967,11 @@ describe('synchronizer', function() { Setting.setValue('encryption.enabled', true); const masterKey = await loadEncryptionMasterKey(); - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); - let resource1 = (await Resource.all())[0]; - let resourcePath1 = Resource.fullPath(resource1); + const resource1 = (await Resource.all())[0]; + const resourcePath1 = Resource.fullPath(resource1); await synchronizer().start(); await switchClient(2); @@ -986,7 +986,7 @@ describe('synchronizer', function() { let resource1_2 = (await Resource.all())[0]; resource1_2 = await Resource.decrypt(resource1_2); - let resourcePath1_2 = Resource.fullPath(resource1_2); + const resourcePath1_2 = Resource.fullPath(resource1_2); expect(fileContentEqual(resourcePath1, resourcePath1_2)).toBe(true); })); @@ -995,7 +995,7 @@ describe('synchronizer', function() { Setting.setValue('encryption.enabled', true); const masterKey = await loadEncryptionMasterKey(); - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); await synchronizer().start(); let allEncrypted = await allSyncTargetItemsEncrypted(); @@ -1016,7 +1016,7 @@ describe('synchronizer', function() { Setting.setValue('encryption.enabled', true); const masterKey = await loadEncryptionMasterKey(); - let folder1 = await Folder.save({ title: 'folder1' }); + const folder1 = await Folder.save({ title: 'folder1' }); await synchronizer().start(); await switchClient(2); @@ -1049,12 +1049,12 @@ describe('synchronizer', function() { Setting.setValue('encryption.enabled', true); const masterKey = await loadEncryptionMasterKey(); - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); - let resource1 = (await Resource.all())[0]; + const resource1 = (await Resource.all())[0]; await Resource.setFileSizeOnly(resource1.id, -1); - let resourcePath1 = Resource.fullPath(resource1); + const resourcePath1 = Resource.fullPath(resource1); await synchronizer().start(); await switchClient(2); @@ -1075,8 +1075,8 @@ describe('synchronizer', function() { it('should encrypt remote resources after encryption has been enabled', asyncTest(async () => { while (insideBeforeEach) await time.msleep(100); - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); await synchronizer().start(); @@ -1094,22 +1094,22 @@ describe('synchronizer', function() { it('should upload encrypted resource, but it should not mark the blob as encrypted locally', asyncTest(async () => { while (insideBeforeEach) await time.msleep(100); - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`); const masterKey = await loadEncryptionMasterKey(); await encryptionService().enableEncryption(masterKey, '123456'); await encryptionService().loadMasterKeysFromSettings(); await synchronizer().start(); - let resource1 = (await Resource.all())[0]; + const resource1 = (await Resource.all())[0]; expect(resource1.encryption_blob_encrypted).toBe(0); })); it('should create remote items with UTF-8 content', asyncTest(async () => { - let folder = await Folder.save({ title: 'Fahrräder' }); + const folder = await Folder.save({ title: 'Fahrräder' }); await Note.save({ title: 'Fahrräder', body: 'Fahrräder', parent_id: folder.id }); - let all = await allNotesFolders(); + const all = await allNotesFolders(); await synchronizer().start(); @@ -1117,8 +1117,8 @@ describe('synchronizer', function() { })); it('should update remote items but not pull remote changes', asyncTest(async () => { - let folder = await Folder.save({ title: 'folder1' }); - let note = await Note.save({ title: 'un', parent_id: folder.id }); + const folder = await Folder.save({ title: 'folder1' }); + const note = await Note.save({ title: 'un', parent_id: folder.id }); await synchronizer().start(); await switchClient(2); @@ -1131,13 +1131,13 @@ describe('synchronizer', function() { await Note.save({ title: 'un UPDATE', id: note.id }); await synchronizer().start({ syncSteps: ['update_remote'] }); - let all = await allNotesFolders(); + const all = await allNotesFolders(); expect(all.length).toBe(2); await switchClient(2); await synchronizer().start(); - let note2 = await Note.load(note.id); + const note2 = await Note.load(note.id); expect(note2.title).toBe('un UPDATE'); })); @@ -1544,8 +1544,8 @@ describe('synchronizer', function() { Setting.setValue('encryption.enabled', true); await loadEncryptionMasterKey(); - let folder1 = await Folder.save({ title: 'folder1' }); - let note1 = await Note.save({ title: 'un', parent_id: folder1.id }); + const folder1 = await Folder.save({ title: 'folder1' }); + const note1 = await Note.save({ title: 'un', parent_id: folder1.id }); let note2 = await Note.save({ title: 'deux', parent_id: folder1.id }); await synchronizer().start(); @@ -1570,12 +1570,12 @@ describe('synchronizer', function() { await synchronizer().start(); // The shared note should be decrypted - let note2_2 = await Note.load(note2.id); + const note2_2 = await Note.load(note2.id); expect(note2_2.title).toBe('deux'); expect(note2_2.is_shared).toBe(1); // The non-shared note should be encrypted - let note1_2 = await Note.load(note1.id); + const note1_2 = await Note.load(note1.id); expect(note1_2.title).toBe(''); })); diff --git a/CliClient/tests/test-utils.js b/CliClient/tests/test-utils.js index 809a802c6e..5b70f86c44 100644 --- a/CliClient/tests/test-utils.js +++ b/CliClient/tests/test-utils.js @@ -3,7 +3,7 @@ const fs = require('fs-extra'); const { JoplinDatabase } = require('lib/joplin-database.js'); const { DatabaseDriverNode } = require('lib/database-driver-node.js'); -const { BaseApplication }= require('lib/BaseApplication.js'); +const { BaseApplication } = require('lib/BaseApplication.js'); const BaseModel = require('lib/BaseModel.js'); const Folder = require('lib/models/Folder.js'); const Note = require('lib/models/Note.js'); @@ -41,13 +41,13 @@ const KvStore = require('lib/services/KvStore.js'); const WebDavApi = require('lib/WebDavApi'); const DropboxApi = require('lib/DropboxApi'); -let databases_ = []; -let synchronizers_ = []; -let encryptionServices_ = []; -let revisionServices_ = []; -let decryptionWorkers_ = []; -let resourceServices_ = []; -let kvStores_ = []; +const databases_ = []; +const synchronizers_ = []; +const encryptionServices_ = []; +const revisionServices_ = []; +const decryptionWorkers_ = []; +const resourceServices_ = []; +const kvStores_ = []; let fileApi_ = null; let currentClient_ = 1; @@ -341,7 +341,7 @@ function fileApi() { function objectsEqual(o1, o2) { if (Object.getOwnPropertyNames(o1).length !== Object.getOwnPropertyNames(o2).length) return false; - for (let n in o1) { + for (const n in o1) { if (!o1.hasOwnProperty(n)) continue; if (o1[n] !== o2[n]) return false; } @@ -427,7 +427,7 @@ function sortedIds(a) { } function at(a, indexes) { - let out = []; + const out = []; for (let i = 0; i < indexes.length; i++) { out.push(a[indexes[i]]); } @@ -435,19 +435,19 @@ function at(a, indexes) { } async function createNTestFolders(n) { - let folders = []; + const folders = []; for (let i = 0; i < n; i++) { - let folder = await Folder.save({ title: 'folder' }); + const folder = await Folder.save({ title: 'folder' }); folders.push(folder); } return folders; } async function createNTestNotes(n, folder, tagIds = null, title = 'note') { - let notes = []; + const notes = []; for (let i = 0; i < n; i++) { - let title_ = n > 1 ? `${title}${i}` : title; - let note = await Note.save({ title: title_, parent_id: folder.id, is_conflict: 0 }); + const title_ = n > 1 ? `${title}${i}` : title; + const note = await Note.save({ title: title_, parent_id: folder.id, is_conflict: 0 }); notes.push(note); } if (tagIds) { @@ -459,9 +459,9 @@ async function createNTestNotes(n, folder, tagIds = null, title = 'note') { } async function createNTestTags(n) { - let tags = []; + const tags = []; for (let i = 0; i < n; i++) { - let tag = await Tag.save({ title: 'tag' }); + const tag = await Tag.save({ title: 'tag' }); tags.push(tag); } return tags; diff --git a/Clipper/content_scripts/index.js b/Clipper/content_scripts/index.js index 7852ca66d8..9a4f0f93f5 100644 --- a/Clipper/content_scripts/index.js +++ b/Clipper/content_scripts/index.js @@ -290,8 +290,8 @@ // option to clip pages as HTML. function getStyleSheets(doc) { const output = []; - for (var i=0; i { + if (fs.existsSync(dotenvFile)) { + require('dotenv-expand')( + require('dotenv').config({ + path: dotenvFile, + }) + ); + } +}); + +// We support resolving modules according to `NODE_PATH`. +// This lets you use absolute paths in imports inside large monorepos: +// https://github.com/facebook/create-react-app/issues/253. +// It works similar to `NODE_PATH` in Node itself: +// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders +// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. +// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. +// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 +// We also resolve them to make sure all tools using them work consistently. +const appDirectory = fs.realpathSync(process.cwd()); +process.env.NODE_PATH = (process.env.NODE_PATH || '') + .split(path.delimiter) + .filter(folder => folder && !path.isAbsolute(folder)) + .map(folder => path.resolve(appDirectory, folder)) + .join(path.delimiter); + +// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be +// injected into the application via DefinePlugin in Webpack configuration. +const REACT_APP = /^REACT_APP_/i; + +function getClientEnvironment(publicUrl) { + const raw = Object.keys(process.env) + .filter(key => REACT_APP.test(key)) + .reduce( + (env, key) => { + env[key] = process.env[key]; + return env; + }, + { + // Useful for determining whether we’re running in production mode. + // Most importantly, it switches React into the correct mode. + NODE_ENV: process.env.NODE_ENV || 'development', + // Useful for resolving the correct path to static assets in `public`. + // For example, . + // This should only be used as an escape hatch. Normally you would put + // images into the `src` and `import` them in code to get their paths. + PUBLIC_URL: publicUrl, + } + ); + // Stringify all values so we can feed into Webpack DefinePlugin + const stringified = { + 'process.env': Object.keys(raw).reduce((env, key) => { + env[key] = JSON.stringify(raw[key]); + return env; + }, {}), + }; + + return { raw, stringified }; +} + +module.exports = getClientEnvironment; diff --git a/Clipper/popup/config/jest/cssTransform.js b/Clipper/popup/config/jest/cssTransform.js new file mode 100644 index 0000000000..9459da3e73 --- /dev/null +++ b/Clipper/popup/config/jest/cssTransform.js @@ -0,0 +1,14 @@ +'use strict'; + +// This is a custom Jest transformer turning style imports into empty objects. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process() { + return 'module.exports = {};'; + }, + getCacheKey() { + // The output is always the same. + return 'cssTransform'; + }, +}; diff --git a/Clipper/popup/config/jest/fileTransform.js b/Clipper/popup/config/jest/fileTransform.js new file mode 100644 index 0000000000..9884a1891b --- /dev/null +++ b/Clipper/popup/config/jest/fileTransform.js @@ -0,0 +1,40 @@ +'use strict'; + +const path = require('path'); +const camelcase = require('camelcase'); + +// This is a custom Jest transformer turning file imports into filenames. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process(src, filename) { + const assetFilename = JSON.stringify(path.basename(filename)); + + if (filename.match(/\.svg$/)) { + // Based on how SVGR generates a component name: + // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 + const pascalCaseFilename = camelcase(path.parse(filename).name, { + pascalCase: true, + }); + const componentName = `Svg${pascalCaseFilename}`; + return `const React = require('react'); + module.exports = { + __esModule: true, + default: ${assetFilename}, + ReactComponent: React.forwardRef(function ${componentName}(props, ref) { + return { + $$typeof: Symbol.for('react.element'), + type: 'svg', + ref: ref, + key: null, + props: Object.assign({}, props, { + children: ${assetFilename} + }) + }; + }), + };`; + } + + return `module.exports = ${assetFilename};`; + }, +}; diff --git a/Clipper/popup/config/modules.js b/Clipper/popup/config/modules.js new file mode 100644 index 0000000000..1c5dec3199 --- /dev/null +++ b/Clipper/popup/config/modules.js @@ -0,0 +1,141 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const paths = require('./paths'); +const chalk = require('react-dev-utils/chalk'); +const resolve = require('resolve'); + +/** + * Get additional module paths based on the baseUrl of a compilerOptions object. + * + * @param {Object} options + */ +function getAdditionalModulePaths(options = {}) { + const baseUrl = options.baseUrl; + + // We need to explicitly check for null and undefined (and not a falsy value) because + // TypeScript treats an empty string as `.`. + if (baseUrl == null) { + // If there's no baseUrl set we respect NODE_PATH + // Note that NODE_PATH is deprecated and will be removed + // in the next major release of create-react-app. + + const nodePath = process.env.NODE_PATH || ''; + return nodePath.split(path.delimiter).filter(Boolean); + } + + const baseUrlResolved = path.resolve(paths.appPath, baseUrl); + + // We don't need to do anything if `baseUrl` is set to `node_modules`. This is + // the default behavior. + if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { + return null; + } + + // Allow the user set the `baseUrl` to `appSrc`. + if (path.relative(paths.appSrc, baseUrlResolved) === '') { + return [paths.appSrc]; + } + + // If the path is equal to the root directory we ignore it here. + // We don't want to allow importing from the root directly as source files are + // not transpiled outside of `src`. We do allow importing them with the + // absolute path (e.g. `src/Components/Button.js`) but we set that up with + // an alias. + if (path.relative(paths.appPath, baseUrlResolved) === '') { + return null; + } + + // Otherwise, throw an error. + throw new Error( + chalk.red.bold( + 'Your project\'s `baseUrl` can only be set to `src` or `node_modules`.' + + ' Create React App does not support other values at this time.' + ) + ); +} + +/** + * Get webpack aliases based on the baseUrl of a compilerOptions object. + * + * @param {*} options + */ +function getWebpackAliases(options = {}) { + const baseUrl = options.baseUrl; + + if (!baseUrl) { + return {}; + } + + const baseUrlResolved = path.resolve(paths.appPath, baseUrl); + + if (path.relative(paths.appPath, baseUrlResolved) === '') { + return { + src: paths.appSrc, + }; + } +} + +/** + * Get jest aliases based on the baseUrl of a compilerOptions object. + * + * @param {*} options + */ +function getJestAliases(options = {}) { + const baseUrl = options.baseUrl; + + if (!baseUrl) { + return {}; + } + + const baseUrlResolved = path.resolve(paths.appPath, baseUrl); + + if (path.relative(paths.appPath, baseUrlResolved) === '') { + return { + '^src/(.*)$': '/src/$1', + }; + } +} + +function getModules() { + // Check if TypeScript is setup + const hasTsConfig = fs.existsSync(paths.appTsConfig); + const hasJsConfig = fs.existsSync(paths.appJsConfig); + + if (hasTsConfig && hasJsConfig) { + throw new Error( + 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' + ); + } + + let config; + + // If there's a tsconfig.json we assume it's a + // TypeScript project and set up the config + // based on tsconfig.json + if (hasTsConfig) { + const ts = require(resolve.sync('typescript', { + basedir: paths.appNodeModules, + })); + config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config; + // Otherwise we'll check if there is jsconfig.json + // for non TS projects. + } else if (hasJsConfig) { + config = require(paths.appJsConfig); + } + + config = config || {}; + const options = config.compilerOptions || {}; + + const additionalModulePaths = getAdditionalModulePaths(options); + + return { + additionalModulePaths: additionalModulePaths, + webpackAliases: getWebpackAliases(options), + jestAliases: getJestAliases(options), + hasTsConfig, + }; +} + +module.exports = getModules(); diff --git a/Clipper/popup/config/paths.js b/Clipper/popup/config/paths.js new file mode 100644 index 0000000000..808d271101 --- /dev/null +++ b/Clipper/popup/config/paths.js @@ -0,0 +1,90 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +// Make sure any symlinks in the project folder are resolved: +// https://github.com/facebook/create-react-app/issues/637 +const appDirectory = fs.realpathSync(process.cwd()); +const resolveApp = relativePath => path.resolve(appDirectory, relativePath); + +const envPublicUrl = process.env.PUBLIC_URL; + +function ensureSlash(inputPath, needsSlash) { + const hasSlash = inputPath.endsWith('/'); + if (hasSlash && !needsSlash) { + return inputPath.substr(0, inputPath.length - 1); + } else if (!hasSlash && needsSlash) { + return `${inputPath}/`; + } else { + return inputPath; + } +} + +const getPublicUrl = appPackageJson => + envPublicUrl || require(appPackageJson).homepage; + +// We use `PUBLIC_URL` environment variable or "homepage" field to infer +// "public path" at which the app is served. +// Webpack needs to know it to put the right