@internetarchive/bookreader 5.0.0-61 → 5.0.0-62

Sign up to get free protection for your applications and to get access to all the features.
@@ -60,6 +60,21 @@ describe('getMedianPageSizeInches', () => {
60
60
  expect(bm.getMedianPageSizeInches()).toEqual({ width: 300, height: 2300 });
61
61
  });
62
62
 
63
+ test('does not lexicographic sort for median', () => {
64
+ const sizes = [
65
+ {width: 100, height: 100},
66
+ {width: 20, height: 20},
67
+ {width: 30, height: 30},
68
+ ];
69
+ const data = deepCopy(SAMPLE_DATA);
70
+ delete data[2];
71
+ Object.assign(data[0][0], sizes[0]);
72
+ Object.assign(data[1][0], sizes[1]);
73
+ Object.assign(data[1][1], sizes[2]);
74
+ const bm = new BookModel({ data, options: {ppi: 1} });
75
+ expect(bm.getMedianPageSizeInches()).toEqual({ width: 30, height: 30 });
76
+ });
77
+
63
78
  test('caches result', () => {
64
79
  const bm = new BookModel({ data: SAMPLE_DATA });
65
80
  const firstResult = bm.getMedianPageSizeInches();
@@ -0,0 +1,43 @@
1
+ // @ts-check
2
+ import sinon from "sinon";
3
+ import { SelectionObserver } from "@/src/BookReader/utils/SelectionObserver";
4
+
5
+ afterEach(() => {
6
+ sinon.restore();
7
+ });
8
+
9
+ describe("SelectionObserver", () => {
10
+ test("_onSelectionChange", () => {
11
+ const handler = sinon.spy();
12
+ const observer = new SelectionObserver(".text-layer", handler);
13
+ const target = document.createElement("div");
14
+ target.classList.add("text-layer");
15
+
16
+ // stub window.getSelection
17
+ const getSelectionStub = sinon.stub(window, "getSelection");
18
+ getSelectionStub.returns({ toString: () => "test", anchorNode: target });
19
+ observer._onSelectionChange();
20
+ expect(handler.callCount).toBe(1);
21
+ expect(handler.calledWith("started", target)).toBe(true);
22
+ expect(observer.selecting).toBe(true);
23
+
24
+ // Calling it again does not call the handler again
25
+ observer._onSelectionChange();
26
+ expect(handler.callCount).toBe(1);
27
+
28
+ // Until the selection is cleared
29
+ getSelectionStub.returns({ toString: () => "", anchorNode: null });
30
+ expect(observer.selecting).toBe(true);
31
+ expect(handler.callCount).toBe(1);
32
+
33
+ observer._onSelectionChange();
34
+ expect(handler.callCount).toBe(2);
35
+ expect(handler.calledWith("cleared", target)).toBe(true);
36
+
37
+ // Calling it again does not call the handler again
38
+ sinon.restore();
39
+ sinon.stub(window, "getSelection").returns({ toString: () => "" });
40
+ observer._onSelectionChange();
41
+ expect(handler.callCount).toBe(2);
42
+ });
43
+ });
@@ -1,21 +1,88 @@
1
1
  import sinon from 'sinon';
2
2
 
3
3
  import '@/src/BookReader.js';
4
- import { BookreaderWithTextSelection, TextSelectionPlugin, Cache } from '@/src/plugins/plugin.text_selection.js';
4
+ import {
5
+ BookreaderWithTextSelection,
6
+ Cache,
7
+ genMap,
8
+ lookAroundWindow,
9
+ zip,
10
+ } from '@/src/plugins/plugin.text_selection.js';
5
11
 
6
12
 
7
13
  /** @type {BookReader} */
8
14
 
9
15
  // djvu.xml book infos copied from https://ia803103.us.archive.org/14/items/goodytwoshoes00newyiala/goodytwoshoes00newyiala_djvu.xml
10
- const FAKE_XML_1WORD = `<OBJECT data="file://localhost//tmp/derive/goodytwoshoes00newyiala//goodytwoshoes00newyiala.djvu" height="3192" type="image/x.djvu" usemap="goodytwoshoes00newyiala_0001.djvu" width="2454">
11
- <PARAGRAPH><LINE><WORD coords="1216,2768,1256,2640">test</WORD></LINE></PARAGRAPH>
12
- </OBJECT>`;
13
- const FAKE_XML_MULT_WORDS = `<OBJECT data="file://localhost//tmp/derive/goodytwoshoes00newyiala//goodytwoshoes00newyiala.djvu" height="3192" type="image/x.djvu" usemap="goodytwoshoes00newyiala_0001.djvu" width="2454">
14
- <PARAGRAPH><LINE><WORD coords="1216,2768,1256,2640">test1</WORD>
15
- <WORD coords="1400,2768,1500,2640">test2</WORD>
16
- <WORD coords="1600,2768,1700,2640">test3</WORD></LINE></PARAGRAPH></OBJECT>`;
17
- const FAKE_XML_5COORDS = `<OBJECT data="file://localhost//tmp/derive/goodytwoshoes00newyiala//goodytwoshoes00newyiala.djvu" height="3192" type="image/x.djvu" usemap="goodytwoshoes00newyiala_0001.djvu" width="2454">
18
- <PARAGRAPH><LINE><WORD coords="1216,2768,1256,2640,2690">test</WORD></LINE></PARAGRAPH></OBJECT>`;
16
+ const FAKE_XML_1WORD = `
17
+ <OBJECT data="file://localhost//tmp/derive/goodytwoshoes00newyiala//goodytwoshoes00newyiala.djvu" height="3192" type="image/x.djvu" usemap="goodytwoshoes00newyiala_0001.djvu" width="2454">
18
+ <PARAGRAPH>
19
+ <LINE>
20
+ <WORD coords="1216,2768,1256,2640">test</WORD>
21
+ </LINE>
22
+ </PARAGRAPH>
23
+ </OBJECT>`;
24
+ const FAKE_XML_MULT_WORDS = `
25
+ <OBJECT data="file://localhost//tmp/derive/goodytwoshoes00newyiala//goodytwoshoes00newyiala.djvu" height="3192" type="image/x.djvu" usemap="goodytwoshoes00newyiala_0001.djvu" width="2454">
26
+ <PARAGRAPH>
27
+ <LINE>
28
+ <WORD coords="1216,2768,1256,2640">test1</WORD>
29
+ <WORD coords="1400,2768,1500,2640">test2</WORD>
30
+ <WORD coords="1600,2768,1700,2640">test3</WORD>
31
+ </LINE>
32
+ </PARAGRAPH>
33
+ </OBJECT>`;
34
+ const FAKE_XML_MULT_LINES = `
35
+ <OBJECT data="file://localhost//tmp/derive/goodytwoshoes00newyiala//goodytwoshoes00newyiala.djvu" height="3192" type="image/x.djvu" usemap="goodytwoshoes00newyiala_0001.djvu" width="2454">
36
+ <PARAGRAPH>
37
+ <LINE>
38
+ <WORD coords="119,2050,230,2014" x-confidence="29">way </WORD>
39
+ <WORD coords="230,2038,320,2002" x-confidence="30">can </WORD>
40
+ <WORD coords="320,2039,433,2002" x-confidence="28">false </WORD>
41
+ <WORD coords="433,2051,658,2003" x-confidence="29">judgment </WORD>
42
+ <WORD coords="658,2039,728,2002" x-confidence="30">be </WORD>
43
+ <WORD coords="658,2039,728,2002" x-confidence="30">-</WORD>
44
+ <WORD coords="728,2039,939,2001" x-confidence="29">formed. </WORD>
45
+ <WORD coords="939,2039,1087,2001" x-confidence="29">There </WORD>
46
+ <WORD coords="1087,2039,1187,2002" x-confidence="29">still </WORD>
47
+ <WORD coords="1187,2038,1370,2003" x-confidence="29">remains </WORD>
48
+ <WORD coords="1370,2037,1433,2014" x-confidence="28">an-</WORD>
49
+ </LINE>
50
+ <LINE>
51
+ <WORD coords="244,2099,370,2063" x-confidence="29">other mode </WORD>
52
+ <WORD coords="370,2100,427,2064" x-confidence="29">in </WORD>
53
+ <WORD coords="427,2100,566,2063" x-confidence="29">which </WORD>
54
+ <WORD coords="566,2100,670,2063" x-confidence="29">false </WORD>
55
+ <WORD coords="670,2112,907,2063" x-confidence="29">judgments </WORD>
56
+ <WORD coords="907,2112,1006,2064" x-confidence="29">may </WORD>
57
+ <WORD coords="1006,2100,1071,2063" x-confidence="29">be </WORD>
58
+ <WORD coords="1071,2100,1266,2062" x-confidence="29">formed. </WORD>
59
+ <WORD coords="1266,2110,1435,2062" x-confidence="29">Suppose</WORD>
60
+ </LINE>
61
+ <LINE>
62
+ <WORD coords="118,2160,217,2123" x-confidence="29">that </WORD>
63
+ <WORD coords="217,2160,289,2124" x-confidence="29">we </WORD>
64
+ <WORD coords="289,2160,400,2124" x-confidence="29">have </WORD>
65
+ <WORD coords="400,2160,456,2124" x-confidence="30">in </WORD>
66
+ <WORD coords="456,2161,542,2136" x-confidence="29">our </WORD>
67
+ <WORD coords="542,2161,660,2124" x-confidence="29">souls </WORD>
68
+ <WORD coords="660,2160,700,2136" x-confidence="29">a </WORD>
69
+ <WORD coords="700,2160,847,2129" x-confidence="28">waxen </WORD>
70
+ <WORD coords="847,2160,983,2123" x-confidence="29">tablet </WORD>
71
+ <WORD coords="983,2160,1045,2124" x-confidence="29">of </WORD>
72
+ <WORD coords="1045,2160,1211,2124" x-confidence="29">various </WORD>
73
+ <WORD coords="1211,2171,1398,2122" x-confidence="29">qualities </WORD>
74
+ <WORD coords="1398,2157,1434,2122" x-confidence="29">lastWord</WORD>
75
+ </LINE>
76
+ </PARAGRAPH>
77
+ </OBJECT>`;
78
+ const FAKE_XML_5COORDS = `
79
+ <OBJECT data="file://localhost//tmp/derive/goodytwoshoes00newyiala//goodytwoshoes00newyiala.djvu" height="3192" type="image/x.djvu" usemap="goodytwoshoes00newyiala_0001.djvu" width="2454">
80
+ <PARAGRAPH>
81
+ <LINE>
82
+ <WORD coords="1216,2768,1256,2640,2690">test</WORD>
83
+ </LINE>
84
+ </PARAGRAPH>
85
+ </OBJECT>`;
19
86
  const FAKE_XML_EMPTY = '';
20
87
 
21
88
  describe("Generic tests", () => {
@@ -44,7 +111,7 @@ describe("Generic tests", () => {
44
111
 
45
112
  afterEach(() => {
46
113
  sinon.restore();
47
- $('.textSelectionSVG').remove();
114
+ $('.BRtextLayer').remove();
48
115
  });
49
116
 
50
117
  test("_createPageContainer overridden function still creates a BRpagecontainer element", () => {
@@ -76,8 +143,8 @@ describe("Generic tests", () => {
76
143
  .returns($(new DOMParser().parseFromString(FAKE_XML_1WORD, "text/xml")));
77
144
  const pageIndex = br.data.length - 1;
78
145
  await br.textSelectionPlugin.createTextLayer({ $container, page: { index: pageIndex, width: 100, height: 100 }});
79
- expect($container.find(".textSelectionSVG").length).toBe(1);
80
- expect($container.find(".BRparagElement").length).toBe(1);
146
+ expect($container.find(".BRtextLayer").length).toBe(1);
147
+ expect($container.find("p").length).toBe(1);
81
148
  });
82
149
 
83
150
  test("createTextLayer will not create text layer if there are too many words", async () => {
@@ -86,50 +153,76 @@ describe("Generic tests", () => {
86
153
  sinon.stub(br.textSelectionPlugin, "getPageText")
87
154
  .returns($(new DOMParser().parseFromString(xml, "text/xml")));
88
155
  await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 0, width: 100, height: 100 }});
89
- expect($container.find(".textSelectionSVG").length).toBe(0);
90
- expect($container.find(".BRparagElement").length).toBe(0);
156
+ expect($container.find(".BRtextLayer").length).toBe(0);
157
+ expect($container.find("p").length).toBe(0);
91
158
  expect($container.find(".BRwordElement").length).toBe(0);
92
159
  });
93
160
 
94
- test("createTextLayer creates an svg layer with paragraph with 1 word element", async () => {
161
+ test("createTextLayer creates text layer with paragraph with 1 word element", async () => {
95
162
  const $container = br.refs.$brContainer;
96
163
  sinon.stub(br.textSelectionPlugin, "getPageText")
97
164
  .returns($(new DOMParser().parseFromString(FAKE_XML_1WORD, "text/xml")));
98
165
  await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 1, width: 100, height: 100 }});
99
- expect($container.find(".textSelectionSVG").length).toBe(1);
100
- expect($container.find(".BRparagElement").length).toBe(1);
166
+ expect($container.find(".BRtextLayer").length).toBe(1);
167
+ expect($container.find("p").length).toBe(1);
101
168
  expect($container.find(".BRwordElement").length).toBe(1);
102
169
  expect($container.find(".BRwordElement").text()).toBe("test");
103
170
  });
104
171
 
105
- test("createTextLayer creates an svg layer with paragraph with multiple word elements", async () => {
172
+ test("createTextLayer creates text layer with paragraph with multiple word elements", async () => {
106
173
  const $container = br.refs.$brContainer;
107
174
  sinon.stub(br.textSelectionPlugin, "getPageText")
108
175
  .returns($(new DOMParser().parseFromString(FAKE_XML_MULT_WORDS, "text/xml")));
109
176
  await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 2, width: 100, height: 100 }});
110
- expect($container.find(".textSelectionSVG").length).toBe(1);
111
- expect($container.find(".BRparagElement").length).toBe(1);
112
- expect($container.find(".BRwordElement").length).toBe(5);
113
- expect($container.find(".BRwordElement").last()).not.toBe(" ");
177
+ expect($container.find(".BRtextLayer").length).toBe(1);
178
+ expect($container.find("p").length).toBe(1);
179
+ expect($container.find(".BRwordElement").length).toBe(3);
180
+ expect($container.find(".BRspace").length).toBe(2);
114
181
  });
115
182
 
116
- test("createTextLayer creates an svg layer with paragraph with word with 5 params coordinates", async () => {
183
+ test("createTextLayer creates text layer with paragraph with word with 5 params coordinates", async () => {
117
184
  const $container = br.refs.$brContainer;
118
185
  sinon.stub(br.textSelectionPlugin, "getPageText")
119
186
  .returns($(new DOMParser().parseFromString(FAKE_XML_5COORDS, "text/xml")));
120
187
  await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 3, width: 100, height: 100 }});
121
- expect($container.find(".textSelectionSVG").length).toBe(1);
122
- expect($container.find(".BRparagElement").length).toBe(1);
188
+ expect($container.find(".BRtextLayer").length).toBe(1);
189
+ expect($container.find("p").length).toBe(1);
123
190
  expect($container.find(".BRwordElement").length).toBe(1);
124
191
  });
125
192
 
193
+ test("createTextLayer handles multiple lines", async () => {
194
+ const $container = br.refs.$brContainer;
195
+ sinon.stub(br.textSelectionPlugin, "getPageText")
196
+ .returns($(new DOMParser().parseFromString(FAKE_XML_MULT_LINES, "text/xml")));
197
+ await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 3, width: 100, height: 100 }});
198
+ expect($container.find(".BRtextLayer").length).toBe(1);
199
+ expect($container.find("p").length).toBe(1);
200
+ expect($container.find(".BRlineElement").length).toBe(3);
201
+ // Adds space at end of line; except last line/hyphens
202
+ expect($container.find("p").text()).toMatch(/another/);
203
+ expect($container.find("p").text()).toMatch(/Suppose /);
204
+ expect($container.find("p").text()).toMatch(/lastWord$/);
205
+ expect($container.find("p > br").length).toBe(1);
206
+ });
207
+
208
+ test("createTextLayer repairs trailing hyphens", async () => {
209
+ const $container = br.refs.$brContainer;
210
+ sinon.stub(br.textSelectionPlugin, "getPageText")
211
+ .returns($(new DOMParser().parseFromString(FAKE_XML_MULT_LINES, "text/xml")));
212
+ await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 3, width: 100, height: 100 }});
213
+
214
+ expect($container.find(".BRwordElement--hyphen").length).toBe(1);
215
+ expect($container.find(".BRwordElement--hyphen").closest(".BRlineElement").text().endsWith(' ')).toBe(false);
216
+ expect($container.find(".BRwordElement--hyphen").closest(".BRlineElement").text().endsWith('-')).toBe(false);
217
+ });
218
+
126
219
  test("createTextLayer can handle empty xml", async () => {
127
220
  const $container = br.refs.$brContainer;
128
221
  sinon.stub(br.textSelectionPlugin, "getPageText")
129
222
  .returns($(new DOMParser().parseFromString(FAKE_XML_EMPTY, "text/xml")));
130
223
  await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 4, width: 100, height: 100 }});
131
- expect($container.find(".textSelectionSVG").length).toBe(1);
132
- expect($container.find(".BRparagElement").length).toBe(0);
224
+ expect($container.find(".BRtextLayer").length).toBe(1);
225
+ expect($container.find("p").length).toBe(0);
133
226
  expect($container.find(".BRwordElement").length).toBe(0);
134
227
  });
135
228
 
@@ -150,22 +243,6 @@ describe("Generic tests", () => {
150
243
  });
151
244
  });
152
245
 
153
- describe("textSelectionPlugin constructor", () => {
154
- test("textSelectionPlugin constructor with firefox browser", () => {
155
- const tsp = new TextSelectionPlugin({}, {}, true);
156
- expect(tsp.djvuPagesPromise).toBe(null);
157
- expect(tsp.svgParagraphElement).toBe("g");
158
- expect(tsp.svgWordElement).toBe("text");
159
- });
160
-
161
- test("textSelectionPlugin constructor not on firefox browser", () => {
162
- const tsp = new TextSelectionPlugin({}, {}, false);
163
- expect(tsp.djvuPagesPromise).toBe(null);
164
- expect(tsp.svgParagraphElement).toBe("text");
165
- expect(tsp.svgWordElement).toBe("tspan");
166
- });
167
- });
168
-
169
246
  describe("Cache", () => {
170
247
  test('Adding works', () => {
171
248
  const c = new Cache(10);
@@ -193,3 +270,48 @@ describe("Cache", () => {
193
270
  expect(c.entries).toEqual([12, 10]);
194
271
  });
195
272
  });
273
+
274
+ describe('genMap', () => {
275
+ test('handles empty', () => {
276
+ expect(Array.from(genMap([], x => x ** 2))).toEqual([]);
277
+ });
278
+
279
+ test('handles non-empty', () => {
280
+ expect(Array.from(genMap([1,2,3], x => x ** 2))).toEqual([1,4,9]);
281
+ });
282
+ });
283
+
284
+ describe('lookAroundWindow', () => {
285
+ test('handles empty', () => {
286
+ expect(Array.from(lookAroundWindow([]))).toEqual([]);
287
+ });
288
+
289
+ test('handles smaller than window', () => {
290
+ expect(Array.from(lookAroundWindow([1]))).toEqual([[undefined, 1, undefined]]);
291
+ expect(Array.from(lookAroundWindow([1, 2]))).toEqual([[undefined, 1, 2], [1, 2, undefined]]);
292
+ expect(Array.from(lookAroundWindow([1, 2, 3]))).toEqual([[undefined, 1, 2], [1, 2, 3], [2, 3, undefined]]);
293
+ });
294
+
295
+ test('handles larger than window', () => {
296
+ expect(Array.from(lookAroundWindow([1, 2, 3, 4]))).toEqual([
297
+ [undefined, 1, 2],
298
+ [1, 2, 3],
299
+ [2, 3, 4],
300
+ [3, 4, undefined],
301
+ ]);
302
+ });
303
+ });
304
+
305
+ describe('zip', () => {
306
+ test('handles empty', () => {
307
+ expect(Array.from(zip([], []))).toEqual([]);
308
+ });
309
+
310
+ test('uneven throws error', () => {
311
+ expect(() => Array.from(zip([1], [2, 3]))).toThrow();
312
+ });
313
+
314
+ test('handles even', () => {
315
+ expect(Array.from(zip([1, 2], [3, 4]))).toEqual([[1, 3], [2, 4]]);
316
+ });
317
+ });
@@ -1,46 +0,0 @@
1
- // @ts-check
2
- export class SelectionStartedObserver {
3
- loggedForSelection = false;
4
- startedInSelector = false;
5
-
6
- /**
7
- * @param {string} selector
8
- * @param {function(): any} handler
9
- */
10
- constructor(selector, handler) {
11
- this.selector = selector;
12
- this.handler = handler;
13
- }
14
-
15
- attach() {
16
- // We can't just use select start, because Chrome fires that willy
17
- // nilly even when a user slightly long presses.
18
- document.addEventListener("selectstart", this._onSelectStart);
19
- // This has to be on document :/
20
- document.addEventListener("selectionchange", this._onSelectionChange);
21
- }
22
-
23
- detach() {
24
- document.removeEventListener("selectstart", this._onSelectStart);
25
- document.removeEventListener("selectionchange", this._onSelectionChange);
26
- }
27
-
28
- /**
29
- * @param {Event} ev
30
- */
31
- _onSelectStart = (ev) => {
32
- this.loggedForSelection = false;
33
- // Use jQuery because ev.target could be a Node (eg TextNode), which
34
- // doesn't have .closest on it.
35
- this.startedInSelector = $(ev.target).closest(this.selector).length > 0;
36
- };
37
-
38
- _onSelectionChange = () => {
39
- if (this.loggedForSelection || !this.startedInSelector) return;
40
- const sel = window.getSelection();
41
- if (sel.toString()) {
42
- this.loggedForSelection = true;
43
- this.handler();
44
- }
45
- };
46
- }
@@ -1,73 +0,0 @@
1
- // @ts-check
2
- import sinon from "sinon";
3
- import { SelectionStartedObserver } from "@/src/BookReader/utils/SelectionStartedObserver";
4
-
5
- afterEach(() => {
6
- sinon.restore();
7
- });
8
-
9
- describe("SelectionStartedObserver", () => {
10
- describe("_onSelectStart", () => {
11
- test("sets matches selector correctly", () => {
12
- const observer = new SelectionStartedObserver(".text-layer", () => {});
13
- const ev = new Event("selectstart", {});
14
- const target = document.createElement("div");
15
- Object.defineProperty(ev, "target", { get: () => target });
16
- observer._onSelectStart(ev);
17
- expect(observer.startedInSelector).toBe(false);
18
- target.classList.add("text-layer");
19
- observer._onSelectStart(ev);
20
- expect(observer.startedInSelector).toBe(true);
21
- expect(observer.loggedForSelection).toBe(false);
22
- });
23
-
24
- test("resets loggedForSelction", () => {
25
- const observer = new SelectionStartedObserver(".text-layer", () => {});
26
- const ev = new Event("selectstart", {});
27
- const target = document.createElement("div");
28
- Object.defineProperty(ev, "target", { get: () => target });
29
- target.classList.add("text-layer");
30
- observer._onSelectStart(ev);
31
- expect(observer.loggedForSelection).toBe(false);
32
- observer.loggedForSelection = true;
33
- observer._onSelectStart(ev);
34
- expect(observer.loggedForSelection).toBe(false);
35
- });
36
- });
37
-
38
- test("_onSelectionChange", () => {
39
- const handler = sinon.spy();
40
- const observer = new SelectionStartedObserver(".text-layer", handler);
41
- const ev = new Event("selectstart", {});
42
- const target = document.createElement("div");
43
- target.classList.add("text-layer");
44
- Object.defineProperty(ev, "target", { get: () => target });
45
- observer._onSelectStart(ev);
46
-
47
- // stub window.getSelection
48
- sinon.stub(window, "getSelection").returns({ toString: () => "test" });
49
- observer._onSelectionChange();
50
- expect(handler.callCount).toBe(1);
51
- expect(observer.loggedForSelection).toBe(true);
52
-
53
- // Calling it again does not call the handler again
54
- observer._onSelectionChange();
55
- expect(handler.callCount).toBe(1);
56
-
57
- // Until the selection is cleared
58
- observer._onSelectStart(ev);
59
- expect(observer.loggedForSelection).toBe(false);
60
- expect(handler.callCount).toBe(1);
61
-
62
- observer._onSelectionChange();
63
- expect(handler.callCount).toBe(2);
64
-
65
- observer._onSelectStart(ev);
66
-
67
- // Calling it again does not call the handler again
68
- sinon.restore();
69
- sinon.stub(window, "getSelection").returns({ toString: () => "" });
70
- observer._onSelectionChange();
71
- expect(handler.callCount).toBe(2);
72
- });
73
- });