// Verifies the Event Board extension to the Neighbourhood board: posting an // event (date/time/location), RSVP toggle + count, the Announcements/Events // filter, and that the seeded demo neighbourhood ("Masjid Al-Falah Kariah") // renders real content for a demo account. const { chromium } = require('playwright'); const { signInFreshFamily, gotoTab } = require('./e2e-auth-helper.cjs'); const BASE = 'https://moslem04.falahos.my/'; const results = []; const consoleErrors = []; function record(name, pass, detail = '') { results.push({ name, pass, detail }); console.log(`${pass ? 'PASS' : 'FAIL'} ${name}${detail ? ' — ' + detail : ''}`); } async function main() { const browser = await chromium.launch(); const page = await browser.newPage({ viewport: { width: 390, height: 844 } }); page.on('console', m => { if (m.type() === 'error') consoleErrors.push(m.text()); }); page.on('pageerror', e => consoleErrors.push(e.message)); await signInFreshFamily(page, BASE, 'e2e-eventboard'); await gotoTab(page, 'Neighbourhood'); await page.waitForTimeout(600); const addToggle = await page.locator('.add-toggle').isVisible().catch(() => false); if (addToggle) await page.locator('.add-toggle').click(); await page.waitForTimeout(300); await page.locator('.form-card .field:has-text("Name") input').fill(`Event Board Test ${Date.now()}`); await page.locator('button.btn-primary', { hasText: 'Create & get a join code' }).click(); await page.waitForTimeout(1000); // Plain announcement — no event fields shown until the toggle is checked const eventFieldsHiddenByDefault = !(await page.locator('.field:has-text("Date & time")').isVisible().catch(() => false)); record('Neighbourhood: event date/location fields hidden until toggled', eventFieldsHiddenByDefault); await page.locator('.field:has-text("Title") input').fill('General announcement'); await page.locator('button.btn-primary', { hasText: 'Post announcement' }).click(); await page.waitForTimeout(1000); record('Neighbourhood: plain announcement posts without an event badge', await page.locator('.post-row:not(.event-row)', { hasText: 'General announcement' }).isVisible().catch(() => false)); // Event post await page.locator('.field:has-text("Title") input').fill('Community Quran Class'); await page.locator('.event-toggle input').check(); await page.waitForTimeout(200); const eventFieldsShown = await page.locator('.field:has-text("Date & time")').isVisible().catch(() => false); record('Neighbourhood: event fields appear once toggled', eventFieldsShown); await page.locator('.field:has-text("Date & time") input').fill('2026-12-01T18:30'); await page.locator('.field:has-text("Location") input').fill('Masjid Test Hall'); await page.locator('button.btn-primary', { hasText: 'Post event' }).click(); await page.waitForTimeout(1000); const eventCardVisible = await page.locator('.event-row', { hasText: 'Community Quran Class' }).isVisible().catch(() => false); record('Event Board: event posts with the 📅 badge and event styling', eventCardVisible); const eventDetailsVisible = await page.locator('.event-row', { hasText: 'Community Quran Class' }).locator('.event-when, .event-where').count(); record('Event Board: shows date/time and location on the event card', eventDetailsVisible === 2, `${eventDetailsVisible} detail lines`); // RSVP const rsvpBtn = page.locator('.event-row', { hasText: 'Community Quran Class' }).locator('.rsvp-btn'); record('Event Board: starts as not going', !(await rsvpBtn.evaluate(el => el.classList.contains('going')))); await rsvpBtn.click(); await page.waitForTimeout(800); record('Event Board: RSVP toggles to "Going" with a count of 1', await rsvpBtn.evaluate(el => el.classList.contains('going')) && (await rsvpBtn.textContent()).includes('1')); await rsvpBtn.click(); await page.waitForTimeout(800); record('Event Board: RSVP toggles back off (cancel)', !(await rsvpBtn.evaluate(el => el.classList.contains('going')))); // Filter await page.locator('.board-filter button', { hasText: 'Events' }).click(); await page.waitForTimeout(300); const onlyEventsShown = await page.locator('.post-row').count() === 1 && await page.locator('.event-row').isVisible(); record('Event Board: Events filter hides plain announcements', onlyEventsShown); await page.locator('.board-filter button', { hasText: 'Announcements' }).click(); await page.waitForTimeout(300); const onlyAnnouncementsShown = await page.locator('.post-row').count() === 1 && !(await page.locator('.event-row').isVisible().catch(() => false)); record('Event Board: Announcements filter hides events', onlyAnnouncementsShown); // ── Seeded demo neighbourhood ── const demoPage = await browser.newPage({ viewport: { width: 390, height: 844 } }); demoPage.on('console', m => { if (m.type() === 'error') consoleErrors.push('[demo] ' + m.text()); }); await demoPage.goto(BASE, { waitUntil: 'networkidle' }); await demoPage.locator('.field:has-text("Email") input').fill('nf.demo.ismail@gmail.com'); await demoPage.locator('.field:has-text("Password") input').fill('DemoPassword123!'); await demoPage.locator('button.btn-primary', { hasText: 'Sign in' }).click(); await demoPage.waitForTimeout(1500); if (await demoPage.locator('.switcher-screen').isVisible().catch(() => false)) { await demoPage.locator('.family-row').first().click(); await demoPage.waitForTimeout(1000); } await gotoTab(demoPage, 'Neighbourhood'); await demoPage.waitForTimeout(800); record('Demo neighbourhood: Masjid Al-Falah Kariah is visible to the demo owner', await demoPage.locator('.active-card', { hasText: 'Masjid Al-Falah Kariah' }).isVisible().catch(() => false)); const demoPostCount = await demoPage.locator('.post-row').count(); record('Demo neighbourhood: has real seeded content (7 posts)', demoPostCount === 7, `${demoPostCount} posts`); const demoEventCount = await demoPage.locator('.event-row').count(); record('Demo neighbourhood: has real seeded events (4 events)', demoEventCount === 4, `${demoEventCount} events`); await demoPage.close(); record('No uncaught JS console errors during full session', consoleErrors.length === 0, consoleErrors.slice(0, 5).join(' || ')); await browser.close(); const passCount = results.filter(r => r.pass).length; const failCount = results.length - passCount; console.log(`\n${passCount} passed, ${failCount} failed, ${results.length} total`); if (failCount > 0) results.filter(r => !r.pass).forEach(r => console.log(` - ${r.name}: ${r.detail}`)); process.exit(failCount > 0 ? 1 : 0); } main().catch(e => { console.error('SCRIPT ERROR:', e); process.exit(2); });