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.
82 lines
5.1 KiB
JavaScript
82 lines
5.1 KiB
JavaScript
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('Уже редактирую');
|
||
});
|