caterium-app/tests/mobile-menu.spec.mjs
pavlov346346-source 5ce7d8d353
Mobile menu editor above boxes with a matching return arrow (#34)
On phones place the existing menu editor above the catalog and add the same return-to-top control used by New Order. Preserve form nodes, unsaved values and active input focus; keep desktop/tablet layout and read-only permissions. Full QA passed, including iPhone WebKit and existing promotion focus regression. Extend real-asset production verification with a backend-blocked mobile-menu scenario.
2026-09-19 20:02:43 +03:00

82 lines
5.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {test,expect} from '@playwright/test';
import {bootMenuFixture,menuGeometry,exerciseMenuNavigation} from './mobile-menu-scenario.mjs';
test.use({serviceWorkers:'block'});
test('mobile menu puts editing above boxes and returns without losing unsaved values',async({page})=>{
await bootMenuFixture(page);await exerciseMenuNavigation(page);
});
test('mobile menu viewport changes move the list, not the focused editor',async({page})=>{
await bootMenuFixture(page);
await page.locator('[data-menu-item-v1762="mobile-box-1"]').click();
await expect(page.locator('#sunMenuDetailV1762 > .dialog')).toBeVisible();
// The automatic return is phone-only; desktop keeps its existing scroll.
if(await page.evaluate(()=>matchMedia('(max-width:760px)').matches))await expect.poll(()=>page.evaluate(()=>window.scrollY)).toBe(0);
await page.waitForTimeout(80);
await page.locator('#boxName').fill('Сохрани ввод при повороте');
await page.locator('#boxName').focus();
await page.evaluate(()=>{window.formInput=document.getElementById('boxName');window.inputsSeen=0;formInput.addEventListener('input',()=>window.inputsSeen++);});
for(const width of [1440,390,760,1440,390]){
await page.setViewportSize({width,height:900});
await expect.poll(()=>page.evaluate(()=>{const d=document.getElementById('sunMenuDetailV1762'),list=document.querySelector('.sun-menu-list-pane');return matchMedia('(max-width:760px)').matches?d.nextElementSibling===list:list.nextElementSibling===d;})).toBe(true);
await menuGeometry(page);
await expect(page.locator('#boxName')).toHaveValue('Сохрани ввод при повороте');
await expect(page.locator('#boxName')).toBeFocused();
expect(await page.evaluate(()=>formInput===document.getElementById('boxName'))).toBe(true);
}
await page.locator('#boxName').fill('Обработчик остался');
expect(await page.evaluate(()=>inputsSeen)).toBeGreaterThan(0);
await expect(page.locator('#ctMenuTop')).toHaveCount(1);
});
test('mobile menu search, category and new item keep working; leaving prevents a delayed scroll',async({page})=>{
await bootMenuFixture(page);
await page.locator('#sunMenuSearchV1762').fill('Бокс 07');
await expect(page.locator('[data-menu-item-v1762]')).toHaveCount(1);
await page.locator('[data-menu-cat-v1762="5"]').click();
await page.locator('#sunMenuAddV1762').click();
await expect(page.locator('#sunMenuDetailV1762 > .dialog')).toBeVisible();
await expect(page.locator('#itemCategory')).toHaveValue('5');
if(await page.evaluate(()=>matchMedia('(max-width:760px)').matches))await expect.poll(()=>page.evaluate(()=>window.scrollY)).toBe(0);
await menuGeometry(page);
await page.locator('#boxName').fill('Премиум с телефона');await page.locator('#boxPrice').fill('2200');await page.locator('#saveBoxButton').click();
await expect.poll(()=>page.evaluate(()=>JSON.parse(localStorage.sunBoxes).find(x=>x.name==='Премиум с телефона')?.category)).toBe(5);
await page.evaluate(()=>{document.getElementById('sunMenuAddV1762').click();[...document.querySelectorAll('header nav button')].find(b=>b.textContent.trim()==='Заказы').click();});
await page.waitForTimeout(100);
await expect(page.locator('#editor > .dialog')).toHaveCount(1);
await expect(page.locator('#ctMenuTop')).toBeHidden();
await page.locator('header nav').getByRole('button',{name:'Меню',exact:true}).click();
await expect(page.locator('#ctMenuTop')).toHaveCount(1);
});
test('read-only mobile menu opens the card above the list without enabling editing',async({page})=>{
await bootMenuFixture(page);
const before=await page.evaluate(()=>localStorage.sunBoxes);
await page.evaluate(()=>{SunCloudV2.hasPermission=p=>p==='catalog.view'||p==='app.read';window.dispatchEvent(new Event('sun:cloud-permissions-changed'));});
await expect(page.locator('#sunMenuAddV1762')).toBeDisabled();
await page.locator('[data-menu-item-v1762="mobile-box-24"]').click();
await expect(page.locator('#sunMenuDetailV1762 .sun-menu-readonly')).toContainText('Бокс 24');
if(await page.evaluate(()=>matchMedia('(max-width:760px)').matches))await expect.poll(()=>page.evaluate(()=>window.scrollY)).toBe(0);
await menuGeometry(page);
await expect(page.locator('#sunMenuDetailV1762 > .dialog')).toHaveCount(0);
expect(await page.evaluate(()=>localStorage.sunBoxes)).toBe(before);
});
test('a queued mobile return does not steal focus once the user starts typing',async({page})=>{
await bootMenuFixture(page);
await page.evaluate(()=>{
document.querySelector('[data-menu-item-v1762="mobile-box-1"]').click();
// Docking is queued by the existing menu handler. Start typing in the next
// task, before the mobile requestAnimationFrame navigation callback runs.
setTimeout(()=>{
const input=document.getElementById('boxName');input.value='Уже редактирую';input.focus({preventScroll:true});
},0);
});
await expect(page.locator('#sunMenuDetailV1762 > .dialog #boxName')).toHaveValue('Уже редактирую');
await page.waitForTimeout(150);
await expect(page.locator('#boxName')).toBeFocused();
await expect(page.locator('#boxName')).toHaveValue('Уже редактирую');
});