fix: align accessible UI and browser coverage

This commit is contained in:
SimpleTest 2026-07-22 17:20:30 +03:00
parent 3224d7d697
commit 1db67b1048
25 changed files with 65 additions and 53 deletions

View File

@ -63,12 +63,16 @@ test("activity approval, privacy controls, reporting, and moderation work end to
.getByLabel("Plan and expectations")
.fill("Meet at a public café for an hour of safe conversation.");
const startsAt = new Date(Date.now() + 4 * 60 * 60 * 1000).toISOString().slice(0, 16);
const joinDeadline = new Date(Date.now() + 2 * 60 * 60 * 1000)
.toISOString()
.slice(0, 16);
await organizer.page.getByLabel("Starts at (UTC)").fill(startsAt);
await organizer.page.getByLabel("Join requests close (UTC)").fill(joinDeadline);
const startsAt = new Date(Date.now() + 4 * 60 * 60 * 1000).toISOString();
const joinDeadline = new Date(Date.now() + 2 * 60 * 60 * 1000).toISOString();
const startsAtPicker = organizer.page.getByRole("group", { name: "Starts at" });
const joinDeadlinePicker = organizer.page.getByRole("group", {
name: "Join requests close",
});
await startsAtPicker.getByLabel("Date").fill(startsAt.slice(0, 10));
await startsAtPicker.getByLabel("Time").fill(startsAt.slice(11, 16));
await joinDeadlinePicker.getByLabel("Date").fill(joinDeadline.slice(0, 10));
await joinDeadlinePicker.getByLabel("Time").fill(joinDeadline.slice(11, 16));
await organizer.page.getByLabel("Total group capacity").fill("3");
await organizer.page
.getByLabel("Approximate public meeting area")
@ -173,9 +177,13 @@ test("activity approval, privacy controls, reporting, and moderation work end to
await expect(socialSection.getByText("No social links added.")).toBeVisible();
await gotoLiveView(participant.page, "/requests/new");
await expect(participant.page.getByLabel("Location visibility")).toHaveValue(
"exact_for_active_match",
);
const requestVisibility = participant.page.getByRole("group", {
name: "What should people see?",
});
await expect(
requestVisibility.getByRole("radio", { name: /Approximate area/ }),
).toBeChecked();
await expect(participant.page.getByText("After a helper accepts")).toBeVisible();
const adminEmail = process.env.E2E_ADMIN_EMAIL ?? "e2e-admin@example.invalid";
const admin =

View File

@ -12,14 +12,18 @@ test("public language selection persists across pages", async ({ browser }) => {
await page.goto("/?locale=ru");
await expect(page.locator("html")).toHaveAttribute("lang", "ru");
await expect(page.getByRole("heading", { name: "Помощь может быть ближе, чем кажется." })).toBeVisible();
await expect(
page.getByRole("heading", { name: "Нужна помощь рядом? Обратитесь к сообществу." }),
).toBeVisible();
await page.goto("/safety");
await expect(page.getByRole("heading", { name: "Правила безопасности" })).toBeVisible();
await page.goto("/?locale=uk");
await expect(page.locator("html")).toHaveAttribute("lang", "uk");
await expect(page.getByRole("heading", { name: "Допомога може бути ближче, ніж здається." })).toBeVisible();
await expect(
page.getByRole("heading", { name: "Потрібна допомога поруч? Зверніться до спільноти." }),
).toBeVisible();
await context.close();
});

View File

@ -8,11 +8,11 @@ test("public safety pages load and private discovery requires authentication", a
await page.goto("/");
await expect(
page.getByRole("heading", { name: "Help can be closer than you think." }),
page.getByRole("heading", { name: "Need help nearby? Ask the community." }),
).toBeVisible();
await expect(page.getByText("Not an emergency or medical service.")).toBeVisible();
await page.getByRole("link", { name: "Safety rules" }).click();
await page.getByRole("link", { name: "Safety rules", exact: true }).click();
await expect(page.getByRole("heading", { name: "Safety rules" })).toBeVisible();
await expect(page.getByRole("heading", { name: "Medicine requests" })).toBeVisible();

View File

@ -399,8 +399,8 @@ defmodule WhoNeedHelpWeb.CoreComponents do
|> assign(:errors, Enum.map(errors, &translate_error(&1)))
~H"""
<div class="fieldset mb-2">
<span class="label mb-1">{@label}</span>
<fieldset class="fieldset mb-2">
<legend class="label mb-1">{@label}</legend>
<div
id={"#{@id}-picker"}
phx-hook="DateTimePicker"
@ -457,12 +457,12 @@ defmodule WhoNeedHelpWeb.CoreComponents do
{label}
</button>
</div>
<p class="mt-2 text-xs text-base-content/55">
<p class="mt-2 text-xs text-base-content/65">
{gettext("Your time zone:")} <span data-datetime-timezone></span>
</p>
</div>
<.error :for={msg <- @errors}>{msg}</.error>
</div>
</fieldset>
"""
end

View File

@ -31,7 +31,7 @@
]}>
{status_label(notice.status)}
</span>
<span class="text-xs text-base-content/55">{category_label(notice.category)}</span>
<span class="text-xs text-base-content/65">{category_label(notice.category)}</span>
</div>
<p class="mt-3 line-clamp-2 text-sm text-base-content/70">{notice.explanation}</p>
<time class="mt-2 block text-xs text-base-content/50">{notice.inserted_at}</time>

View File

@ -46,7 +46,7 @@
value={@contact_email || @form[:contact_email].value}
readonly={not is_nil(@contact_email)}
/>
<p :if={@contact_email} class="text-xs text-base-content/55">
<p :if={@contact_email} class="text-xs text-base-content/65">
{gettext("Status updates go to the email address of your signed-in account.")}
</p>
<.input
@ -99,7 +99,7 @@
)
}
/>
<p class="text-xs text-base-content/55">
<p class="text-xs text-base-content/65">
{gettext(
"Identity and contact fields may be omitted for reports of sexual material involving a minor. Do not include or upload the material itself."
)}

View File

@ -18,7 +18,7 @@
</span>
</div>
<h1 class="mt-4 text-3xl font-black">{gettext("Content-removal notice")}</h1>
<p class="mt-2 text-sm text-base-content/55">{category_label(@notice.category)}</p>
<p class="mt-2 text-sm text-base-content/65">{category_label(@notice.category)}</p>
<section class="mt-6 rounded-2xl bg-base-200 p-5">
<h2 class="font-bold">{gettext("Reported locations")}</h2>
<ul class="mt-3 list-disc space-y-1 pl-5 text-sm">
@ -37,7 +37,7 @@
<h2 class="font-bold">{gettext("Decision or request for information")}</h2>
<p class="mt-2 whitespace-pre-wrap">{@notice.resolution_note}</p>
</section>
<p class="mt-5 text-xs text-base-content/55">
<p class="mt-5 text-xs text-base-content/65">
{gettext(
"Keep this status link private. The submitter identity is not shown publicly or automatically disclosed to the person who posted the reported content."
)}

View File

@ -43,7 +43,7 @@
readonly={not is_nil(@contact_email)}
required
/>
<p :if={@contact_email} class="text-xs text-base-content/55">
<p :if={@contact_email} class="text-xs text-base-content/65">
{gettext("Status updates go to the email address of your signed-in account.")}
</p>
<.input

View File

@ -78,7 +78,7 @@
<div class="mt-7 flex items-center justify-between gap-4 border-t border-base-300 pt-5">
<div>
<div class="text-sm font-semibold">{gettext("Approximate area")}</div>
<div class="text-xs text-base-content/55">
<div class="text-xs text-base-content/65">
{gettext("Exact address after matching")}
</div>
</div>

View File

@ -28,7 +28,7 @@
readonly={not is_nil(@contact_email)}
required
/>
<p :if={@contact_email} class="text-xs text-base-content/55">
<p :if={@contact_email} class="text-xs text-base-content/65">
{gettext("This request is linked to your signed-in account and its email address.")}
</p>
<.input

View File

@ -17,7 +17,7 @@
<div class="flex flex-wrap items-center gap-2">
<span class="badge badge-outline">{request.reference}</span>
<span class="badge badge-primary">{status_label(request.status)}</span>
<span class="text-xs text-base-content/55">{kind_label(request.kind)}</span>
<span class="text-xs text-base-content/65">{kind_label(request.kind)}</span>
</div>
<h2 class="mt-3 font-bold">{request.subject}</h2>
<time class="mt-1 block text-xs text-base-content/50">{request.inserted_at}</time>

View File

@ -48,7 +48,7 @@
readonly={not is_nil(@contact_email)}
required
/>
<p :if={@contact_email} class="text-xs text-base-content/55">
<p :if={@contact_email} class="text-xs text-base-content/65">
{gettext(
"This request is linked to your signed-in account. Replies go to its email address."
)}
@ -60,7 +60,7 @@
label={gettext("Describe the problem")}
required
/>
<p class="text-xs text-base-content/55">
<p class="text-xs text-base-content/65">
{gettext(
"Do not include passwords, payment card details, access codes, prescriptions, or unnecessary medical information."
)}

View File

@ -5,7 +5,7 @@
<span class="badge badge-primary">{status_label(@request.status)}</span>
</div>
<h1 class="mt-4 text-3xl font-black">{@request.subject}</h1>
<p class="mt-2 text-sm text-base-content/55">{kind_label(@request.kind)}</p>
<p class="mt-2 text-sm text-base-content/65">{kind_label(@request.kind)}</p>
<div class="mt-6 rounded-2xl bg-base-200 p-5">
<p class="whitespace-pre-wrap">{@request.details}</p>
</div>
@ -16,7 +16,7 @@
<h2 class="font-bold">{gettext("Support response")}</h2>
<p class="mt-2 whitespace-pre-wrap">{@request.resolution_note}</p>
</section>
<p class="mt-5 text-xs text-base-content/55">
<p class="mt-5 text-xs text-base-content/65">
{gettext(
"Keep this status link private. A copy is sent to the contact email when email delivery is configured."
)}

View File

@ -56,7 +56,7 @@
label={gettext("Sign up with Google")}
disabled={!@google_auth_enabled}
/>
<p :if={!@google_auth_enabled} class="text-center text-xs text-base-content/55">
<p :if={!@google_auth_enabled} class="text-center text-xs text-base-content/65">
{gettext("Google sign-in will be available after the operator configures it.")}
</p>
</.form>

View File

@ -79,7 +79,7 @@
label={gettext("Continue with Google")}
disabled={!@google_auth_enabled}
/>
<p :if={!@google_auth_enabled} class="mt-2 text-center text-xs text-base-content/55">
<p :if={!@google_auth_enabled} class="mt-2 text-center text-xs text-base-content/65">
{gettext("Google sign-in will be available after the operator configures it.")}
</p>
</.form>

View File

@ -49,7 +49,7 @@
label={gettext("Connect Google account")}
disabled={!@google_auth_enabled}
/>
<p :if={!@google_auth_enabled} class="mt-2 text-center text-xs text-base-content/55">
<p :if={!@google_auth_enabled} class="mt-2 text-center text-xs text-base-content/65">
{gettext("Google sign-in will be available after the operator configures it.")}
</p>
</.form>

View File

@ -254,7 +254,7 @@ defmodule WhoNeedHelpWeb.ActivityLive.Index do
</div>
<.icon name="hero-chevron-right" class="mt-2 size-5 shrink-0" />
</div>
<div class="mt-5 flex flex-wrap gap-x-5 gap-y-2 border-t border-base-300 pt-4 text-xs text-base-content/55">
<div class="mt-5 flex flex-wrap gap-x-5 gap-y-2 border-t border-base-300 pt-4 text-xs text-base-content/65">
<span>📍 {activity.location_label}</span>
<span>🕒 {Calendar.strftime(activity.starts_at, "%d.%m.%Y, %H:%M UTC")}</span>
<span>

View File

@ -170,7 +170,7 @@ defmodule WhoNeedHelpWeb.CategoryProposalLive do
<div class="mt-5 space-y-3">
<div
:if={@proposals == []}
class="rounded-2xl border border-dashed p-8 text-center text-base-content/55"
class="rounded-2xl border border-dashed p-8 text-center text-base-content/65"
>
{gettext("No proposals yet.")}
</div>

View File

@ -62,7 +62,7 @@ defmodule WhoNeedHelpWeb.LeaderboardLive do
</thead>
<tbody>
<tr :if={@leaders == []}>
<td colspan="7" class="py-10 text-center text-base-content/55">
<td colspan="7" class="py-10 text-center text-base-content/65">
{gettext("No completed help yet.")}
</td>
</tr>

View File

@ -434,7 +434,7 @@ defmodule WhoNeedHelpWeb.ModerationLive do
<div class="font-semibold">
{signal_label(signal.kind)} · {signal.subject.display_name}
</div>
<p class="mt-1 text-xs text-base-content/55">
<p class="mt-1 text-xs text-base-content/65">
{gettext(
"A signal requests human review; optional tracking absence is not proof of abuse."
)}

View File

@ -174,7 +174,7 @@ defmodule WhoNeedHelpWeb.ProfileLive do
label={gettext("Optional external thank-you link")}
placeholder="https://…"
/>
<p class="text-xs text-base-content/55">
<p class="text-xs text-base-content/65">
{gettext("The platform never handles, requires, splits, or guarantees a payment.")}
</p>
<.button class="btn btn-primary w-full">{gettext("Save profile")}</.button>
@ -208,7 +208,7 @@ defmodule WhoNeedHelpWeb.ProfileLive do
</div>
<div class="rounded-3xl bg-base-200 p-5">
<h3 class="font-bold">{gettext("Visible reviews")}</h3>
<p :if={@reviews == []} class="mt-3 text-sm text-base-content/55">
<p :if={@reviews == []} class="mt-3 text-sm text-base-content/65">
{gettext("None revealed yet.")}
</p>
<div :for={review <- @reviews} class="mt-3 border-t border-base-300 pt-3 text-sm">
@ -239,7 +239,7 @@ defmodule WhoNeedHelpWeb.ProfileLive do
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<h2 class="text-xl font-bold">{gettext("Social links")}</h2>
<p class="mt-1 text-sm text-base-content/55">
<p class="mt-1 text-sm text-base-content/65">
{gettext(
"Manually added links are public and marked unverified. A verified badge is only granted after completing a provider authorization flow."
)}
@ -265,7 +265,7 @@ defmodule WhoNeedHelpWeb.ProfileLive do
<div class="mt-5 grid gap-4 md:grid-cols-[1fr_.9fr]">
<div class="space-y-3">
<p :if={@social_identities == []} class="text-sm text-base-content/55">
<p :if={@social_identities == []} class="text-sm text-base-content/65">
{gettext("No social links added.")}
</p>
<div
@ -337,7 +337,7 @@ defmodule WhoNeedHelpWeb.ProfileLive do
<section class="mt-6 rounded-3xl border border-base-300 p-6">
<h2 class="text-xl font-bold">{gettext("Blocked users")}</h2>
<p :if={@blocks == []} class="mt-3 text-sm text-base-content/55">
<p :if={@blocks == []} class="mt-3 text-sm text-base-content/65">
{gettext("You have not blocked anyone.")}
</p>
<div

View File

@ -67,18 +67,18 @@ defmodule WhoNeedHelpWeb.PublicProfileLive do
<p :if={@user.bio not in [nil, ""]} class="mt-4 whitespace-pre-wrap text-base-content/70">
{@user.bio}
</p>
<p :if={@user.bio in [nil, ""]} class="mt-4 text-sm text-base-content/55">
<p :if={@user.bio in [nil, ""]} class="mt-4 text-sm text-base-content/65">
{gettext("This person has not added a public bio yet.")}
</p>
<div class="mt-7">
<h2 class="text-xl font-bold">{gettext("Public social links")}</h2>
<p class="mt-1 text-sm text-base-content/55">
<p class="mt-1 text-sm text-base-content/65">
{gettext(
"A verified badge means the account completed that provider's authorization flow. Manually added links remain unverified."
)}
</p>
<p :if={@user.social_identities == []} class="mt-4 text-sm text-base-content/55">
<p :if={@user.social_identities == []} class="mt-4 text-sm text-base-content/65">
{gettext("No public social links added.")}
</p>
<div class="mt-4 flex flex-wrap gap-3">
@ -144,10 +144,10 @@ defmodule WhoNeedHelpWeb.PublicProfileLive do
<section class="rounded-3xl border border-base-300 p-6">
<h2 class="text-xl font-bold">{gettext("Revealed reviews")}</h2>
<p class="mt-1 text-sm text-base-content/55">
<p class="mt-1 text-sm text-base-content/65">
{gettext("A review becomes public only after both people submit for that match.")}
</p>
<p :if={@reviews == []} class="mt-4 text-sm text-base-content/55">
<p :if={@reviews == []} class="mt-4 text-sm text-base-content/65">
{gettext("None revealed yet.")}
</p>
<article :for={review <- @reviews} class="mt-4 border-t border-base-300 pt-4 text-sm">

View File

@ -465,7 +465,7 @@ defmodule WhoNeedHelpWeb.RequestLive.Index do
</div>
<.icon name="hero-chevron-right" class="mt-2 size-5 shrink-0" />
</div>
<div class="mt-4 flex flex-wrap items-center gap-x-5 gap-y-2 border-t border-base-300 pt-4 text-xs text-base-content/55">
<div class="mt-4 flex flex-wrap items-center gap-x-5 gap-y-2 border-t border-base-300 pt-4 text-xs text-base-content/65">
<span class="inline-flex items-center gap-1.5">
<.icon name="hero-map-pin" class="size-4" /> {request.location_label}
</span>

View File

@ -1309,7 +1309,7 @@ defmodule WhoNeedHelpWeb.RequestLive.Show do
class="rounded-3xl border border-base-300 p-5"
>
<h3 class="font-bold">{gettext("Live location")}</h3>
<p class="mt-2 text-xs text-base-content/55">
<p class="mt-2 text-xs text-base-content/65">
{gettext(
"In a browser this works only while the page remains open. The Android app shows a persistent notification and can continue after it is minimized. Raw coordinates are removed when sharing stops."
)}
@ -1335,7 +1335,7 @@ defmodule WhoNeedHelpWeb.RequestLive.Show do
<div :if={@other_user_id} class="rounded-3xl border border-base-300 p-5">
<h3 class="font-bold">{gettext("Safety controls")}</h3>
<p class="mt-2 text-xs text-base-content/55">
<p class="mt-2 text-xs text-base-content/65">
{gettext(
"Reports go to moderators. Blocking hides requests in both directions and prevents new chat messages."
)}

View File

@ -149,7 +149,7 @@ defmodule WhoNeedHelpWeb.SupportOperationsLive do
<span :if={is_nil(notice.contact_verified_at)} class="badge badge-warning">
{gettext("contact unverified")}
</span>
<span class="text-xs text-base-content/55">{removal_category(notice.category)}</span>
<span class="text-xs text-base-content/65">{removal_category(notice.category)}</span>
<span :if={notice.response_due_at} class="badge badge-error badge-outline">
{gettext("review by %{time}", time: notice.response_due_at)}
</span>
@ -234,10 +234,10 @@ defmodule WhoNeedHelpWeb.SupportOperationsLive do
<span :if={is_nil(request.contact_verified_at)} class="badge badge-warning">
{gettext("contact unverified")}
</span>
<span class="text-xs text-base-content/55">{support_kind(request.kind)}</span>
<span class="text-xs text-base-content/65">{support_kind(request.kind)}</span>
</div>
<h3 class="mt-3 font-bold">{request.subject}</h3>
<p class="mt-1 text-xs text-base-content/55">{request.contact_email}</p>
<p class="mt-1 text-xs text-base-content/65">{request.contact_email}</p>
<p class="mt-3 whitespace-pre-wrap text-sm">{request.details}</p>
<.form
for={form}