Skip to content

Commit 9991b30

Browse files
committed
fix: tighten mobile tool accessibility
1 parent c9b2b6f commit 9991b30

6 files changed

Lines changed: 175 additions & 23 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Buffer } from 'node:buffer';
2+
import { expect, test } from '@playwright/test';
3+
4+
test.beforeEach(async ({ context }) => {
5+
await context.addInitScript(() => localStorage.clear());
6+
await context.route(/^https:\/\//, (route) =>
7+
route.fulfill({ status: 204, contentType: 'text/plain', body: '' })
8+
);
9+
});
10+
11+
test('image compressor upload control is keyboard accessible', async ({ page }) => {
12+
await page.goto('/tools/media/image-compressor.html');
13+
14+
const uploadButton = page.getByRole('button', { name: // });
15+
await expect(uploadButton).toBeVisible();
16+
await expect(uploadButton).toHaveAttribute('type', 'button');
17+
18+
const fileInput = page.getByLabel('选择图片文件');
19+
await expect(fileInput).toHaveAttribute('name', 'image-file');
20+
await expect(fileInput).toHaveAttribute('accept', 'image/*');
21+
22+
await uploadButton.focus();
23+
await expect(uploadButton).toBeFocused();
24+
25+
const [fileChooser] = await Promise.all([
26+
page.waitForEvent('filechooser'),
27+
uploadButton.press('Enter')
28+
]);
29+
await fileChooser.setFiles({
30+
name: 'keyboard-upload.png',
31+
mimeType: 'image/png',
32+
buffer: Buffer.from(
33+
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
34+
'base64'
35+
)
36+
});
37+
38+
await expect(page.locator('#previewArea')).toBeVisible();
39+
await expect(page.locator('#originalFormat')).toHaveText('PNG');
40+
await expect(page.locator('#uploadArea')).toBeHidden();
41+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
test.beforeEach(async ({ context }) => {
4+
await context.route(/^https:\/\//, (route) =>
5+
route.fulfill({ status: 204, contentType: 'text/plain', body: '' })
6+
);
7+
});
8+
9+
test('QR generator exposes labelled controls and a semantic generate button', async ({ page }) => {
10+
await page.setViewportSize({ width: 390, height: 844 });
11+
await page.goto('/tools/generator/qrcode-generator.html');
12+
13+
const textInput = page.getByLabel('文本或 URL', { exact: true });
14+
await expect(textInput).toBeVisible();
15+
await expect(textInput).toHaveAttribute('name', 'qr-text');
16+
await expect(textInput).toHaveAttribute('autocomplete', 'off');
17+
await expect(textInput).toHaveAttribute('placeholder', '输入要编码的文本或 URL…');
18+
19+
const sizeInput = page.getByLabel('二维码大小 (px)', { exact: true });
20+
await expect(sizeInput).toHaveValue('256');
21+
await expect(sizeInput).toHaveAttribute('name', 'qr-size');
22+
await expect(sizeInput).toHaveAttribute('autocomplete', 'off');
23+
await expect(sizeInput).toHaveAttribute('placeholder', '例如 256');
24+
25+
const foregroundColor = page.getByLabel('前景色', { exact: true });
26+
await expect(foregroundColor).toHaveValue('#000000');
27+
await expect(foregroundColor).toHaveAttribute('name', 'foreground-color');
28+
await expect(foregroundColor).toHaveAttribute('autocomplete', 'off');
29+
30+
const backgroundColor = page.getByLabel('背景色', { exact: true });
31+
await expect(backgroundColor).toHaveValue('#ffffff');
32+
await expect(backgroundColor).toHaveAttribute('name', 'background-color');
33+
await expect(backgroundColor).toHaveAttribute('autocomplete', 'off');
34+
35+
const generateButton = page.getByRole('button', { name: '生成二维码', exact: true });
36+
await expect(generateButton).toBeVisible();
37+
await expect(generateButton).toHaveAttribute('type', 'button');
38+
});

tests/e2e/timestamp-mobile.spec.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
test('timestamp converter fits a narrow mobile viewport and converts Unix time', async ({
4+
page
5+
}) => {
6+
await page.setViewportSize({ width: 320, height: 568 });
7+
await page.goto('/tools/time/timestamp.html');
8+
9+
const widths = await page.evaluate(() => ({
10+
client: document.documentElement.clientWidth,
11+
scroll: document.documentElement.scrollWidth
12+
}));
13+
14+
expect(widths.scroll).toBe(widths.client);
15+
16+
await page.getByLabel('输入时间戳').fill('0');
17+
await page.getByLabel('时间戳单位').selectOption('s');
18+
await page.getByRole('button', { name: '转换', exact: true }).first().click();
19+
20+
await expect(page.locator('#resultIso')).toHaveText('1970-01-01T00:00:00.000Z');
21+
await expect(page.locator('#tsStatus')).toHaveText('转换成功');
22+
});

tools/generator/qrcode-generator.html

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,14 @@
552552
color: var(--color-text-secondary);
553553
}
554554

555+
.input-group .input-label {
556+
display: block;
557+
font-weight: 500;
558+
margin-bottom: var(--space-2);
559+
font-size: 0.9375rem;
560+
color: var(--color-text-secondary);
561+
}
562+
555563
textarea,
556564
input[type="text"],
557565
input[type="number"],
@@ -602,6 +610,11 @@
602610
gap: var(--space-3);
603611
}
604612

613+
.color-group label {
614+
display: inline;
615+
margin-bottom: 0;
616+
}
617+
605618
/* Buttons */
606619
button {
607620
padding: var(--space-3) var(--space-4);
@@ -850,43 +863,66 @@ <h1 class="tool-page-heading">二维码生成器</h1>
850863
<h2 class="card-title">输入内容</h2>
851864

852865
<div class="input-group">
853-
<label>文本或 URL</label>
854-
<textarea id="textInput" placeholder="输入要编码的文本或 URL..."></textarea>
866+
<label for="textInput">文本或 URL</label>
867+
<textarea
868+
id="textInput"
869+
name="qr-text"
870+
placeholder="输入要编码的文本或 URL…"
871+
autocomplete="off"
872+
></textarea>
855873
</div>
856874

857875
<div class="input-group">
858-
<label for="input--------px-">二维码大小 (px)</label>
876+
<label for="qrSize">二维码大小 (px)</label>
859877
<input
860878
type="number"
861879
id="qrSize"
880+
name="qr-size"
862881
value="256"
863882
min="64"
864883
max="1024"
865884
step="32"
866-
aria-label="数字输入框"
867-
placeholder="请输入数字输入框"
885+
placeholder="例如 256"
886+
autocomplete="off"
868887
/>
869888
</div>
870889

871890
<div class="input-group">
872-
<label>颜色</label>
891+
<span class="input-label">颜色</span>
873892
<div class="color-row">
874893
<div class="color-group">
875-
<span>前景色</span>
876-
<input type="color" id="fgColor" value="#000000" aria-label="颜色选择器" />
894+
<label for="fgColor">前景色</label>
895+
<input
896+
type="color"
897+
id="fgColor"
898+
name="foreground-color"
899+
value="#000000"
900+
autocomplete="off"
901+
/>
877902
</div>
878903
<div class="color-group">
879-
<span>背景色</span>
880-
<input type="color" id="bgColor" value="#ffffff" aria-label="颜色选择器" />
904+
<label for="bgColor">背景色</label>
905+
<input
906+
type="color"
907+
id="bgColor"
908+
name="background-color"
909+
value="#ffffff"
910+
autocomplete="off"
911+
/>
881912
</div>
882913
</div>
883914
</div>
884915

885916
<div class="btn-row">
886-
<button class="primary" onclick="generateQR()" aria-label="生成二维码">
917+
<button
918+
class="primary"
919+
type="button"
920+
onclick="generateQR()"
921+
aria-label="生成二维码"
922+
>
887923
生成二维码
888924
</button>
889-
<button onclick="clearInput()" aria-label="清空">清空</button>
925+
<button type="button" onclick="clearInput()" aria-label="清空">清空</button>
890926
</div>
891927
</div>
892928

tools/media/image-compressor.html

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,15 @@
546546
padding: var(--space-8) var(--space-6);
547547
text-align: center;
548548
cursor: pointer;
549-
transition: all var(--transition-normal);
549+
display: block;
550+
width: 100%;
551+
font: inherit;
552+
color: inherit;
553+
transition:
554+
background-color var(--transition-normal),
555+
border-color var(--transition-normal),
556+
box-shadow var(--transition-normal),
557+
transform var(--transition-normal);
550558
margin-bottom: var(--space-5);
551559
box-shadow: var(--shadow-md);
552560
}
@@ -565,18 +573,21 @@
565573
}
566574

567575
.upload-icon {
576+
display: block;
568577
font-size: 48px;
569578
margin-bottom: var(--space-4);
570579
}
571580

572581
.upload-text {
582+
display: block;
573583
color: var(--color-text-primary);
574584
font-size: 1.0625rem;
575585
font-weight: 500;
576586
margin-bottom: var(--space-2);
577587
}
578588

579589
.upload-hint {
590+
display: block;
580591
color: var(--text-muted);
581592
font-size: 0.875rem;
582593
}
@@ -994,16 +1005,13 @@ <h1 class="tool-page-heading">图片压缩器</h1>
9941005
<main class="main-content">
9951006
<div class="container">
9961007
<!-- Upload Area -->
997-
<div
998-
class="upload-area"
999-
id="uploadArea"
1000-
onclick="document.getElementById('fileInput').click()"
1001-
>
1002-
<div class="upload-icon">📷</div>
1003-
<div class="upload-text">点击或拖拽图片到这里</div>
1004-
<div class="upload-hint">支持 JPG、PNG、WebP 格式 • 最大 50 MB</div>
1005-
</div>
1006-
<input type="file" id="fileInput" accept="image/*" onchange="handleFile(this.files[0])" />
1008+
<button type="button" class="upload-area" id="uploadArea">
1009+
<span class="upload-icon" aria-hidden="true">📷</span>
1010+
<span class="upload-text">点击或拖拽图片到这里</span>
1011+
<span class="upload-hint">支持 JPG、PNG、WebP 格式 • 最大 50 MB</span>
1012+
</button>
1013+
<label class="sr-only" for="fileInput">选择图片文件</label>
1014+
<input type="file" id="fileInput" name="image-file" accept="image/*" />
10071015

10081016
<!-- Controls Card -->
10091017
<div class="card" id="controlsCard" style="display: none">
@@ -1230,6 +1238,9 @@ <h2>常见问题</h2>
12301238
// Drag and Drop
12311239
// ============================================
12321240
const uploadArea = document.getElementById("uploadArea");
1241+
const fileInput = document.getElementById("fileInput");
1242+
uploadArea.addEventListener("click", () => fileInput.click());
1243+
fileInput.addEventListener("change", () => handleFile(fileInput.files[0]));
12331244
uploadArea.addEventListener("dragover", (e) => {
12341245
e.preventDefault();
12351246
uploadArea.classList.add("dragover");

tools/time/timestamp.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,10 @@
784784
width: 100%;
785785
}
786786

787+
input[type="datetime-local"] {
788+
max-width: 100%;
789+
}
790+
787791
.result-table th {
788792
width: 100px;
789793
font-size: 0.8125rem;

0 commit comments

Comments
 (0)