File size: 3,364 Bytes
2ecdf7d
 
 
 
 
 
 
 
2c1d035
2ecdf7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aa69781
 
 
2ecdf7d
 
aa69781
2ecdf7d
 
 
 
 
 
 
 
aa69781
2ecdf7d
aa69781
2ecdf7d
 
 
 
 
 
 
 
 
 
 
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
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const vm = require("vm");

function loadManager(options) {
  const sandbox = { self: {} };
  sandbox.self = sandbox;
  vm.runInNewContext(fs.readFileSync(path.join(__dirname, "../static/reader/ui/reader-scroll-anchor.js"), "utf8"), sandbox);
  return sandbox.VoiceOfMLReaderScroll.createScrollAnchorManager(options);
}

function main() {
  const frames = new Map();
  let nextFrame = 1, resizeCallback;
  class ResizeObserverMock {
    constructor(callback) { resizeCallback = callback; this.observed = new Set(); this.disconnected = false; }
    observe(node) { this.observed.add(node); }
    disconnect() { this.disconnected = true; this.observed.clear(); }
  }
  let idleCallback;
  const listeners = new Map();
  const viewport = { scrollTop: 200, getBoundingClientRect: () => ({ top: 0, bottom: 100 }), addEventListener: (name, callback) => listeners.set(name, callback), removeEventListener: (name) => listeners.delete(name) };
  const article = { isConnected: true, top: 0, height: 400, getBoundingClientRect() { return { top: this.top - viewport.scrollTop, bottom: this.top - viewport.scrollTop + this.height, height: this.height }; } };
  const paragraph = { isConnected: true, top: 210, height: 30, getBoundingClientRect() { return { top: this.top - viewport.scrollTop, bottom: this.top - viewport.scrollTop + this.height, height: this.height }; } };
  const manager = loadManager({ viewport, candidates: () => [article, paragraph], ResizeObserverImpl: ResizeObserverMock, requestFrame: (callback) => { const id = nextFrame++; frames.set(id, callback); return id; }, cancelFrame: (id) => frames.delete(id), setTimer: (callback) => { idleCallback = callback; return 1; }, clearTimer: () => { idleCallback = null; } });

  const anchor = manager.capture();
  assert.strictEqual(anchor.node, paragraph);
  assert.strictEqual(anchor.offset, 10);
  paragraph.top += 50;
  assert.strictEqual(manager.restore(anchor), true);
  assert.strictEqual(viewport.scrollTop, 250);
  assert.strictEqual(paragraph.getBoundingClientRect().top, 10);

  manager.observe(article);
  manager.capture();
  paragraph.top += 40;
  resizeCallback();
  assert.strictEqual(frames.size, 0);
  assert.strictEqual(viewport.scrollTop, 290);

  listeners.get("wheel")();
  listeners.get("scroll")();
  assert.strictEqual(manager.scrolling, true);
  manager.capture();
  manager.preserve(() => { paragraph.top += 25; });
  assert.strictEqual(viewport.scrollTop, 315, "synchronous insertion preserves a line during scrolling");
  paragraph.top += 60;
  resizeCallback();
  assert.strictEqual(viewport.scrollTop, 315);
  let idleRuns = 0;
  manager.whenIdle(() => { idleRuns += 1; });
  assert.strictEqual(idleRuns, 0);
  idleCallback();
  assert.strictEqual(manager.scrolling, false);
  assert.strictEqual(idleRuns, 1);

  manager.preserve(() => { paragraph.top += 25; });
  assert.strictEqual(viewport.scrollTop, 340);
  manager.remember();
  assert.strictEqual(frames.size, 0);
  manager.invalidate();
  assert.strictEqual(frames.size, 0);
  assert.strictEqual(manager.restore(anchor), false);
  manager.dispose();
  assert.strictEqual(frames.size, 0);
  paragraph.top += 100;
  assert.strictEqual(manager.restore(anchor), false);
  console.log("reader scroll anchor contracts passed");
}

main();