Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
db161766
Unverified
Commit
db161766
authored
Feb 03, 2020
by
Kyle Keating
Committed by
GitHub
Feb 03, 2020
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1740 from hypothesis/tag-editor-a11y-tests
Improve a11y testing for AutocompleteList and TagEditor
parents
1ef0b98d
4c8c6867
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
150 additions
and
85 deletions
+150
-85
autocomplete-list.js
src/sidebar/components/autocomplete-list.js
+17
-11
tag-editor.js
src/sidebar/components/tag-editor.js
+2
-0
autocomplete-list-test.js
src/sidebar/components/test/autocomplete-list-test.js
+21
-2
tag-editor-test.js
src/sidebar/components/test/tag-editor-test.js
+106
-71
autocomplete-list.scss
src/styles/sidebar/components/autocomplete-list.scss
+3
-0
variables.scss
src/styles/variables.scss
+1
-1
No files found.
src/sidebar/components/autocomplete-list.js
View file @
db161766
...
...
@@ -53,19 +53,25 @@ export default function AutocompleteList({
},
[
activeItem
,
itemPrefixId
,
list
,
listFormatter
,
onSelectItem
]);
const
props
=
id
?
{
id
}
:
{};
// only add the id if its passed
const
isHidden
=
list
.
length
===
0
||
!
open
;
return
(
<
div
className
=
"autocomplete-list"
>
{
list
.
length
>
0
&&
open
&&
(
<
ul
className
=
"autocomplete-list__items"
tabIndex
=
"-1"
aria
-
label
=
"Suggestions"
role
=
"listbox"
{...
props
}
>
{
items
}
<
/ul
>
<
div
className
=
{
classnames
(
{
'is-hidden'
:
isHidden
,
},
'autocomplete-list'
)}
>
<
ul
className
=
"autocomplete-list__items"
tabIndex
=
"-1"
aria
-
label
=
"Suggestions"
role
=
"listbox"
{...
props
}
>
{
items
}
<
/ul
>
<
/div
>
);
}
...
...
src/sidebar/components/tag-editor.js
View file @
db161766
...
...
@@ -109,6 +109,7 @@ function TagEditor({ onEditTags, tags: tagsService, tagList }) {
}
updateTags
([...
tagList
,
value
]);
setSuggestionsListOpen
(
false
);
setActiveItem
(
-
1
);
inputEl
.
current
.
value
=
''
;
inputEl
.
current
.
focus
();
...
...
@@ -248,6 +249,7 @@ function TagEditor({ onEditTags, tags: tagsService, tagList }) {
className
=
"tag-editor__input"
type
=
"text"
autoComplete
=
"off"
aria
-
label
=
"Add tag field"
aria
-
autocomplete
=
"list"
aria
-
activedescendant
=
{
activeDescendant
}
aria
-
controls
=
{
`
${
tagEditorId
}
-autocomplete-list`
}
...
...
src/sidebar/components/test/autocomplete-list-test.js
View file @
db161766
...
...
@@ -4,6 +4,7 @@ import { createElement } from 'preact';
import
AutocompleteList
from
'../autocomplete-list'
;
import
{
$imports
}
from
'../autocomplete-list'
;
import
{
checkAccessibility
}
from
'../../../test-util/accessibility'
;
import
mockImportedComponents
from
'../../../test-util/mock-imported-components'
;
describe
(
'AutocompleteList'
,
function
()
{
...
...
@@ -33,12 +34,12 @@ describe('AutocompleteList', function() {
it
(
'does not render the list when `open` is false'
,
()
=>
{
const
wrapper
=
createComponent
();
assert
.
is
False
(
wrapper
.
find
(
'.autocomplete-list__items'
).
exists
(
));
assert
.
is
True
(
wrapper
.
find
(
'.autocomplete-list'
).
hasClass
(
'is-hidden'
));
});
it
(
'does not render the list when `list` is empty'
,
()
=>
{
const
wrapper
=
createComponent
({
open
:
true
,
list
:
[]
});
assert
.
is
False
(
wrapper
.
find
(
'.autocomplete-list__items'
).
exists
(
));
assert
.
is
True
(
wrapper
.
find
(
'.autocomplete-list'
).
hasClass
(
'is-hidden'
));
});
it
(
'sets unique keys to the <li> items'
,
()
=>
{
...
...
@@ -160,4 +161,22 @@ describe('AutocompleteList', function() {
.
prop
(
'aria-selected'
)
);
});
it
(
'should pass a11y checks'
,
checkAccessibility
([
{
name
:
'list open'
,
content
:
()
=>
{
return
createComponent
({
open
:
true
});
},
},
{
name
:
'list open, first item selected'
,
content
:
()
=>
{
return
createComponent
({
open
:
true
,
activeItem
:
1
});
},
},
])
);
});
src/sidebar/components/test/tag-editor-test.js
View file @
db161766
...
...
@@ -2,6 +2,7 @@ import { mount } from 'enzyme';
import
{
createElement
}
from
'preact'
;
import
{
act
}
from
'preact/test-utils'
;
import
AutocompleteList
from
'../autocomplete-list'
;
import
TagEditor
from
'../tag-editor'
;
import
{
$imports
}
from
'../tag-editor'
;
...
...
@@ -112,70 +113,6 @@ describe('TagEditor', function() {
assert
.
isTrue
(
fakeTagsService
.
filter
.
calledWith
(
'tag3'
));
});
describe
(
'accessibility attributes and ids'
,
()
=>
{
it
(
'creates multiple <TagEditor> components with unique autocomplete-list `id` props'
,
()
=>
{
const
wrapper1
=
createComponent
();
const
wrapper2
=
createComponent
();
assert
.
notEqual
(
wrapper1
.
find
(
'AutocompleteList'
).
prop
(
'id'
),
wrapper2
.
find
(
'AutocompleteList'
).
prop
(
'id'
)
);
});
it
(
'sets the <AutocompleteList> `id` prop to the same value as the `aria-owns` attribute'
,
()
=>
{
const
wrapper
=
createComponent
();
wrapper
.
find
(
'AutocompleteList'
);
assert
.
equal
(
wrapper
.
find
(
'.tag-editor__combobox-wrapper'
).
prop
(
'aria-owns'
),
wrapper
.
find
(
'AutocompleteList'
).
prop
(
'id'
)
);
});
it
(
'sets `aria-expanded` value to match open state'
,
()
=>
{
const
wrapper
=
createComponent
();
wrapper
.
find
(
'input'
).
instance
().
value
=
'non-empty'
;
// to open list
typeInput
(
wrapper
);
assert
.
equal
(
wrapper
.
find
(
'.tag-editor__combobox-wrapper'
).
prop
(
'aria-expanded'
),
'true'
);
selectOption
(
wrapper
,
'tag4'
);
wrapper
.
update
();
assert
.
equal
(
wrapper
.
find
(
'.tag-editor__combobox-wrapper'
).
prop
(
'aria-expanded'
),
'false'
);
});
it
(
'sets the <AutocompleteList> `activeItem` prop to match the selected item index'
,
()
=>
{
function
checkAttributes
(
wrapper
)
{
const
activeDescendant
=
wrapper
.
find
(
'input'
)
.
prop
(
'aria-activedescendant'
);
const
itemPrefixId
=
wrapper
.
find
(
'AutocompleteList'
)
.
prop
(
'itemPrefixId'
);
const
activeDescendantIndex
=
activeDescendant
.
split
(
itemPrefixId
);
assert
.
equal
(
activeDescendantIndex
[
1
],
wrapper
.
find
(
'AutocompleteList'
).
prop
(
'activeItem'
)
);
}
const
wrapper
=
createComponent
();
wrapper
.
find
(
'input'
).
instance
().
value
=
'non-empty'
;
typeInput
(
wrapper
);
// initial aria-activedescendant value is "" when index is -1
assert
.
equal
(
wrapper
.
find
(
'input'
).
prop
(
'aria-activedescendant'
),
''
);
// 2 suggestions: ['tag3', 'tag4'];
navigateDown
(
wrapper
);
// press down once
checkAttributes
(
wrapper
);
navigateDown
(
wrapper
);
// press down again once
checkAttributes
(
wrapper
);
});
});
describe
(
'suggestions open / close'
,
()
=>
{
it
(
'closes the suggestions when selecting a tag from autocomplete-list'
,
()
=>
{
const
wrapper
=
createComponent
();
...
...
@@ -271,6 +208,8 @@ describe('TagEditor', function() {
assert
.
isTrue
(
fakeOnEditTags
.
calledWith
({
tags
:
tagList
}));
// hides the suggestions
assert
.
equal
(
wrapper
.
find
(
'AutocompleteList'
).
prop
(
'open'
),
false
);
// removes the selected index
assert
.
equal
(
wrapper
.
find
(
'AutocompleteList'
).
prop
(
'activeItem'
),
-
1
);
// assert the input value is cleared out
assert
.
equal
(
wrapper
.
find
(
'input'
).
instance
().
value
,
''
);
// input element should have focus
...
...
@@ -408,11 +347,107 @@ describe('TagEditor', function() {
});
});
// FIXME-A11Y
it
.
skip
(
'should pass a11y checks'
,
checkAccessibility
({
content
:
()
=>
createComponent
(),
})
);
describe
(
'accessibility attributes and ids'
,
()
=>
{
it
(
'creates multiple <TagEditor> components with unique autocomplete-list `id` props'
,
()
=>
{
const
wrapper1
=
createComponent
();
const
wrapper2
=
createComponent
();
assert
.
notEqual
(
wrapper1
.
find
(
'AutocompleteList'
).
prop
(
'id'
),
wrapper2
.
find
(
'AutocompleteList'
).
prop
(
'id'
)
);
});
it
(
'sets the <AutocompleteList> `id` prop to the same value as the `aria-owns` attribute'
,
()
=>
{
const
wrapper
=
createComponent
();
wrapper
.
find
(
'AutocompleteList'
);
assert
.
equal
(
wrapper
.
find
(
'.tag-editor__combobox-wrapper'
).
prop
(
'aria-owns'
),
wrapper
.
find
(
'AutocompleteList'
).
prop
(
'id'
)
);
});
it
(
'sets `aria-expanded` value to match open state'
,
()
=>
{
const
wrapper
=
createComponent
();
wrapper
.
find
(
'input'
).
instance
().
value
=
'non-empty'
;
// to open list
typeInput
(
wrapper
);
assert
.
equal
(
wrapper
.
find
(
'.tag-editor__combobox-wrapper'
).
prop
(
'aria-expanded'
),
'true'
);
selectOption
(
wrapper
,
'tag4'
);
wrapper
.
update
();
assert
.
equal
(
wrapper
.
find
(
'.tag-editor__combobox-wrapper'
).
prop
(
'aria-expanded'
),
'false'
);
});
it
(
'sets the <AutocompleteList> `activeItem` prop to match the selected item index'
,
()
=>
{
function
checkAttributes
(
wrapper
)
{
const
activeDescendant
=
wrapper
.
find
(
'input'
)
.
prop
(
'aria-activedescendant'
);
const
itemPrefixId
=
wrapper
.
find
(
'AutocompleteList'
)
.
prop
(
'itemPrefixId'
);
const
activeDescendantIndex
=
activeDescendant
.
split
(
itemPrefixId
);
assert
.
equal
(
activeDescendantIndex
[
1
],
wrapper
.
find
(
'AutocompleteList'
).
prop
(
'activeItem'
)
);
}
const
wrapper
=
createComponent
();
wrapper
.
find
(
'input'
).
instance
().
value
=
'non-empty'
;
typeInput
(
wrapper
);
// initial aria-activedescendant value is "" when index is -1
assert
.
equal
(
wrapper
.
find
(
'input'
).
prop
(
'aria-activedescendant'
),
''
);
// 2 suggestions: ['tag3', 'tag4'];
navigateDown
(
wrapper
);
// press down once
checkAttributes
(
wrapper
);
navigateDown
(
wrapper
);
// press down again once
checkAttributes
(
wrapper
);
});
});
describe
(
'accessibility validation'
,
()
=>
{
beforeEach
(
function
()
{
// create a full dom tree for a11y testing
$imports
.
$mock
({
'./autocomplete-list'
:
AutocompleteList
,
});
});
it
(
'should pass a11y checks'
,
checkAccessibility
([
{
name
:
'suggestions open'
,
content
:
()
=>
{
const
wrapper
=
createComponent
();
wrapper
.
find
(
'input'
).
instance
().
value
=
'non-empty'
;
typeInput
(
wrapper
);
return
wrapper
;
},
},
{
name
:
'suggestions open, first item selected'
,
content
:
()
=>
{
const
wrapper
=
createComponent
();
wrapper
.
find
(
'input'
).
instance
().
value
=
'non-empty'
;
typeInput
(
wrapper
);
navigateDown
(
wrapper
);
return
wrapper
;
},
},
{
name
:
'suggestions closed'
,
content
:
()
=>
{
return
createComponent
();
},
},
])
);
});
});
src/styles/sidebar/components/autocomplete-list.scss
View file @
db161766
...
...
@@ -2,6 +2,9 @@
.autocomplete-list
{
position
:
relative
;
&
.is-hidden
{
display
:
none
;
}
}
.autocomplete-list__items
{
...
...
src/styles/variables.scss
View file @
db161766
...
...
@@ -20,7 +20,7 @@ $grey-4: #a6a6a6;
// minus blue tint.
$grey-semi
:
#9c9c9c
;
$grey-5
:
#7
67676
;
$grey-5
:
#7
37373
;
// Interim color variable for migration purposes, as the step between `$grey-5`
// and `$grey-6` is large. Represents `base-mid` in proposed future palette,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment