Skip to content

Commit 66beb55

Browse files
fix: preserve pdisk preview focus and verify disk rendering
1 parent e7d9b3d commit 66beb55

18 files changed

Lines changed: 110 additions & 11 deletions

‎src/containers/Storage/PDisks/PDisksPreview.tsx‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,23 @@ function PDiskPreviewItem({
232232
const [previewFocused, setPreviewFocused] = React.useState(false);
233233
const popupContainerRef = React.useRef<HTMLElement | null>(null);
234234
const detailsRef = React.useRef<HTMLDivElement>(null);
235+
const previewRef = React.useRef<HTMLButtonElement>(null);
236+
const transferFocusRef = React.useRef(false);
235237
const popupHostRef = React.useRef<HTMLDivElement>(null);
236238
const [popupAvailableHeight, setPopupAvailableHeight] = React.useState(0);
237239

240+
React.useLayoutEffect(() => {
241+
if (!transferFocusRef.current) {
242+
return;
243+
}
244+
transferFocusRef.current = false;
245+
// Transfer keyboard focus after React replaces the activated control.
246+
const target = detailsOpened
247+
? detailsRef.current?.querySelector<HTMLElement>('.pdisk-storage__content')
248+
: previewRef.current;
249+
target?.focus({preventScroll: true});
250+
}, [detailsOpened]);
251+
238252
React.useLayoutEffect(() => {
239253
const container = popupContainerRef.current;
240254
const details = detailsRef.current;
@@ -295,6 +309,7 @@ function PDiskPreviewItem({
295309

296310
event.preventDefault();
297311
event.stopPropagation();
312+
transferFocusRef.current = event.detail === 0;
298313
closeDetails();
299314
},
300315
[closeDetails],
@@ -305,12 +320,14 @@ function PDiskPreviewItem({
305320
<Button
306321
view="flat"
307322
className={b('control')}
323+
ref={previewRef}
308324
onMouseEnter={() => setPreviewHovered(true)}
309325
onMouseLeave={() => setPreviewHovered(false)}
310326
onFocus={() => setPreviewFocused(true)}
311327
onBlur={() => setPreviewFocused(false)}
312328
onClick={(event) => {
313329
event.stopPropagation();
330+
transferFocusRef.current = event.detail === 0;
314331
// Keep popup wheel events in the table's native scroll container.
315332
let container = event.currentTarget.parentElement;
316333
while (

‎tests/suites/nodes/pdisksPreview.test.ts‎

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import {setupPDiskPreviewMocks} from './pdisksPreviewMocks';
1515

1616
const settingTitle = 'Compact PDisk previews';
1717
const previewSelector = '.ydb-storage-pdisks-preview__control';
18+
// Linux ARM64 and x64 round translucent disk colors differently by 1–2 RGB levels.
19+
// Keep exact palette comparisons against a baseline from the same architecture.
20+
const paletteArchitecture = process.platform === 'linux' ? `-${process.arch}` : '';
1821

1922
async function openNodeDisks(page: Page) {
2023
await new NodesPage(page).goto({clusterName: 'preview-test', backend: 'https://preview.test'});
@@ -62,6 +65,11 @@ for (const theme of ['light', 'dark']) {
6265
.getByRole('button', {name: `Show PDisk 2-${diskId} details`, exact: true})
6366
.press('Enter');
6467
}
68+
// Compare the palette without a focus outline; keyboard focus has its own regression test.
69+
await page
70+
.locator('.pdisk-storage__content')
71+
.last()
72+
.evaluate((element) => element.blur());
6573
await page.mouse.move(0, 0);
6674
const previews = page.locator(previewSelector);
6775
const details = page.locator('.pdisk-storage');
@@ -84,11 +92,14 @@ for (const theme of ['light', 'dark']) {
8492
};
8593
// Keep previews and their original controls together in the same reference image.
8694
// Tiny translucent squares need exact comparison to catch missing color layers.
87-
await expect(page).toHaveScreenshot(`pdisk-preview-palette-${theme}.png`, {
88-
clip,
89-
threshold: 0,
90-
maxDiffPixels: 0,
91-
});
95+
await expect(page).toHaveScreenshot(
96+
`pdisk-preview-palette-${theme}${paletteArchitecture}.png`,
97+
{
98+
clip,
99+
threshold: 0,
100+
maxDiffPixels: 0,
101+
},
102+
);
92103

93104
// As in vdiskColoring.test.ts, force every tone's hover state for one stable snapshot.
94105
await previews.locator('.ydb-storage-pdisks-preview__color').evaluateAll((elements) => {
@@ -101,11 +112,14 @@ for (const theme of ['light', 'dark']) {
101112
element.classList.add('storage-disk-progress-bar_highlighted'),
102113
);
103114
});
104-
await expect(page).toHaveScreenshot(`pdisk-preview-palette-${theme}-hover.png`, {
105-
clip,
106-
threshold: 0,
107-
maxDiffPixels: 0,
108-
});
115+
await expect(page).toHaveScreenshot(
116+
`pdisk-preview-palette-${theme}-hover${paletteArchitecture}.png`,
117+
{
118+
clip,
119+
threshold: 0,
120+
maxDiffPixels: 0,
121+
},
122+
);
109123
});
110124
}
111125

@@ -205,7 +219,7 @@ test('preview uses compact SVG, preserves row height, and keeps detail popups in
205219
await details.press('Enter');
206220
await expect(preview).toBeVisible();
207221
await expect(action).toHaveCount(0);
208-
await expect(preview.locator('.ydb-storage-pdisks-preview__color_highlighted')).toHaveCount(0);
222+
await expect(preview).toBeFocused();
209223
});
210224

211225
test('15-second refresh updates open details, their popup and the collapsed preview', async ({
@@ -359,3 +373,71 @@ test('wheel scrolling over a disk popup reaches the table', async ({page}) => {
359373
.poll(() => scroll.evaluate((element) => element.scrollTop))
360374
.toBeGreaterThan(initialScroll);
361375
});
376+
377+
test('expanded disks remain interactive beyond the compact column boundary', async ({page}) => {
378+
await setupPDiskPreviewMocks(page);
379+
await page.addInitScript(() => localStorage.setItem('enablePDisksPreview', 'true'));
380+
await openNodeDisks(page);
381+
const preview = page.getByRole('button', {name: 'Show PDisk 1-1 details', exact: true});
382+
const cell = preview.locator('xpath=ancestor::td');
383+
const cellBox = await cell.boundingBox();
384+
assert(cellBox);
385+
await preview.click();
386+
const lastVDisk = page.locator('.pdisk-storage__vdisks-item').last();
387+
await expect(lastVDisk).toBeVisible();
388+
const diskBox = await lastVDisk.boundingBox();
389+
assert(diskBox);
390+
expect(diskBox.x + diskBox.width / 2).toBeGreaterThan(cellBox.x + cellBox.width);
391+
expect(
392+
await lastVDisk.evaluate((element) => {
393+
const rect = element.getBoundingClientRect();
394+
return element.contains(
395+
document.elementFromPoint(rect.right - 1, rect.top + rect.height / 2),
396+
);
397+
}),
398+
).toBe(true);
399+
await lastVDisk.hover();
400+
await expect(page.getByRole('link', {name: 'Go to VDisk', exact: true})).toBeVisible();
401+
await lastVDisk.click();
402+
await expect(preview).toBeVisible();
403+
});
404+
405+
test('Enter repeatedly expands and collapses the same disk without losing focus', async ({
406+
page,
407+
}) => {
408+
await setupPDiskPreviewMocks(page, 2);
409+
await page.addInitScript(() => localStorage.setItem('enablePDisksPreview', 'true'));
410+
await openNodeDisks(page);
411+
const preview = page.getByRole('button', {name: 'Show PDisk 2-1 details', exact: true});
412+
const details = page.locator('.pdisk-storage__content');
413+
const initialUrl = page.url();
414+
await preview.focus();
415+
for (let cycle = 0; cycle < 3; cycle++) {
416+
await page.keyboard.press('Enter');
417+
await expect(details).toBeFocused();
418+
await expect(details).toHaveAttribute('href', /nodeId=2/);
419+
await page.keyboard.press('Enter');
420+
await expect(preview).toBeFocused();
421+
await expect(details).toHaveCount(0);
422+
}
423+
// Collapsing from a VDisk should return to the same preview as well.
424+
await page.keyboard.press('Enter');
425+
await page.locator('.pdisk-storage__vdisks-item a').last().focus();
426+
await page.keyboard.press('Enter');
427+
await expect(preview).toBeFocused();
428+
expect(page.url()).toBe(initialUrl);
429+
});
430+
431+
test('mouse expansion and collapse do not transfer focus to the new control', async ({page}) => {
432+
await setupPDiskPreviewMocks(page);
433+
await page.addInitScript(() => localStorage.setItem('enablePDisksPreview', 'true'));
434+
await openNodeDisks(page);
435+
const preview = page.getByRole('button', {name: 'Show PDisk 1-1 details', exact: true});
436+
const details = page.locator('.pdisk-storage__content');
437+
await preview.click();
438+
await expect(details).toBeVisible();
439+
await expect(details).not.toBeFocused();
440+
await details.click();
441+
await expect(preview).toBeVisible();
442+
await expect(preview).not.toBeFocused();
443+
});

tests/suites/nodes/pdisksPreview.test.ts-snapshots/pdisk-preview-palette-dark-chromium-linux.png renamed to tests/suites/nodes/pdisksPreview.test.ts-snapshots/pdisk-preview-palette-dark-arm64-chromium-linux.png

File renamed without changes.

tests/suites/nodes/pdisksPreview.test.ts-snapshots/pdisk-preview-palette-dark-safari-linux.png renamed to tests/suites/nodes/pdisksPreview.test.ts-snapshots/pdisk-preview-palette-dark-arm64-safari-linux.png

File renamed without changes.

tests/suites/nodes/pdisksPreview.test.ts-snapshots/pdisk-preview-palette-dark-hover-chromium-linux.png renamed to tests/suites/nodes/pdisksPreview.test.ts-snapshots/pdisk-preview-palette-dark-hover-arm64-chromium-linux.png

File renamed without changes.

tests/suites/nodes/pdisksPreview.test.ts-snapshots/pdisk-preview-palette-dark-hover-safari-linux.png renamed to tests/suites/nodes/pdisksPreview.test.ts-snapshots/pdisk-preview-palette-dark-hover-arm64-safari-linux.png

File renamed without changes.
5.25 KB
Loading
6.66 KB
Loading
4.89 KB
Loading
6.43 KB
Loading

0 commit comments

Comments
 (0)