forked from electron/electron
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathspellchecker-spec.ts
238 lines (204 loc) · 10.6 KB
/
spellchecker-spec.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import { BrowserWindow, Session, session } from 'electron/main';
import { expect } from 'chai';
import * as path from 'path';
import * as fs from 'fs';
import * as http from 'http';
import { AddressInfo } from 'net';
import { closeWindow } from './window-helpers';
import { emittedOnce } from './events-helpers';
import { ifit, ifdescribe, delay } from './spec-helpers';
const features = process._linkedBinding('electron_common_features');
const v8Util = process._linkedBinding('electron_common_v8_util');
ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function () {
this.timeout((process.env.IS_ASAN ? 200 : 20) * 1000);
let w: BrowserWindow;
async function rightClick () {
const contextMenuPromise = emittedOnce(w.webContents, 'context-menu');
w.webContents.sendInputEvent({
type: 'mouseDown',
button: 'right',
x: 43,
y: 42
});
return (await contextMenuPromise)[1] as Electron.ContextMenuParams;
}
// When the page is just loaded, the spellchecker might not be ready yet. Since
// there is no event to know the state of spellchecker, the only reliable way
// to detect spellchecker is to keep checking with a busy loop.
async function rightClickUntil (fn: (params: Electron.ContextMenuParams) => boolean) {
const now = Date.now();
const timeout = (process.env.IS_ASAN ? 180 : 10) * 1000;
let contextMenuParams = await rightClick();
while (!fn(contextMenuParams) && (Date.now() - now < timeout)) {
await delay(100);
contextMenuParams = await rightClick();
}
return contextMenuParams;
}
// Setup a server to download hunspell dictionary.
const server = http.createServer((req, res) => {
// The provided is minimal dict for testing only, full list of words can
// be found at src/third_party/hunspell_dictionaries/xx_XX.dic.
fs.readFile(path.join(__dirname, '/../../third_party/hunspell_dictionaries/xx-XX-3-0.bdic'), function (err, data) {
if (err) {
console.error('Failed to read dictionary file');
res.writeHead(404);
res.end(JSON.stringify(err));
return;
}
res.writeHead(200);
res.end(data);
});
});
before((done) => {
server.listen(0, '127.0.0.1', () => done());
});
after(() => server.close());
const fixtures = path.resolve(__dirname, '../spec/fixtures');
const preload = path.join(fixtures, 'module', 'preload-electron.js');
const generateSpecs = (description: string, sandbox: boolean) => {
describe(description, () => {
beforeEach(async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
partition: `unique-spell-${Date.now()}`,
contextIsolation: false,
preload,
sandbox
}
});
w.webContents.session.setSpellCheckerDictionaryDownloadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}/`);
w.webContents.session.setSpellCheckerLanguages(['en-US']);
await w.loadFile(path.resolve(__dirname, './fixtures/chromium/spellchecker.html'));
});
afterEach(async () => {
await closeWindow(w);
});
// Context menu test can not run on Windows.
const shouldRun = process.platform !== 'win32';
ifit(shouldRun)('should detect correctly spelled words as correct', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typography"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.selectionText.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('');
expect(contextMenuParams.dictionarySuggestions).to.have.lengthOf(0);
});
ifit(shouldRun)('should detect incorrectly spelled words as incorrect', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('typograpy');
expect(contextMenuParams.dictionarySuggestions).to.have.length.of.at.least(1);
});
ifit(shouldRun)('should detect incorrectly spelled words as incorrect after disabling all languages and re-enabling', async () => {
w.webContents.session.setSpellCheckerLanguages([]);
await delay(500);
w.webContents.session.setSpellCheckerLanguages(['en-US']);
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('typograpy');
expect(contextMenuParams.dictionarySuggestions).to.have.length.of.at.least(1);
});
ifit(shouldRun)('should expose webFrame spellchecker correctly', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
const callWebFrameFn = (expr: string) => w.webContents.executeJavaScript(`electron.webFrame.${expr}`);
expect(await callWebFrameFn('isWordMisspelled("typography")')).to.equal(false);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(true);
expect(await callWebFrameFn('getWordSuggestions("typography")')).to.be.empty();
expect(await callWebFrameFn('getWordSuggestions("typograpy")')).to.not.be.empty();
});
describe('spellCheckerEnabled', () => {
it('is enabled by default', async () => {
expect(w.webContents.session.spellCheckerEnabled).to.be.true();
});
ifit(shouldRun)('can be dynamically changed', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
const callWebFrameFn = (expr: string) => w.webContents.executeJavaScript(`electron.webFrame.${expr}`);
w.webContents.session.spellCheckerEnabled = false;
v8Util.runUntilIdle();
expect(w.webContents.session.spellCheckerEnabled).to.be.false();
// spellCheckerEnabled is sent to renderer asynchronously and there is
// no event notifying when it is finished, so wait a little while to
// ensure the setting has been changed in renderer.
await delay(500);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(false);
w.webContents.session.spellCheckerEnabled = true;
v8Util.runUntilIdle();
expect(w.webContents.session.spellCheckerEnabled).to.be.true();
await delay(500);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(true);
});
});
describe('custom dictionary word list API', () => {
let ses: Session;
beforeEach(async () => {
// ensure a new session runs on each test run
ses = session.fromPartition(`persist:customdictionary-test-${Date.now()}`);
});
afterEach(async () => {
if (ses) {
await ses.clearStorageData();
ses = null as any;
}
});
describe('ses.listWordsFromSpellCheckerDictionary', () => {
it('should successfully list words in custom dictionary', async () => {
const words = ['foo', 'bar', 'baz'];
const results = words.map(word => ses.addWordToSpellCheckerDictionary(word));
expect(results).to.eql([true, true, true]);
const wordList = await ses.listWordsInSpellCheckerDictionary();
expect(wordList).to.have.deep.members(words);
});
it('should return an empty array if no words are added', async () => {
const wordList = await ses.listWordsInSpellCheckerDictionary();
expect(wordList).to.have.length(0);
});
});
describe('ses.addWordToSpellCheckerDictionary', () => {
it('should successfully add word to custom dictionary', async () => {
const result = ses.addWordToSpellCheckerDictionary('foobar');
expect(result).to.equal(true);
const wordList = await ses.listWordsInSpellCheckerDictionary();
expect(wordList).to.eql(['foobar']);
});
it('should fail for an empty string', async () => {
const result = ses.addWordToSpellCheckerDictionary('');
expect(result).to.equal(false);
const wordList = await ses.listWordsInSpellCheckerDictionary;
expect(wordList).to.have.length(0);
});
// remove API will always return false because we can't add words
it('should fail for non-persistent sessions', async () => {
const tempSes = session.fromPartition('temporary');
const result = tempSes.addWordToSpellCheckerDictionary('foobar');
expect(result).to.equal(false);
});
});
describe('ses.removeWordFromSpellCheckerDictionary', () => {
it('should successfully remove words to custom dictionary', async () => {
const result1 = ses.addWordToSpellCheckerDictionary('foobar');
expect(result1).to.equal(true);
const wordList1 = await ses.listWordsInSpellCheckerDictionary();
expect(wordList1).to.eql(['foobar']);
const result2 = ses.removeWordFromSpellCheckerDictionary('foobar');
expect(result2).to.equal(true);
const wordList2 = await ses.listWordsInSpellCheckerDictionary();
expect(wordList2).to.have.length(0);
});
it('should fail for words not in custom dictionary', () => {
const result2 = ses.removeWordFromSpellCheckerDictionary('foobar');
expect(result2).to.equal(false);
});
});
});
});
};
generateSpecs('without sandbox', false);
generateSpecs('with sandbox', true);
});