{#if translation_key === 'filesharing.attributes.pbdf.sidn-pbdf.mobilenumber.mobilenumber'}
@@ -215,6 +218,11 @@
display: block;
}
+ .optional-text {
+ font-weight: var(--pg-font-weight-regular);
+ color: var(--pg-text-secondary);
+ }
+
.btn-delete {
all: unset;
aspect-ratio: 1 / 1;
diff --git a/src/lib/locales/en.json b/src/lib/locales/en.json
index f542d50..259ad6a 100644
--- a/src/lib/locales/en.json
+++ b/src/lib/locales/en.json
@@ -207,6 +207,7 @@
"emailRecipient": "Email address",
"emailRecipientPlaceholder": "recipient@example.com",
"RecipientsText": "Specify the recipients and what attributes they must prove to decrypt the files. Only the recipients will see this data.",
+ "requiredFieldsLegend": "Fields marked with * are required.",
"RecipientsOptionalHeading": "Additional required attributes",
"emailSender": "Email address",
"emailSenderHeading": "Your information",
@@ -289,7 +290,8 @@
"pbdf.gemeente.personalData.fullname": "Full name",
"pbdf.gemeente.personalData.fullname.placeholder": "John Doe",
"pbdf.gemeente.personalData.dateofbirth": "Date of birth",
- "pbdf.gemeente.personalData.dateofbirth.placeholder": "15-01-1990"
+ "pbdf.gemeente.personalData.dateofbirth.placeholder": "15-01-1990",
+ "optional": "optional"
}
},
"error": {
diff --git a/src/lib/locales/nl.json b/src/lib/locales/nl.json
index 0067ebe..17fce1c 100644
--- a/src/lib/locales/nl.json
+++ b/src/lib/locales/nl.json
@@ -206,6 +206,7 @@
"emailRecipient": "E-mailadres",
"emailRecipientPlaceholder": "ontvanger@voorbeeld.nl",
"RecipientsText": "Geef de ontvangers op en welke attributen ze moeten aantonen om de bestanden te ontsleutelen. Alleen de ontvangers kunnen deze gegevens zien.",
+ "requiredFieldsLegend": "Velden gemarkeerd met * zijn verplicht.",
"RecipientsOptionalHeading": "Extra vereiste attributen",
"emailSender": "E-mailadres",
"emailSenderHeading": "Jouw gegevens",
@@ -288,7 +289,8 @@
"pbdf.gemeente.personalData.fullname": "Volledige naam",
"pbdf.gemeente.personalData.fullname.placeholder": "Jan Jansen",
"pbdf.gemeente.personalData.dateofbirth": "Geboortedatum",
- "pbdf.gemeente.personalData.dateofbirth.placeholder": "15-01-1990"
+ "pbdf.gemeente.personalData.dateofbirth.placeholder": "15-01-1990",
+ "optional": "optioneel"
}
},
"error": {
diff --git a/tests/attribute-form-a11y.test.ts b/tests/attribute-form-a11y.test.ts
new file mode 100644
index 0000000..e0cfc6b
--- /dev/null
+++ b/tests/attribute-form-a11y.test.ts
@@ -0,0 +1,66 @@
+import { expect, test } from '@playwright/test'
+
+// Accessibility regression tests for the recipient attribute-entry form
+// (issues encryption4all/postguard-website#268 and #269).
+//
+// #268: a screenreader did not clearly announce whether a field was required.
+// The required email field must expose `aria-required`, carry a visible
+// asterisk, and the form must show an explanatory legend for the asterisk.
+// #269: it was not clear that the phone number and birthday attributes are
+// optional. Each optional attribute label must say "(optional)" so it is
+// announced to screenreader users.
+
+test.beforeEach(async ({ page }) => {
+ await page.goto('/fileshare/')
+ // The recipient form renders in the initial FileSelection state.
+ await expect(
+ page.getByRole('textbox', { name: /email address/i })
+ ).toBeVisible()
+})
+
+test('required email field exposes aria-required and a visible asterisk', async ({
+ page,
+}) => {
+ const email = page.getByRole('textbox', { name: /email address/i })
+ await expect(email).toHaveAttribute('aria-required', 'true')
+ // Native required is still present so browser validation also fires.
+ await expect(email).toHaveAttribute('required', '')
+
+ // The asterisk is rendered in the label and hidden from the a11y tree
+ // (the required state itself is conveyed by aria-required).
+ const asterisk = page.locator('label.field-label .required-asterisk')
+ await expect(asterisk).toHaveText('*')
+ await expect(asterisk).toHaveAttribute('aria-hidden', 'true')
+})
+
+test('the form shows a legend explaining the required-field asterisk', async ({
+ page,
+}) => {
+ const legend = page.locator('#required-fields-legend')
+ await expect(legend).toBeVisible()
+ await expect(legend).toContainText('*')
+
+ // The required email input points at the legend for assistive tech.
+ const email = page.getByRole('textbox', { name: /email address/i })
+ await expect(email).toHaveAttribute(
+ 'aria-describedby',
+ 'required-fields-legend'
+ )
+})
+
+test('optional attributes are announced as optional via their label', async ({
+ page,
+}) => {
+ // Add the mobile-number attribute (#269 phone number).
+ await page.getByRole('button', { name: /mobile phone number/i }).click()
+ const phone = page.getByLabel(/mobile phone number/i)
+ await expect(phone).toBeVisible()
+ // The accessible name of the field must include "(optional)".
+ await expect(phone).toHaveAccessibleName(/\(optional\)/i)
+
+ // Add the date-of-birth attribute (#269 birthday).
+ await page.getByRole('button', { name: /date of birth/i }).click()
+ const birthday = page.getByLabel(/date of birth/i)
+ await expect(birthday).toBeVisible()
+ await expect(birthday).toHaveAccessibleName(/\(optional\)/i)
+})