Skip to main content

max / balanced_breakfast

2.9 KB · 92 lines History Blame Raw
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('&lt;') && r.includes('&gt;'));
9 });
10
11 test('escapes ampersand', () => {
12 assert.ok(BB.utils.escapeHtml('A & B').includes('&amp;'));
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('&quot;'));
29 });
30
31 test('escapes single quotes', () => {
32 assert.ok(BB.utils.escapeAttr("a'b").includes('&#39;'));
33 });
34
35 test('escapes < and >', () => {
36 const r = BB.utils.escapeAttr('<>');
37 assert.ok(r.includes('&lt;') && r.includes('&gt;'));
38 });
39
40 test('escapes ampersand', () => {
41 assert.strictEqual(BB.utils.escapeAttr('a&b'), 'a&amp;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('&lt;'));
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 // Swaps the global timer functions for the length of one synchronous test
68 // body, so nothing else can schedule against them mid-swap.
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