style(web): close control-system gaps found in the deep sweep
This commit is contained in:
@@ -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
@@ -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',
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user