diff --git a/craft/src/components/basic/Menu.toHtml.test.ts b/craft/src/components/basic/Menu.toHtml.test.ts
new file mode 100644
index 0000000..ded9a0d
--- /dev/null
+++ b/craft/src/components/basic/Menu.toHtml.test.ts
@@ -0,0 +1,32 @@
+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 `;
return {
diff --git a/craft/src/components/forms/InputField.toHtml.test.ts b/craft/src/components/forms/InputField.toHtml.test.ts
index bdd83cc..53ecc4c 100644
--- a/craft/src/components/forms/InputField.toHtml.test.ts
+++ b/craft/src/components/forms/InputField.toHtml.test.ts
@@ -27,3 +27,34 @@ describe('InputField.toHtml accessibility (F2.1)', () => {
expect(html).toContain('aria-label="Phone number"');
});
});
+
+describe('InputField.toHtml deterministic + unique ids (thread node id, resolves id-collision finding)', () => {
+ test('label for= still matches input id= after threading the node id', () => {
+ const { html } = toHtml({ label: 'Your Name', name: 'name' }, '', 'node-in1');
+ const forMatch = html.match(/