-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathlinkImproper.test.js
More file actions
222 lines (212 loc) · 5.99 KB
/
Copy pathlinkImproper.test.js
File metadata and controls
222 lines (212 loc) · 5.99 KB
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
import axe from 'axe-core';
beforeAll( async () => {
const ruleModule = await import( '../../../src/pageScanner/rules/link-improper.js' );
const checkModule = await import( '../../../src/pageScanner/checks/link-has-valid-href-or-role.js' );
axe.configure( {
rules: [ ruleModule.default ],
checks: [ checkModule.default ],
} );
} );
beforeEach( () => {
document.body.innerHTML = '';
const style = document.createElement( 'style' );
style.innerHTML = `
.hidden { display: none; }
.invisible { visibility: hidden; }
`;
document.head.appendChild( style );
} );
describe( 'Link Improper Rule', () => {
test.each( [
// ❌ Failing cases
{
name: 'Fails when anchor has no href and no role',
html: '<a>Click me</a>',
shouldPass: false,
},
{
name: 'Fails when anchor has href="#" and no role',
html: '<a href="#">Link</a>',
shouldPass: false,
},
{
name: 'Fails when anchor has javascript:void(0) href',
html: '<a href="javascript:void(0)">Bad practice</a>',
shouldPass: false,
},
{
name: 'Fails when anchor has javascript: href with leading whitespace',
html: '<a href=" javascript:alert(1)">Bad practice</a>',
shouldPass: false,
},
{
name: 'Fails when anchor href contains only whitespace',
html: '<a href=" ">Whitespace link</a>',
shouldPass: false,
},
{
name: 'Fails when anchor href contains only whitespace',
html: '<a href=" ">Whitespace link</a>',
shouldPass: false,
},
{
name: 'Fails when anchor has malformed URL',
html: '<a href="http://example.com:invalid-port">Invalid URL</a>',
shouldPass: false,
},
{
name: 'Fails when anchor has data: protocol',
html: '<a href="data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==">Data URL</a>',
shouldPass: false,
},
{
name: 'Fails when anchor has file: protocol',
html: '<a href="file:///C:/example.txt">Local file</a>',
shouldPass: false,
},
// ✅ Passing cases
{
name: 'Passes with valid absolute URL',
html: '<a href="https://example.com/page?param=value">Valid URL</a>',
shouldPass: true,
},
{
name: 'Passes with valid relative URL',
html: '<a href="/path/to/page?query=string">Valid relative URL</a>',
shouldPass: true,
},
{
name: 'Passes with valid href',
html: '<a href="/about">About</a>',
shouldPass: true,
},
{
name: 'Passes with href="#" and role="button"',
html: '<a href="#" role="button">Click</a>',
shouldPass: true,
},
{
name: 'Passes with multiple roles including button',
html: '<a href="#" role="link button primary">Multiple roles</a>',
shouldPass: true,
},
{
name: 'Passes when hidden with display:none inline',
html: '<a style="display:none;">Hidden link</a>',
shouldPass: true,
},
{
name: 'Passes when hidden with visibility:hidden inline',
html: '<a style="visibility:hidden;">Invisible link</a>',
shouldPass: true,
},
{
name: 'Passes when hidden via class',
html: '<a class="hidden">Class hidden</a>',
shouldPass: true,
},
{
name: 'Passes when visually hidden via class',
html: '<a class="invisible">Invisible class</a>',
shouldPass: true,
},
{
name: 'Passes with aria-hidden="true"',
html: '<a aria-hidden="true">Aria hidden</a>',
shouldPass: true,
},
{
name: 'Passes when aria-hidden is dynamically added',
html: '<a id="dynamic-aria">Dynamic aria</a>',
shouldPass: true,
setup: () => {
const element = document.getElementById( 'dynamic-aria' );
element.setAttribute( 'aria-hidden', 'true' );
},
},
{
name: 'Passes with role="tab"',
html: '<a href="#" role="tab">Link with role of tab</a>',
shouldPass: true,
},
{
name: 'Passes with role="menuitem" and aria-expanded',
html: '<a href="#" role="menuitem" aria-haspopup="true" aria-expanded="false">Mega Menu</a>',
shouldPass: true,
},
{
name: 'Passes with role="menuitem" and aria-expanded="true"',
html: '<a href="#" role="menuitem" aria-expanded="true">Expanded Menu</a>',
shouldPass: true,
},
{
name: 'Passes with multiple roles including menuitem and aria-expanded',
html: '<a href="#" role="foo menuitem bar" aria-expanded="false">Multiple roles with menuitem</a>',
shouldPass: true,
},
{
name: 'Fails with role="menuitem" but no aria-expanded',
html: '<a href="#" role="menuitem">Menu without aria-expanded</a>',
shouldPass: false,
},
// Named anchor (jump target) cases
{
name: 'Passes when empty anchor is used as a named jump target',
html: '<a id="meet-the-team"></a>',
shouldPass: true,
},
{
name: 'Passes when whitespace-only anchor is used as a named jump target',
html: '<a id="section-top"> </a>',
shouldPass: true,
},
{
name: 'Passes when legacy name-only anchor is used as a jump target',
html: '<a name="section-top"></a>',
shouldPass: true,
},
{
name: 'Fails when legacy name-only anchor has text',
html: '<a name="section-top">Text</a>',
shouldPass: false,
},
{
name: 'Fails when anchor has id and text content but no href',
html: '<a id="meet-the-team">Meet the Team</a>',
shouldPass: false,
},
{
name: 'Fails when anchor has id and empty href',
html: '<a id="top" href=""></a>',
shouldPass: false,
},
{
name: 'Fails when anchor has id and child image but no href',
html: '<a id="logo"><img src="logo.png" alt="Home"/></a>',
shouldPass: false,
},
{
name: 'Fails when anchor is keyboard focusable via tabindex and has no href',
html: '<a id="meet-the-team" tabindex="0"></a>',
shouldPass: false,
},
{
name: 'Passes when anchor has tabindex of -1 and no href',
html: '<a id="meet-the-team" tabindex="-1"></a>',
shouldPass: true,
},
] )( '$name', async ( { html, shouldPass, setup } ) => {
document.body.innerHTML = html;
if ( setup ) {
setup();
}
const results = await axe.run( document.body, {
runOnly: [ 'link_improper' ],
} );
if ( shouldPass ) {
expect( results.violations.length ).toBe( 0 );
} else {
expect( results.violations.length ).toBeGreaterThan( 0 );
}
} );
} );