// @ts-check import { test, expect } from '@playwright/test'; import { loadTestUser } from '../utils/testUser.js'; test.describe('@p1 Educational Programs — distance filter (with geocode + schools stubs)', () => { test.setTimeout(20000); test('Distance (max) narrows results when ZIP is present', async ({ page }) => { const u = loadTestUser(); // ---- Stubs ---- // 1) Profile ZIP/state so distance can be computed (match any query suffix) await page.route('**/api/user-profile**', async route => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ zipcode: '30301', state: 'GA', area: 'Atlanta, GA' }) }); }); // 2) Geocode ZIP → lat/lng (actual endpoint used by clientGeocodeZip) await page.route('https://maps.googleapis.com/maps/api/geocode/json**', async route => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ status: 'OK', results: [ { geometry: { location: { lat: 33.7495, lng: -84.3883 } } } ] }) }); }); // 3) Schools list for CIP codes (cover common paths) const schools = [ // near Atlanta (~3–5 mi) { INSTNM: 'Atlanta Tech College', CIPDESC: 'Data Analytics', CREDDESC: 'Certificate', 'In_state cost': '5000', 'Out_state cost': '7000', LATITUDE: '33.80', LONGITUD: '-84.39', Website: 'atl.tech.edu', UNITID: '1001', State: 'GA' }, // far (Chicago, ~590 mi) { INSTNM: 'Chicago State', CIPDESC: 'Data Analytics', CREDDESC: 'Bachelor', 'In_state cost': '6000', 'Out_state cost': '8000', LATITUDE: '41.8781', LONGITUD: '-87.6298', Website: 'chicago.state.edu', UNITID: '2002', State: 'IL' } ]; const fulfillSchools = async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(schools) }); }; await page.route('**/api/**schools**', fulfillSchools); await page.route('**/api/**program**', fulfillSchools); // 4) Avoid premium KSA latency: return empty quickly await page.route('**/api/premium/ksa/**', async route => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: { knowledge: [], skills: [], abilities: [] } }) }); }); // ---- Sign in ---- await page.context().clearCookies(); await page.goto('/signin', { waitUntil: 'networkidle' }); await page.getByPlaceholder('Username', { exact: true }).fill(u.username); await page.getByPlaceholder('Password', { exact: true }).fill(u.password); await page.getByRole('button', { name: /^Sign In$/ }).click(); await page.waitForURL('**/signin-landing**', { timeout: 15000 }); // Seed selectedCareer so Programs loads without Explorer handoff await page.evaluate(() => { localStorage.setItem('selectedCareer', JSON.stringify({ title: 'Data Analyst', soc_code: '15-2051.00', cip_code: ['11.0802', '04.0201'] })); }); // ---- Go to Programs ---- await page.goto('/educational-programs', { waitUntil: 'networkidle' }); // Wait for cards to render const cards = page.locator('div.rounded.border.p-3.text-sm'); await expect(cards.first()).toBeVisible({ timeout: 30000 }); // Confirm we have at least one card up front const countBefore = await cards.count(); expect(countBefore).toBeGreaterThan(0); // Set Distance (max) to a small number to keep only the ATL card const distanceInput = page.getByLabel(/Distance \(max\)/i); await expect(distanceInput).toBeVisible(); await distanceInput.fill('10'); // ensure onChange/onBlur handlers fire await distanceInput.press('Enter').catch(() => {}); await distanceInput.blur().catch(() => {}); await page.waitForTimeout(200); // small debounce cushion // Trigger change — many components require blur or Enter await distanceInput.press('Enter').catch(() => {}); await distanceInput.blur().catch(() => {}); // After applying Distance(max)=10, the Chicago card should disappear // (don’t rely on count; assert by card names) await expect .poll(async () => { const names = await page .locator('div.rounded.border.p-3.text-sm strong') .allInnerTexts() .catch(() => []); const anyVisible = names.length > 0; const hasChicago = names.some(n => /chicago/i.test(n)); return anyVisible && !hasChicago; }, { timeout: 20000 }) .toBeTruthy(); // And at least one visible card should be the ATL school const namesAfter = await page .locator('div.rounded.border.p-3.text-sm strong') .allInnerTexts() .catch(() => []); expect(namesAfter.some(n => /atlanta/i.test(n))).toBeTruthy(); }); });