@@ -10,7 +10,29 @@ import {
10
10
wrapAllByQueryWithSuggestion ,
11
11
wrapSingleQueryWithSuggestion ,
12
12
} from './all-utils'
13
- import { queryAllByText } from './text'
13
+
14
+ function queryAllLabels ( container ) {
15
+ return Array . from ( container . querySelectorAll ( 'label,input' ) )
16
+ . map ( node => {
17
+ let textToMatch =
18
+ node . tagName . toLowerCase ( ) === 'label'
19
+ ? node . textContent
20
+ : node . value || null
21
+ // The children of a textarea are part of `textContent` as well. We
22
+ // need to remove them from the string so we can match it afterwards.
23
+ Array . from ( node . querySelectorAll ( 'textarea' ) ) . forEach ( textarea => {
24
+ textToMatch = textToMatch . replace ( textarea . value , '' )
25
+ } )
26
+
27
+ // The children of a select are also part of `textContent`, so we
28
+ // need also to remove their text.
29
+ Array . from ( node . querySelectorAll ( 'select' ) ) . forEach ( select => {
30
+ textToMatch = textToMatch . replace ( select . textContent , '' )
31
+ } )
32
+ return { node, textToMatch}
33
+ } )
34
+ . filter ( ( { textToMatch} ) => textToMatch !== null )
35
+ }
14
36
15
37
function queryAllLabelsByText (
16
38
container ,
@@ -19,23 +41,25 @@ function queryAllLabelsByText(
19
41
) {
20
42
const matcher = exact ? matches : fuzzyMatches
21
43
const matchNormalizer = makeNormalizer ( { collapseWhitespace, trim, normalizer} )
22
- return Array . from ( container . querySelectorAll ( 'label' ) ) . filter ( label => {
23
- let textToMatch = label . textContent
24
44
25
- // The children of a textarea are part of `textContent` as well. We
26
- // need to remove them from the string so we can match it afterwards.
27
- Array . from ( label . querySelectorAll ( 'textarea' ) ) . forEach ( textarea => {
28
- textToMatch = textToMatch . replace ( textarea . value , '' )
29
- } )
45
+ const textToMatchByLabels = queryAllLabels ( container )
30
46
31
- // The children of a select are also part of `textContent`, so we
32
- // need also to remove their text.
33
- Array . from ( label . querySelectorAll ( 'select' ) ) . forEach ( select => {
34
- textToMatch = textToMatch . replace ( select . textContent , '' )
35
- } )
47
+ return textToMatchByLabels
48
+ . filter ( ( { node, textToMatch} ) =>
49
+ matcher ( textToMatch , node , text , matchNormalizer ) ,
50
+ )
51
+ . map ( ( { node} ) => node )
52
+ }
36
53
37
- return matcher ( textToMatch , label , text , matchNormalizer )
54
+ function getLabelContent ( label ) {
55
+ let labelContent = label . getAttribute ( 'value' ) || label . textContent
56
+ Array . from ( label . querySelectorAll ( 'textarea' ) ) . forEach ( textarea => {
57
+ labelContent = labelContent . replace ( textarea . value , '' )
38
58
} )
59
+ Array . from ( label . querySelectorAll ( 'select' ) ) . forEach ( select => {
60
+ labelContent = labelContent . replace ( select . textContent , '' )
61
+ } )
62
+ return labelContent
39
63
}
40
64
41
65
function queryAllByLabelText (
@@ -45,74 +69,69 @@ function queryAllByLabelText(
45
69
) {
46
70
checkContainerType ( container )
47
71
72
+ const matcher = exact ? matches : fuzzyMatches
48
73
const matchNormalizer = makeNormalizer ( { collapseWhitespace, trim, normalizer} )
49
- const labels = queryAllLabelsByText ( container , text , {
50
- exact,
51
- normalizer : matchNormalizer ,
52
- } )
53
- const labelledElements = labels
54
- . reduce ( ( matchedElements , label ) => {
55
- const elementsForLabel = [ ]
56
- if ( label . control ) {
57
- elementsForLabel . push ( label . control )
58
- }
59
- /* istanbul ignore if */
60
- if ( label . getAttribute ( 'for' ) ) {
61
- // we're using this notation because with the # selector we would have to escape special characters e.g. user.name
62
- // see https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector#Escaping_special_characters
63
- // <label for="someId">text</label><input id="someId" />
64
-
65
- // .control support has landed in jsdom (https://github.com/jsdom/jsdom/issues/2175)
66
- elementsForLabel . push (
67
- container . querySelector ( `[id="${ label . getAttribute ( 'for' ) } "]` ) ,
68
- )
69
- }
70
- if ( label . getAttribute ( 'id' ) ) {
71
- // <label id="someId">text</label><input aria-labelledby="someId" />
72
- Array . from (
73
- container . querySelectorAll (
74
- `[aria-labelledby~="${ label . getAttribute ( 'id' ) } "]` ,
75
- ) ,
76
- ) . forEach ( element => elementsForLabel . push ( element ) )
77
- }
78
- if ( label . childNodes . length ) {
79
- // <label>text: <input /></label>
80
- const formControlSelector =
81
- 'button, input, meter, output, progress, select, textarea'
82
- const labelledFormControl = Array . from (
83
- label . querySelectorAll ( formControlSelector ) ,
84
- ) . filter ( element => element . matches ( selector ) ) [ 0 ]
85
- if ( labelledFormControl ) elementsForLabel . push ( labelledFormControl )
74
+ const matchingLabelledElements = Array . from ( container . querySelectorAll ( '*' ) )
75
+ . filter (
76
+ element => element . labels || element . hasAttribute ( 'aria-labelledby' ) ,
77
+ )
78
+ . reduce ( ( labelledElements , labelledElement ) => {
79
+ const labelsId = labelledElement . getAttribute ( 'aria-labelledby' )
80
+ ? labelledElement . getAttribute ( 'aria-labelledby' ) . split ( ' ' )
81
+ : [ ]
82
+ const labelsValue = labelsId . length
83
+ ? labelsId . map ( labelId => {
84
+ const labellingElement = container . querySelector ( `[id=${ labelId } ]` )
85
+ return getLabelContent ( labellingElement )
86
+ } )
87
+ : Array . from ( labelledElement . labels ) . map ( label => {
88
+ const textToMatch = getLabelContent ( label )
89
+ const formControlSelector =
90
+ 'button, input, meter, output, progress, select, textarea'
91
+ const labelledFormControl = Array . from (
92
+ label . querySelectorAll ( formControlSelector ) ,
93
+ ) . filter ( element => element . matches ( selector ) ) [ 0 ]
94
+ if ( labelledFormControl ) {
95
+ if (
96
+ matcher ( textToMatch , labelledFormControl , text , matchNormalizer )
97
+ )
98
+ labelledElements . push ( labelledFormControl )
99
+ }
100
+ return textToMatch
101
+ } )
102
+ if (
103
+ matcher ( labelsValue . join ( ' ' ) , labelledElement , text , matchNormalizer )
104
+ )
105
+ labelledElements . push ( labelledElement )
106
+ if ( labelsValue . length > 1 ) {
107
+ labelsValue . forEach ( ( labelValue , index ) => {
108
+ if ( matcher ( labelValue , labelledElement , text , matchNormalizer ) )
109
+ labelledElements . push ( labelledElement )
110
+
111
+ const labelsFiltered = [ ...labelsValue ]
112
+ labelsFiltered . splice ( index , 1 )
113
+
114
+ if ( labelsFiltered . length > 1 ) {
115
+ if (
116
+ matcher (
117
+ labelsFiltered . join ( ' ' ) ,
118
+ labelledElement ,
119
+ text ,
120
+ matchNormalizer ,
121
+ )
122
+ )
123
+ labelledElements . push ( labelledElement )
124
+ }
125
+ } )
86
126
}
87
- return matchedElements . concat ( elementsForLabel )
127
+
128
+ return labelledElements
88
129
} , [ ] )
89
- . filter ( element => element !== null )
90
130
. concat ( queryAllByAttribute ( 'aria-label' , container , text , { exact} ) )
91
131
92
- const possibleAriaLabelElements = queryAllByText ( container , text , {
93
- exact,
94
- normalizer : matchNormalizer ,
95
- } )
96
-
97
- const ariaLabelledElements = possibleAriaLabelElements . reduce (
98
- ( allLabelledElements , nextLabelElement ) => {
99
- const labelId = nextLabelElement . getAttribute ( 'id' )
100
-
101
- if ( ! labelId ) return allLabelledElements
102
-
103
- // ARIA labels can label multiple elements
104
- const labelledNodes = Array . from (
105
- container . querySelectorAll ( `[aria-labelledby~="${ labelId } "]` ) ,
106
- )
107
-
108
- return allLabelledElements . concat ( labelledNodes )
109
- } ,
110
- [ ] ,
132
+ return Array . from ( new Set ( matchingLabelledElements ) ) . filter ( element =>
133
+ element . matches ( selector ) ,
111
134
)
112
-
113
- return Array . from (
114
- new Set ( [ ...labelledElements , ...ariaLabelledElements ] ) ,
115
- ) . filter ( element => element . matches ( selector ) )
116
135
}
117
136
118
137
// the getAll* query would normally look like this:
0 commit comments