@internetarchive/bookreader 5.0.0-61 → 5.0.0-62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
- });