/
suggestions.js
129 lines (106 loc) 路 3.67 KB
/
suggestions.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import {computeAccessibleName} from 'dom-accessibility-api'
import {getDefaultNormalizer} from './matches'
import {getNodeText} from './get-node-text'
import {DEFAULT_IGNORE_TAGS} from './config'
import {getImplicitAriaRoles} from './role-helpers'
const normalize = getDefaultNormalizer()
function getLabelTextFor(element) {
let label =
element.labels &&
Array.from(element.labels).find(el => Boolean(normalize(el.textContent)))
// non form elements that are using aria-labelledby won't be included in `element.labels`
if (!label) {
const ariaLabelledBy = element.getAttribute('aria-labelledby')
if (ariaLabelledBy) {
// we're using this notation because with the # selector we would have to escape special characters e.g. user.name
// see https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector#Escaping_special_characters
label = document.querySelector(`[id=${ariaLabelledBy}]`)
}
}
if (label) {
return label.textContent
}
return undefined
}
function escapeRegExp(string) {
return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
}
function getRegExpMatcher(string) {
return new RegExp(string.toLowerCase(), 'i')
}
function makeSuggestion(queryName, content, {variant = 'get', name}) {
const queryArgs = [
queryName === 'Role' || queryName === 'TestId'
? content
: getRegExpMatcher(content),
]
if (name) {
queryArgs.push({name: new RegExp(escapeRegExp(name.toLowerCase()), 'i')})
}
const queryMethod = `${variant}By${queryName}`
return {
queryName,
queryMethod,
queryArgs,
variant,
toString() {
let [text, options] = queryArgs
text = typeof text === 'string' ? `'${text}'` : text
options = options
? `, { ${Object.entries(options)
.map(([k, v]) => `${k}: ${v}`)
.join(', ')} }`
: ''
return `${queryMethod}(${text}${options})`
},
}
}
function canSuggest(currentMethod, requestedMethod, data) {
return (
data &&
(!requestedMethod ||
requestedMethod.toLowerCase() === currentMethod.toLowerCase())
)
}
export function getSuggestedQuery(element, variant = 'get', method) {
// don't create suggestions for script and style elements
if (element.matches(DEFAULT_IGNORE_TAGS)) {
return undefined
}
const role =
element.getAttribute('role') ?? getImplicitAriaRoles(element)?.[0]
if (canSuggest('Role', method, role)) {
return makeSuggestion('Role', role, {
variant,
name: computeAccessibleName(element),
})
}
const labelText = getLabelTextFor(element)
if (canSuggest('LabelText', method, labelText)) {
return makeSuggestion('LabelText', labelText, {variant})
}
const placeholderText = element.getAttribute('placeholder')
if (canSuggest('PlaceholderText', method, placeholderText)) {
return makeSuggestion('PlaceholderText', placeholderText, {variant})
}
const textContent = normalize(getNodeText(element))
if (canSuggest('Text', method, textContent)) {
return makeSuggestion('Text', textContent, {variant})
}
if (canSuggest('DisplayValue', method, element.value)) {
return makeSuggestion('DisplayValue', normalize(element.value), {variant})
}
const alt = element.getAttribute('alt')
if (canSuggest('AltText', method, alt)) {
return makeSuggestion('AltText', alt, {variant})
}
const title = element.getAttribute('title')
if (canSuggest('Title', method, title)) {
return makeSuggestion('Title', title, {variant})
}
const testId = element.getAttribute('data-testid')
if (canSuggest('TestId', method, testId)) {
return makeSuggestion('TestId', testId, {variant})
}
return undefined
}