From e4aac3b2ee95d75b135a708717ad54100f63c20b Mon Sep 17 00:00:00 2001 From: Anthony Tseng Date: Mon, 10 Jul 2017 19:50:24 -0700 Subject: [PATCH 1/3] Using Chromium's spellchecker fixes #9880 Auditors: @bridiver, @bbondy, @bsclifton Test plan: a. Suggestions 1. Go to `about:styles` 2. Type `braave` in textbox 3. There should be redline under `braave` 4. Open context menu on `braave` 5. You can see `bravo`, `brave`, `Brava` 6. Click on `brave` 7. The text should be replaced with `brave` with no redline b. Learn Spelling 1. Go to `about:styles` 2. Type `braave` in textbox 3. There should be redline under `braave` 4. Open context menu on `braave` 5. Click `Learn Spelling` 6. `braave` will no longer be redlined 7. Next time you type `braave`, you will not see redline c. Forget Learned Spelling 1. Go to `about:styles` 2. Type `braave` in textbox 3. There should be redline under `braave` 4. Open context menu on `braave` 5. Click `Learn Spelling` 6. `braave` will no longer be redlined 7. Open context menu on `braave` 8. Click `Forget Learned Spelling` 9. Next time you type `braave`, you will see redline under it d. Legacy data migration 1. Use older Brave to `Learn Spelling` for `braave` and `Ignore Spelling` for `braaave` 2. Update to the version with chromium spellchecker 3. There will be no `dictionary` structure in session store 4. You can see those two words will be added to `Custom Dictionary.txt` under user folder 5. And try to type `braave` and `braaave` in `about:styles` 6. They should be valid words --- app/browser/reducers/spellCheckReducer.js | 21 ----- app/browser/reducers/spellCheckerReducer.js | 66 ++++++++++++++ app/extensions.js | 1 - .../brave/content/scripts/spellCheck.js | 22 ----- .../brave/locales/en-US/menu.properties | 2 +- app/index.js | 2 - app/locale.js | 2 +- app/sessionStore.js | 4 - app/spellCheck.js | 90 ------------------- app/spellChecker.js | 14 +++ docs/appActions.md | 22 ----- docs/state.md | 5 -- js/actions/appActions.js | 48 +++++----- js/actions/webviewActions.js | 11 --- js/constants/appConstants.js | 7 +- js/constants/messages.js | 2 - js/contextMenus.js | 32 ++++--- js/stores/appStore.js | 16 +--- package.json | 1 - 19 files changed, 129 insertions(+), 239 deletions(-) delete mode 100644 app/browser/reducers/spellCheckReducer.js create mode 100644 app/browser/reducers/spellCheckerReducer.js delete mode 100644 app/extensions/brave/content/scripts/spellCheck.js delete mode 100644 app/spellCheck.js create mode 100644 app/spellChecker.js diff --git a/app/browser/reducers/spellCheckReducer.js b/app/browser/reducers/spellCheckReducer.js deleted file mode 100644 index 8fcfd04ebc5..00000000000 --- a/app/browser/reducers/spellCheckReducer.js +++ /dev/null @@ -1,21 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'use strict' - -const appConstants = require('../../../js/constants/appConstants') -const tabs = require('../tabs') -const {makeImmutable} = require('../../common/state/immutableUtil') - -const spellCheckReducer = (state, action, immutableAction) => { - action = immutableAction || makeImmutable(action) - switch (action.get('actionType')) { - case appConstants.APP_ADD_WORD: - tabs.sendToAll('add-word', action.get('word')) - break - } - return state -} - -module.exports = spellCheckReducer diff --git a/app/browser/reducers/spellCheckerReducer.js b/app/browser/reducers/spellCheckerReducer.js new file mode 100644 index 00000000000..e811500e3f2 --- /dev/null +++ b/app/browser/reducers/spellCheckerReducer.js @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict' + +const appConstants = require('../../../js/constants/appConstants') +const {makeImmutable} = require('../../common/state/immutableUtil') +const {getWebContents} = require('../webContentsCache') +const spellChecker = require('../../spellChecker') + +const migrate = (state) => { + if (state.get('dictionary')) { + const addedWords = state.getIn(['dictionary', 'addedWords']) + const ignoredWords = state.getIn(['dictionary', 'ignoredWords']) + if (addedWords.size) { + addedWords.forEach((word) => { + spellChecker.addWord(word) + }) + } + if (ignoredWords.size) { + ignoredWords.forEach((word) => { + spellChecker.addWord(word) + }) + } + state = state.delete('dictionary') + } + return state +} + +const spellCheckerReducer = (state, action, immutableAction) => { + action = immutableAction || makeImmutable(action) + switch (action.get('actionType')) { + case appConstants.APP_SET_STATE: + state = migrate(state) + break + case appConstants.APP_SPELLING_SUGGESTED: + if (typeof action.get('suggestion') === 'string') { + const webContents = getWebContents(action.get('tabId')) + if (webContents && !webContents.isDestroyed()) { + webContents.replaceMisspelling(action.get('suggestion')) + } + } + break + case appConstants.APP_LEARN_SPELLING: + if (typeof action.get('word') === 'string') { + spellChecker.addWord(action.get('word')) + const webContents = getWebContents(action.get('tabId')) + if (webContents && !webContents.isDestroyed()) { + webContents.replaceMisspelling(action.get('word')) + } + } + break + case appConstants.APP_FORGET_LEARNED_SPELLING: + if (typeof action.get('word') === 'string') { + spellChecker.removeWord(action.get('word')) + const webContents = getWebContents(action.get('tabId')) + if (webContents && !webContents.isDestroyed()) { + webContents.replace(action.get('word')) + } + } + } + return state +} + +module.exports = spellCheckerReducer diff --git a/app/extensions.js b/app/extensions.js index 8b96ec25569..ec6199cb71f 100644 --- a/app/extensions.js +++ b/app/extensions.js @@ -113,7 +113,6 @@ let generateBraveManifest = () => { getBraveExtUrl('about-blank.html') + '#*' ], js: [ - 'content/scripts/spellCheck.js', 'content/scripts/themeColor.js' ] }, diff --git a/app/extensions/brave/content/scripts/spellCheck.js b/app/extensions/brave/content/scripts/spellCheck.js deleted file mode 100644 index dd25116d3c8..00000000000 --- a/app/extensions/brave/content/scripts/spellCheck.js +++ /dev/null @@ -1,22 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -let cache = {} - -chrome.ipcRenderer.on('add-word', (e, word) => { - cache[word] = true -}) - -let lang = navigator.language.split('-')[0].split('_')[0] -chrome.webFrame.setSpellCheckProvider(lang || '', true, { - spellCheck: (word) => { - if (typeof cache[word] !== 'boolean') { - const result = !chrome.ipcRenderer.sendSync('is-misspelled', word) - cache[word] = result - return result - } else { - return cache[word] - } - } -}) diff --git a/app/extensions/brave/locales/en-US/menu.properties b/app/extensions/brave/locales/en-US/menu.properties index 84a42c1d0b5..2bb55a468ed 100644 --- a/app/extensions/brave/locales/en-US/menu.properties +++ b/app/extensions/brave/locales/en-US/menu.properties @@ -155,7 +155,7 @@ extensionsManager=Extensions… zoom=Zoom new=New learnSpelling=Learn Spelling -ignoreSpelling=Ignore Spelling +forgetLearnedSpelling=Forget Learned Spelling autoHideMenuBar=Menu Bar updateChannel=Update Channel licenseText=This software uses libraries from the FFmpeg project under the LGPLv2.1 diff --git a/app/index.js b/app/index.js index 5c71816d3a0..2ee37e47f22 100644 --- a/app/index.js +++ b/app/index.js @@ -67,7 +67,6 @@ const PDFJS = require('./pdfJS') const SiteHacks = require('./siteHacks') const CmdLine = require('./cmdLine') const urlParse = require('./common/urlParse') -const spellCheck = require('./spellCheck') const locale = require('./locale') const contentSettings = require('../js/state/contentSettings') const privacy = require('../js/state/privacy') @@ -181,7 +180,6 @@ app.on('ready', () => { Autofill.init() Extensions.init() SiteHacks.init() - spellCheck.init() HttpsEverywhere.init() TrackingProtection.init() AdBlock.init() diff --git a/app/locale.js b/app/locale.js index dbf54f9b8a2..0508a3b980b 100644 --- a/app/locale.js +++ b/app/locale.js @@ -178,7 +178,7 @@ var rendererIdentifiers = function () { 'braveryStartUsingPayments', 'blockPopups', 'learnSpelling', - 'ignoreSpelling', + 'forgetLearnedSpelling', 'lookupSelection', // Other identifiers 'aboutBlankTitle', diff --git a/app/sessionStore.js b/app/sessionStore.js index c66b0b1687c..c735a6ce572 100644 --- a/app/sessionStore.js +++ b/app/sessionStore.js @@ -636,10 +636,6 @@ module.exports.defaultAppState = () => { passwords: [], notifications: [], temporarySiteSettings: {}, - dictionary: { - addedWords: [], - ignoredWords: [] - }, autofill: { addresses: { guid: [], diff --git a/app/spellCheck.js b/app/spellCheck.js deleted file mode 100644 index 5919e5ec787..00000000000 --- a/app/spellCheck.js +++ /dev/null @@ -1,90 +0,0 @@ -// @flow -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'use strict' - -const spellchecker = require('spellchecker') -const messages = require('../js/constants/messages') -const electron = require('electron') -const ipcMain = electron.ipcMain -const app = electron.app -const appStore = require('../js/stores/appStore') -const appActions = require('../js/actions/appActions') -const contractionSet = new Set() -const getSetting = require('../js/settings').getSetting -const settings = require('../js/constants/settings') - -let dictionaryLocale - -// Stores a reference to the last added immutable words -let lastAddedWords - -const isMisspelled = (word) => - !appStore.getState().getIn(['dictionary', 'ignoredWords']).includes(word) && - !appStore.getState().getIn(['dictionary', 'addedWords']).includes(word) && - spellchecker.isMisspelled(word) - -module.exports.init = () => { - ipcMain.on(messages.IS_MISSPELLED, (e, word) => { - let misspelled = isMisspelled(word) - // If the word is misspelled and it's English, then make sure it's nt a contraction. - if (misspelled && (!dictionaryLocale || dictionaryLocale.includes('en'))) { - misspelled = !contractionSet.has(word) - } - e.returnValue = misspelled - }) - ipcMain.on(messages.GET_MISSPELLING_INFO, (e, word) => { - const misspelled = isMisspelled(word) - e.returnValue = { - isMisspelled: misspelled, - suggestions: !misspelled ? [] : spellchecker.getCorrectionsForMisspelling(word) - } - }) - - appStore.addChangeListener(() => { - let addedWords = appStore.getState().getIn(['dictionary', 'addedWords']) - if (lastAddedWords !== addedWords) { - if (lastAddedWords) { - addedWords = addedWords.splice(lastAddedWords.size) - } - addedWords.forEach(spellchecker.add.bind(spellchecker)) - lastAddedWords = appStore.getState().getIn(['dictionary', 'addedWords']) - } - }) - - // Thank you Slack team. - // NB: This is to work around tinyspeck/slack-winssb#267, where contractions - // are incorrectly marked as spelling errors. This lets people get away with - // incorrectly spelled contracted words, but it's the best we can do for now. - const contractions = [ - "ain't", "aren't", "can't", "could've", "couldn't", "couldn't've", "didn't", "doesn't", "don't", "hadn't", - "hadn't've", "hasn't", "haven't", "he'd", "he'd've", "he'll", "he's", "how'd", "how'll", "how's", "I'd", - "I'd've", "I'll", "I'm", "I've", "isn't", "it'd", "it'd've", "it'll", "it's", "let's", "ma'am", "mightn't", - "mightn't've", "might've", "mustn't", "must've", "needn't", "not've", "o'clock", "shan't", "she'd", "she'd've", - "she'll", "she's", "should've", "shouldn't", "shouldn't've", "that'll", "that's", "there'd", "there'd've", - "there're", "there's", "they'd", "they'd've", "they'll", "they're", "they've", "wasn't", "we'd", "we'd've", - "we'll", "we're", "we've", "weren't", "what'll", "what're", "what's", "what've", "when's", "where'd", - "where's", "where've", "who'd", "who'll", "who're", "who's", "who've", "why'll", "why're", "why's", "won't", - "would've", "wouldn't", "wouldn't've", "y'all", "y'all'd've", "you'd", "you'd've", "you'll", "you're", "you've" - ] - contractions.forEach((word) => contractionSet.add(word.replace(/'.*/, ''))) - - const availableDictionaries = spellchecker.getAvailableDictionaries() - let dict = (getSetting(settings.LANGUAGE) || app.getLocale()).replace('-', '_') - if (availableDictionaries.includes(dict)) { - dictionaryLocale = dict - spellchecker.setDictionary(dict) - } else { - dict = dict.split('_')[0] - if (availableDictionaries.includes(dict)) { - dictionaryLocale = dict - spellchecker.setDictionary(dict) - } - } - - if (dictionaryLocale) { - appActions.setDictionary(dictionaryLocale) - } -} diff --git a/app/spellChecker.js b/app/spellChecker.js new file mode 100644 index 00000000000..b53cd3fe504 --- /dev/null +++ b/app/spellChecker.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const electron = require('electron') +const session = electron.session + +module.exports.addWord = (word) => { + session.defaultSession.spellChecker.addWord(word) +} + +module.exports.removeWord = (word) => { + session.defaultSession.spellChecker.removeWord(word) +} diff --git a/docs/appActions.md b/docs/appActions.md index 937f1e32ec3..92b5ef49830 100644 --- a/docs/appActions.md +++ b/docs/appActions.md @@ -450,28 +450,6 @@ Hides a message in the notification bar -### addWord(word, learn) - -Adds a word to the dictionary - -**Parameters** - -**word**: `string`, The word to add - -**learn**: `boolean`, true if the word should be learned, false if ignored - - - -### setDictionary(locale) - -Adds a word to the dictionary - -**Parameters** - -**locale**: `string`, The locale to set for the dictionary - - - ### setLoginRequiredDetail(tabId, detail) Adds information about pending basic auth login requests diff --git a/docs/state.md b/docs/state.md index 0e457260a43..f095bbefdeb 100644 --- a/docs/state.md +++ b/docs/state.md @@ -79,11 +79,6 @@ AppStore y: number }, defaultWindowWidth: number, // DEPRECATED (0.12.7); replaced w/ defaultWindowParams.width - dictionary: { - addedWords: Array, // list of words to add to the dictionary - ignoredWords: Array, // list of words to ignore - locale: string // en_US, en, or any other locale string - }, downloads: [{ [downloadId]: { filename: string, diff --git a/js/actions/appActions.js b/js/actions/appActions.js index bb79c6a1725..66a7f64c879 100644 --- a/js/actions/appActions.js +++ b/js/actions/appActions.js @@ -548,30 +548,6 @@ const appActions = { }) }, - /** - * Adds a word to the dictionary - * @param {string} word - The word to add - * @param {boolean} learn - true if the word should be learned, false if ignored - */ - addWord: function (word, learn) { - dispatch({ - actionType: appConstants.APP_ADD_WORD, - word, - learn - }) - }, - - /** - * Adds a word to the dictionary - * @param {string} locale - The locale to set for the dictionary - */ - setDictionary: function (locale) { - dispatch({ - actionType: appConstants.APP_SET_DICTIONARY, - locale - }) - }, - /** * Adds information about pending basic auth login requests * @param {number} tabId - The tabId that generated the request @@ -1515,6 +1491,30 @@ const appActions = { windowId } }) + }, + + spellingSuggested: function (suggestion, tabId) { + dispatch({ + actionType: appConstants.APP_SPELLING_SUGGESTED, + suggestion, + tabId + }) + }, + + learnSpelling: function (word, tabId) { + dispatch({ + actionType: appConstants.APP_LEARN_SPELLING, + word, + tabId + }) + }, + + forgetLearnedSpelling: function (word, tabId) { + dispatch({ + actionType: appConstants.APP_FORGET_LEARNED_SPELLING, + word, + tabId + }) } } diff --git a/js/actions/webviewActions.js b/js/actions/webviewActions.js index 5f5a554158e..5b98fa70573 100644 --- a/js/actions/webviewActions.js +++ b/js/actions/webviewActions.js @@ -32,17 +32,6 @@ const webviewActions = { } }, - /** - * Replaces the selected text in an editable - * @param {string} text - The text to replace with - */ - replace: function (text) { - const webview = getWebview() - if (webview) { - webview.replaceMisspelling(text) - } - }, - /** * Shows the certificate infomation */ diff --git a/js/constants/appConstants.js b/js/constants/appConstants.js index f41334fdf1e..3a2e0dea424 100644 --- a/js/constants/appConstants.js +++ b/js/constants/appConstants.js @@ -41,8 +41,6 @@ const appConstants = { APP_UPDATE_PUBLISHER_INFO: _, APP_SHOW_NOTIFICATION: _, /** @param {Object} detail */ APP_HIDE_NOTIFICATION: _, /** @param {string} message */ - APP_ADD_WORD: _, /** @param {string} word, @param {boolean} learn */ - APP_SET_DICTIONARY: _, /** @param {string} locale */ APP_BACKUP_KEYS: _, APP_RECOVER_WALLET: _, APP_ADD_AUTOFILL_ADDRESS: _, @@ -142,7 +140,10 @@ const appConstants = { APP_SWIPE_RIGHT: _, APP_ADD_BOOKMARK: _, APP_EDIT_BOOKMARK: _, - APP_DEBUG_NO_REPORT_STATE_MODE_CLICKED: _ + APP_DEBUG_NO_REPORT_STATE_MODE_CLICKED: _, + APP_SPELLING_SUGGESTED: _, + APP_LEARN_SPELLING: _, + APP_FORGET_LEARNED_SPELLING: _ } module.exports = mapValuesByKeys(appConstants) diff --git a/js/constants/messages.js b/js/constants/messages.js index 2473ce0c53c..1928552a7b9 100644 --- a/js/constants/messages.js +++ b/js/constants/messages.js @@ -76,8 +76,6 @@ const messages = { GO_FORWARD: _, RELOAD: _, DETACH: _, - IS_MISSPELLED: _, /** @arg {string} word, the word to check */ - GET_MISSPELLING_INFO: _, /** @arg {string} word, the word to lookup */ PASSWORD_DETAILS_UPDATED: _, /** @arg {Object} passwords app state */ PASSWORD_SITE_DETAILS_UPDATED: _, /** @arg {Object} passwords app state */ // Init diff --git a/js/contextMenus.js b/js/contextMenus.js index 01eb1ec0af2..6141d029182 100644 --- a/js/contextMenus.js +++ b/js/contextMenus.js @@ -541,7 +541,7 @@ function tabTemplateInit (frameProps) { return menuUtil.sanitizeTemplateItems(template) } -function getMisspelledSuggestions (selection, isMisspelled, suggestions) { +function getMisspelledSuggestions (selection, isMisspelled, suggestions, tabId) { const hasSelection = selection.length > 0 const template = [] if (hasSelection) { @@ -552,7 +552,7 @@ function getMisspelledSuggestions (selection, isMisspelled, suggestions) { return { label: suggestion, click: () => { - webviewActions.replace(suggestion) + appActions.spellingSuggested(suggestion, tabId) } } }), CommonMenu.separatorMenuItem) @@ -561,16 +561,14 @@ function getMisspelledSuggestions (selection, isMisspelled, suggestions) { template.push({ label: locale.translation('learnSpelling'), click: () => { - appActions.addWord(selection, true) - // This is needed so the underline goes away - webviewActions.replace(selection) + appActions.learnSpelling(selection, tabId) } - }, { - label: locale.translation('ignoreSpelling'), + }, CommonMenu.separatorMenuItem) + } else { + template.push({ + label: locale.translation('forgetLearnedSpelling'), click: () => { - appActions.addWord(selection, false) - // This is needed so the underline goes away - webviewActions.replace(selection) + appActions.forgetLearnedSpelling(selection, tabId) } }, CommonMenu.separatorMenuItem) } @@ -970,10 +968,16 @@ function mainTemplateInit (nodeProps, frame, tab) { if (isInputField) { let misspelledSuggestions = [] if (nodeProps.misspelledWord) { - const info = ipc.sendSync(messages.GET_MISSPELLING_INFO, nodeProps.selectionText) - if (info) { - misspelledSuggestions = getMisspelledSuggestions(nodeProps.selectionText, info.isMisspelled, info.suggestions) - } + misspelledSuggestions = + getMisspelledSuggestions(nodeProps.selectionText, + true, nodeProps.dictionarySuggestions, + frame.get('tabId')) + } else if (nodeProps.properties.hasOwnProperty('customDictionaryWord') && + nodeProps.properties['customDictionaryWord'] === nodeProps.selectionText) { + misspelledSuggestions = + getMisspelledSuggestions(nodeProps.selectionText, + false, nodeProps.dictionarySuggestions, + frame.get('tabId')) } const editableItems = getEditableItems(nodeProps.selectionText, nodeProps.editFlags, true) diff --git a/js/stores/appStore.js b/js/stores/appStore.js index 240273c4182..31260006363 100644 --- a/js/stores/appStore.js +++ b/js/stores/appStore.js @@ -397,10 +397,10 @@ const handleAppAction = (action) => { require('../../app/browser/reducers/sitesReducer'), require('../../app/browser/reducers/windowsReducer'), require('../../app/browser/reducers/syncReducer'), - require('../../app/browser/reducers/spellCheckReducer'), require('../../app/browser/reducers/clipboardReducer'), require('../../app/browser/reducers/urlBarSuggestionsReducer'), require('../../app/browser/reducers/passwordManagerReducer'), + require('../../app/browser/reducers/spellCheckerReducer'), require('../../app/browser/reducers/tabMessageBoxReducer'), require('../../app/browser/reducers/dragDropReducer'), require('../../app/browser/reducers/extensionsReducer'), @@ -647,20 +647,6 @@ const handleAppAction = (action) => { } } break - case appConstants.APP_ADD_WORD: - let listType = 'ignoredWords' - if (action.learn) { - listType = 'addedWords' - } - const path = ['dictionary', listType] - let wordList = appState.getIn(path) - if (!wordList.includes(action.word)) { - appState = appState.setIn(path, wordList.push(action.word)) - } - break - case appConstants.APP_SET_DICTIONARY: - appState = appState.setIn(['dictionary', 'locale'], action.locale) - break case appConstants.APP_LEDGER_RECOVERY_STATUS_CHANGED: { const date = new Date().getTime() diff --git a/package.json b/package.json index 361f68f908b..f017260bb9b 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,6 @@ "react-dnd": "^2.1.4", "react-dnd-html5-backend": "^2.1.2", "react-dom": "^15.5.4", - "spellchecker": "brave/node-spellchecker", "string.prototype.endswith": "^0.2.0", "string.prototype.startswith": "^0.2.0", "tablesort": "5.0.0", From 3b5fbd54397a0abc69cec5a208be4f0f6fe45641 Mon Sep 17 00:00:00 2001 From: Brian Clifton Date: Fri, 14 Jul 2017 16:02:39 -0700 Subject: [PATCH 2/3] Added unit tests for spellCheckerReducer :) Auditors: @darkdh Test Plan: `npm run unittest -- --grep="spellCheckerReducer unit tests"` --- .../reducers/spellCheckerReducerTest.js | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 test/unit/app/browser/reducers/spellCheckerReducerTest.js diff --git a/test/unit/app/browser/reducers/spellCheckerReducerTest.js b/test/unit/app/browser/reducers/spellCheckerReducerTest.js new file mode 100644 index 00000000000..84174bae5ad --- /dev/null +++ b/test/unit/app/browser/reducers/spellCheckerReducerTest.js @@ -0,0 +1,129 @@ +/* global describe, it, before, after */ +const mockery = require('mockery') +const sinon = require('sinon') +const Immutable = require('immutable') +const assert = require('assert') +const fakeElectron = require('../../../lib/fakeElectron') + +const appConstants = require('../../../../../js/constants/appConstants') +require('../../../braveUnit') + +describe('spellCheckerReducer unit tests', function () { + let spellCheckerReducer + let fakeWebContentsCache, fakeWebContents, fakeSpellChecker + let getWebContentsSpy, replaceMisspellingSpy, replaceSpy, addWordSpy, removeWordSpy + const dictionaryWord = 'braave' + const dictionarySuggestion = 'brave' + const tabId = 111 + + before(function () { + mockery.enable({ + warnOnReplace: false, + warnOnUnregistered: false, + useCleanCache: true + }) + + fakeSpellChecker = { + addWord: () => {}, + removeWord: () => {} + } + + fakeWebContents = { + isDestroyed: () => false, + replaceMisspelling: (suggestion) => {}, + replace: (word) => {} + } + + fakeWebContentsCache = { + getWebContents: (tabId) => { + return fakeWebContents + } + } + + getWebContentsSpy = sinon.spy(fakeWebContentsCache, 'getWebContents') + replaceMisspellingSpy = sinon.spy(fakeWebContents, 'replaceMisspelling') + replaceSpy = sinon.spy(fakeWebContents, 'replace') + addWordSpy = sinon.spy(fakeSpellChecker, 'addWord') + removeWordSpy = sinon.spy(fakeSpellChecker, 'removeWord') + + mockery.registerMock('electron', fakeElectron) + mockery.registerMock('../webContentsCache', fakeWebContentsCache) + mockery.registerMock('../../spellChecker', fakeSpellChecker) + + spellCheckerReducer = require('../../../../../app/browser/reducers/spellCheckerReducer') + }) + + after(function () { + getWebContentsSpy.restore() + replaceMisspellingSpy.restore() + replaceSpy.restore() + addWordSpy.restore() + removeWordSpy.restore() + mockery.disable() + }) + + describe('APP_SPELLING_SUGGESTED', function () { + before(function () { + getWebContentsSpy.reset() + replaceMisspellingSpy.reset() + + spellCheckerReducer(Immutable.Map(), Immutable.fromJS({ + actionType: appConstants.APP_SPELLING_SUGGESTED, + tabId: tabId, + suggestion: dictionarySuggestion + })) + }) + it('gets the webcontents by tabId', function () { + assert(getWebContentsSpy.withArgs(tabId).calledOnce) + }) + it('calls webcontents.replaceMisspelling', function () { + assert(replaceMisspellingSpy.withArgs(dictionarySuggestion).calledOnce) + }) + }) + + describe('APP_LEARN_SPELLING', function () { + before(function () { + getWebContentsSpy.reset() + replaceMisspellingSpy.reset() + addWordSpy.reset() + + spellCheckerReducer(Immutable.Map(), Immutable.fromJS({ + actionType: appConstants.APP_LEARN_SPELLING, + tabId: tabId, + word: dictionaryWord + })) + }) + it('calls spellChecker.addWord', function () { + assert(addWordSpy.withArgs(dictionaryWord).calledOnce) + }) + it('gets the webcontents by tabId', function () { + assert(getWebContentsSpy.withArgs(tabId).calledOnce) + }) + it('calls webcontents.replaceMisspelling', function () { + assert(replaceMisspellingSpy.withArgs(dictionaryWord).calledOnce) + }) + }) + + describe('APP_FORGET_LEARNED_SPELLING', function () { + before(function () { + getWebContentsSpy.reset() + replaceSpy.reset() + removeWordSpy.reset() + + spellCheckerReducer(Immutable.Map(), Immutable.fromJS({ + actionType: appConstants.APP_FORGET_LEARNED_SPELLING, + tabId: tabId, + word: dictionaryWord + })) + }) + it('calls spellChecker.removeWord', function () { + assert(removeWordSpy.withArgs(dictionaryWord).calledOnce) + }) + it('gets the webcontents by tabId', function () { + assert(getWebContentsSpy.withArgs(tabId).calledOnce) + }) + it('calls webcontents.replace', function () { + assert(replaceSpy.withArgs(dictionaryWord).calledOnce) + }) + }) +}) From 166b32191a527f2104e004b1b440c08a1e53c002 Mon Sep 17 00:00:00 2001 From: Brian Clifton Date: Fri, 14 Jul 2017 16:17:14 -0700 Subject: [PATCH 3/3] Fix for context menu unit test Auditors: @darkdh --- js/contextMenus.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/contextMenus.js b/js/contextMenus.js index 6141d029182..50079596af9 100644 --- a/js/contextMenus.js +++ b/js/contextMenus.js @@ -972,7 +972,8 @@ function mainTemplateInit (nodeProps, frame, tab) { getMisspelledSuggestions(nodeProps.selectionText, true, nodeProps.dictionarySuggestions, frame.get('tabId')) - } else if (nodeProps.properties.hasOwnProperty('customDictionaryWord') && + } else if (nodeProps.properties && + nodeProps.properties.hasOwnProperty('customDictionaryWord') && nodeProps.properties['customDictionaryWord'] === nodeProps.selectionText) { misspelledSuggestions = getMisspelledSuggestions(nodeProps.selectionText,