test+fix: chat layout modes — 38 tests, CSS positioning fixes
This commit is contained in:
@@ -88,6 +88,7 @@
|
||||
bottom: 0;
|
||||
left: auto;
|
||||
width: 50vw;
|
||||
height: auto;
|
||||
max-height: none;
|
||||
border-radius: 0;
|
||||
border-left: 2px solid var(--border-light);
|
||||
@@ -98,14 +99,17 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Size: Floating — draggable, resizable feel */
|
||||
/* Size: Floating — draggable, resizable window */
|
||||
.chat-panel.chat-float {
|
||||
top: 10vh;
|
||||
left: auto;
|
||||
bottom: 10.5rem;
|
||||
right: 2rem;
|
||||
bottom: auto;
|
||||
top: auto;
|
||||
left: auto;
|
||||
width: 420px;
|
||||
height: 450px;
|
||||
max-height: 70vh;
|
||||
min-width: 300px;
|
||||
min-height: 250px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
resize: both;
|
||||
@@ -124,13 +128,14 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Size: Full width */
|
||||
/* Size: Full screen */
|
||||
.chat-panel.chat-full {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: none;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
|
||||
@@ -210,10 +210,13 @@ document.addEventListener('htmx:afterRequest', function(event) {
|
||||
function setChatSize(size) {
|
||||
var panel = document.getElementById('chat-panel');
|
||||
panel.classList.remove('chat-half', 'chat-full', 'chat-float');
|
||||
// Reset all inline styles from drag/resize
|
||||
panel.style.top = '';
|
||||
panel.style.left = '';
|
||||
panel.style.right = '';
|
||||
panel.style.bottom = '';
|
||||
panel.style.width = '';
|
||||
panel.style.height = '';
|
||||
if (size) panel.classList.add(size);
|
||||
// Update active button
|
||||
document.querySelectorAll('.chat-mode-btn[data-mode]').forEach(function(btn) {
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
#!/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);
|
||||
});
|
||||
Reference in New Issue
Block a user