feat: en-tete allege, compte utilisateur en sidebar et pellicule d'images defilable (molette + fleches) #112
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* E2E — Header allégé + section compte en bas de la sidebar (#112).
|
||||
*
|
||||
* Le premier test s'exécute partout ; le second (section compte) nécessite
|
||||
* l'authentification activée et se marque `skip` sinon (job CI e2e, serveur
|
||||
* local `run-e2e-local` en auth désactivée). Pour le valider manuellement,
|
||||
* lancer Playwright contre l'instance Docker de test (auth active) :
|
||||
*
|
||||
* BASE_URL=http://localhost:2020 OBSIGATE_PASS=test123 \
|
||||
* npx playwright test tests/e2e/header-sidebar.spec.js --project=chromium-desktop
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE = process.env.BASE_URL || 'http://localhost:2029';
|
||||
|
||||
const CREDS = {
|
||||
username: process.env.OBSIGATE_USER || 'admin',
|
||||
password: process.env.OBSIGATE_PASS || 'test123',
|
||||
};
|
||||
|
||||
async function login(page) {
|
||||
await page.goto(BASE);
|
||||
const loginForm = page.locator('#login-screen');
|
||||
await expect(loginForm).toBeVisible({ timeout: 5000 }).catch(() => {});
|
||||
if (await loginForm.isVisible()) {
|
||||
await page.fill('#login-username', CREDS.username);
|
||||
await page.fill('#login-password', CREDS.password);
|
||||
await page.click('#login-btn');
|
||||
}
|
||||
await page.waitForFunction(() => window.__OBSIGATE_BOOTED === true, { timeout: 20000 });
|
||||
}
|
||||
|
||||
async function authEnabled(page) {
|
||||
return page.evaluate(async () => {
|
||||
try {
|
||||
const r = await fetch('/api/auth/status');
|
||||
const d = await r.json();
|
||||
return !!d.auth_enabled;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
test.describe('Header allégé + compte en sidebar (#112)', () => {
|
||||
test('le header ne porte plus utilisateur/version/déconnexion ; la version est dans le menu Options', async ({ page }) => {
|
||||
await login(page);
|
||||
|
||||
await expect(page.locator('.header-right #user-menu')).toHaveCount(0);
|
||||
await expect(page.locator('#logout-btn')).toHaveCount(0);
|
||||
await expect(page.locator('.header-right > .version-badge')).toHaveCount(0);
|
||||
|
||||
await page.locator('#header-menu-btn').click();
|
||||
await expect(page.locator('#header-menu-dropdown #version-badge')).toBeVisible();
|
||||
});
|
||||
|
||||
test('le compte est affiché en bas de la sidebar (auth active)', async ({ page }) => {
|
||||
await login(page);
|
||||
test.skip(!(await authEnabled(page)), 'auth disabled in this environment');
|
||||
|
||||
await expect(page.locator('#sidebar-user')).toBeVisible();
|
||||
await expect(page.locator('#sidebar-user-name')).not.toBeEmpty();
|
||||
await expect(page.locator('#sidebar-user-role')).not.toBeEmpty();
|
||||
await expect(page.locator('#sidebar-user-logout')).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -159,4 +159,31 @@ test.describe('Image viewer — zoom / pan / navigation (#108)', () => {
|
||||
await expect(container).toHaveAttribute('data-inplace', '1');
|
||||
await expect(page.locator('#content-area .image-counter')).toContainText('/');
|
||||
});
|
||||
|
||||
test('la pellicule est défilable (molette + flèches latérales) sans changer d\'image (#112)', async ({ page }) => {
|
||||
await login(page);
|
||||
await openFile(page, 'TestVault', 'sample-image.png');
|
||||
|
||||
const nav = page.locator('#content-area .image-nav');
|
||||
const strip = page.locator('#content-area .image-nav-strip');
|
||||
await expect(strip).toBeVisible({ timeout: 10000 });
|
||||
await expect(nav).toBeAttached();
|
||||
|
||||
// Les deux flèches de défilement existent (translucides, révélées au survol).
|
||||
const prev = page.locator('#content-area .image-strip-arrow-prev');
|
||||
const next = page.locator('#content-area .image-strip-arrow-next');
|
||||
await expect(prev).toBeAttached();
|
||||
await expect(next).toBeAttached();
|
||||
await expect
|
||||
.poll(() => next.evaluate((el) => getComputedStyle(el).opacity))
|
||||
.not.toBe('1'); // au repos, la flèche n'est pas pleinement visible
|
||||
|
||||
// Faire défiler la pellicule ne change PAS l'image courante.
|
||||
const title = page.locator('#content-area .image-title');
|
||||
const before = await title.innerText();
|
||||
await next.hover();
|
||||
await next.click();
|
||||
await prev.click();
|
||||
await expect(title).toHaveText(before);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -122,6 +122,21 @@ test("image viewer navigates between siblings in place (#111)", () => {
|
||||
assert.match(viewer, /const _imageDirCache = new Map\(\)/);
|
||||
});
|
||||
|
||||
// #112 — the filmstrip scrolls horizontally (mouse wheel + two edge arrows).
|
||||
test("image viewer filmstrip scrolls with the wheel and edge arrows (#112)", () => {
|
||||
assert.match(viewer, /const navWrap = el\("div", \{ class: "image-nav", hidden: true \}\)/);
|
||||
assert.match(viewer, /image-strip-arrow-prev/);
|
||||
assert.match(viewer, /image-strip-arrow-next/);
|
||||
assert.match(viewer, /strip\.addEventListener\("wheel"/);
|
||||
assert.match(viewer, /strip\.scrollBy\(\{ left: dir/);
|
||||
assert.match(viewer, /navWrap\.hidden = true/);
|
||||
assert.match(viewer, /navWrap\.hidden = false/);
|
||||
assert.match(css, /\.image-nav\s*\{[\s\S]*?position:\s*relative/);
|
||||
assert.match(css, /\.image-strip-arrow\s*\{/);
|
||||
assert.match(css, /\.image-strip-arrow-prev\s*\{\s*left:\s*0/);
|
||||
assert.match(css, /\.image-strip-arrow-next\s*\{\s*right:\s*0/);
|
||||
});
|
||||
|
||||
// ── Static checks: CSS ─────────────────────────────────────────────────────
|
||||
test("style.css defines the image viewer layout + lightbox", () => {
|
||||
assert.match(css, /\.image-stage\s*\{/);
|
||||
|
||||
@@ -240,6 +240,50 @@ function testAIFabModuleExports() {
|
||||
console.log(' ✓ ai-fab.js exports initAIFab');
|
||||
}
|
||||
|
||||
// ── Test header trimming + sidebar account section (#112) ──────────────────
|
||||
const AUTH_PATH = join(JS_DIR, 'auth.js');
|
||||
const STYLE_PATH = join(FRONTEND_DIR, 'style.css');
|
||||
|
||||
function testSidebarUserSection() {
|
||||
const html = readFileSync(INDEX_HTML, 'utf-8');
|
||||
const auth = readFileSync(AUTH_PATH, 'utf-8');
|
||||
const css = readFileSync(STYLE_PATH, 'utf-8');
|
||||
|
||||
// The header no longer carries the user block / version badge…
|
||||
assert.doesNotMatch(html, /id="user-menu"/, 'header #user-menu must be removed');
|
||||
assert.doesNotMatch(html, /id="logout-btn"/, 'header sign-out button must be removed');
|
||||
const headerRight = html.slice(
|
||||
html.indexOf('class="header-right"'),
|
||||
html.indexOf('class="header-menu"'),
|
||||
);
|
||||
assert.doesNotMatch(headerRight, /version-badge/, 'version badge must leave the header-right');
|
||||
assert.doesNotMatch(headerRight, /user-menu/, 'user block must leave the header-right');
|
||||
|
||||
// …the version lives in the options dropdown…
|
||||
assert.match(html, /class="menu-list-row menu-list-version"[\s\S]*?id="version-badge"/);
|
||||
|
||||
// …and the account section sits at the bottom of the sidebar.
|
||||
assert.match(html, /id="sidebar-user"/);
|
||||
assert.match(html, /id="sidebar-user-logout"/);
|
||||
assert.match(html, /id="sidebar-user-name"/);
|
||||
assert.match(html, /id="sidebar-user-avatar"/);
|
||||
|
||||
// auth.js drives it.
|
||||
assert.match(auth, /renderUserSection\(\)/);
|
||||
assert.match(auth, /getElementById\("sidebar-user"\)/);
|
||||
|
||||
// styles + i18n exist in FR and EN.
|
||||
assert.match(css, /\.sidebar-user\s*\{/);
|
||||
assert.match(css, /\.menu-list-version\s*\{/);
|
||||
for (const lang of ['fr', 'en']) {
|
||||
const locale = JSON.parse(readFileSync(join(FRONTEND_DIR, 'locales', `${lang}.json`), 'utf-8'));
|
||||
for (const key of ['header.menu_version', 'sidebar.user_role_admin', 'sidebar.user_role_user']) {
|
||||
assert.ok(locale[key], `${lang}: missing i18n key ${key}`);
|
||||
}
|
||||
}
|
||||
console.log(' ✓ header trimmed + sidebar account section (#112)');
|
||||
}
|
||||
|
||||
// ── Run all tests ──────────────────────────────────────────────────────────
|
||||
async function main() {
|
||||
let passed = 0, failed = 0;
|
||||
@@ -255,6 +299,7 @@ async function main() {
|
||||
['ai-fab module exists', testAIFabModuleExists],
|
||||
['ai-fab module exports', testAIFabModuleExports],
|
||||
['config TOC icons', testConfigTocIcons],
|
||||
['sidebar user section', testSidebarUserSection],
|
||||
];
|
||||
|
||||
for (const [name, fn] of tests) {
|
||||
|
||||
Reference in New Issue
Block a user