fix(forms): close completed drawers and restore portal styling

This commit is contained in:
DeCentN2Madness committed 2026-10-30 14:21:27 -07:00
1 parent e97f0ea9b4
commit 55adb79705
15 files changed
+598 -9

No files matched your search

+110 -3
View File
@@ -49,6 +49,7 @@ try {
browser = await chromium.launch(process.env.CHROMIUM_EXECUTABLE
? { headless: true, executablePath: process.env.CHROMIUM_EXECUTABLE }
: { headless: true });
evidence.browser = browser.version();
preview = spawn(process.execPath, ['node_modules/astro/bin/astro.mjs', 'preview', '--host', '127.0.0.1', '--port', String(port)], {
cwd: root,
env: { ...process.env, ASTRO_TELEMETRY_DISABLED: '1' },
@@ -67,9 +68,10 @@ try {
const duration = 30 * 86400000;
const staffCode = randomBytes(24).toString('hex');
const managementCode = randomBytes(24).toString('hex');
const styledFixture = '<section><h2>Synthetic Directory</h2><h3>Operational reports</h3><p><a class="hs-button arbitrary-legacy-class" style="position:fixed" onclick="window.portalUnsafe=true" href="https://example.invalid/report">Synthetic report</a></p><ul><li>Synthetic list item</li></ul><table><caption>Fixture directory</caption><thead><tr><th scope="col">Role</th><th scope="col">Contact</th></tr></thead><tbody><tr><td>Fixture role</td><td><a href="mailto:fixture@example.invalid">Fixture email</a></td></tr></tbody></table><p><a style="background-color:#ffbd59" href="https://example.invalid/second">Second fixture report</a></p><a href="javascript:alert(1)">Unsafe destination</a><iframe src="https://example.invalid/frame"></iframe><form action="https://example.invalid/post"><input></form></section>';
const payload = { alg: portalAlgorithm, streams: [
await encryptPortalStream('fixture-staff', '<h2>Synthetic Staff</h2><p>Staff fixture only</p><a href="https://example.invalid/document">Synthetic resource</a><script>window.portalUnsafe=true</script><img src="https://example.invalid/image">', staffCode),
await encryptPortalStream('fixture-management', '<h2>Synthetic Management</h2><p>Management fixture only</p>', managementCode),
await encryptPortalStream('fixture-staff', '<h2>Synthetic Staff</h2><p>Staff fixture only</p><a href="https://example.invalid/document">Synthetic resource</a><script>window.portalUnsafe=true</script><img src="https://example.invalid/image">' + styledFixture, staffCode),
await encryptPortalStream('fixture-management', '<h2>Synthetic Management</h2><p>Management fixture only</p>' + styledFixture, managementCode),
] };
const guard = async (context, inject = false) => {
await context.routeWebSocket('**/*', (socket) => { evidence.blocked.push({ method: 'WEBSOCKET' }); socket.close(); });
@@ -91,7 +93,7 @@ try {
return route.abort();
});
};
if (!process.argv.includes('--portal-only')) {
if (!process.argv.includes('--portal-only') && !process.argv.includes('--correction-only')) {
for (const width of widths) {
for (const target of routes.filter((r) => r.drawer)) {
const context = await browser.newContext({ viewport: { width, height: 844 }, serviceWorkers: 'block', reducedMotion: 'reduce' });
@@ -194,6 +196,92 @@ try {
evidence.checks++;
await backContext.close();
}
if (!process.argv.includes('--portal-only')) {
// Completion messages originate from a locally fulfilled cross-origin frame.
// No fixture submits anything; malformed browser envelopes are injected only in tests.
for (const locale of ['en', 'es']) {
const ctx = await browser.newContext({ serviceWorkers: 'block', viewport: { width: locale === 'es' ? 390 : 1440, height: 900 } });
await guard(ctx);
await ctx.addInitScript((key) => localStorage.setItem(key, String(Date.now() + 86400000)), key);
const p = await ctx.newPage();
await p.clock.install({ time: new Date('2026-10-30T12:00:00Z') });
await p.clock.pauseAt(new Date('2026-10-30T12:00:01Z'));
await p.goto(origin + (locale === 'es' ? '/es/aba-therapy' : '/aba-therapy'));
const d = p.locator('[data-service-inquiry-drawer]');
const ack = d.locator('[data-service-inquiry-completed]');
const trigger = p.locator('[data-service-inquiry-trigger]');
const send = async (changes = {}) => p.evaluate((changes) => {
const frame = document.querySelector('dialog iframe');
const source = changes.wrongSource ? window : frame.contentWindow;
dispatchEvent(new MessageEvent('message', { origin: changes.origin ?? 'https://form.jotform.com', source,
data: { action: 'submission-completed', formID: '262946078314059', ...changes.data } }));
}, changes);
await send(); // Closed and unactivated.
assert.equal(await d.getAttribute('data-submission-completed'), null);
await trigger.click(); await p.clock.runFor(600);
// CSS transition rendering uses browser time, independently of mocked JS timers.
await delay(550);
await p.waitForFunction(() => [...document.querySelectorAll('iframe')].some(f => f.src.includes('consultation')));
for (const wrong of [{ origin: 'https://form.jotform.com.evil.invalid' }, { wrongSource: true },
{ data: { formID: 'other' } }, { data: { action: 'setHeight' } }]) await send(wrong);
await p.clock.runFor(6000);
assert.equal(await d.evaluate(d => d.open), true);
assert.equal(await ack.isVisible(), false);
evidence.checks++;
const child = p.frames().find(f => f.url() === expectedUrl(locale));
assert.ok(child, 'authorized fixture frame loaded');
await child.evaluate(() => parent.postMessage({ action: 'submission-completed', formID: '262946078314059' }, '*'));
await p.waitForFunction(() => document.querySelector('dialog')?.dataset.submissionCompleted === 'true');
assert.equal(await ack.isVisible(), true);
assert.match(await ack.textContent(), locale === 'es' ? /¡Gracias!/ : /Thank you!/);
const expiry = await p.evaluate(key => Number(localStorage.getItem(key)), key);
const now = await p.evaluate(() => Date.now());
assert.equal(expiry, now + duration);
await p.clock.runFor(3000); await send(); // Duplicate must not restart countdown.
await p.clock.runFor(1999);
assert.equal(await d.evaluate(d => d.open && d.classList.contains('is-open')), true);
const acknowledgmentBox = await ack.boundingBox();
assert.ok(acknowledgmentBox && acknowledgmentBox.x >= 0 && acknowledgmentBox.y >= 0 &&
acknowledgmentBox.y + acknowledgmentBox.height <= 900, 'acknowledgment is inside the visible viewport');
await p.screenshot({ path: path.join(output, `completion-${locale}.png`) });
evidence.screenshots.push(`completion-${locale}.png`);
await p.clock.runFor(1);
assert.equal(await d.evaluate(d => d.open && !d.classList.contains('is-open')), true, 'slide-out starts after full 5 seconds');
await p.clock.runFor(500);
assert.equal(await d.evaluate(d => d.open), false);
assert.equal(await trigger.evaluate(e => e === document.activeElement), true);
assert.equal(await p.evaluate(key => Number(localStorage.getItem(key)), key), expiry);
evidence.checks++;
await p.evaluate(() => window.oldFixtureWindow = document.querySelector('dialog iframe').contentWindow);
const count = evidence.jotformAttempts.length;
await trigger.click(); await p.clock.runFor(600);
assert.equal(await ack.isVisible(), false);
assert.equal(await d.locator('iframe').count(), 1);
assert.equal(await p.evaluate(() => window.oldFixtureWindow === document.querySelector('dialog iframe').contentWindow), false);
await p.waitForFunction(() => document.querySelector('[data-jotform-form-a]')?.dataset.jotformDocumentLoaded === 'true');
assert.equal(evidence.jotformAttempts.length, count + 1);
await p.evaluate(() => dispatchEvent(new MessageEvent('message', { origin: 'https://form.jotform.com', source: window.oldFixtureWindow,
data: { action: 'submission-completed', formID: '262946078314059' } })));
assert.equal(await ack.isVisible(), false);
await send(); await p.clock.runFor(1000);
await p.keyboard.press('Escape'); await p.clock.runFor(500);
assert.equal(await d.evaluate(d => d.open), false);
await trigger.click(); await p.clock.runFor(5000);
assert.equal(await d.evaluate(d => d.open), true, 'manual close canceled prior countdown');
assert.equal(await ack.isVisible(), false);
evidence.checks++;
await send();
await p.evaluate(() => document.dispatchEvent(new Event('astro:before-swap')));
await p.clock.runFor(6000);
assert.equal(await d.evaluate(d => d.open), false);
await p.evaluate(() => document.dispatchEvent(new Event('astro:page-load')));
await trigger.click(); await p.clock.runFor(6000);
assert.equal(await ack.isVisible(), false);
assert.equal(await d.evaluate(d => d.open), true);
evidence.checks++;
await ctx.close();
}
}
// Actual portal UI with synthetic encrypted response fixtures, never real private source.
for (const width of widths) {
const ctx = await browser.newContext({ viewport: { width, height: 844 }, serviceWorkers: 'block' }); await guard(ctx, true);
@@ -212,6 +300,22 @@ try {
assert.equal(await p.locator('[data-portal-content] img, [data-portal-content] script').count(), 0);
assert.equal(await p.evaluate(() => document.activeElement?.hasAttribute('data-portal-content')), true);
assert.equal(await input.inputValue(), '');
const checkPresentation = async () => {
const content = p.locator('[data-portal-content]');
assert.equal(await content.locator('.portal-resource-button').count(), 2);
assert.equal(await content.locator('[style], [onclick], .arbitrary-legacy-class, iframe, form, input').count(), 0);
assert.equal(await content.locator('a[href^="javascript:"]').count(), 0);
assert.equal(await content.locator('th[scope="col"]').count(), 2);
const button = content.locator('.portal-resource-button').first();
assert.equal(await button.evaluate(e => getComputedStyle(e).backgroundColor), 'rgb(255, 189, 89)');
assert.equal(await content.locator('h2').first().evaluate(e => getComputedStyle(e).color), 'rgb(37, 64, 128)');
assert.ok(await content.locator('h2').first().evaluate(e => parseFloat(getComputedStyle(e).fontSize) >= 28));
assert.equal(await content.locator('.portal-contact-link').evaluate(e => getComputedStyle(e).color), 'rgb(169, 70, 0)');
await button.focus();
assert.equal(await button.evaluate(e => getComputedStyle(e).outlineStyle), 'solid');
assert.equal(await button.evaluate(e => getComputedStyle(e).outlineWidth), '3px');
};
await checkPresentation();
assert.ok(await p.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1));
await p.screenshot({ path: path.join(output, 'portal-synthetic-' + width + '.png'), fullPage: true });
evidence.screenshots.push('portal-synthetic-' + width + '.png');
@@ -220,6 +324,9 @@ try {
await p.locator('[data-portal-content]').waitFor({ state: 'visible' });
assert.match(await p.locator('[data-portal-content]').textContent(), /Synthetic Management/);
assert.doesNotMatch(await p.locator('[data-portal-content]').textContent(), /Staff fixture/);
await checkPresentation();
await p.screenshot({ path: path.join(output, 'portal-management-synthetic-' + width + '.png'), fullPage: true });
evidence.screenshots.push('portal-management-synthetic-' + width + '.png');
evidence.checks++; await ctx.close();
}
for (const js of [false, true]) {