dev1/tests/e2e/09-comparison-duplicate-remove.spec.mjs

123 lines
5.3 KiB
JavaScript

// @ts-check
import { test, expect } from '@playwright/test';
import { loadTestUser } from '../utils/testUser.js';
test.describe('@p0 Comparison: add → duplicate blocked → remove → persists', () => {
test.setTimeout(20000);
test('add one, block duplicate, remove and persist', async ({ page }) => {
const user = loadTestUser();
// Helpers
async function closeAnyOverlay() {
const overlay = page.locator('div.fixed.inset-0');
if (!(await overlay.isVisible({ timeout: 500 }).catch(() => false))) return;
const dlg = overlay.locator('div[role="dialog"], div.bg-white').first();
// Fill selects (neutral '3') if present
const selects = dlg.locator('select');
const sc = await selects.count().catch(() => 0);
for (let i = 0; i < sc; i++) {
const sel = selects.nth(i);
const has3 = await sel.locator('option[value="3"]').count().catch(() => 0);
if (has3) await sel.selectOption('3');
}
// Fill textbox if present
const tb = dlg.locator('input, textarea, [role="textbox"]').first();
if (await tb.isVisible().catch(() => false)) await tb.fill('3');
const save = dlg.getByRole('button', { name: /^(Save|Save Ratings|Confirm|Done|OK)$/i });
if (await save.isVisible({ timeout: 500 }).catch(() => false)) await save.click();
else await page.keyboard.press('Enter');
await overlay.waitFor({ state: 'hidden', timeout: 5000 }).catch(() => {});
}
async function ensureSuggestions() {
const firstTile = page.locator('div.grid button').first();
if (await firstTile.isVisible({ timeout: 1500 }).catch(() => false)) return;
const reloadBtn = page.getByRole('button', { name: /Reload Career Suggestions/i });
await expect(reloadBtn).toBeVisible();
await reloadBtn.click();
// Wait for cache
await expect
.poll(async () => {
return await page.evaluate(() => {
try {
const s = localStorage.getItem('careerSuggestionsCache');
const arr = s ? JSON.parse(s) : [];
return Array.isArray(arr) ? arr.length : 0;
} catch { return 0; }
});
}, { timeout: 60000, message: 'careerSuggestionsCache not populated' })
.toBeGreaterThan(0);
await expect(firstTile).toBeVisible({ timeout: 10000 });
}
// Sign in
await page.context().clearCookies();
await page.goto('/signin', { waitUntil: 'networkidle' });
await page.getByPlaceholder('Username', { exact: true }).fill(user.username);
await page.getByPlaceholder('Password', { exact: true }).fill(user.password);
await page.getByRole('button', { name: /^Sign In$/ }).click();
await page.waitForURL('**/signin-landing**', { timeout: 15000 });
// Explorer
await page.goto('/career-explorer', { waitUntil: 'networkidle' });
await expect(page.getByRole('heading', { name: /Explore Careers - use these tools/i })).toBeVisible();
await closeAnyOverlay();
// Ensure at least one tile is present
await ensureSuggestions();
// Open first suggestion -> modal
const firstTile = page.locator('div.grid button').first();
await expect(firstTile).toBeVisible({ timeout: 8000 });
const tileTitle = (await firstTile.textContent())?.replace('⚠️', '').trim() || null;
await firstTile.click();
// Add to Comparison (then ratings modal Save)
await expect(page.getByRole('button', { name: /Add to Comparison/i })).toBeVisible({ timeout: 15000 });
await page.getByRole('button', { name: /Add to Comparison/i }).click();
await closeAnyOverlay();
// Table row must exist for that title (fallback: any row exists)
const table = page.locator('table');
await table.waitFor({ state: 'attached', timeout: 8000 }).catch(() => {});
let row = null;
if (tileTitle) {
const cell = table.getByText(new RegExp(tileTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i')).first();
if (await cell.isVisible().catch(() => false)) row = cell.locator('xpath=ancestor::tr').first();
}
if (!row) row = table.locator('tbody tr').first();
await expect(row).toBeVisible({ timeout: 8000 });
// Try to add the same career again → expect duplicate alert
let sawDuplicate = false;
page.once('dialog', async d => {
if (/already in comparison/i.test(d.message())) sawDuplicate = true;
await d.accept();
});
// Open the same modal again (either by clicking the same tile or by search commit)
if (tileTitle) await firstTile.click();
else await page.locator('div.grid button').first().click();
await expect(page.getByRole('button', { name: /Add to Comparison/i })).toBeVisible({ timeout: 15000 });
await page.getByRole('button', { name: /Add to Comparison/i }).click();
await closeAnyOverlay();
expect(sawDuplicate).toBeTruthy();
// Remove the row
await row.getByRole('button', { name: /^Remove$/ }).click();
// Row should disappear
await expect(row).toBeHidden({ timeout: 8000 });
// Reload page → row should still be gone (persisted)
await page.reload({ waitUntil: 'networkidle' });
await table.waitFor({ state: 'attached', timeout: 8000 }).catch(() => {});
if (tileTitle) {
const cellAgain = table.getByText(new RegExp(tileTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'));
expect(await cellAgain.isVisible().catch(() => false)).toBeFalsy();
}
});
});