fix: validate image order and alt text updates
Quality / test (push) Canceled after 0s

This commit is contained in:
2026-09-11 00:18:11 -04:00
parent 0e81fc4022
commit cce395baf6
11 changed files with 169 additions and 14 deletions
@@ -55,3 +55,33 @@ export const DisabledAfterRefreshFailure = {
}
},
};
export const AltTextLimit = {
args: { images: [{ ...images[0], altText: 'card '.repeat(200) }] },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole('button', { name: 'Edit alt text' }));
const input = canvas.getByLabelText('Alt text');
await expect(input).toHaveAttribute('maxlength', '1000');
await expect(input).toHaveAccessibleDescription(/up to 1,000 characters/);
await userEvent.type(input, 'extra');
await expect(input).toHaveValue('card '.repeat(200));
await userEvent.clear(input);
await userEvent.type(input, ' Front of card ');
await userEvent.click(canvas.getByRole('button', { name: 'Save alt text' }));
await expect(input).toHaveValue('Front of card');
},
};
export const OversizedExistingAltText = {
args: { images: [{ ...images[0], altText: 'card '.repeat(201) }] },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole('button', { name: 'Edit alt text' }));
await expect(canvas.getByRole('alert')).toHaveTextContent('Alt text must be 1,000 characters or fewer.');
await expect(canvas.getByLabelText('Alt text')).toHaveAttribute('aria-invalid', 'true');
await expect(canvas.getByRole('button', { name: 'Save alt text' })).toBeDisabled();
await userEvent.clear(canvas.getByLabelText('Alt text'));
await expect(canvas.getByRole('button', { name: 'Save alt text' })).toBeEnabled();
await expect(canvas.queryByRole('alert')).toBeNull();
},
};
@@ -23,7 +23,10 @@ function beginEditAltText(image) {
}
function saveAltText(imageId) {
emit('alt-text', { id: imageId, altText: altTextDrafts[imageId] ?? '' });
const altText = (altTextDrafts[imageId] ?? '').trim();
if (props.isSaving || props.disabled || altText.length > 1000) return;
altTextDrafts[imageId] = altText;
emit('alt-text', { id: imageId, altText });
}
watch(() => props.images, (images) => {
@@ -85,11 +88,14 @@ function handleDrop(event) {
<div class="admin-image-manager__row">
<label class="fixture-label" :for="`admin-alt-text-${image.id}`">Alt text</label>
<input :id="`admin-alt-text-${image.id}`" v-model="altTextDrafts[image.id]" class="fixture-input" :disabled="isSaving || disabled || editingImageId !== image.id" />
<input :id="`admin-alt-text-${image.id}`" v-model="altTextDrafts[image.id]" class="fixture-input" maxlength="1000" :aria-describedby="`admin-alt-help-${image.id}${(altTextDrafts[image.id] ?? '').trim().length > 1000 ? ` admin-alt-error-${image.id}` : ''}`" :aria-invalid="(altTextDrafts[image.id] ?? '').trim().length > 1000" :disabled="isSaving || disabled || editingImageId !== image.id" />
<button v-if="editingImageId !== image.id" class="button button--secondary" type="button" :disabled="isSaving || disabled" @click="beginEditAltText(image)">Edit alt text</button>
<button v-else class="button button--primary" type="button" :disabled="isSaving || disabled" @click="saveAltText(image.id)">Save alt text</button>
<button v-else class="button button--primary" type="button" :disabled="isSaving || disabled || (altTextDrafts[image.id] ?? '').trim().length > 1000" @click="saveAltText(image.id)">Save alt text</button>
</div>
<p :id="`admin-alt-help-${image.id}`">Describe the image in up to 1,000 characters. Clear the field to remove its alt text.</p>
<p :id="`admin-alt-error-${image.id}`" v-if="(altTextDrafts[image.id] ?? '').trim().length > 1000" class="state state--error" role="alert">Alt text must be 1,000 characters or fewer.</p>
<div class="admin-image-manager__row">
<button class="button button--secondary" type="button" :disabled="isSaving || disabled || image.isPrimary" @click="emit('primary', image.id)">Make primary</button>
<button class="button button--secondary" type="button" :disabled="isSaving || disabled || index === 0" @click="emit('move', { id: image.id, direction: 'up' })">Move up</button>