style(web): close control-system gaps found in the deep sweep

This commit is contained in:
chick
2026-09-08 08:02:01 +08:00
parent c0adbcbc8a
commit bfe5025943
7 changed files with 100 additions and 30 deletions
@@ -59,6 +59,7 @@ describe('FleetIdentityPanel', () => {
render(<FleetIdentityPanel dataSource={source()} onClose={() => {}} onChanged={() => {}} />);
const dialog = await screen.findByRole('dialog', { name: '设备身份核对' });
await screen.findByText('已记录 2 台 · 待确认 1 台');
expect(dialog.textContent).toContain('已记录 2 台 · 待确认 1 台');
expect(screen.getByText('机房 A')).toBeTruthy();
expect(screen.getByText('同一 IMEI 出现在多台设备记录上')).toBeTruthy();
@@ -40,6 +40,7 @@ describe('FleetOrganizationPanel', () => {
);
expect(await screen.findByRole('dialog', { name: '分组与标签' })).toBeTruthy();
await screen.findByRole('button', { name: '编辑分组 核心' });
const core = screen.getByRole('button', { name: '编辑分组 核心' });
const row = core.closest('li');
expect(row?.textContent).toContain('2 台设备');
@@ -148,7 +148,8 @@ describe('Phase 6.3 Device Network read module', () => {
within(screen.getByRole('region', { name: '接口与地址' })).queryByRole('button'),
).toBeNull();
// Actions come from the pinned catalog; without a reachable API the card stays honest.
const actions = await screen.findByRole('region', { name: '设备操作' });
await screen.findByText('无法读取该模块的可用操作,请稍后重试。');
const actions = screen.getByRole('region', { name: '设备操作' });
expect(actions.textContent).toContain('无法读取该模块的可用操作');
});
@@ -1,5 +1,5 @@
// @vitest-environment jsdom
import { cleanup, render, screen, within } from '@testing-library/react';
import { cleanup, render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, describe, expect, it, vi } from 'vitest';
@@ -28,6 +28,11 @@ async function mount(overrides: Partial<SystemMaintenanceDataSource> = {}) {
const data = source(overrides);
const view = render(<ConnectionSettingsPanel dataSource={data} />);
const region = await screen.findByRole('region', { name: '连接设置' });
if (!overrides.connection) {
await waitFor(() => {
expect(within(region).getByLabelText('心跳间隔(秒)').value).toBe('30');
});
}
return { ...view, data, region };
}
+36 -26
View File
@@ -321,7 +321,7 @@ code {
font-size: 0.76rem;
}
.sensitive-reveal-toggle {
min-height: 2rem;
min-height: var(--button-height-compact);
padding: 0 0.7rem;
color: #cdd9e8;
border: 1px solid rgba(154, 175, 201, 0.22);
@@ -370,7 +370,7 @@ code {
background: rgba(255, 255, 255, 0.05);
}
.global-navigation a {
min-height: 2.25rem;
min-height: var(--button-height);
display: inline-flex;
align-items: center;
gap: 0.4rem;
@@ -513,10 +513,10 @@ main > section > header p,
font-size: 0.98rem;
}
.fleet-sidebar-toggle {
width: 2.15rem;
min-width: 2.15rem;
height: 2.15rem;
min-height: 2.15rem;
width: var(--button-height-compact);
min-width: var(--button-height-compact);
height: var(--button-height-compact);
min-height: var(--button-height-compact);
display: grid;
place-items: center;
padding: 0;
@@ -599,10 +599,10 @@ main > section > header p,
align-items: center;
justify-content: space-between;
gap: 0.5rem;
min-height: 2.2rem;
min-height: var(--button-height-compact);
padding: 0.35rem 0.65rem;
border: 1px solid var(--line);
border-radius: 9px;
border-radius: var(--button-radius-compact);
color: var(--ink);
background: var(--surface-soft);
text-decoration: none;
@@ -691,7 +691,7 @@ button.fleet-cross-health-item {
gap: 0.4rem;
}
.fleet-status-chip {
min-height: 2.2rem;
min-height: var(--button-height-compact);
display: flex;
justify-content: space-between;
gap: 0.4rem;
@@ -726,7 +726,7 @@ button.fleet-cross-health-item {
flex-wrap: wrap;
}
.fleet-active-chip {
min-height: 2rem;
min-height: var(--button-height-compact);
max-width: 100%;
display: inline-flex;
gap: 0.35rem;
@@ -742,7 +742,7 @@ button.fleet-cross-health-item {
white-space: nowrap;
}
.fleet-clear-filters {
min-height: 2rem;
min-height: var(--button-height-compact);
padding: 0.25rem 0.5rem;
color: var(--muted);
font-size: 0.72rem;
@@ -803,7 +803,7 @@ button.fleet-cross-health-item {
flex-wrap: wrap;
}
.batch-entry-actions button {
min-height: 2.15rem;
min-height: var(--button-height-compact);
font-size: 0.74rem;
}
.batch-progress {
@@ -845,7 +845,7 @@ button.fleet-cross-health-item {
}
.fleet-toolbar-controls select,
.fleet-select-all button {
min-height: 2.25rem;
min-height: var(--button-height-compact);
font-size: 0.77rem;
}
.fleet-card-section {
@@ -1890,13 +1890,22 @@ button.fleet-cross-health-item {
font-size: 0.74rem;
}
.instance-header-actions a {
min-height: var(--button-height-compact);
display: inline-flex;
align-items: center;
padding: 0.3rem 0.55rem;
color: var(--button-secondary-text);
color: #d8e5f2;
border: 1px solid rgba(153, 177, 202, 0.2);
border-radius: 7px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--button-secondary-border);
border-radius: var(--button-radius-compact);
background: var(--button-secondary-bg);
text-decoration: none;
}
.instance-header-actions a:hover {
color: var(--button-secondary-hover-text);
border-color: var(--button-secondary-hover-border);
background: var(--button-secondary-hover-bg);
}
.instance-context nav {
margin: 0.8rem -1.1rem 0;
overflow-x: auto;
@@ -3426,7 +3435,7 @@ a.icon-button:hover {
margin-top: 0.1rem;
}
.risk-confirm:has(input:disabled) {
opacity: 0.62;
opacity: 1;
}
.schedule-drawer .schedule-confirmation {
background: #f3faf6;
@@ -3899,8 +3908,9 @@ a.icon-button:hover {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
width: var(--icon-button-size);
height: var(--icon-button-size);
min-height: var(--icon-button-size);
border: 1px solid transparent;
border-radius: 50%;
color: var(--workbench-muted);
@@ -4871,7 +4881,7 @@ main > section > header h1,
}
.fleet-card-entry:active {
opacity: 0.68;
transform: translateY(1px);
}
.fleet-card h2 {
@@ -5696,7 +5706,7 @@ svg {
.fleet-group-tabs button,
.fleet-group-tabs button:hover:not(:disabled) {
min-height: 1.9rem;
min-height: var(--button-height-compact);
flex: 0 0 auto;
padding: 0 0.72rem;
border: 1px solid transparent;
@@ -5772,7 +5782,7 @@ svg {
.fleet-subnavigation a {
display: inline-flex;
align-items: center;
min-height: 1.9rem;
min-height: var(--button-height-compact);
flex: 0 0 auto;
padding: 0 0.72rem;
border: 1px solid transparent;
@@ -6549,7 +6559,7 @@ button.fleet-card-menu-trigger:focus-visible {
.automation-workspace .row-actions button,
.automation-workspace .row-actions button:hover:not(:disabled) {
min-height: 2.25rem;
min-height: var(--button-height-compact);
padding: 0 0.45rem;
border: 0;
border-radius: 8px;
@@ -6815,7 +6825,7 @@ button.fleet-card-menu-trigger:focus-visible {
border-color: var(--workbench-danger-border);
background: var(--workbench-danger-soft);
cursor: default;
opacity: 0.75;
opacity: 1;
}
.fleet-messages-device-item[data-unavailable] small {
color: var(--workbench-danger);
@@ -7714,7 +7724,7 @@ button.fleet-card-menu-trigger:focus-visible {
margin-top: 0.6rem;
}
.fleet-log-retention-actions .primary-action {
min-height: 2rem;
min-height: var(--button-height-compact);
display: inline-flex;
align-items: center;
gap: 0.35rem;
@@ -8193,7 +8203,7 @@ button.fleet-card-menu-trigger:focus-visible {
[class*='animal-btn-danger'][class*='animal-btn-default'],
[class*='animal-btn-danger'][class*='animal-btn-dashed'] {
color: var(--button-danger-text);
border-color: rgba(182, 77, 72, 0.45);
border-color: var(--button-danger-bg);
background: var(--workbench-danger-soft);
}
@@ -66,7 +66,7 @@ afterEach(() => {
});
describe('painted control contrast', () => {
it('keeps every rendered control at WCAG AA', { timeout: 30_000 }, async () => {
it('keeps every rendered control at WCAG AA', { timeout: 60_000 }, async () => {
const raw = await readFile(
new URL('file:///Users/chick/.local/opt/multi-simadmin/apps/web/src/styles.css'),
'utf8',
+53 -1
View File
@@ -196,10 +196,62 @@ describe('button system', () => {
'',
);
expect(css).not.toMatch(
/#8a6722|#fff8e8|#fffafa|rgba\(17,\s*119,\s*110|rgba\(170,\s*120,\s*35|rgba\(183,\s*77,\s*72/,
/#8a6722|#fff8e8|#fffafa|rgba\(17,\s*119,\s*110|rgba\(170,\s*120,\s*35|rgba\(182,\s*77,\s*72/,
);
});
it('keeps disabled and unavailable states legible without fading controls', async () => {
const { rules } = await loadStylesheet();
const stateSelectors = [
'.risk-confirm:has(input:disabled)',
'.fleet-messages-device-item[data-unavailable]',
'.fleet-log-retention-value:disabled',
'.fleet-notification-entry-button:disabled',
'[class*=animal-btn-]:disabled',
];
const faded = rules
.filter(
(rule) =>
stateSelectors.some((selector) => rule.selector.includes(selector)) &&
rule.declarations.opacity !== undefined &&
rule.declarations.opacity !== '1',
)
.map((rule) => rule.selector);
expect(faded).toEqual([]);
});
it('does not leave stale compact command-control sizes in later overrides', async () => {
const { rules } = await loadStylesheet();
const compactControls = [
'.global-navigation a',
'.fleet-sidebar-toggle',
'.fleet-cross-health-item',
'.fleet-select-all button',
'.batch-entry-actions button',
'.fleet-card-context-trigger',
'.fleet-group-tabs button',
'.fleet-subnavigation a',
'.automation-workspace .row-actions button',
'.fleet-log-retention-actions .primary-action',
];
const stale = rules
.filter((rule) =>
compactControls.some((selector) => rule.selector.replace(/\s+/g, ' ').includes(selector)),
)
.filter((rule) => {
const heights = [rule.declarations['min-height'], rule.declarations.height].filter(
(value): value is string => value !== undefined,
);
return heights.some((value) =>
['1.9rem', '2rem', '2.15rem', '2.2rem', '2.25rem'].includes(value),
);
})
.map((rule) => rule.selector);
expect(stale).toEqual([]);
});
it('keeps workbench text and focus indicators on the accessible brand scale', async () => {
const { tokens, resolve } = await loadStylesheet();