| 1 |
const { describe, test } = require('node:test'); |
| 2 |
const assert = require('node:assert'); |
| 3 |
const { BB } = require('./setup'); |
| 4 |
|
| 5 |
describe('BB.utils.escapeHtml', () => { |
| 6 |
test('escapes angle brackets', () => { |
| 7 |
const r = BB.utils.escapeHtml('<b>hi</b>'); |
| 8 |
assert.ok(r.includes('<') && r.includes('>')); |
| 9 |
}); |
| 10 |
|
| 11 |
test('escapes ampersand', () => { |
| 12 |
assert.ok(BB.utils.escapeHtml('A & B').includes('&')); |
| 13 |
}); |
| 14 |
|
| 15 |
test('returns empty for falsy', () => { |
| 16 |
assert.strictEqual(BB.utils.escapeHtml(''), ''); |
| 17 |
assert.strictEqual(BB.utils.escapeHtml(null), ''); |
| 18 |
assert.strictEqual(BB.utils.escapeHtml(undefined), ''); |
| 19 |
}); |
| 20 |
|
| 21 |
test('passes safe strings through', () => { |
| 22 |
assert.strictEqual(BB.utils.escapeHtml('hello'), 'hello'); |
| 23 |
}); |
| 24 |
}); |
| 25 |
|
| 26 |
describe('BB.utils.escapeAttr', () => { |
| 27 |
test('escapes double quotes', () => { |
| 28 |
assert.ok(BB.utils.escapeAttr('a"b').includes('"')); |
| 29 |
}); |
| 30 |
|
| 31 |
test('escapes single quotes', () => { |
| 32 |
assert.ok(BB.utils.escapeAttr("a'b").includes(''')); |
| 33 |
}); |
| 34 |
|
| 35 |
test('escapes < and >', () => { |
| 36 |
const r = BB.utils.escapeAttr('<>'); |
| 37 |
assert.ok(r.includes('<') && r.includes('>')); |
| 38 |
}); |
| 39 |
|
| 40 |
test('escapes ampersand', () => { |
| 41 |
assert.strictEqual(BB.utils.escapeAttr('a&b'), 'a&b'); |
| 42 |
}); |
| 43 |
|
| 44 |
test('returns empty for falsy', () => { |
| 45 |
assert.strictEqual(BB.utils.escapeAttr(''), ''); |
| 46 |
assert.strictEqual(BB.utils.escapeAttr(null), ''); |
| 47 |
}); |
| 48 |
|
| 49 |
test('handles all special chars together', () => { |
| 50 |
const r = BB.utils.escapeAttr(`<"&'>`); |
| 51 |
assert.ok(!r.includes('<') || r.includes('<')); |
| 52 |
}); |
| 53 |
|
| 54 |
test('converts non-string to string', () => { |
| 55 |
assert.strictEqual(BB.utils.escapeAttr(123), '123'); |
| 56 |
}); |
| 57 |
}); |
| 58 |
|
| 59 |
describe('BB.utils.debounce', () => { |
| 60 |
test('does not fire immediately', () => { |
| 61 |
let called = false; |
| 62 |
const fn = BB.utils.debounce(() => { called = true; }, 10); |
| 63 |
fn(); |
| 64 |
assert.ok(!called, 'Should not fire immediately'); |
| 65 |
}); |
| 66 |
|
| 67 |
|
| 68 |
|
| 69 |
test('rapid calls only execute last one', () => { |
| 70 |
let callCount = 0, lastArg = null; |
| 71 |
const origST = globalThis.setTimeout; |
| 72 |
const origCT = globalThis.clearTimeout; |
| 73 |
let pendingCb = null; |
| 74 |
globalThis.setTimeout = (cb) => { pendingCb = cb; return 1; }; |
| 75 |
globalThis.clearTimeout = () => { pendingCb = null; }; |
| 76 |
|
| 77 |
try { |
| 78 |
const fn = BB.utils.debounce((arg) => { callCount++; lastArg = arg; }, 100); |
| 79 |
fn('a'); |
| 80 |
fn('b'); |
| 81 |
fn('c'); |
| 82 |
if (pendingCb) pendingCb(); |
| 83 |
} finally { |
| 84 |
globalThis.setTimeout = origST; |
| 85 |
globalThis.clearTimeout = origCT; |
| 86 |
} |
| 87 |
|
| 88 |
assert.strictEqual(callCount, 1); |
| 89 |
assert.strictEqual(lastArg, 'c'); |
| 90 |
}); |
| 91 |
}); |
| 92 |
|