diff --git a/extension/rikaicontent.ts b/extension/rikaicontent.ts
index b09922d0f..ca670c801 100644
--- a/extension/rikaicontent.ts
+++ b/extension/rikaicontent.ts
@@ -1094,7 +1094,14 @@ class RcxContent {
fake.scrollLeft = eventTarget.scrollLeft;
}
// Calculate range and friends here after we've made our fake textarea/input divs.
- range = document.caretRangeFromPoint(ev.clientX, ev.clientY);
+ range = document.caretRangeFromPoint(
+ ev.clientX,
+ ev.clientY
+ ) as Range | null;
+ // If we don't have a valid range, don't do any more work
+ if (range === null) {
+ return;
+ }
const startNode = range.startContainer;
ro = range.startOffset;
diff --git a/extension/test/rikaicontent_test.ts b/extension/test/rikaicontent_test.ts
index e0fd45250..e2a29c5a3 100644
--- a/extension/test/rikaicontent_test.ts
+++ b/extension/test/rikaicontent_test.ts
@@ -1,61 +1,80 @@
+import { Config } from '../configuration';
import { TestOnlyRcxContent } from '../rikaicontent';
import { expect, use } from '@esm-bundle/chai';
import chrome from 'sinon-chrome';
+import simulant from 'simulant';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
use(sinonChai);
-describe('RcxContent.show', () => {
+let rcxContent = new TestOnlyRcxContent();
+
+describe('RcxContent', () => {
beforeEach(() => {
chrome.reset();
+ rcxContent = new TestOnlyRcxContent();
+ // Default enable rcxContent since no tests care about that now.
+ rcxContent.enableTab({ showOnKey: '' } as Config);
});
+ describe('.show', () => {
+ describe('when given Japanese word interrupted with text wrapped by `display: none`', () => {
+ it('sends "xsearch" message with invisible text omitted', () => {
+ const span = insertHtmlIntoDomAndReturnFirstTextNode(
+ '試testす'
+ );
+
+ executeShowForGivenNode(rcxContent, span);
- describe('when given Japanese word interrupted with text wrapped by `display: none`', () => {
- it('sends "xsearch" message with invisible text omitted', () => {
- const rcxContent = new TestOnlyRcxContent();
- const span = insertHtmlIntoDomAndReturnFirstTextNode(
- '試testす'
- );
+ expect(chrome.runtime.sendMessage).to.have.been.calledWith(
+ sinon.match({ type: 'xsearch', text: '試す' }),
+ sinon.match.any
+ );
+ });
+ });
- executeShowForGivenNode(rcxContent, span);
+ describe('when given Japanese word interrupted with text wrapped by `visibility: hidden`', () => {
+ it('sends "xsearch" message with invisible text omitted', () => {
+ const span = insertHtmlIntoDomAndReturnFirstTextNode(
+ '試testす'
+ );
- expect(chrome.runtime.sendMessage).to.have.been.calledWith(
- sinon.match({ type: 'xsearch', text: '試す' }),
- sinon.match.any
- );
+ executeShowForGivenNode(rcxContent, span);
+
+ expect(chrome.runtime.sendMessage).to.have.been.calledWith(
+ sinon.match({ type: 'xsearch', text: '試す' }),
+ sinon.match.any
+ );
+ });
});
- });
- describe('when given Japanese word interrupted with text wrapped by `visibility: hidden`', () => {
- it('sends "xsearch" message with invisible text omitted', () => {
- const rcxContent = new TestOnlyRcxContent();
- const span = insertHtmlIntoDomAndReturnFirstTextNode(
- '試testす'
- );
+ describe('when given Japanese word is interrupted with text wrapped by visible span', () => {
+ it('sends "xsearch" message with all text included', () => {
+ const rcxContent = new TestOnlyRcxContent();
+ const span = insertHtmlIntoDomAndReturnFirstTextNode(
+ '試testす'
+ );
- executeShowForGivenNode(rcxContent, span);
+ executeShowForGivenNode(rcxContent, span);
- expect(chrome.runtime.sendMessage).to.have.been.calledWith(
- sinon.match({ type: 'xsearch', text: '試す' }),
- sinon.match.any
- );
+ expect(chrome.runtime.sendMessage).to.have.been.calledWith(
+ sinon.match({ type: 'xsearch', text: '試testす' }),
+ sinon.match.any
+ );
+ });
});
});
- describe('when given Japanese word is interrupted with text wrapped by visible span', () => {
- it('sends "xsearch" message with all text included', () => {
- const rcxContent = new TestOnlyRcxContent();
- const span = insertHtmlIntoDomAndReturnFirstTextNode(
- '試testす'
- );
+ describe('mousemove', () => {
+ it('handled without logging errors if `caretRangeFromPoint` returns null', () => {
+ sinon
+ .stub(document, 'caretRangeFromPoint')
+ .returns(null as unknown as Range);
+ sinon.spy(console, 'log');
- executeShowForGivenNode(rcxContent, span);
+ simulant.fire(document, 'mousemove');
- expect(chrome.runtime.sendMessage).to.have.been.calledWith(
- sinon.match({ type: 'xsearch', text: '試testす' }),
- sinon.match.any
- );
+ expect(console.log).to.not.have.been.called;
});
});
});
diff --git a/package-lock.json b/package-lock.json
index f6fb48c40..eb3ac045b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1785,6 +1785,12 @@
"@types/node": "*"
}
},
+ "@types/simulant": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@types/simulant/-/simulant-0.2.0.tgz",
+ "integrity": "sha512-pQcnO5/JMR9KEnQGuYkDNQ9IDFAp0nrCfCjxqZ03WY2QDcuMPR6w0VpL6MO5VQEn93YkNCW9nTuRl/q0+iasVg==",
+ "dev": true
+ },
"@types/sinon": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.2.tgz",
@@ -12329,6 +12335,12 @@
}
}
},
+ "simulant": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simulant/-/simulant-0.2.2.tgz",
+ "integrity": "sha1-8bzlJxK2p6DaON392n6DsgsdoB4=",
+ "dev": true
+ },
"sinon": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz",
diff --git a/package.json b/package.json
index 7a972b357..6c4715f50 100644
--- a/package.json
+++ b/package.json
@@ -58,6 +58,7 @@
"@types/chrome": "0.0.147",
"@types/mocha": "^8.2.2",
"@types/node": "^16.3.3",
+ "@types/simulant": "^0.2.0",
"@types/sinon-chai": "^3.2.5",
"@types/sinon-chrome": "^2.2.10",
"@web/test-runner": "^0.13.13",
@@ -78,6 +79,7 @@
"prettier-plugin-jsdoc": "^0.3.23",
"semantic-release": "^17.4.4",
"semantic-release-chrome": "^1.1.3",
+ "simulant": "^0.2.2",
"sinon": "^7.5.0",
"sinon-chai": "^3.7.0",
"sinon-chrome": "^3.0.1",