Files
site-builder/craft/src/components/basic/Menu.toHtml.test.ts
T

47 lines
1.9 KiB
TypeScript
Raw Normal View History

import { describe, test, expect } from 'vitest';
import { Menu } from './Menu';
const toHtml = (Menu as any).toHtml;
describe('Menu.toHtml deterministic + unique scope ids (thread node id, no Math.random)', () => {
test('same node id -> identical output across calls (deterministic)', () => {
const { html: html1 } = toHtml({}, '', 'node-menu1');
const { html: html2 } = toHtml({}, '', 'node-menu1');
expect(html1).toBe(html2);
});
test('different node ids -> different, non-colliding scope classes (identical default links, no collision)', () => {
const { html: html1 } = toHtml({}, '', 'node-menu1');
const { html: html2 } = toHtml({}, '', 'node-menu2');
const cls1 = html1.match(/\.([a-z0-9_]+-link):hover/)![1];
const cls2 = html2.match(/\.([a-z0-9_]+-link):hover/)![1];
expect(cls1).not.toBe(cls2);
});
test('the anchor class= and the <style> hover rule use the SAME scope', () => {
const { html } = toHtml({}, '', 'node-menu1');
const hoverCls = html.match(/\.([a-z0-9_]+-link):hover/)![1];
expect(html).toContain(`class="${hoverCls}"`);
});
test('no nodeId (legacy 2-arg call): still deterministic across repeated calls, not random', () => {
const { html: html1 } = toHtml({}, '');
const { html: html2 } = toHtml({}, '');
expect(html1).toBe(html2);
});
});
describe('Menu.toHtml XSS hardening (linkHoverColor into <style>)', () => {
test('a linkHoverColor value containing </style><script> is neutralized', () => {
const malicious = '#fff}</style><script>alert(1)</script><style>{';
const { html } = toHtml({ linkHoverColor: malicious }, '', 'node-xss');
expect(html).not.toContain('</style><script');
expect(html).not.toContain('<script>alert(1)</script>');
});
test('a normal linkHoverColor still renders in the hover rule', () => {
const { html } = toHtml({ linkHoverColor: '#ff0000' }, '', 'node-normal');
expect(html).toMatch(/:hover\s*\{\s*color:\s*#ff0000/);
});
});