gbu-accessibility-package 3.2.1 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QUICK_START.md +38 -0
- package/README-vi.md +85 -0
- package/README.md +82 -0
- package/cli.js +23 -1
- package/demo/form-labels-test.html +87 -0
- package/demo/nested-controls-test.html +92 -0
- package/lib/fixer.js +1277 -0
- package/package.json +1 -1
package/QUICK_START.md
CHANGED
|
@@ -15,6 +15,22 @@ npm install gbu-accessibility-package
|
|
|
15
15
|
gbu-a11y
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
## 🔄 Cài đặt lại / Cập nhật
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Gỡ cài đặt cũ
|
|
22
|
+
npm uninstall -g gbu-accessibility-package
|
|
23
|
+
|
|
24
|
+
# Xóa cache
|
|
25
|
+
npm cache clean --force
|
|
26
|
+
|
|
27
|
+
# Cài đặt phiên bản mới nhất
|
|
28
|
+
npm install -g gbu-accessibility-package@latest
|
|
29
|
+
|
|
30
|
+
# Kiểm tra version
|
|
31
|
+
gbu-a11y --version
|
|
32
|
+
```
|
|
33
|
+
|
|
18
34
|
## 🎯 Sử dụng cơ bản
|
|
19
35
|
|
|
20
36
|
### Cách 1: CLI (Đơn giản nhất)
|
|
@@ -115,9 +131,31 @@ gbu-a11y --dry-run
|
|
|
115
131
|
|
|
116
132
|
**Lỗi "Cannot find module"**
|
|
117
133
|
```bash
|
|
134
|
+
# Cài đặt lại
|
|
135
|
+
npm uninstall -g gbu-accessibility-package
|
|
136
|
+
npm cache clean --force
|
|
118
137
|
npm install -g gbu-accessibility-package
|
|
119
138
|
```
|
|
120
139
|
|
|
140
|
+
**Lỗi permission (macOS/Linux)**
|
|
141
|
+
```bash
|
|
142
|
+
sudo npm install -g gbu-accessibility-package
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Package không update**
|
|
146
|
+
```bash
|
|
147
|
+
# Force update
|
|
148
|
+
npm cache clean --force
|
|
149
|
+
npm install -g gbu-accessibility-package@latest --force
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Kiểm tra cài đặt**
|
|
153
|
+
```bash
|
|
154
|
+
which gbu-a11y
|
|
155
|
+
npm list -g gbu-accessibility-package
|
|
156
|
+
gbu-a11y --version
|
|
157
|
+
```
|
|
158
|
+
|
|
121
159
|
**Duplicate attributes**
|
|
122
160
|
- Tool tự động tránh duplicate
|
|
123
161
|
- Nếu có, chạy lại tool sẽ tự clean up
|
package/README-vi.md
CHANGED
|
@@ -52,6 +52,47 @@ npm install -g gbu-accessibility-package
|
|
|
52
52
|
npm install gbu-accessibility-package
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
### Gỡ cài đặt và Cài đặt lại
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Gỡ cài đặt package global
|
|
59
|
+
npm uninstall -g gbu-accessibility-package
|
|
60
|
+
|
|
61
|
+
# Gỡ cài đặt package local
|
|
62
|
+
npm uninstall gbu-accessibility-package
|
|
63
|
+
|
|
64
|
+
# Xóa cache npm (khuyến nghị khi có vấn đề)
|
|
65
|
+
npm cache clean --force
|
|
66
|
+
|
|
67
|
+
# Cài đặt lại phiên bản mới nhất
|
|
68
|
+
npm install -g gbu-accessibility-package@latest
|
|
69
|
+
|
|
70
|
+
# Kiểm tra phiên bản đã cài đặt
|
|
71
|
+
npm list -g gbu-accessibility-package
|
|
72
|
+
gbu-a11y --version
|
|
73
|
+
|
|
74
|
+
# Cài đặt phiên bản cụ thể
|
|
75
|
+
npm install -g gbu-accessibility-package@3.2.1
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Khắc phục sự cố cài đặt
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Nếu gặp lỗi permission (macOS/Linux)
|
|
82
|
+
sudo npm install -g gbu-accessibility-package
|
|
83
|
+
|
|
84
|
+
# Nếu gặp lỗi cache
|
|
85
|
+
npm cache clean --force
|
|
86
|
+
npm install -g gbu-accessibility-package --force
|
|
87
|
+
|
|
88
|
+
# Kiểm tra cài đặt
|
|
89
|
+
which gbu-a11y
|
|
90
|
+
gbu-a11y --help
|
|
91
|
+
|
|
92
|
+
# Cập nhật lên phiên bản mới nhất
|
|
93
|
+
npm update -g gbu-accessibility-package
|
|
94
|
+
```
|
|
95
|
+
|
|
55
96
|
### Sử dụng cơ bản
|
|
56
97
|
|
|
57
98
|
```bash
|
|
@@ -277,6 +318,50 @@ console.log("Hoàn thành sửa lỗi với enhanced features:", results);
|
|
|
277
318
|
- **URL không hợp lệ** → Phát hiện định dạng URL sai
|
|
278
319
|
- **Liên kết chậm** → Cảnh báo timeout và phản hồi chậm
|
|
279
320
|
|
|
321
|
+
## 🔧 Quản lý Package
|
|
322
|
+
|
|
323
|
+
### Kiểm tra thông tin package
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
# Xem version hiện tại
|
|
327
|
+
gbu-a11y --version
|
|
328
|
+
npm list -g gbu-accessibility-package
|
|
329
|
+
|
|
330
|
+
# Xem thông tin package
|
|
331
|
+
npm info gbu-accessibility-package
|
|
332
|
+
|
|
333
|
+
# Kiểm tra package đã cài đặt
|
|
334
|
+
which gbu-a11y
|
|
335
|
+
npm list -g | grep gbu-accessibility-package
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Cập nhật package
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
# Kiểm tra version mới
|
|
342
|
+
npm outdated -g gbu-accessibility-package
|
|
343
|
+
|
|
344
|
+
# Cập nhật lên version mới nhất
|
|
345
|
+
npm update -g gbu-accessibility-package
|
|
346
|
+
|
|
347
|
+
# Hoặc cài đặt lại version mới
|
|
348
|
+
npm uninstall -g gbu-accessibility-package
|
|
349
|
+
npm install -g gbu-accessibility-package@latest
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Quản lý cache
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# Xem cache info
|
|
356
|
+
npm cache verify
|
|
357
|
+
|
|
358
|
+
# Xóa cache (khi có vấn đề)
|
|
359
|
+
npm cache clean --force
|
|
360
|
+
|
|
361
|
+
# Xem cache location
|
|
362
|
+
npm config get cache
|
|
363
|
+
```
|
|
364
|
+
|
|
280
365
|
## 🧪 Kiểm tra và Demo
|
|
281
366
|
|
|
282
367
|
```bash
|
package/README.md
CHANGED
|
@@ -49,6 +49,47 @@ npm install -g gbu-accessibility-package
|
|
|
49
49
|
npm install gbu-accessibility-package
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
### Uninstall and Reinstall
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Uninstall global package
|
|
56
|
+
npm uninstall -g gbu-accessibility-package
|
|
57
|
+
|
|
58
|
+
# Uninstall local package
|
|
59
|
+
npm uninstall gbu-accessibility-package
|
|
60
|
+
|
|
61
|
+
# Clear npm cache (recommended when having issues)
|
|
62
|
+
npm cache clean --force
|
|
63
|
+
|
|
64
|
+
# Reinstall latest version
|
|
65
|
+
npm install -g gbu-accessibility-package@latest
|
|
66
|
+
|
|
67
|
+
# Check installed version
|
|
68
|
+
npm list -g gbu-accessibility-package
|
|
69
|
+
gbu-a11y --version
|
|
70
|
+
|
|
71
|
+
# Install specific version
|
|
72
|
+
npm install -g gbu-accessibility-package@3.2.1
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Installation Troubleshooting
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# If permission errors (macOS/Linux)
|
|
79
|
+
sudo npm install -g gbu-accessibility-package
|
|
80
|
+
|
|
81
|
+
# If cache issues
|
|
82
|
+
npm cache clean --force
|
|
83
|
+
npm install -g gbu-accessibility-package --force
|
|
84
|
+
|
|
85
|
+
# Verify installation
|
|
86
|
+
which gbu-a11y
|
|
87
|
+
gbu-a11y --help
|
|
88
|
+
|
|
89
|
+
# Update to latest version
|
|
90
|
+
npm update -g gbu-accessibility-package
|
|
91
|
+
```
|
|
92
|
+
|
|
52
93
|
### Basic Usage
|
|
53
94
|
|
|
54
95
|
```bash
|
|
@@ -266,6 +307,47 @@ console.log('Accessibility fixes completed with enhanced features:', results);
|
|
|
266
307
|
- **Invalid URLs** → Detect malformed URL formats
|
|
267
308
|
- **Slow links** → Warn about timeouts and slow responses
|
|
268
309
|
|
|
310
|
+
## 🔧 Package Management
|
|
311
|
+
|
|
312
|
+
### Check package information
|
|
313
|
+
```bash
|
|
314
|
+
# Check current version
|
|
315
|
+
gbu-a11y --version
|
|
316
|
+
npm list -g gbu-accessibility-package
|
|
317
|
+
|
|
318
|
+
# View package info
|
|
319
|
+
npm info gbu-accessibility-package
|
|
320
|
+
|
|
321
|
+
# Verify installation
|
|
322
|
+
which gbu-a11y
|
|
323
|
+
npm list -g | grep gbu-accessibility-package
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Update package
|
|
327
|
+
```bash
|
|
328
|
+
# Check for new versions
|
|
329
|
+
npm outdated -g gbu-accessibility-package
|
|
330
|
+
|
|
331
|
+
# Update to latest version
|
|
332
|
+
npm update -g gbu-accessibility-package
|
|
333
|
+
|
|
334
|
+
# Or reinstall latest version
|
|
335
|
+
npm uninstall -g gbu-accessibility-package
|
|
336
|
+
npm install -g gbu-accessibility-package@latest
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Cache management
|
|
340
|
+
```bash
|
|
341
|
+
# Verify cache
|
|
342
|
+
npm cache verify
|
|
343
|
+
|
|
344
|
+
# Clean cache (when having issues)
|
|
345
|
+
npm cache clean --force
|
|
346
|
+
|
|
347
|
+
# View cache location
|
|
348
|
+
npm config get cache
|
|
349
|
+
```
|
|
350
|
+
|
|
269
351
|
## 🧪 Testing and Demo
|
|
270
352
|
|
|
271
353
|
```bash
|
package/cli.js
CHANGED
|
@@ -23,6 +23,7 @@ const options = {
|
|
|
23
23
|
langOnly: false,
|
|
24
24
|
roleOnly: false,
|
|
25
25
|
formsOnly: false,
|
|
26
|
+
nestedOnly: false,
|
|
26
27
|
buttonsOnly: false,
|
|
27
28
|
linksOnly: false,
|
|
28
29
|
landmarksOnly: false,
|
|
@@ -80,6 +81,9 @@ for (let i = 0; i < args.length; i++) {
|
|
|
80
81
|
case '--forms-only':
|
|
81
82
|
options.formsOnly = true;
|
|
82
83
|
break;
|
|
84
|
+
case '--nested-only':
|
|
85
|
+
options.nestedOnly = true;
|
|
86
|
+
break;
|
|
83
87
|
case '--buttons-only':
|
|
84
88
|
options.buttonsOnly = true;
|
|
85
89
|
break;
|
|
@@ -227,7 +231,7 @@ async function main() {
|
|
|
227
231
|
try {
|
|
228
232
|
// Handle different modes - All modes now include cleanup
|
|
229
233
|
if (options.cleanupOnly || options.altOnly || options.langOnly || options.roleOnly ||
|
|
230
|
-
options.formsOnly || options.buttonsOnly || options.linksOnly || options.landmarksOnly ||
|
|
234
|
+
options.formsOnly || options.nestedOnly || options.buttonsOnly || options.linksOnly || options.landmarksOnly ||
|
|
231
235
|
options.headingsOnly || options.brokenLinksOnly) {
|
|
232
236
|
// Individual modes - handle each separately, then run cleanup
|
|
233
237
|
} else {
|
|
@@ -333,6 +337,24 @@ async function main() {
|
|
|
333
337
|
showCompletionMessage(options, 'Form label fixes + cleanup');
|
|
334
338
|
return;
|
|
335
339
|
|
|
340
|
+
} else if (options.nestedOnly) {
|
|
341
|
+
// Fix nested interactive controls + cleanup
|
|
342
|
+
console.log(chalk.blue('🎯 Running nested interactive controls fixes + cleanup...'));
|
|
343
|
+
const nestedResults = await fixer.fixNestedInteractiveControls(options.directory);
|
|
344
|
+
const nestedFixed = nestedResults.filter(r => r.status === 'fixed').length;
|
|
345
|
+
const totalNestedIssues = nestedResults.reduce((sum, r) => sum + (r.issues || 0), 0);
|
|
346
|
+
|
|
347
|
+
console.log(chalk.green(`\n✅ Fixed nested interactive controls in ${nestedFixed} files (${totalNestedIssues} issues)`));
|
|
348
|
+
|
|
349
|
+
// Run cleanup
|
|
350
|
+
console.log(chalk.blue('\n🧹 Running cleanup for duplicate role attributes...'));
|
|
351
|
+
const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
|
|
352
|
+
const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
|
|
353
|
+
console.log(chalk.green(`✅ Cleaned duplicate roles in ${cleanupFixed} files`));
|
|
354
|
+
|
|
355
|
+
showCompletionMessage(options, 'Nested interactive controls fixes + cleanup');
|
|
356
|
+
return;
|
|
357
|
+
|
|
336
358
|
} else if (options.buttonsOnly) {
|
|
337
359
|
// Fix button names + cleanup
|
|
338
360
|
console.log(chalk.blue('🔘 Running button name fixes + cleanup...'));
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ja">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Form Labels Test - Accessibility Issues</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<h1>Form Labels Test Cases</h1>
|
|
10
|
+
|
|
11
|
+
<!-- Test Case 1: Input without any label -->
|
|
12
|
+
<div>
|
|
13
|
+
<input type="text" name="username">
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- Test Case 2: Input with empty label -->
|
|
17
|
+
<div>
|
|
18
|
+
<label for="email"></label>
|
|
19
|
+
<input type="email" id="email" name="email">
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<!-- Test Case 3: Input without aria-label -->
|
|
23
|
+
<div>
|
|
24
|
+
<input type="password" name="password" placeholder="Enter password">
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Test Case 4: Input with invalid aria-labelledby -->
|
|
28
|
+
<div>
|
|
29
|
+
<input type="tel" name="phone" aria-labelledby="nonexistent-id">
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<!-- Test Case 5: Input without title attribute -->
|
|
33
|
+
<div>
|
|
34
|
+
<input type="url" name="website">
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<!-- Test Case 6: Textarea without proper labeling -->
|
|
38
|
+
<div>
|
|
39
|
+
<textarea name="comments"></textarea>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Test Case 7: Select without proper labeling -->
|
|
43
|
+
<div>
|
|
44
|
+
<select name="country">
|
|
45
|
+
<option value="">Choose country</option>
|
|
46
|
+
<option value="jp">Japan</option>
|
|
47
|
+
<option value="us">USA</option>
|
|
48
|
+
</select>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- Test Case 8: Input with implicit label but empty text -->
|
|
52
|
+
<div>
|
|
53
|
+
<label><input type="checkbox" name="agree"></label>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<!-- Test Case 9: Input with aria-labelledby pointing to empty element -->
|
|
57
|
+
<div>
|
|
58
|
+
<span id="empty-label"></span>
|
|
59
|
+
<input type="number" name="age" aria-labelledby="empty-label">
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<!-- Test Case 10: Input without role override -->
|
|
63
|
+
<div>
|
|
64
|
+
<input type="range" name="volume" min="0" max="100">
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<!-- Test Case 11: Multiple inputs without proper structure -->
|
|
68
|
+
<form>
|
|
69
|
+
<input type="text" name="firstname">
|
|
70
|
+
<input type="text" name="lastname">
|
|
71
|
+
<input type="email" name="user_email">
|
|
72
|
+
<textarea name="message"></textarea>
|
|
73
|
+
<select name="priority">
|
|
74
|
+
<option value="low">Low</option>
|
|
75
|
+
<option value="high">High</option>
|
|
76
|
+
</select>
|
|
77
|
+
<input type="submit" value="Submit">
|
|
78
|
+
</form>
|
|
79
|
+
|
|
80
|
+
<!-- Test Case 12: Inputs with only placeholder (not sufficient) -->
|
|
81
|
+
<div>
|
|
82
|
+
<input type="search" name="query" placeholder="Search...">
|
|
83
|
+
<input type="date" name="birthdate" placeholder="Select date">
|
|
84
|
+
<input type="time" name="appointment" placeholder="Select time">
|
|
85
|
+
</div>
|
|
86
|
+
</body>
|
|
87
|
+
</html>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ja">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Nested Interactive Controls Test - Accessibility Issues</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<h1>Nested Interactive Controls Test Cases</h1>
|
|
10
|
+
|
|
11
|
+
<!-- Test Case 1: div[role="button"] containing links (like in the axe error) -->
|
|
12
|
+
<div class="card-buttons" role="button">
|
|
13
|
+
<a href="https://business.mobile.rakuten.co.jp/solution/service/rakuten-ai-for-business/?scid=we_solution09_2504" class="btn btn-secondary" target="_blank" role="link">詳細を見る</a>
|
|
14
|
+
<a href="https://business.mobile.rakuten.co.jp/solution/ai/inquiry/?l=id=solution_ai_inquiry1&scid=we_solution10_2504" class="btn btn-primary" target="_blank" role="link">お問い合わせ</a>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<!-- Test Case 2: Button containing links -->
|
|
18
|
+
<button type="button" onclick="handleClick()">
|
|
19
|
+
<a href="/page1">Link inside button</a>
|
|
20
|
+
<span>Click me</span>
|
|
21
|
+
</button>
|
|
22
|
+
|
|
23
|
+
<!-- Test Case 3: Link containing button -->
|
|
24
|
+
<a href="/page2">
|
|
25
|
+
<button type="button">Button inside link</button>
|
|
26
|
+
</a>
|
|
27
|
+
|
|
28
|
+
<!-- Test Case 4: div[role="button"] containing input -->
|
|
29
|
+
<div role="button" tabindex="0" onclick="submit()">
|
|
30
|
+
<input type="text" placeholder="Search...">
|
|
31
|
+
<span>Submit</span>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<!-- Test Case 5: Link containing select -->
|
|
35
|
+
<a href="/settings">
|
|
36
|
+
<select name="language">
|
|
37
|
+
<option value="ja">Japanese</option>
|
|
38
|
+
<option value="en">English</option>
|
|
39
|
+
</select>
|
|
40
|
+
Settings
|
|
41
|
+
</a>
|
|
42
|
+
|
|
43
|
+
<!-- Test Case 6: Button containing textarea -->
|
|
44
|
+
<button type="submit">
|
|
45
|
+
<textarea name="comment" placeholder="Enter comment"></textarea>
|
|
46
|
+
<span>Send</span>
|
|
47
|
+
</button>
|
|
48
|
+
|
|
49
|
+
<!-- Test Case 7: Multiple levels of nesting -->
|
|
50
|
+
<div role="button" tabindex="0">
|
|
51
|
+
<div class="container">
|
|
52
|
+
<a href="/nested">
|
|
53
|
+
<button type="button">Deeply nested</button>
|
|
54
|
+
</a>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<!-- Test Case 8: div[role="button"] with tabindex containing interactive elements -->
|
|
59
|
+
<div role="button" tabindex="0" onclick="handleAction()">
|
|
60
|
+
<input type="checkbox" id="agree">
|
|
61
|
+
<label for="agree">I agree</label>
|
|
62
|
+
<a href="/terms">Terms</a>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<!-- Test Case 9: Link containing details/summary -->
|
|
66
|
+
<a href="/info">
|
|
67
|
+
<details>
|
|
68
|
+
<summary>More info</summary>
|
|
69
|
+
<p>Details content</p>
|
|
70
|
+
</details>
|
|
71
|
+
</a>
|
|
72
|
+
|
|
73
|
+
<!-- Test Case 10: Form elements nested in buttons -->
|
|
74
|
+
<button type="button" class="form-button">
|
|
75
|
+
<input type="radio" name="choice" value="1">
|
|
76
|
+
<input type="radio" name="choice" value="2">
|
|
77
|
+
<span>Choose option</span>
|
|
78
|
+
</button>
|
|
79
|
+
|
|
80
|
+
<!-- Test Case 11: Correct structure (should not be flagged) -->
|
|
81
|
+
<div class="card-buttons">
|
|
82
|
+
<a href="/page1" class="btn btn-secondary">詳細を見る</a>
|
|
83
|
+
<a href="/page2" class="btn btn-primary">お問い合わせ</a>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<!-- Test Case 12: Another correct structure -->
|
|
87
|
+
<button type="button" onclick="handleClick()">
|
|
88
|
+
<span>Click me</span>
|
|
89
|
+
<i class="icon"></i>
|
|
90
|
+
</button>
|
|
91
|
+
</body>
|
|
92
|
+
</html>
|