cdnV2

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

fasttree.test.js (4891B)


      1 // Run with: node --test tests/*.test.js
      2 const test = require('node:test');
      3 const assert = require('node:assert/strict');
      4 const { escapeHtml, jsonForScript, formatSize, buildIndex, listFolder, compose, folderPathFromUrl } =
      5   require('../app/static/fasttree.js');
      6 
      7 const FILES = [
      8   ['README.md', 1200], ['.gitattributes', 66],
      9   ['bsc/zeta.pdf', 2500000], ['bsc/Alpha.pdf', 999], ['bsc/sem1/n.txt', 12], ['bsc/sem1/deep/a.bin', 5],
     10   ['$&/<b>.txt', 7], ['@@name@@/x.txt', 1152],
     11 ];
     12 const index = buildIndex(FILES);
     13 
     14 test('buildIndex: folders, files, totals', () => {
     15   assert.deepEqual([...index.folders.keys()].sort(), ['', '$&', '@@name@@', 'bsc', 'bsc/sem1', 'bsc/sem1/deep']);
     16   assert.equal(index.count, 8);
     17   assert.equal(index.bytes, 1200 + 66 + 2500000 + 999 + 12 + 5 + 7 + 1152);
     18 });
     19 
     20 test('listFolder: folders first, then case-insensitive by name, with sizes', () => {
     21   const items = listFolder(index, 'bsc');
     22   assert.deepEqual(items.map((i) => [i.name, i.is_dir, i.size_str]), [
     23     ['sem1', true, '-'], ['Alpha.pdf', false, '999 B'], ['zeta.pdf', false, '2.38 MB'],
     24   ]);
     25   assert.equal(items[1].path, 'bsc/Alpha.pdf');
     26   assert.equal(listFolder(index, 'nope'), null);
     27 });
     28 
     29 test('formatSize: matches Python, including round-half-even ties', () => {
     30   const cases = { 0: '0 B', 1023: '1023 B', 1024: '1.00 KB', 1152: '1.12 KB', 1408: '1.38 KB', 1048576: '1.00 MB', 2500000: '2.38 MB' };
     31   for (const [n, want] of Object.entries(cases)) assert.equal(formatSize(Number(n)), want, n);
     32 });
     33 
     34 test('escapeHtml / jsonForScript', () => {
     35   assert.equal(escapeHtml(`<a href="x">'&'</a>`), '&lt;a href=&#34;x&#34;&gt;&#39;&amp;&#39;&lt;/a&gt;');
     36   assert.equal(jsonForScript('a</script>&\'é'), '"a\\u003c/script\\u003e\\u0026\\u0027\\u00e9"');
     37 });
     38 
     39 test('folderPathFromUrl: plain content paths only', () => {
     40   const p = (s) => folderPathFromUrl(new URL(s, 'https://cdn.example.com'));
     41   assert.equal(p('/'), '');
     42   assert.equal(p('/bsc/'), 'bsc');
     43   assert.equal(p('/a%26b/it%27s%20here'), "a&b/it's here");
     44   assert.equal(p('/bsc?x=1'), null);
     45   assert.equal(p('/bsc#top'), null);
     46   for (const r of ['/api/get/x', '/admin', '/mask/abc', '/pack/b1', '/static/sw.js', '/raw/x']) assert.equal(p(r), null, r);
     47   assert.equal(p('/administrator'), 'administrator');   // only whole segments are reserved
     48   assert.equal(p('/%E0%A4%A'), null);                   // malformed escape
     49 });
     50 
     51 // A hand-made shell: every fragment is visible in the output, so substitution is easy to assert on.
     52 const shell = {
     53   cdn_base: 'https://cdn.example.com',
     54   html: 'A<!--@root-header-->B<!--@breadcrumb-->C<!--@toolbar-->D<!--@list-->E<!--@stats-->F/*@visit*/G',
     55   parts: {
     56     root_header: '[RH]', count: '<c>@@n@@ @@word@@@@s@@</c>', toolbar: '[TB:@@counts@@|@@path@@]',
     57     go_up: '[UP:@@parent_path@@]', row_dir: '[D:@@name@@|@@path@@]', row_file: '[F:@@name@@|@@path@@|@@size_str@@]',
     58     empty: '[EMPTY]', stats: '[ST:@@n@@@@s@@|@@size_str@@]', crumb_container: '[CC:@@inner@@|@@tail@@]',
     59     crumb_link: '<@@href@@:@@name@@>', crumb_collapsed: '[COL:@@menu@@]', copy_btn: '[CP:@@path@@]',
     60     visit: 'V(@@path_json@@,@@cdn_url_json@@)',
     61   },
     62 };
     63 const norm = (s) => s.replace(/\s+/g, ' ');
     64 
     65 test('compose: root has the header, no breadcrumb/go-up/visit; hidden .gitattributes is not a row or a count', () => {
     66   const html = norm(compose(shell, index, ''));
     67   assert.match(html, /^A\[RH\]BC\[TB: <c>3 folders<\/c> &amp; <c>1 file<\/c> \|\]D/);
     68   assert.ok(!html.includes('.gitattributes'));
     69   assert.ok(html.includes('[F:README.md|README.md|1.17 KB]'));
     70   assert.ok(!html.includes('[UP:'));
     71   assert.ok(html.endsWith('E[ST:8s|2.39 MB]FG'), html.slice(-40));
     72 });
     73 
     74 test('compose: a folder gets breadcrumb, go-up row, counts and a visit entry', () => {
     75   const html = norm(compose(shell, index, 'bsc/sem1'));
     76   assert.ok(html.includes('[CC: </:root> / </bsc:bsc> / </bsc/sem1:sem1> |[CP:bsc/sem1]]'), html);
     77   assert.ok(html.includes('[UP:/bsc]'));
     78   assert.ok(html.includes('<c>1 folder</c> &amp; <c>1 file</c>'));
     79   assert.ok(html.includes('V("bsc/sem1","https://cdn.example.com/bsc/sem1/")'));
     80   assert.ok(!html.includes('[RH]'));
     81 });
     82 
     83 test('compose: top-level folders go up to "/"', () => {
     84   assert.ok(norm(compose(shell, index, 'bsc')).includes('[UP:/]'));
     85 });
     86 
     87 test('compose: names are substituted literally (no $-patterns, no re-expansion of @@x@@)', () => {
     88   const html = norm(compose(shell, index, '$&'));
     89   assert.ok(html.includes('[F:&lt;b&gt;.txt|$&amp;/&lt;b&gt;.txt|7 B]'), html);
     90   const html2 = norm(compose(shell, index, '@@name@@'));
     91   assert.ok(html2.includes('[F:x.txt|@@name@@/x.txt|1.12 KB]'), html2);
     92 });
     93 
     94 test('compose: empty-looking folder shows the empty state and "0 items"', () => {
     95   const only = buildIndex([['solo/.gitattributes', 1]]);
     96   const html = norm(compose(shell, only, 'solo'));
     97   assert.ok(html.includes('0 items'));
     98   assert.ok(html.includes('[EMPTY]'));
     99 });