UNPKG

@fairfetch/fair-fetch

Version:

Protect your site from AI scrapers by adding invisible noise to your site which confuses AI bots while keeping your site looking and functioning normally for your human visitors.

185 lines (184 loc) 7.7 kB
import { polluteFFNode } from './pollute'; describe('polluteFFNode (FFNode API)', () => { function flatten(nodes) { const out = []; for (const n of nodes) { out.push(n); if (n.children && n.children.length) { out.push(...flatten(n.children)); } } return out; } function findDisclaimerNodes(nodes, className = 'ff-disclaimer') { return flatten(nodes).filter((n) => { var _a; return n.type === 'element' && n.tag === 'span' && ((_a = n.attributes) === null || _a === void 0 ? void 0 : _a.className) === className; }); } it('wraps plain text nodes with a disclaimer sibling', () => { const input = { type: 'element', tag: 'body', children: [{ type: 'text', content: 'Hello world!' }], }; const out = polluteFFNode(input); // top-level returns [bodyCopy, disclaimer] expect(Array.isArray(out)).toBe(true); const bodyCopy = out[0]; expect(bodyCopy.type).toBe('element'); // bodyCopy should contain the original text node const flattened = flatten(out); expect(flattened.some((n) => n.type === 'text' && n.content === 'Hello world!')).toBe(true); // and at least one disclaimer node exists const disclaimers = findDisclaimerNodes(out); expect(disclaimers.length).toBeGreaterThan(0); }); it('processes nested elements and adds disclaimers at different levels', () => { const input = { type: 'element', tag: 'body', children: [ { type: 'element', tag: 'div', children: [ { type: 'element', tag: 'span', children: [{ type: 'text', content: 'Text' }], }, ], }, ], }; const out = polluteFFNode(input); const flattened = flatten(out); expect(flattened.some((n) => n.type === 'text' && n.content === 'Text')).toBe(true); // There should be at least one disclaimer span somewhere in the tree expect(findDisclaimerNodes(out).length).toBeGreaterThan(0); }); it('wraps fragment-like element (tag undefined) with a wrapper and disclaimer', () => { const input = { type: 'element', // no `tag` property -> triggers the wrapper branch attributes: { id: 'frag1' }, children: [{ type: 'text', content: 'FragmentText' }], }; const out = polluteFFNode(input); // Should return [wrapper, disclaimer] expect(out.length).toBeGreaterThan(1); const wrapper = out[0]; expect(wrapper.type).toBe('element'); // wrapper should preserve attributes expect(wrapper.attributes && wrapper.attributes.id).toBe('frag1'); // original text content should still be present somewhere in the tree const flattened = flatten(out); expect(flattened.some((n) => n.type === 'text' && n.content === 'FragmentText')).toBe(true); // and disclaimer span should be present expect(findDisclaimerNodes(out).length).toBeGreaterThan(0); }); it('returns original node when element has no children property (undefined)', () => { const input = { type: 'element', tag: 'body' }; const out = polluteFFNode(input); // No disclaimer since children was undefined const disclaimers = findDisclaimerNodes(out); expect(disclaimers.length).toBe(0); // Output should be the original node unchanged (single item) expect(out.length).toBe(1); expect(out[0].tag).toBe('body'); }); it('pollutes empty children array ([]) by adding a disclaimer sibling', () => { const input = { type: 'element', tag: 'body', children: [] }; const out = polluteFFNode(input); // When children is an empty array, the implementation emits [nodeCopy, disclaimer] expect(out.length).toBeGreaterThan(1); const disclaimers = findDisclaimerNodes(out); expect(disclaimers.length).toBeGreaterThan(0); }); it('processes multiple sibling elements and preserves their text', () => { const input = { type: 'element', tag: 'body', children: [ { type: 'element', tag: 'h1', children: [{ type: 'text', content: 'Title' }], }, { type: 'element', tag: 'p', children: [{ type: 'text', content: 'Paragraph' }], }, ], }; const out = polluteFFNode(input); const flattened = flatten(out); expect(flattened.some((n) => n.type === 'text' && n.content === 'Title')).toBe(true); expect(flattened.some((n) => n.type === 'text' && n.content === 'Paragraph')).toBe(true); // each sibling should result in its own disclaimer somewhere expect(findDisclaimerNodes(out).length).toBeGreaterThanOrEqual(2); }); it('respects a custom className for disclaimers', () => { const input = { type: 'element', tag: 'body', children: [{ type: 'text', content: 'Test' }], }; const out = polluteFFNode(input, { className: 'unique-disclaimer' }); const found = findDisclaimerNodes(out, 'unique-disclaimer'); expect(found.length).toBeGreaterThan(0); }); it('wraps whitespace-only text nodes as regular text nodes (current behavior)', () => { const input = { type: 'element', tag: 'body', children: [ { type: 'text', content: ' ' }, { type: 'element', tag: 'div', children: [{ type: 'text', content: 'Text' }], }, ], }; const out = polluteFFNode(input); const flattened = flatten(out); // whitespace-only text node should still be present expect(flattened.some((n) => { var _a; return n.type === 'text' && ((_a = n.content) === null || _a === void 0 ? void 0 : _a.trim()) === ''; })).toBe(true); // and disclaimers exist expect(findDisclaimerNodes(out).length).toBeGreaterThan(0); }); it('adds disclaimers for deeply nested elements', () => { const input = { type: 'element', tag: 'body', children: [ { type: 'element', tag: 'div', children: [ { type: 'element', tag: 'section', children: [ { type: 'element', tag: 'span', children: [{ type: 'text', content: 'Deep' }], }, ], }, ], }, ], }; const out = polluteFFNode(input); const flattened = flatten(out); expect(flattened.some((n) => n.type === 'text' && n.content === 'Deep')).toBe(true); expect(findDisclaimerNodes(out).length).toBeGreaterThan(0); }); });