#!/usr/bin/env bun /** * CHAT LAYOUT MODES TEST * ======================= * Tests the 4 chat panel layout modes: Compact, Side panel, Floating, Full screen. * Verifies positioning, sizing, drag behavior, resize, and button state. */ import { chromium } from 'playwright'; const URL = "http://localhost:1999"; async function testChatLayoutModes() { console.log('📐 CHAT LAYOUT MODES TEST\n'); console.log('='.repeat(70)); const browser = await chromium.launch({ headless: true }); const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } }); let passed = 0; let failed = 0; function record(name, ok, detail = '') { ok ? passed++ : failed++; console.log(` ${ok ? '✅' : '❌'} ${name}${detail ? ' — ' + detail : ''}`); } // ====================================================================== console.log("\n📋 Loading page..."); await page.goto(`${URL}/?lang=en`); await page.waitForTimeout(2000); // Open chat panel await page.click('#chat-toggle-btn'); await page.waitForTimeout(500); record('Chat panel opens', await page.locator('#chat-panel.chat-open').isVisible()); // ====================================================================== // 1. MODE BUTTONS EXIST // ====================================================================== console.log("\n1️⃣ Mode Buttons"); const modeButtons = await page.locator('.chat-mode-btn[data-mode]').count(); record('4 mode buttons exist', modeButtons === 4, `found ${modeButtons}`); const helpBtn = await page.locator('.chat-mode-btn[command="show-modal"]').count(); record('Help button exists', helpBtn === 1); const divider = await page.locator('.chat-header-divider').count(); record('Divider between modes and help', divider === 1); // Check compact is active by default const compactActive = await page.locator('.chat-mode-btn[data-mode=""].active').count(); record('Compact mode active by default', compactActive === 1); // ====================================================================== // 2. COMPACT MODE (default) // ====================================================================== console.log("\n2️⃣ Compact Mode"); const compactBox = await page.locator('#chat-panel').boundingBox(); record('Compact: has fixed width ~360px', compactBox && compactBox.width >= 340 && compactBox.width <= 380, `width=${compactBox?.width}`); record('Compact: positioned bottom-right', compactBox && compactBox.x > 1400 && compactBox.y > 300, `x=${compactBox?.x} y=${compactBox?.y}`); record('Compact: not full height', compactBox && compactBox.height < 600, `height=${compactBox?.height}`); // No special classes const hasHalf = await page.locator('#chat-panel.chat-half').count(); const hasFull = await page.locator('#chat-panel.chat-full').count(); const hasFloat = await page.locator('#chat-panel.chat-float').count(); record('Compact: no size classes', hasHalf === 0 && hasFull === 0 && hasFloat === 0); // ====================================================================== // 3. SIDE PANEL MODE // ====================================================================== console.log("\n3️⃣ Side Panel Mode"); await page.click('.chat-mode-btn[data-mode="chat-half"]'); await page.waitForTimeout(400); const halfActive = await page.locator('.chat-mode-btn[data-mode="chat-half"].active').count(); record('Side panel: button is active', halfActive === 1); const compactNotActive = await page.locator('.chat-mode-btn[data-mode=""].active').count(); record('Side panel: compact button not active', compactNotActive === 0); const halfClass = await page.locator('#chat-panel.chat-half').count(); record('Side panel: has chat-half class', halfClass === 1); const halfBox = await page.locator('#chat-panel').boundingBox(); record('Side panel: ~50% viewport width', halfBox && halfBox.width >= 900 && halfBox.width <= 1000, `width=${halfBox?.width}`); record('Side panel: full height', halfBox && halfBox.height >= 1050, `height=${halfBox?.height}`); record('Side panel: docked to right (x ~960)', halfBox && halfBox.x >= 920 && halfBox.x <= 1000, `x=${halfBox?.x}`); record('Side panel: starts at top', halfBox && halfBox.y === 0, `y=${halfBox?.y}`); // Messages area should expand const halfMsgBox = await page.locator('#chat-panel .chat-messages').boundingBox(); record('Side panel: messages area expanded', halfMsgBox && halfMsgBox.height > 400, `height=${halfMsgBox?.height}`); // ====================================================================== // 4. FULL SCREEN MODE // ====================================================================== console.log("\n4️⃣ Full Screen Mode"); await page.click('.chat-mode-btn[data-mode="chat-full"]'); await page.waitForTimeout(400); const fullActive = await page.locator('.chat-mode-btn[data-mode="chat-full"].active').count(); record('Full screen: button is active', fullActive === 1); const fullClass = await page.locator('#chat-panel.chat-full').count(); record('Full screen: has chat-full class', fullClass === 1); const fullBox = await page.locator('#chat-panel').boundingBox(); record('Full screen: covers full width', fullBox && fullBox.width >= 1900, `width=${fullBox?.width}`); record('Full screen: covers full height', fullBox && fullBox.height >= 1050, `height=${fullBox?.height}`); record('Full screen: starts at 0,0', fullBox && fullBox.x === 0 && fullBox.y === 0, `x=${fullBox?.x} y=${fullBox?.y}`); // Messages area should be large const fullMsgBox = await page.locator('#chat-panel .chat-messages').boundingBox(); record('Full screen: messages area large', fullMsgBox && fullMsgBox.height > 700, `height=${fullMsgBox?.height}`); // ====================================================================== // 5. FLOATING MODE // ====================================================================== console.log("\n5️⃣ Floating Mode"); await page.click('.chat-mode-btn[data-mode="chat-float"]'); await page.waitForTimeout(400); const floatActive = await page.locator('.chat-mode-btn[data-mode="chat-float"].active').count(); record('Floating: button is active', floatActive === 1); const floatClass = await page.locator('#chat-panel.chat-float').count(); record('Floating: has chat-float class', floatClass === 1); const floatBox = await page.locator('#chat-panel').boundingBox(); record('Floating: ~420px wide', floatBox && floatBox.width >= 400 && floatBox.width <= 440, `width=${floatBox?.width}`); record('Floating: has reasonable height', floatBox && floatBox.height >= 200 && floatBox.height <= 800, `height=${floatBox?.height}`); // ====================================================================== // 6. FLOATING DRAG // ====================================================================== console.log("\n6️⃣ Floating Drag"); const header = await page.locator('#chat-panel .chat-header').boundingBox(); const startX = header.x + header.width / 2; const startY = header.y + header.height / 2; // Drag 200px left and 100px up await page.mouse.move(startX, startY); await page.mouse.down(); await page.mouse.move(startX - 200, startY - 100, { steps: 10 }); await page.mouse.up(); await page.waitForTimeout(200); const draggedBox = await page.locator('#chat-panel').boundingBox(); const movedX = Math.abs((draggedBox.x + draggedBox.width / 2) - (floatBox.x + floatBox.width / 2)); const movedY = Math.abs((draggedBox.y + draggedBox.height / 2) - (floatBox.y + floatBox.height / 2)); record('Drag: panel moved horizontally', movedX > 150, `delta=${Math.round(movedX)}px`); record('Drag: panel moved vertically', movedY > 60, `delta=${Math.round(movedY)}px`); // ====================================================================== // 7. SWITCH BACK TO COMPACT // ====================================================================== console.log("\n7️⃣ Return to Compact"); await page.click('.chat-mode-btn[data-mode=""]'); await page.waitForTimeout(400); const backCompact = await page.locator('.chat-mode-btn[data-mode=""].active').count(); record('Back to compact: button active', backCompact === 1); const noFloat = await page.locator('#chat-panel.chat-float').count(); const noHalf = await page.locator('#chat-panel.chat-half').count(); const noFull = await page.locator('#chat-panel.chat-full').count(); record('Back to compact: no size classes', noFloat === 0 && noHalf === 0 && noFull === 0); const backBox = await page.locator('#chat-panel').boundingBox(); record('Back to compact: width restored ~360px', backBox && backBox.width >= 340 && backBox.width <= 380, `width=${backBox?.width}`); record('Back to compact: position restored (bottom-right)', backBox && backBox.x > 1400, `x=${backBox?.x}`); // ====================================================================== // 8. RAPID MODE SWITCHING // ====================================================================== console.log("\n8️⃣ Rapid Mode Switching"); const modes = ['chat-half', 'chat-full', 'chat-float', '', 'chat-full', 'chat-half', '']; for (const mode of modes) { await page.click(`.chat-mode-btn[data-mode="${mode}"]`); await page.waitForTimeout(200); } // Should end in compact const finalCompact = await page.locator('.chat-mode-btn[data-mode=""].active').count(); record('Rapid switching: ends in compact', finalCompact === 1); const finalBox = await page.locator('#chat-panel').boundingBox(); record('Rapid switching: panel is valid size', finalBox && finalBox.width > 100 && finalBox.height > 100, `${finalBox?.width}x${finalBox?.height}`); const finalNoClasses = await page.evaluate(() => { const p = document.getElementById('chat-panel'); return !p.classList.contains('chat-half') && !p.classList.contains('chat-full') && !p.classList.contains('chat-float'); }); record('Rapid switching: no stale classes', finalNoClasses); // ====================================================================== // 9. USER AVATAR // ====================================================================== console.log("\n9️⃣ User Avatar in Messages"); // Type a message manually await page.fill('#chat-input', 'Hello'); await page.click('.chat-send-btn'); await page.waitForTimeout(500); const userBubble = await page.locator('.chat-row-user').last(); const hasAvatar = await userBubble.locator('.chat-avatar-user').count(); record('User message has avatar', hasAvatar === 1); const avatarIcon = await userBubble.locator('iconify-icon[icon="mdi:account"]').count(); record('Avatar has user icon', avatarIcon === 1); // ====================================================================== // SUMMARY // ====================================================================== console.log('\n' + '='.repeat(70)); console.log(`\n📊 RESULTS: ${passed} passed, ${failed} failed, ${passed + failed} total`); if (failed > 0) { console.log('\n❌ SOME TESTS FAILED'); } else { console.log('\n✅ ALL TESTS PASSED'); } await browser.close(); process.exit(failed > 0 ? 1 : 0); } testChatLayoutModes().catch(err => { console.error('Fatal error:', err); process.exit(1); });