54451d2ea6
Bulk regions dialog reworked from federal-district bitmask to subject/region selection, consistent with ProjectDetailsDrawer/NewProjectDialog. Full-stack: add_regions/remove_regions on projects.regions INT[], BulkProjectActionRequest split validation, ProjectService model-instance update. federal-districts.ts removed (zero consumers). +menuRepositionFix util for v-autocomplete menu. phpstan-baseline: bump actingAs ignore count 14->15 (new validation test). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
57 lines
2.4 KiB
TypeScript
57 lines
2.4 KiB
TypeScript
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
import { repositionMenuAfterOpen } from '../../resources/js/utils/menuRepositionFix';
|
|
|
|
/**
|
|
* Unit-тесты воркэраунда Vuetify location-strategy (см. menuRepositionFix.ts).
|
|
* Реальный баг — гонка позиционирования в браузере под prefers-reduced-motion —
|
|
* в jsdom не воспроизводится (нет layout); он покрыт Playwright-пробой. Здесь
|
|
* проверяется контракт утилиты: при стабилизации overlay-меню шлётся один resize.
|
|
*/
|
|
function makeStableMenu(left: number): HTMLElement {
|
|
const overlay = document.createElement('div');
|
|
overlay.className = 'v-overlay v-menu';
|
|
const content = document.createElement('div');
|
|
content.className = 'v-overlay__content';
|
|
content.getBoundingClientRect = () =>
|
|
({ width: 400, height: 300, left, top: 50, right: left + 400, bottom: 350, x: left, y: 50, toJSON() {} }) as DOMRect;
|
|
overlay.appendChild(content);
|
|
document.body.appendChild(overlay);
|
|
return overlay;
|
|
}
|
|
|
|
const wait = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));
|
|
|
|
describe('repositionMenuAfterOpen', () => {
|
|
afterEach(() => {
|
|
document.querySelectorAll('.v-overlay').forEach((el) => el.remove());
|
|
});
|
|
|
|
it('does nothing when menu is closing (open=false)', async () => {
|
|
const spy = vi.fn();
|
|
window.addEventListener('resize', spy);
|
|
repositionMenuAfterOpen(false);
|
|
await wait(200);
|
|
window.removeEventListener('resize', spy);
|
|
expect(spy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('dispatches a single resize once the overlay content is geometrically stable', async () => {
|
|
makeStableMenu(120);
|
|
const spy = vi.fn();
|
|
window.addEventListener('resize', spy);
|
|
repositionMenuAfterOpen(true);
|
|
await wait(400);
|
|
window.removeEventListener('resize', spy);
|
|
expect(spy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('does not dispatch resize or throw when no overlay is present', async () => {
|
|
const spy = vi.fn();
|
|
window.addEventListener('resize', spy);
|
|
expect(() => repositionMenuAfterOpen(true)).not.toThrow();
|
|
await wait(300);
|
|
window.removeEventListener('resize', spy);
|
|
expect(spy).not.toHaveBeenCalled();
|
|
});
|
|
});
|