test: 38 layout mode tests + fix half/full/float CSS positioning

- Fix side panel and full screen not covering full viewport
- Fix floating mode initial position (near chat button, not top-right)
- Reset width/height inline styles when switching modes
- Add 84-chat-layout-modes.test.mjs: 38 assertions covering
  compact, side panel, full screen, floating, drag, rapid switching,
  and user avatar rendering
This commit is contained in:
juanatsap
2026-04-09 11:09:30 +01:00
parent 482350a924
commit 823030dcf2
3 changed files with 272 additions and 5 deletions
+10 -5
View File
@@ -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) {
+259
View File
@@ -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);
});