diff --git a/tests/browser/legacy-system-links.spec.mjs b/tests/browser/legacy-system-links.spec.mjs index d08fd990..192265ea 100644 --- a/tests/browser/legacy-system-links.spec.mjs +++ b/tests/browser/legacy-system-links.spec.mjs @@ -82,6 +82,8 @@ for (const link of LINKS) { test('a System link sends no request from the legacy page', async ({ page, context, request }) => { for (const link of LINKS) { const control = await openLink(page, request, link); + // The page's own start-up reads (list, film strips, settings) finish first; then the click must add none. + await page.waitForLoadState('networkidle'); const sent = []; const record = item => sent.push(`${item.method()} ${item.url()}`); page.on('request', record); @@ -100,17 +102,29 @@ test('kept admin flows still work', async ({ page, context }) => { const texts = page.locator('#resultsList .prompt-text'); await expect(texts.first()).toBeVisible(); - // K1: sorting reorders the list, a search narrows it, and a selection enables the bulk buttons. + // K1: sorting reorders the list. for (const [value, order] of [['text_asc', 1], ['text_desc', -1]]) { const listed = page.waitForResponse(item => /\/prompt_manager\/(recent|search)\?/.test(item.url()) && item.url().includes(`sort=${value}`)); await page.locator('#sortBy').selectOption(value); await listed; await expect.poll(async () => { const shown = await texts.allTextContents(); - const sorted = [...shown].sort((a, b) => order * a.localeCompare(b)); + // The server sorts case-insensitively (SQLite NOCASE), not by locale. + const key = text => text.toLowerCase(); + const sorted = [...shown].sort((a, b) => order * (key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0)); return shown.length > 1 && shown.join('\n') === sorted.join('\n'); }).toBe(true); } + // K3: Add Prompt saves a prompt the list then shows. + await page.locator('#addPromptBtn').click(); + await page.locator('#addPromptText').fill('Kept flow lighthouse'); + const saved = page.waitForResponse(item => item.request().method() === 'POST' && item.url().endsWith('/prompt_manager/save')); + await page.locator('#saveNewPromptBtn').click(); + expect((await saved).ok()).toBe(true); + await expect(page.locator('#addPromptModal')).toBeHidden(); + await expect(texts.filter({ hasText: 'Kept flow lighthouse' })).toBeVisible(); + + // K1: a search narrows the list, and a selection enables the bulk buttons. const all = await texts.allTextContents(); const word = all[0].trim().split(/\s+/)[0]; const searched = page.waitForResponse(item => item.url().includes('/prompt_manager/search?') && item.url().includes('text=')); @@ -125,14 +139,6 @@ test('kept admin flows still work', async ({ page, context }) => { await page.locator('#resultsList .prompt-checkbox').first().check(); for (const id of ['#bulkDeleteBtn', '#bulkTagBtn', '#bulkCategoryBtn']) await expect(page.locator(id)).toBeEnabled(); - // K3: Add Prompt saves a prompt the list then shows. - await page.locator('#addPromptBtn').click(); - await page.locator('#addPromptText').fill('Kept flow lighthouse'); - const saved = page.waitForResponse(item => item.request().method() === 'POST' && item.url().endsWith('/prompt_manager/save')); - await page.locator('#saveNewPromptBtn').click(); - expect((await saved).ok()).toBe(true); - await expect(page.locator('#addPromptModal')).toBeHidden(); - // K4: Stats reads the statistics again; Export downloads the prompts. const stats = page.waitForResponse(item => item.url().endsWith('/prompt_manager/stats') && item.ok()); await page.locator('#statsBtn').click(); diff --git a/tests/vendor/legacy-coverage.test.mjs b/tests/vendor/legacy-coverage.test.mjs index f165af3b..4ab74223 100644 --- a/tests/vendor/legacy-coverage.test.mjs +++ b/tests/vendor/legacy-coverage.test.mjs @@ -153,6 +153,8 @@ test('a removed id or method is gone from its page', () => { html: '
', script: [ "document.getElementById('logLevel').value;", + "this.showModal('diagnosticsModal');", + '', 'document.querySelector("#logLimit option");', "const backdrops = ['settingsModal', 'duplicatesModal'];", 'class Admin {', @@ -170,6 +172,8 @@ test('a removed id or method is gone from its page', () => { assert.deepEqual(idUses('logLimit', page), ["querySelector('#logLimit')"]); assert.deepEqual(idUses('duplicatesModal', page), ["'duplicatesModal' in a modal list"]); assert.deepEqual(idUses('logLevelX', page), []); + assert.deepEqual(idUses('diagnosticsModal', page), ["a …Modal('diagnosticsModal') call"]); + assert.deepEqual(idUses('logsContainer', page), []); assert.deepEqual(methodUses('closeLogs', page), ['.closeLogs( called in the markup']); assert.deepEqual(methodUses('refreshLogs', page), ['refreshLogs() defined']); assert.deepEqual(methodUses('displayLogs', page), ['displayLogs() defined']); diff --git a/tools/vendor/legacy-coverage.mjs b/tools/vendor/legacy-coverage.mjs index 3afa5cca..4da4a74f 100644 --- a/tools/vendor/legacy-coverage.mjs +++ b/tools/vendor/legacy-coverage.mjs @@ -263,15 +263,21 @@ function removedMethods(row) { const escapeRegExp = text => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -/** Where `id` is still on a page: `id=""` in its markup or script, a lookup or a modal-list entry in its script. */ +/** + * Where `id` is still on a page: `id=""` in its markup or script; in its script a lookup, a `…Modal("")` call + * (`showModal`, `hideModal`) or a modal-list entry. + */ export function idUses(id, { html, script }) { const name = escapeRegExp(id); const code = stripJsComments(script); const uses = []; - if (new RegExp(`\\bid=["']${name}["']`).test(html)) uses.push(`id="${id}" in the markup`); - if (new RegExp(`\\bid=["']${name}["']`).test(code)) uses.push(`id="${id}" in the script`); + // `id=`, not `data-id=`: the attribute starts after a space or a tag's `