fix(frontend): a11y backdrop, ≥44px PIN button, test-ids on auth & upload

- account/+page.svelte: remove `aria-hidden="true"` from the
  leave-confirm and data-mode-warning bottom-sheet backdrops. The
  attribute cascaded into the dialog children, making the inner
  Abmelden/Aktivieren/Abbrechen buttons unreachable in the accessibility
  tree (and to Playwright's `getByRole`). Discovered while writing the
  E2E suite; the visual layout is unchanged.

- join/+page.svelte: bump the PIN-copy button from `py-1` (28px tall) to
  `min-h-11 min-w-11 py-2` so it clears the ≥44px touch-target floor on
  mobile. Touch-target audit revealed the gap.

- data-testid attributes on stable interactive elements (join name input,
  join submit, PIN modal + copy + continue, recovery PIN + submit + try-
  different-name, admin login password + submit + error, recover name +
  PIN + submit + error, upload header submit + sticky submit + caption
  textarea). Targeted at ~20 spots where semantic locators were ambiguous
  (e.g. two "Hochladen" buttons on /upload, German strings that may iterate).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-16 19:01:54 +02:00
parent 05f76514a2
commit 1cdab21514
5 changed files with 25 additions and 10 deletions

View File

@@ -384,7 +384,7 @@
<!-- Data-mode warning bottom sheet — shown once when the user picks Original. --> <!-- Data-mode warning bottom sheet — shown once when the user picks Original. -->
{#if dataModeWarningOpen} {#if dataModeWarningOpen}
<div class="fixed inset-0 z-50 flex items-end bg-black/40" onclick={() => (dataModeWarningOpen = false)} aria-hidden="true"> <div class="fixed inset-0 z-50 flex items-end bg-black/40" onclick={() => (dataModeWarningOpen = false)}>
<div <div
class="w-full rounded-t-2xl bg-white px-5 pb-10 pt-6 dark:bg-gray-900" class="w-full rounded-t-2xl bg-white px-5 pb-10 pt-6 dark:bg-gray-900"
onclick={(e) => e.stopPropagation()} onclick={(e) => e.stopPropagation()}
@@ -418,7 +418,7 @@
<!-- Leave-confirm bottom sheet --> <!-- Leave-confirm bottom sheet -->
{#if leaveConfirmOpen} {#if leaveConfirmOpen}
<div class="fixed inset-0 z-50 flex items-end bg-black/40" onclick={() => (leaveConfirmOpen = false)} aria-hidden="true"> <div class="fixed inset-0 z-50 flex items-end bg-black/40" onclick={() => (leaveConfirmOpen = false)}>
<div <div
class="w-full rounded-t-2xl bg-white px-5 pb-10 pt-6 dark:bg-gray-900" class="w-full rounded-t-2xl bg-white px-5 pb-10 pt-6 dark:bg-gray-900"
onclick={(e) => e.stopPropagation()} onclick={(e) => e.stopPropagation()}

View File

@@ -45,16 +45,18 @@
bind:value={password} bind:value={password}
placeholder="Passwort" placeholder="Passwort"
autocomplete="current-password" autocomplete="current-password"
data-testid="admin-password-input"
class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-lg text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-lg text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
/> />
{#if error} {#if error}
<p class="mb-3 text-sm text-red-600 dark:text-red-400">{error}</p> <p class="mb-3 text-sm text-red-600 dark:text-red-400" data-testid="admin-login-error">{error}</p>
{/if} {/if}
<button <button
type="submit" type="submit"
disabled={loading || !password} disabled={loading || !password}
data-testid="admin-login-submit"
class="w-full rounded-lg bg-blue-600 px-4 py-3 text-lg font-medium text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400" class="w-full rounded-lg bg-blue-600 px-4 py-3 text-lg font-medium text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400"
> >
{loading ? 'Wird angemeldet…' : 'Anmelden'} {loading ? 'Wird angemeldet…' : 'Anmelden'}

View File

@@ -111,16 +111,18 @@
maxlength={4} maxlength={4}
inputmode="numeric" inputmode="numeric"
pattern="[0-9]*" pattern="[0-9]*"
data-testid="recovery-pin-input"
class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-center text-2xl font-mono tracking-widest text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100" class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-center text-2xl font-mono tracking-widest text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100"
/> />
{#if recoveryError} {#if recoveryError}
<p class="mb-3 text-sm text-red-600 dark:text-red-400">{recoveryError}</p> <p class="mb-3 text-sm text-red-600 dark:text-red-400" data-testid="recovery-error">{recoveryError}</p>
{/if} {/if}
<button <button
type="submit" type="submit"
disabled={recoveryLoading || recoveryPin.length < 4} disabled={recoveryLoading || recoveryPin.length < 4}
data-testid="recovery-submit"
class="mb-3 w-full rounded-lg bg-blue-600 px-4 py-3 font-medium text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400" class="mb-3 w-full rounded-lg bg-blue-600 px-4 py-3 font-medium text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400"
> >
{recoveryLoading ? 'Wird angemeldet...' : 'Anmelden'} {recoveryLoading ? 'Wird angemeldet...' : 'Anmelden'}
@@ -129,6 +131,7 @@
<button <button
onclick={tryDifferentName} onclick={tryDifferentName}
data-testid="try-different-name"
class="w-full rounded-lg border border-gray-300 px-4 py-3 font-medium text-gray-700 transition hover:bg-gray-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800" class="w-full rounded-lg border border-gray-300 px-4 py-3 font-medium text-gray-700 transition hover:bg-gray-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800"
> >
Anderen Namen wählen Anderen Namen wählen
@@ -145,16 +148,18 @@
bind:value={displayName} bind:value={displayName}
placeholder="Dein Name" placeholder="Dein Name"
maxlength={50} maxlength={50}
data-testid="join-name-input"
class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-lg text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-lg text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
/> />
{#if error} {#if error}
<p class="mb-3 text-sm text-red-600 dark:text-red-400">{error}</p> <p class="mb-3 text-sm text-red-600 dark:text-red-400" data-testid="join-error">{error}</p>
{/if} {/if}
<button <button
type="submit" type="submit"
disabled={loading || !displayName.trim()} disabled={loading || !displayName.trim()}
data-testid="join-submit"
class="w-full rounded-lg bg-blue-600 px-4 py-3 text-lg font-medium text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400" class="w-full rounded-lg bg-blue-600 px-4 py-3 text-lg font-medium text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400"
> >
{loading ? 'Wird geladen...' : 'Beitreten'} {loading ? 'Wird geladen...' : 'Beitreten'}
@@ -162,7 +167,7 @@
</form> </form>
<p class="mt-4 text-center text-sm"> <p class="mt-4 text-center text-sm">
<a href="/recover" class="text-blue-600 hover:underline dark:text-blue-400">Ich habe bereits einen Account</a> <a href="/recover" data-testid="link-to-recover" class="text-blue-600 hover:underline dark:text-blue-400">Ich habe bereits einen Account</a>
</p> </p>
{/if} {/if}
@@ -170,7 +175,7 @@
</div> </div>
{#if showPinModal} {#if showPinModal}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4"> <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4" data-testid="pin-modal">
<div class="w-full max-w-sm rounded-xl bg-white p-6 shadow-lg dark:bg-gray-900"> <div class="w-full max-w-sm rounded-xl bg-white p-6 shadow-lg dark:bg-gray-900">
<h2 class="mb-2 text-xl font-bold text-gray-900 dark:text-gray-100">Dein Wiederherstellungs-PIN</h2> <h2 class="mb-2 text-xl font-bold text-gray-900 dark:text-gray-100">Dein Wiederherstellungs-PIN</h2>
<p class="mb-4 text-sm text-gray-600 dark:text-gray-400"> <p class="mb-4 text-sm text-gray-600 dark:text-gray-400">
@@ -178,10 +183,11 @@
</p> </p>
<div class="mb-4 flex items-center justify-center gap-3 rounded-lg bg-gray-100 p-4 dark:bg-gray-800"> <div class="mb-4 flex items-center justify-center gap-3 rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
<span class="text-4xl font-mono font-bold tracking-widest text-gray-900 dark:text-gray-100">{pin}</span> <span class="text-4xl font-mono font-bold tracking-widest text-gray-900 dark:text-gray-100" data-testid="pin-display">{pin}</span>
<button <button
onclick={copyPin} onclick={copyPin}
class="rounded-md bg-gray-200 px-3 py-1 text-sm font-medium text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600" data-testid="pin-copy"
class="min-h-11 min-w-11 rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600"
> >
{copied ? 'Kopiert!' : 'Kopieren'} {copied ? 'Kopiert!' : 'Kopieren'}
</button> </button>
@@ -189,6 +195,7 @@
<button <button
onclick={goToFeed} onclick={goToFeed}
data-testid="continue-to-feed"
class="w-full rounded-lg bg-blue-600 px-4 py-3 font-medium text-white transition hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-400" class="w-full rounded-lg bg-blue-600 px-4 py-3 font-medium text-white transition hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-400"
> >
Weiter zur Galerie Weiter zur Galerie

View File

@@ -50,6 +50,7 @@
bind:value={displayName} bind:value={displayName}
placeholder="Dein Name" placeholder="Dein Name"
maxlength={50} maxlength={50}
data-testid="recover-name-input"
class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-lg text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-lg text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
/> />
<input <input
@@ -59,16 +60,18 @@
maxlength={4} maxlength={4}
inputmode="numeric" inputmode="numeric"
pattern="[0-9]*" pattern="[0-9]*"
data-testid="recover-pin-input"
class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-center text-2xl font-mono tracking-widest text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-center text-2xl font-mono tracking-widest text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
/> />
{#if error} {#if error}
<p class="mb-3 text-sm text-red-600 dark:text-red-400">{error}</p> <p class="mb-3 text-sm text-red-600 dark:text-red-400" data-testid="recover-error">{error}</p>
{/if} {/if}
<button <button
type="submit" type="submit"
disabled={loading || !displayName.trim() || pin.length < 4} disabled={loading || !displayName.trim() || pin.length < 4}
data-testid="recover-submit"
class="w-full rounded-lg bg-blue-600 px-4 py-3 text-lg font-medium text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400" class="w-full rounded-lg bg-blue-600 px-4 py-3 text-lg font-medium text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400"
> >
{loading ? 'Wird geladen...' : 'Wiederherstellen'} {loading ? 'Wird geladen...' : 'Wiederherstellen'}

View File

@@ -119,6 +119,7 @@
<button <button
onclick={handleSubmit} onclick={handleSubmit}
disabled={stagedFiles.length === 0 || submitting} disabled={stagedFiles.length === 0 || submitting}
data-testid="upload-submit-header"
class="rounded-lg bg-blue-600 px-4 py-1.5 text-sm font-semibold text-white transition class="rounded-lg bg-blue-600 px-4 py-1.5 text-sm font-semibold text-white transition
hover:bg-blue-700 disabled:opacity-40 dark:bg-blue-500 dark:hover:bg-blue-400" hover:bg-blue-700 disabled:opacity-40 dark:bg-blue-500 dark:hover:bg-blue-400"
> >
@@ -179,6 +180,7 @@
bind:this={captionEl} bind:this={captionEl}
bind:value={caption} bind:value={caption}
maxlength={MAX_CAPTION_LENGTH} maxlength={MAX_CAPTION_LENGTH}
data-testid="upload-caption"
placeholder="Beschreibung hinzufügen… (#hashtags möglich)" placeholder="Beschreibung hinzufügen… (#hashtags möglich)"
rows="4" rows="4"
class="w-full resize-none rounded-xl border border-gray-200 bg-gray-50 px-4 py-3 text-sm text-gray-900 class="w-full resize-none rounded-xl border border-gray-200 bg-gray-50 px-4 py-3 text-sm text-gray-900
@@ -230,6 +232,7 @@
<button <button
onclick={handleSubmit} onclick={handleSubmit}
disabled={stagedFiles.length === 0 || submitting} disabled={stagedFiles.length === 0 || submitting}
data-testid="upload-submit"
class="flex w-full items-center justify-center gap-2 rounded-xl bg-blue-600 py-3.5 text-sm font-semibold class="flex w-full items-center justify-center gap-2 rounded-xl bg-blue-600 py-3.5 text-sm font-semibold
text-white transition hover:bg-blue-700 active:scale-[0.98] disabled:opacity-40 dark:bg-blue-500 dark:hover:bg-blue-400" text-white transition hover:bg-blue-700 active:scale-[0.98] disabled:opacity-40 dark:bg-blue-500 dark:hover:bg-blue-400"
> >