From 76ebe912f0969743c4c6c43d5e65d54905ffc740 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Tue, 20 Oct 2026 07:57:27 -0700 Subject: [PATCH] test(images): the singular copy when scrolling loads one image; countCopy in the shared inventory helper --- tests/browser/concept-inventory.mjs | 4 ++++ tests/browser/images-concept.spec.mjs | 20 +++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/browser/concept-inventory.mjs b/tests/browser/concept-inventory.mjs index dcdc813..7e0b560 100644 --- a/tests/browser/concept-inventory.mjs +++ b/tests/browser/concept-inventory.mjs @@ -11,6 +11,10 @@ export const SIZES=Object.freeze([[1600,1100],[390,844]]); export const THEMES=Object.freeze(['dark','light']); export const MOTIONS=Object.freeze(['no-preference','reduce']); +/** D35: production uses the singular for exactly one; the concept's plurals are hard-coded (app.js 447, 489, 517-521). */ +export const countCopy=text=>text.replace(/(^|\D)1 positive prompts\b/g,'$11 positive prompt') + .replace(/(^|\D)1 more images\b/g,'$11 more image').replace(/(^|\D)1 images\b/g,'$11 image'); + /** Seed the concept-shaped real fixture (tests/browser/workspace_concept_fixture.py) and return its manifest. */ export async function seedConcept(request,data={}) { const response=await request.post('/__test__/concept/reset',{data}); diff --git a/tests/browser/images-concept.spec.mjs b/tests/browser/images-concept.spec.mjs index 71ed912..de7c027 100644 --- a/tests/browser/images-concept.spec.mjs +++ b/tests/browser/images-concept.spec.mjs @@ -1,7 +1,7 @@ import {writeFile} from 'node:fs/promises'; import {test,expect} from '../../tools/vendor/node_modules/@playwright/test/index.mjs'; import {captureCanonicalState} from './history-canonical-comparison.mjs'; -import {SIZES,THEMES,MOTIONS,seedConcept,openConcept,openProduction,inventoryOf,diffInventory,mutate,stripExact} from './concept-inventory.mjs'; +import {SIZES,THEMES,MOTIONS,seedConcept,openConcept,openProduction,inventoryOf,diffInventory,mutate,stripExact,countCopy} from './concept-inventory.mjs'; // Task C7 of #249: the production Images page against the immutable concept, on the concept-shaped real fixture (24 // images of the concept's six prompts, its seeds, sizes and favorites). Heading, toolbar, filter row, caption, grid, @@ -21,10 +21,6 @@ const LOAD_MORE_EXTRA=2*PRODUCTION_BATCH-FIXTURE_IMAGES; const CARDS='.gallery-body > .image-grid > .media-card'; const WIDE_SIZES=Object.freeze([[1200,900],[1800,1100]]); -/** D35: production uses the singular for exactly one; the concept's plurals are hard-coded (app.js 447, 489, 517-521). */ -export const countCopy=text=>text.replace(/(^|\D)1 positive prompts\b/g,'$11 positive prompt') - .replace(/(^|\D)1 more images\b/g,'$11 more image').replace(/(^|\D)1 images\b/g,'$11 image'); - /** D17: " · " of the newest file (the fixture's own mtime), in the test's UTC zone. */ function captionDay(seconds,now=new Date()) { const day=value=>Date.UTC(value.getUTCFullYear(),value.getUTCMonth(),value.getUTCDate()); @@ -531,6 +527,20 @@ test('one appended image reads in the singular',async({page,request})=>{ await expect(page.locator('.infinite-continuation > p').first()).toHaveText(`You’re all caught up · ${total} images`); }); +// The same singular copy when the page's own observer loads the one image: the user only scrolls (no press). +test('one image appended by scrolling reads in the singular',async({page,request})=>{ + test.setTimeout(120_000); + await page.setViewportSize({width:1600,height:1100}); + const total=PRODUCTION_BATCH+1; + await seedConcept(request,{extra_images:total-FIXTURE_IMAGES}); + await openProductionLoaded(page,'dark','no-preference',total); + await expect(page.locator('#result-count')).toHaveText(new RegExp(`^${PRODUCTION_BATCH} of ${total} images`)); + await page.locator('.infinite-continuation').scrollIntoViewIfNeeded(); + await expect(page.locator('#result-count')).toHaveText(new RegExp(`^${total} of ${total} images`)); + await expect(page.locator('.toast')).toHaveText(countCopy(`1 more images loaded. ${total} of ${total}.`)); + await expect(page.locator('.infinite-continuation > p').first()).toHaveText(`You’re all caught up · ${total} images`); +}); + // D3 (one footer height for the collection): bounded rendering cannot measure unrendered cards, so every row reserves // the tallest measured footer. Two seedless files (D11 "W × H · PNG", one line) form the first row at 390px, where // every concept subtitle wraps: their row is as tall as the wrapped rows, and every concept row moves down by one.