diff --git a/e2e-info.cjs b/e2e-info.cjs new file mode 100644 index 0000000..774b539 --- /dev/null +++ b/e2e-info.cjs @@ -0,0 +1,49 @@ +// Verifies every tab has a working (i) info button that opens plain-language help. +const { chromium } = require('playwright'); +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 : ''}`); } + +const TABS = ['Coverage', 'Faraid', 'Assets', 'Wassiyah', 'Hibah', 'Family Waqf', 'Nominate', 'Trigger', 'Claims (H2)', 'Settings']; + +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 page.goto(BASE, { waitUntil: 'networkidle' }); + + for (const label of TABS) { + await page.locator('nav button.tab', { hasText: label }).click(); + await page.waitForTimeout(200); + + const infoBtn = page.locator('.module-header .info-btn'); + const btnVisible = await infoBtn.isVisible(); + record(`"${label}": info button visible`, btnVisible); + if (!btnVisible) continue; + + await infoBtn.click(); + await page.waitForTimeout(150); + const panelVisible = await page.locator('.info-panel').isVisible(); + const whatText = await page.locator('.info-panel .info-section p').first().textContent().catch(() => ''); + record(`"${label}": info panel opens with non-trivial explanation`, panelVisible && whatText.length > 40, `${whatText.length} chars`); + + // toggle closed + await infoBtn.click(); + await page.waitForTimeout(150); + const panelClosed = await page.locator('.info-panel').isVisible().catch(() => false); + record(`"${label}": info panel closes on second click`, !panelClosed); + } + + record('No uncaught JS console errors across all info panels', consoleErrors.length === 0, consoleErrors.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); }); diff --git a/src/App.svelte b/src/App.svelte index 156fa8b..2b04df9 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -10,6 +10,7 @@ import DigitalClaims from './lib/horizon2/DigitalClaims.svelte'; import { exportAll, deleteAll } from './lib/storage.js'; import { lang, setLang } from './lib/i18n.js'; + import InfoPanel from './lib/InfoPanel.svelte'; let currentLang = $state('en'); lang.subscribe(v => currentLang = v); @@ -73,7 +74,15 @@ {:else if activeTab === 8} {:else if activeTab === 9}
-

Settings

+
+

Settings

+ +

Your data is stored locally on this device. Nothing is sent to a third party.

@@ -132,6 +141,7 @@ :global(.btn-danger) { width: 100%; padding: 12px; border-radius: 8px; border: 1px solid rgba(239,68,68,0.4); background: rgba(239,68,68,0.1); color: #EF4444; font-weight: 600; cursor: pointer; margin-top: 12px; } :global(.btn-secondary) { width: 100%; padding: 12px; border-radius: 8px; border: none; background: rgba(255,255,255,0.08); color: #E8E4DC; font-weight: 600; cursor: pointer; } + .module-header { display: flex; align-items: center; margin-bottom: 4px; } .lang-switch { margin-bottom: 16px; } .lang-label { display: block; font-size: 12px; color: #B8B2A6; margin-bottom: 8px; } .lang-buttons { display: flex; gap: 8px; } diff --git a/src/lib/AssetRegistry.svelte b/src/lib/AssetRegistry.svelte index bf96cc8..1975912 100644 --- a/src/lib/AssetRegistry.svelte +++ b/src/lib/AssetRegistry.svelte @@ -1,6 +1,7 @@
-

Asset Registry

+
+

Asset Registry

+ +

Log what you own — feeds the Faraid Calculator, Wassiyah one-third meter, and Waqf corpus selector.

@@ -136,7 +150,8 @@ diff --git a/src/lib/NominationRegistry.svelte b/src/lib/NominationRegistry.svelte index eb2ad80..74c194f 100644 --- a/src/lib/NominationRegistry.svelte +++ b/src/lib/NominationRegistry.svelte @@ -12,6 +12,7 @@ import { load, save } from './storage.js'; import { suggestedChannel } from './nonprobate.js'; import Disclaimer from './Disclaimer.svelte'; + import InfoPanel from './InfoPanel.svelte'; const assets = load('assets', []); @@ -165,7 +166,19 @@
-

Nomination Registry

+
+

Nomination Registry

+ +
Third fast-path channel. EPF and Takaful/insurance nominations pay the nominee directly by law — no Faraid/probate involvement at all. Bank mandates and trust/nominee holdings need setup now, while alive, but also bypass the court queue once in place.

Log which non-probate channel covers each asset that can't be fully gifted (Hibah) or dedicated (Waqf).

@@ -276,7 +289,8 @@