import { test, describe, before } from 'node:test'; import assert from 'node:assert/strict'; import fs from 'fs'; import { JSDOM } from 'jsdom'; import crypto from 'node:crypto'; describe('Secret Message Studio - Comprehensive Test Suite', () => { let dom; let window; let document; before(async () => { const htmlContent = fs.readFileSync('index.html', 'utf8'); dom = new JSDOM(htmlContent, { url: 'http://localhost/index.html', runScripts: 'dangerously', beforeParse(win) { // Polyfill Web Crypto API Object.defineProperty(win, 'crypto', { value: crypto.webcrypto, writable: true, }); // Mock QRCode canvas drawer to prevent external network fetches win.QRCode = { toCanvas: (canvas, text, opts) => { const ctx = canvas.getContext('2d'); if (ctx) { ctx.fillStyle = '#000000'; ctx.fillRect(0, 0, canvas.width, canvas.height); } }, }; // Polyfill animationFrame win.requestAnimationFrame = (cb) => setTimeout(cb, 16); win.cancelAnimationFrame = (id) => clearTimeout(id); }, }); window = dom.window; document = window.document; // Allow inline script execution to settle await new Promise((r) => setTimeout(r, 100)); }); test('DOM elements initialized properly', () => { assert.ok(document.querySelector('#messageInput'), 'messageInput exists'); assert.ok(document.querySelector('#createBtn'), 'createBtn exists'); assert.ok(document.querySelector('#openBtn'), 'openBtn exists'); assert.ok(document.querySelector('#decodedResult'), 'decodedResult exists'); }); test('Language toggle works (DE -> EN -> DE)', () => { const deBtn = document.querySelector('[data-lang="de"]'); const enBtn = document.querySelector('[data-lang="en"]'); enBtn.click(); assert.strictEqual(window.state.lang, 'en'); assert.strictEqual(document.querySelector('#createBtn').textContent, 'Create'); deBtn.click(); assert.strictEqual(window.state.lang, 'de'); assert.strictEqual(document.querySelector('#createBtn').textContent, 'Erstellen'); }); test('Tab navigation works (Encode <-> Decode)', () => { const decodeTab = document.querySelector('[data-tab="decode"]'); const encodeTab = document.querySelector('[data-tab="encode"]'); decodeTab.click(); assert.strictEqual(window.state.tab, 'decode'); assert.ok(document.querySelector('#decodeView').classList.contains('active')); encodeTab.click(); assert.strictEqual(window.state.tab, 'encode'); assert.ok(document.querySelector('#encodeView').classList.contains('active')); }); test('Password Generator button works', () => { const genBtn = document.querySelector('#generatePasswordBtn'); const passInput = document.querySelector('#passwordInput'); const secureToggle = document.querySelector('#secureToggle'); genBtn.click(); assert.ok(passInput.value.length >= 12, 'Password generated with sufficient length'); assert.strictEqual(secureToggle.checked, true, 'Secure toggle activated'); }); test('Base64 Encoding & Decoding flow', async () => { document.querySelector('#clearEncodeBtn').click(); document.querySelector('#messageInput').value = 'Test Secret Message 123'; document.querySelector('#toInput').value = 'Alice'; document.querySelector('#fromInput').value = 'Bob'; document.querySelector('#createBtn').click(); // Give microtask queue time to finish async createPayload await new Promise((r) => setTimeout(r, 150)); assert.ok(window.state.payload.startsWith('sm2.'), 'Payload generated with sm2 prefix'); const displayCode = window.state.displayCode; assert.ok(displayCode, 'Display code is present'); // Switch to decode and open message with fade mode document.querySelector('[data-tab="decode"]').click(); document.querySelector('#revealMode').value = 'fade'; document.querySelector('#decodeInput').value = displayCode; document.querySelector('#openBtn').click(); await new Promise((r) => setTimeout(r, 200)); const resultText = document.querySelector('#decodedResult').textContent; assert.strictEqual(resultText, 'Test Secret Message 123', 'Message correctly decoded'); }); test('AES Password Encryption & Decryption flow', async () => { document.querySelector('[data-tab="encode"]').click(); document.querySelector('#clearEncodeBtn').click(); document.querySelector('#messageInput').value = 'Top Secret Encrypted'; document.querySelector('#passwordInput').value = 'MySuperSecretPass123!'; document.querySelector('#secureToggle').checked = true; document.querySelector('#createBtn').click(); await new Promise((r) => setTimeout(r, 150)); const payload = window.state.payload; assert.ok(payload.startsWith('sm2.'), 'Encrypted payload created'); // Decode with wrong password -> should fail document.querySelector('[data-tab="decode"]').click(); document.querySelector('#revealMode').value = 'fade'; document.querySelector('#decodeInput').value = payload; document.querySelector('#decodePassword').value = 'WrongPassword'; document.querySelector('#openBtn').click(); await new Promise((r) => setTimeout(r, 200)); assert.strictEqual(document.querySelector('#toast').textContent, 'Dieses Geheimnis braucht das richtige Passwort.'); // Decode with correct password -> should succeed document.querySelector('#decodePassword').value = 'MySuperSecretPass123!'; document.querySelector('#openBtn').click(); await new Promise((r) => setTimeout(r, 300)); assert.strictEqual(document.querySelector('#decodedResult').textContent, 'Top Secret Encrypted'); }); test('Decoy Message flow on wrong password', async () => { document.querySelector('[data-tab="encode"]').click(); document.querySelector('#clearEncodeBtn').click(); document.querySelector('#messageInput').value = 'Real Secret'; document.querySelector('#passwordInput').value = 'RealPassword123'; document.querySelector('#secureToggle').checked = true; document.querySelector('#decoyInput').value = 'Fake Decoy Message'; document.querySelector('#createBtn').click(); await new Promise((r) => setTimeout(r, 150)); const payload = window.state.payload; // Decode with wrong password -> should display Decoy message document.querySelector('[data-tab="decode"]').click(); document.querySelector('#revealMode').value = 'fade'; document.querySelector('#decodeInput').value = payload; document.querySelector('#decodePassword').value = 'WrongPassword'; document.querySelector('#openBtn').click(); await new Promise((r) => setTimeout(r, 300)); assert.strictEqual(document.querySelector('#decodedResult').textContent, 'Fake Decoy Message'); }); test('Emoji Code conversion', () => { const originalText = 'sm2.SGVsbG8'; const emojiCode = window.toEmojiCode(originalText); const convertedBack = window.fromEmojiCode(emojiCode); assert.strictEqual(convertedBack, originalText, 'Emoji code roundtrip succeeds'); }); test('Steganography Embedding & Extraction', () => { const canvas = window.document.createElement('canvas'); canvas.width = 400; canvas.height = 400; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#ff0000'; ctx.fillRect(0, 0, 400, 400); const testPayload = 'sm2.StegoPayloadTesting123'; const success = window.embedPayloadInCanvas(canvas, testPayload); assert.strictEqual(success, true, 'Payload successfully embedded in canvas'); const extracted = window.extractPayloadFromCanvas(canvas); assert.strictEqual(extracted, testPayload, 'Extracted payload matches original'); }); test('Design Undo & Redo functionality', () => { const initialQrX = window.state.design.qrX; // Change slider const qrXRange = document.querySelector('#qrXRange'); qrXRange.value = '75'; qrXRange.dispatchEvent(new window.Event('input')); assert.strictEqual(window.state.design.qrX, 75); // Click Undo document.querySelector('#undoDesignBtn').click(); assert.strictEqual(window.state.design.qrX, initialQrX, 'Undo restored initial QR X'); // Click Redo document.querySelector('#redoDesignBtn').click(); assert.strictEqual(window.state.design.qrX, 75, 'Redo restored updated QR X'); }); test('Clear Encode & Clear Decode buttons', () => { document.querySelector('#messageInput').value = 'Some message'; document.querySelector('#clearEncodeBtn').click(); assert.strictEqual(document.querySelector('#messageInput').value, '', 'Message input cleared'); document.querySelector('#decodeInput').value = 'Some code'; document.querySelector('#clearDecodeBtn').click(); assert.strictEqual(document.querySelector('#decodeInput').value, '', 'Decode input cleared'); }); });