Skip to content

Commit

Permalink
fix(a11y): fix number of headings (#2183)
Browse files Browse the repository at this point in the history
Fixes #2162
  • Loading branch information
nolanlawson authored Nov 13, 2022
1 parent 1c6387a commit f10e9db
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 13 deletions.
9 changes: 8 additions & 1 deletion src/intl/en-US.js
Original file line number Diff line number Diff line change
Expand Up @@ -680,5 +680,12 @@ export default {
statusOptions: 'Status options',
confirm: 'Confirm',
closeDialog: 'Close dialog',
postPrivacy: 'Post privacy'
postPrivacy: 'Post privacy',
homeOnInstance: 'Home on {instance}',
statusesTimelineOnInstance: 'Statuses: {timeline} timeline on {instance}',
statusesHashtag: 'Statuses: #{hashtag} hashtag',
statusesThread: 'Statuses: thread',
statusesAccountTimeline: 'Statuses: account timeline',
statusesList: 'Statuses: list',
notificationsOnInstance: 'Notifications on {instance}'
}
5 changes: 5 additions & 0 deletions src/routes/_components/DynamicHeading.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{#if level === 2}
<h2 class={className || ''}><slot></slot></h2>
{:else}
<h1 class={className || ''}><slot></slot></h1>
{/if}
5 changes: 4 additions & 1 deletion src/routes/_components/TimelineHomePage.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
without a div wrapper due to sticky-positioned compose button.
TODO: this is a bit hacky due to code duplication
-->
<h1 class="sr-only">{headingLabel}</h1>
<div class="timeline-home-page" aria-busy={hideTimeline}>
{#if hidePage}
<LoadingPage />
Expand Down Expand Up @@ -30,6 +31,7 @@
import { store } from '../_store/store.js'
import LoadingPage from './LoadingPage.html'
import LazyComposeBox from './compose/LazyComposeBox.html'
import { formatIntl } from '../_utils/formatIntl.js'

export default {
oncreate () {
Expand All @@ -40,7 +42,8 @@
},
computed: {
hidePage: ({ $timelineInitialized, $timelinePreinitialized }) => !$timelineInitialized && !$timelinePreinitialized,
hideTimeline: ({ $timelineInitialized }) => !$timelineInitialized
hideTimeline: ({ $timelineInitialized }) => !$timelineInitialized,
headingLabel: ({ $currentInstance }) => formatIntl('intl.homeOnInstance', { instance: $currentInstance })
},
store: () => store,
components: {
Expand Down
2 changes: 1 addition & 1 deletion src/routes/_components/compose/ComposeBox.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{#if realm === 'home'}
<h1 class="sr-only">{intl.composeStatus}</h1>
<h2 class="sr-only">{intl.composeStatus}</h2>
{/if}
<ComposeFileDrop {realm} >
<div class="{computedClassName} {hideAndFadeIn}">
Expand Down
25 changes: 16 additions & 9 deletions src/routes/_components/timeline/Timeline.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<h1 class="sr-only">{label}</h1>
<DynamicHeading className="sr-only" level={headingLevel}>{label}</DynamicHeading>
<FocusRestoration realm={focusRealm}>
<div class="timeline" role="feed">
{#if components}
Expand Down Expand Up @@ -26,6 +26,7 @@ <h1 class="sr-only">{label}</h1>
<ScrollListShortcuts />
<script>
import { store } from '../../_store/store.js'
import DynamicHeading from '../DynamicHeading.html'
import Status from '../status/Status.html'
import LoadingFooter from './LoadingFooter.html'
import MoreHeaderVirtualWrapper from './MoreHeaderVirtualWrapper.html'
Expand All @@ -51,6 +52,7 @@ <h1 class="sr-only">{label}</h1>
import { createMakeProps } from '../../_actions/createMakeProps.js'
import { showMoreAndScrollToTop } from '../../_actions/showMoreAndScrollToTop.js'
import FocusRestoration from '../FocusRestoration.html'
import { formatIntl } from '../../_utils/formatIntl.js'

export default {
oncreate () {
Expand Down Expand Up @@ -89,20 +91,23 @@ <h1 class="sr-only">{label}</h1>
),
label: ({ timeline, $currentInstance, timelineType, timelineValue }) => {
if (timelines[timeline]) {
return `Statuses: ${timelines[timeline].label} timeline on ${$currentInstance}`
return formatIntl('intl.statusesTimelineOnInstance', {
timeline: timelines[timeline].label,
instance: $currentInstance
})
}

switch (timelineType) {
case 'tag':
return `Statuses: #${timelineValue} hashtag`
return formatIntl('intl.statusesHashtag', { hashtag: timelineValue })
case 'status':
return 'Statuses: thread'
return 'intl.statusesThread'
case 'account':
return 'Statuses: account timeline'
return 'intl.statusesAccountTimeline'
case 'list':
return 'Statuses: list'
return 'intl.statusesList'
case 'notifications':
return `Notifications on ${$currentInstance}`
return formatIntl('intl.notificationsOnInstance', { instance: $currentInstance })
}
},
timelineType: ({ $currentTimelineType }) => $currentTimelineType,
Expand All @@ -127,7 +132,8 @@ <h1 class="sr-only">{label}</h1>
onClick: showMoreItemsForCurrentTimeline
}
},
focusRealm: ({ $currentInstance, timeline }) => `${$currentInstance}-${timeline}`
focusRealm: ({ $currentInstance, timeline }) => `${$currentInstance}-${timeline}`,
headingLevel: ({ timeline, timelineType }) => timeline === 'home' || timelineType === 'status' ? 2 : 1
},
store: () => store,
methods: {
Expand Down Expand Up @@ -232,7 +238,8 @@ <h1 class="sr-only">{label}</h1>
components: {
ScrollListShortcuts,
Shortcut,
FocusRestoration
FocusRestoration,
DynamicHeading
}
}
</script>
2 changes: 1 addition & 1 deletion src/routes/_pages/community/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{#if $isUserLoggedIn}
<h1 class="sr-only">{intl.community}</h1>
<div class="community-page">

<FocusRestoration realm="community">
<RadioGroup
id="pinnables"
Expand Down
1 change: 1 addition & 0 deletions src/routes/_pages/search.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{#if $isUserLoggedIn}
<h1 class="sr-only">{intl.search}</h1>
<div class="search-page">
<Search></Search>
</div>
Expand Down
52 changes: 52 additions & 0 deletions tests/spec/042-headings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
settingsNavButton,
notificationsNavButton,
localTimelineNavButton,
communityNavButton,
searchNavButton,
getNumElementsMatchingSelector,
getUrl, getNthStatus
} from '../utils'
import { loginAsFoobar } from '../roles'

fixture`042-headings.js`
.page`http://localhost:4002`

async function testHeadings (t, loggedIn) {
const navButtons = [
{ button: notificationsNavButton, url: 'notifications' },
{ button: localTimelineNavButton, url: 'local' },
{ button: communityNavButton, url: 'community' },
{ button: searchNavButton, url: 'search' },
{ button: settingsNavButton, url: 'settings' }
]

// home page
await t
.expect(getNumElementsMatchingSelector('h1')()).eql(1)

if (loggedIn) {
// status page
await t
.click(getNthStatus(1))
.expect(getUrl()).contains('status')
.expect(getNumElementsMatchingSelector('h1')()).eql(1)
}

// non-home pages
for (const { button, url } of navButtons) {
await t
.click(button)
.expect(getUrl()).contains(url)
.expect(getNumElementsMatchingSelector('h1')()).eql(1)
}
}

test('Only one <h1> when not logged in', async t => {
await testHeadings(t, false)
})

test('Only one <h1> when logged in', async t => {
await loginAsFoobar(t)
await testHeadings(t, true)
})
6 changes: 6 additions & 0 deletions tests/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,12 @@ export function getNthPinnedStatusFavoriteButton (n) {
return $(`${getNthPinnedStatusSelector(n)} .status-toolbar button:nth-child(3)`)
}

export const getNumElementsMatchingSelector = (selector) => (exec(() => {
return document.querySelectorAll(selector).length
}, {
dependencies: { selector }
}))

export async function validateTimeline (t, timeline) {
const timeout = 30000
for (let i = 0; i < timeline.length; i++) {
Expand Down

0 comments on commit f10e9db

Please sign in to comment.