Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions scripts/check-santa-teresa-actions.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';

// Test the promoted entry into the real comparison, with controlled reports.
// External CBP navigation uses a fixture, never the live site or a login.
export async function checkSantaTeresaActions(context, baseUrl) {
const snapshot = JSON.parse(fs.readFileSync(new URL('../public/data/crossings.json', import.meta.url)));
const crossing = snapshot.crossings.find(c => c.port_number === '240801');
const slug = 'santa-teresa-santa-teresa-port-of-entry';
const detailPath = `/crossing/${slug}/`;
const comparisonPath = `/walk-or-drive/${slug}/`;
const sourceUrl = 'https://bwt.cbp.gov/details/06240801/POV';
const feedMatcher = url => ['/data/crossings.json', '/api/public/crossings'].includes(url.pathname);
let fail = false;
const feed = route => fail ? route.fulfill({ status: 503, body: 'fixture unavailable' })
: route.fulfill({ contentType: 'application/json', body: JSON.stringify(snapshot) });
const official = route => route.fulfill({ contentType: 'text/html', body: '<h1>CBP navigation fixture</h1>' });
await context.route(feedMatcher, feed);
await context.route(sourceUrl, official);
const page = await context.newPage();
page.setDefaultTimeout(10_000);
const errors = [];
page.on('pageerror', e => errors.push(e.message));
const now = Date.now();
await page.clock.install({ time: now });
const lane = minutes => ({ delay_minutes: minutes, status: minutes === 0 ? 'No Delay' : 'delay', lanes_open: 1, update_time: 'At 10:00 am MDT' });
const setWaits = (drive, walk) => {
crossing.northbound_wait_time = drive;
crossing.current_wait_time = drive;
crossing.lanes = {
passenger_standard: drive == null ? null : lane(drive),
pedestrian_standard: walk == null ? null : lane(walk),
};
};
try {
for (const lang of ['en', 'es']) {
const es = lang === 'es';
const navName = es ? 'Planifica tu cruce en Santa Teresa' : 'Plan your Santa Teresa crossing';
const actionName = es ? 'Compara a pie y en auto' : 'Compare walking and driving';
const officialName = es ? 'Reporte y horarios de CBP (abre otra pestaña)' : 'CBP report and hours (opens a new tab)';
await page.clock.setSystemTime(now);
fail = false;
crossing.port_status = 'Open';
crossing.hours = 'Fixture CBP hours';
snapshot.fetched_at = new Date(now).toISOString();
setWaits(60, 30);
await page.goto(baseUrl.href);
await page.evaluate(value => localStorage.setItem('borderPulse_language', value), lang);
await page.goto(new URL(detailPath, baseUrl).href);
const nav = page.getByRole('navigation', { name: navName, exact: true });
const action = nav.getByRole('link', { name: actionName, exact: true });
const source = nav.getByRole('link', { name: officialName, exact: true });
const checkEntry = async () => {
await nav.waitFor();
assert.equal(await page.locator(`#root a[href="${comparisonPath}"]`).count(), 1, 'move the comparison action; do not duplicate it');
for (const link of [action, source]) {
const box = await link.boundingBox();
assert.ok(box && box.y >= 0 && box.y + box.height <= page.viewportSize().height, 'planning actions fit on the first screen');
assert.ok(box.height >= 44, 'planning links meet the 44px target');
}
assert.equal(await source.getAttribute('href'), sourceUrl);
assert.equal(await source.getAttribute('target'), '_blank');
assert.match(await source.getAttribute('rel'), /noopener/);
assert.match(await source.getAttribute('rel'), /noreferrer/);
assert.ok(!/0 min|6 am|midnight|Recommended|Recomendado/.test(await nav.innerText()), 'navigation must not invent waits, hours or a recommendation');
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1));
};
await checkEntry();
let reached = false;
for (let i = 0; i < 20; i++) {
await page.keyboard.press('Tab');
if (await action.evaluate(el => document.activeElement === el)) { reached = true; break; }
}
assert.ok(reached, 'first-screen comparison is reachable by keyboard');
await page.keyboard.press('Tab');
assert.ok(await source.evaluate(el => document.activeElement === el), 'official source follows comparison in keyboard order');
const popupPromise = page.waitForEvent('popup');
await page.keyboard.press('Enter');
const popup = await popupPromise;
try {
await popup.getByRole('heading', { name: 'CBP navigation fixture', exact: true }).waitFor();
assert.equal(popup.url(), sourceUrl);
assert.equal(await popup.evaluate(() => window.opener === null), true);
} finally { await popup.close(); }
await action.focus();
await page.keyboard.press('Enter');
await page.waitForURL(url => url.pathname === comparisonPath);
await page.getByText(es ? 'Según las esperas reportadas' : 'Based on reported waits', { exact: true }).waitFor();
await page.goBack();
await page.waitForURL(url => url.pathname === detailPath);
// Enter from the promoted action just before the unchanged 90-minute limit.
snapshot.fetched_at = new Date(now - 90 * 60_000).toISOString();
await page.reload();
await checkEntry();
await action.click();
await page.getByText(es ? 'Según las esperas reportadas' : 'Based on reported waits', { exact: true }).waitFor();
fail = true;
await page.clock.runFor(61_000);
await page.getByTestId('walk-drive-unavailable').waitFor();
assert.ok(!/Based on reported waits|Según las esperas reportadas|Recommended|Recomendado|Walk\.|Camina\.|Drive\.|Maneja\.|It's a wash\.|Está parejo\./.test(await page.locator('#root').innerText()));
// A failed response did not create fresh advice; a new real timestamp does.
fail = false;
snapshot.fetched_at = new Date(now + 61_000).toISOString();
await page.evaluate(() => window.dispatchEvent(new Event('online')));
await page.getByText(es ? 'Según las esperas reportadas' : 'Based on reported waits', { exact: true }).waitFor();
await page.goBack();
await page.waitForURL(url => url.pathname === detailPath);
for (const [drive, walk] of [[60, null], [null, null]]) {
setWaits(drive, walk);
crossing.hours = '';
await page.reload();
await checkEntry();
await action.click();
await page.getByRole('heading', { level: 1, name: es ? `${crossing.name}: sin espera peatonal actual reportada` : `${crossing.name}: no current pedestrian wait reported`, exact: true }).waitFor();
assert.ok(!/\b0 min\b|Recommended|Recomendado/.test(await page.locator('#root').innerText()), 'absent waits do not become zero or a verdict');
await page.goBack();
await page.waitForURL(url => url.pathname === detailPath);
}
setWaits(0, 0);
await page.reload();
await checkEntry();
await action.click();
await page.getByText(es ? 'Según las esperas reportadas' : 'Based on reported waits', { exact: true }).waitFor();
assert.match(await page.locator('#root').innerText(), /\b0 min\b/, 'genuine zero is preserved');
await page.goBack();
crossing.port_status = 'Closed';
await page.reload();
await checkEntry();
await action.click();
await page.getByRole('heading', { level: 1, name: es ? `${crossing.name}: cerrado ahora` : `${crossing.name}: closed now`, exact: true }).waitFor();
assert.ok(!/\b0 min\b|Recommended|Recomendado/.test(await page.locator('#root').innerText()));
}
await page.goto(new URL('/crossing/columbus/', baseUrl).href);
await page.locator('#root h1').waitFor();
assert.equal(await page.getByRole('navigation', { name: /Santa Teresa/ }).count(), 0, 'no actions on unrelated ports');
assert.deepEqual(errors, []);
} finally {
await page.close();
await context.unroute(feedMatcher, feed);
await context.unroute(sourceUrl, official);
}
console.log('[santa-teresa-actions] PASS: EN/ES first-screen links, keyboard/source new tab, Back, aging/failed refresh/recovery, missing waits, zero and closure; other ports unchanged');
}
2 changes: 2 additions & 0 deletions scripts/smoke-routes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { checkWalkDriveFreshness } from './check-walk-drive-freshness.mjs';
import { checkWalkDriveAvailability } from './check-walk-drive-availability.mjs';
import { checkComparisonGuide } from './check-comparison-guide.mjs';
import { checkSentriRenewal } from './check-sentri-renewal.mjs';
import { checkSantaTeresaActions } from './check-santa-teresa-actions.mjs';
import { checkReportTimes } from './check-report-times.mjs';
import { checkAdSlotDiagnostics } from './check-ad-slot-diagnostics.mjs';
import { assertNoGlobalFaq, checkBuiltFaqScope, checkFaqNavigation } from './check-faq-scope.mjs';
Expand Down Expand Up @@ -335,6 +336,7 @@ async function runProfile(browser, profile, manifest, expectedCrossingCount) {
await checkWalkDriveFreshness(context, baseUrl);
await checkComparisonGuide(page, context, baseUrl);
await checkSentriRenewal(page, context, baseUrl);
await checkSantaTeresaActions(context, baseUrl);
} catch (error) {
failures.push(`${profile.name} growth interactions: ${error.stack || error.message}`);
}
Expand Down
20 changes: 20 additions & 0 deletions src/components/dashboard/SantaTeresaActions.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Link } from 'react-router-dom';

// Navigation does not establish current lane availability or resolve hours.
export default function SantaTeresaActions({ slug, language }) {
const es = language === 'es';
const linkClass = 'inline-flex min-h-11 items-center rounded-md border border-slate-200 px-3 py-2 text-sm font-medium text-emerald-700 underline underline-offset-4 dark:border-gray-700 dark:text-emerald-400';
const officialLabel = es ? 'Reporte y horarios de CBP' : 'CBP report and hours';
return (
<nav aria-label={es ? 'Planifica tu cruce en Santa Teresa' : 'Plan your Santa Teresa crossing'}
className="mb-4 grid grid-cols-2 gap-2">
<Link to={`/walk-or-drive/${slug}/`} className={linkClass}>
{es ? 'Compara a pie y en auto' : 'Compare walking and driving'}
</Link>
<a href="https://bwt.cbp.gov/details/06240801/POV" target="_blank" rel="noopener noreferrer"
aria-label={`${officialLabel} (${es ? 'abre otra pestaña' : 'opens a new tab'})`} className={linkClass}>
<span>{officialLabel} <span aria-hidden="true">↗</span></span>
</a>
</nav>
);
}
6 changes: 5 additions & 1 deletion src/pages/CrossingDetail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import CrossingShare from '@/components/dashboard/CrossingShare';
import CrossingCameras from '@/components/dashboard/CrossingCameras';
import { cameraSourceFor } from '@/lib/cameras';
import { crossingDisplayName, crossingSearchTitle } from '@/lib/crossingNames';
import SantaTeresaActions from '@/components/dashboard/SantaTeresaActions';
import SanYsidroDecision from '@/components/dashboard/SanYsidroDecision';
import { subscribeToBorderData } from '@/lib/liveDataSubscription';
import { buildSlugMap } from '@/lib/slugs';
Expand Down Expand Up @@ -426,6 +427,7 @@ export default function CrossingDetail() {
};
});
const isSanYsidro = canonicalSlug === 'san-ysidro';
const isSantaTeresa = String(crossing.port_number) === '240801';
const lowerCompareEntries = isSanYsidro
? compareEntries.filter(({ crossing: c }) => portToSlug[c.port_number] !== 'otay-mesa')
: compareEntries;
Expand Down Expand Up @@ -478,6 +480,8 @@ export default function CrossingDetail() {
</p>
</header>

{isSantaTeresa && <SantaTeresaActions slug={canonicalSlug} language={language} />}

<div className="mb-6">
<BorderCrossingCard
crossing={crossing}
Expand Down Expand Up @@ -827,7 +831,7 @@ export default function CrossingDetail() {
})}
</div>
)}
{hasPedestrianLane(crossing) && !isSanYsidro && (
{hasPedestrianLane(crossing) && !isSanYsidro && !isSantaTeresa && (
<div className="mt-2 text-xs">
<Link
to={`/walk-or-drive/${canonicalSlug}/`}
Expand Down
Loading