cdnV2

git clone https://github.com/notamitgamer/cdnV2.git
· rebuilt hourlyOpen on GitHub
root / tests / fasttree.dom.test.js

fasttree.dom.test.js (10070B)


      1 // Browser-glue tests for app/static/fasttree.js, run in jsdom with a fake Turbo.
      2 // Needs jsdom (CI installs it; locally: `npm install --no-save --no-package-lock jsdom`).
      3 // Run with: node --test tests/*.test.js
      4 const test = require('node:test');
      5 const assert = require('node:assert/strict');
      6 
      7 let JSDOM, VirtualConsole;
      8 try {
      9   ({ JSDOM, VirtualConsole } = require('jsdom'));
     10 } catch (e) {
     11   test('fasttree DOM tests', { skip: 'jsdom is not installed' }, () => {});
     12 }
     13 
     14 if (JSDOM) {
     15   const { boot } = require('../app/static/fasttree.js');
     16 
     17   const parts = {
     18     root_header: '[RH]', count: '<c>@@n@@ @@word@@@@s@@</c>', toolbar: '[TB:@@counts@@|@@path@@]', go_up: '[UP:@@parent_path@@]',
     19     row_dir: '[D:@@name@@]', row_file: '[F:@@name@@]', empty: '[EMPTY]', stats: '[ST:@@n@@@@s@@|@@size_str@@]',
     20     crumb_container: '[CC:@@inner@@|@@tail@@]', crumb_link: '<@@href@@:@@name@@>', crumb_collapsed: '[COL:@@menu@@]',
     21     copy_btn: '[CP:@@path@@]', visit: 'V(@@path_json@@)',
     22   };
     23   const shellV = (v) => ({ v, cdn_base: 'https://cdn.example.com', parts,
     24     html: 'PAGE<!--@root-header-->|<!--@breadcrumb-->|<!--@toolbar-->|<!--@list-->|<!--@stats-->|/*@visit*/' });
     25   const treeV = (v, files) => ({ v, n: files.length, files });
     26   const FILES = [['README.md', 10], ['bsc/a.pdf', 20], ['bsc/sem1/n.txt', 30], ['music/song.mp3', 40]];
     27 
     28   // Build a page with some links, boot the script against it, and return handles for assertions.
     29   function env({ stored, turbo = true, url = 'https://cdn.example.com/', fetchImpl, extraHead = '' } = {}) {
     30     const dom = new JSDOM(`<!doctype html><html><head>${extraHead}</head><body>
     31       <a id="folder" href="/bsc">bsc</a>
     32       <a id="deep" href="/bsc/sem1/">sem1</a>
     33       <a id="root" href="/">home</a>
     34       <a id="file" href="/README.md">readme</a>
     35       <a id="unknown" href="/nope">nope</a>
     36       <a id="query" href="/bsc?x=1">q</a>
     37       <a id="hash" href="/bsc#top">h</a>
     38       <a id="api" href="/api/get/bsc">api</a>
     39       <a id="admin" href="/admin">admin</a>
     40       <a id="blank" href="/bsc" target="_blank">b</a>
     41       <a id="dl" href="/bsc" download>d</a>
     42       <div data-turbo="false"><a id="noturbo" href="/bsc">n</a></div>
     43       <a id="offsite" href="https://example.org/bsc">o</a>
     44     </body></html>`, { url, pretendToBeVisual: true, virtualConsole: new VirtualConsole() });
     45     const win = dom.window;
     46     if (stored !== undefined) win.localStorage.setItem('cdn_fast_v1', typeof stored === 'string' ? stored : JSON.stringify(stored));
     47     const visits = [];
     48     if (turbo) win.Turbo = { visit: (u, o) => visits.push({ url: u, ...o }) };
     49     win.fetch = fetchImpl || (() => Promise.reject(new Error('offline')));
     50     boot(win);
     51     const click = (id, init = {}) => {
     52       const ev = new win.MouseEvent('click', { bubbles: true, cancelable: true, button: 0, ...init });
     53       win.document.getElementById(id).dispatchEvent(ev);
     54       return ev;
     55     };
     56     return { win, visits, click, api: win.__cdnFast };
     57   }
     58   const saved = (tv = 't1', sv = 's1', files = FILES) => ({ tree: { v: tv, files }, shell: shellV(sv) });
     59   const jsonResponse = (body) => Promise.resolve({ ok: true, json: () => Promise.resolve(body) });
     60 
     61   test('warm start: a saved tree is usable immediately, with no network', () => {
     62     const e = env({ stored: saved() });
     63     assert.equal(e.api.stats().ready, true);
     64     assert.equal(e.api.stats().files, 4);
     65   });
     66 
     67   test('clicking a folder link builds the page locally and hands it to Turbo', () => {
     68     const e = env({ stored: saved() });
     69     const ev = e.click('folder');
     70     assert.equal(ev.defaultPrevented, true);
     71     assert.equal(e.visits.length, 1);
     72     const v = e.visits[0];
     73     assert.equal(v.url, '/bsc');
     74     assert.equal(v.action, 'advance');
     75     assert.equal(v.response.statusCode, 200);
     76     assert.ok(v.response.responseHTML.startsWith('PAGE|[CC: </:root> / </bsc:bsc> |[CP:bsc]]|'), v.response.responseHTML);
     77     assert.ok(v.response.responseHTML.includes('[UP:/]'));
     78     assert.ok(v.response.responseHTML.includes('[D:sem1]') && v.response.responseHTML.includes('[F:a.pdf]'));
     79   });
     80 
     81   test('trailing slashes and the root work', () => {
     82     const e = env({ stored: saved() });
     83     assert.equal(e.click('deep').defaultPrevented, true);
     84     assert.equal(e.click('root').defaultPrevented, true);
     85     assert.deepEqual(e.visits.map((v) => v.url), ['/bsc/sem1/', '/']);
     86     assert.ok(e.visits[1].response.responseHTML.startsWith('PAGE[RH]|'));
     87   });
     88 
     89   test('everything else falls through to the normal navigation', () => {
     90     const e = env({ stored: saved() });
     91     for (const id of ['file', 'unknown', 'query', 'hash', 'api', 'admin', 'blank', 'dl', 'noturbo', 'offsite']) {
     92       assert.equal(e.click(id).defaultPrevented, false, id);
     93     }
     94     for (const init of [{ ctrlKey: true }, { metaKey: true }, { shiftKey: true }, { altKey: true }, { button: 1 }]) {
     95       assert.equal(e.click('folder', init).defaultPrevented, false, JSON.stringify(init));
     96     }
     97     assert.equal(e.visits.length, 0);
     98   });
     99 
    100   test('without a saved tree, without Turbo, or when the page opts out: no interception', () => {
    101     assert.equal(env({}).click('folder').defaultPrevented, false);
    102     assert.equal(env({ stored: saved(), turbo: false }).click('folder').defaultPrevented, false);
    103     assert.equal(env({ stored: saved(), extraHead: '<meta name="cdn-fast" content="off">' }).click('folder').defaultPrevented, false);
    104   });
    105 
    106   test('a corrupt or partial saved entry is ignored, not fatal', () => {
    107     for (const bad of ['{not json', '{}', JSON.stringify({ tree: { v: 'x' }, shell: {} }), 'null']) {
    108       const e = env({ stored: bad });
    109       assert.equal(e.api.stats().ready, false, bad);
    110       assert.equal(e.click('folder').defaultPrevented, false);
    111     }
    112   });
    113 
    114   test('an error while building falls back to the normal navigation', () => {
    115     const e = env({ stored: { tree: { v: 't', files: FILES }, shell: { v: 's', cdn_base: 'x', html: 'x', parts: { ...parts, row_dir: null } } } });
    116     assert.equal(e.click('folder').defaultPrevented, false);
    117     assert.equal(e.visits.length, 0);
    118   });
    119 
    120   test('handles(): what the prefetcher should leave alone', () => {
    121     const e = env({ stored: saved() });
    122     assert.equal(e.api.handles('/bsc'), true);
    123     assert.equal(e.api.handles('/README.md'), false);
    124     assert.equal(e.api.handles('/api/get/x'), false);
    125     assert.equal(e.api.handles('https://example.org/bsc'), false);
    126     assert.equal(env({}).api.handles('/bsc'), false);
    127   });
    128 
    129   test('refresh fetches the tree and shell, saves them, and enables local navigation', async () => {
    130     const calls = [];
    131     const e = env({ fetchImpl: (u) => { calls.push(u); return jsonResponse(u === '/api/tree' ? treeV('t9', FILES) : shellV('s9')); } });
    132     assert.equal(e.click('folder').defaultPrevented, false);   // nothing yet
    133     await e.api.refresh(true);
    134     assert.deepEqual(calls.sort(), ['/api/shell', '/api/tree']);
    135     assert.equal(e.api.stats().ready, true);
    136     assert.equal(JSON.parse(e.win.localStorage.getItem('cdn_fast_v1')).tree.v, 't9');
    137     assert.equal(e.click('folder').defaultPrevented, true);
    138   });
    139 
    140   test('forced refreshes arriving together share one request, plus one more afterwards', async () => {
    141     let n = 0;
    142     const e = env({ stored: saved(), fetchImpl: (u) => { n++; return jsonResponse(u === '/api/tree' ? treeV('t1', FILES) : shellV('s1')); } });
    143     await Promise.all([e.api.refresh(true), e.api.refresh(true), e.api.refresh(true)]);
    144     assert.equal(n, 4, 'two requests for the flight, two more for the single follow-up');
    145   });
    146 
    147   test('refresh is throttled unless forced, and an offline refresh keeps what it has', async () => {
    148     let n = 0;
    149     const e = env({ stored: saved(), fetchImpl: () => { n++; return Promise.reject(new Error('offline')); } });
    150     await e.api.refresh(true);
    151     const afterFirst = n;
    152     await e.api.refresh(false);
    153     assert.equal(n, afterFirst, 'throttled');
    154     assert.equal(e.api.stats().ready, true);
    155     assert.equal(e.click('folder').defaultPrevented, true);
    156   });
    157 
    158   test('a changed tree rebuilds the page on screen, but only if that page was built locally', async () => {
    159     const newer = (u) => jsonResponse(u === '/api/tree' ? treeV('t2', [...FILES, ['bsc/new.pdf', 5]]) : shellV('s1'));
    160     // 1) navigate locally to /bsc, then a newer tree arrives -> replace visit with the new content
    161     const e = env({ stored: saved(), fetchImpl: newer });
    162     e.click('folder');
    163     e.win.history.pushState({}, '', '/bsc');                    // what Turbo would do
    164     e.win.document.dispatchEvent(new e.win.Event('turbo:load'));  // marks the page as locally built
    165     await e.api.refresh(true);
    166     const last = e.visits[e.visits.length - 1];
    167     assert.equal(last.action, 'replace');
    168     assert.ok(last.response.responseHTML.includes('[F:new.pdf]'));
    169     // 2) a server-rendered page is never rebuilt behind the user's back
    170     const e2 = env({ stored: saved(), url: 'https://cdn.example.com/bsc', fetchImpl: newer });
    171     await e2.api.refresh(true);
    172     assert.equal(e2.visits.length, 0);
    173   });
    174 
    175   test('a folder deleted in the newer tree is re-requested from the server', async () => {
    176     const gone = (u) => jsonResponse(u === '/api/tree' ? treeV('t3', [['README.md', 1]]) : shellV('s1'));
    177     const e = env({ stored: saved(), fetchImpl: gone });
    178     e.click('folder');
    179     e.win.history.pushState({}, '', '/bsc');
    180     e.win.document.dispatchEvent(new e.win.Event('turbo:load'));
    181     await e.api.refresh(true);
    182     const last = e.visits[e.visits.length - 1];
    183     assert.deepEqual([last.url, last.action, last.response], ['/bsc', 'replace', undefined]);
    184   });
    185 
    186   test('a too-large tree switches the feature off and clears what was saved', async () => {
    187     const e = env({ stored: saved(), fetchImpl: (u) => jsonResponse(u === '/api/tree' ? { v: 'big', n: 99999, too_large: true } : shellV('s1')) });
    188     await e.api.refresh(true);
    189     assert.equal(e.api.stats().ready, false);
    190     assert.equal(e.win.localStorage.getItem('cdn_fast_v1'), null);
    191     assert.equal(e.click('folder').defaultPrevented, false);
    192   });
    193 }