import fs from 'node:fs'; import {test,expect} from '@playwright/test'; import {trainingInit} from './training-catalog-fixture.mjs'; const source=JSON.parse(fs.readFileSync('public/demo/banquet-v1.json','utf8')).banquet; const menu=source.map((x,i)=>({...x,id:`menu-${i}`,demo:false,banquet:{packageId:'client-set',packageName:'Праздничное меню',...(i===9||i===10?{choiceGroup:'main',defaultChoice:i===9}:{})}})); async function boot(page){ page.on('dialog',d=>d.accept()); await page.route('**/*',r=>{const u=new URL(r.request().url());return r.request().method()==='GET'&&u.origin==='http://127.0.0.1:4173'&&!u.pathname.startsWith('/api/')?r.continue():r.abort();}); await page.addInitScript(trainingInit); await page.addInitScript(items=>{localStorage.setItem('sunBoxes',JSON.stringify(items));localStorage.setItem('sunPdfBrandNameV1','Студия вкуса');localStorage.setItem('sunCompanyProfileV1',JSON.stringify({name:'Студия вкуса',phone:'+7 900 000-00-00',email:'hello@example.invalid'}));},menu); await page.goto('/index.html',{waitUntil:'domcontentloaded'}); await expect(page.locator('body > header')).toBeVisible(); await page.waitForFunction(()=>window.CateriumBanquet&&window.SunOpsUXV1762&&window.CateriumDataV1773); await page.locator('header nav').getByRole('button',{name:'Новый заказ',exact:true}).click(); await page.getByRole('button',{name:'Банкетное меню',exact:true}).click(); } async function exportMenu(page){ await page.locator('[data-banquet-client-menu]').click(); await expect(page.locator('#ctBanquetClientDialog')).toBeVisible(); await expect(page.locator('[data-bcm-download]')).toBeEnabled({timeout:20000}); } test.use({serviceWorkers:'block'}); test('ready-menu selection makes exactly one A4 PDF and never saves or applies the draft',async({page},info)=>{ test.setTimeout(60000);await boot(page); await expect(page.locator('[data-banquet-client-menu]')).toBeDisabled(); await page.locator('[data-banquet-package="client-set"]').click();await page.locator('[data-banquet-preset]').click(); await page.locator('[data-banquet-guests]').fill('20'); const before=await page.evaluate(()=>({draft:JSON.stringify(draft),orders:localStorage.sunOrders,boxes:localStorage.sunBoxes})); await exportMenu(page); await expect(page.locator('[data-bcm-status]')).toContainText('одна страница A4'); const dl=page.waitForEvent('download');await page.locator('[data-bcm-download]').click();const download=await dl; const path=info.outputPath('banquet-client-menu.pdf');await download.saveAs(path); const bytes=fs.readFileSync(path);expect(bytes.subarray(0,5).toString()).toBe('%PDF-');expect(bytes.toString('latin1')).toContain('/Count 1');expect(bytes.toString('latin1')).toContain('/MediaBox [0 0 595.28 841.89]'); await page.screenshot({path:info.outputPath('banquet-client-preview.png')}); await page.locator('[data-bcm-prices]').uncheck();await expect(page.locator('[data-bcm-download]')).toBeEnabled();await expect(page.locator('[data-bcm-status]')).toContainText('Без указания стоимости'); await page.locator('[data-bcm-close]').click(); expect(await page.evaluate(()=>({draft:JSON.stringify(draft),orders:localStorage.sunOrders,boxes:localStorage.sunBoxes}))).toEqual(before); }); test('custom selection includes all selected sections, not just the current package or group filter',async({page})=>{ await boot(page); for(const id of ['menu-0','menu-5','menu-9','menu-14'])await page.locator(`[data-banquet-item="${id}"]`).check(); await page.locator('[data-banquet-group="salads"]').click(); await exportMenu(page); const data=await page.evaluate(()=>CateriumBanquetClientMenu.snapshot({catalog:boxes,draft})); expect(data.items.map(i=>i.id)).toEqual(['menu-0','menu-5','menu-9','menu-14']); const rendered=await page.evaluate(async()=>{const s=CateriumBanquetClientMenu.snapshot({catalog:boxes,draft});const r=await CateriumBanquetClientMenu.render(s,{showPrices:false});return {text:r.drawnText,heights:r.layout.columns.map(c=>c.height),room:r.layout.bottom-r.layout.top};}); for(const item of data.items)expect(rendered.text.join(' ')).toContain(item.name); expect(rendered.text.join(' ')).not.toContain('₽');expect(rendered.heights.every(h=>h<=rendered.room)).toBe(true); }); test('saved line prices including zero and promotion kopecks are preserved; a cleared selection is not repopulated',async({page})=>{ await boot(page);await page.addScriptTag({url:'/core/banquet-client-menu.js'}); const result=await page.evaluate(()=>{ const catalog=[{id:'free',name:'Подарок',category:6,price:100},{id:'discount',name:'Блюдо',category:6,price:999,promotion:{type:'percent',value:12.5}}]; const d={guestsCount:3,banquetSelection:['free','discount'],lines:[{id:'free',price:0}]};const before=JSON.stringify(d); const s=CateriumBanquetClientMenu.snapshot({catalog,draft:d});let empty=false;try{CateriumBanquetClientMenu.snapshot({catalog,draft:{...d,banquetSelection:[]}});}catch(_){empty=true;} return {prices:s.items.map(i=>i.cents),total:s.totalCents,unchanged:JSON.stringify(d)===before,empty}; }); expect(result).toEqual({prices:[0,87413],total:262239,unchanged:true,empty:true}); }); test('long names are fully wrapped on one page, oversized menus fail rather than silently truncating dishes',async({page},info)=>{ test.setTimeout(60000);await boot(page);await page.addScriptTag({url:'/core/banquet-client-menu.js'}); const result=await page.evaluate(async()=>{ const catalog=Array.from({length:12},(_,i)=>({id:String(i),category:6,catalogSection:i<6?'Холодные закуски':'Горячие блюда',name:`Позиция ${i+1}: очень длинное название блюда с подробным описанием подачи и праздничного оформления`,price:400.25,weight:'150 г'})); const d={guestsCount:20,banquetSelection:catalog.map(x=>x.id)}; const s=CateriumBanquetClientMenu.snapshot({catalog,draft:d,brand:{name:'Тестовая компания'}}),r=await CateriumBanquetClientMenu.render(s); let tooLarge='';try{const huge=catalog.map(x=>({...x,name:(x.name+' ').repeat(30)}));await CateriumBanquetClientMenu.render(CateriumBanquetClientMenu.snapshot({catalog:huge,draft:d}));}catch(e){tooLarge=e.message;} return {names:s.items.map(x=>x.name),text:r.drawnText.join(' '),fits:r.layout.columns.every(c=>c.height<=r.layout.bottom-r.layout.top),tooLarge,png:r.canvas.toDataURL('image/png')}; }); for(const name of result.names)expect(result.text).toContain(name);expect(result.fits).toBe(true);expect(result.tooLarge).toContain('не помещается'); fs.writeFileSync(info.outputPath('long-menu.png'),Buffer.from(result.png.split(',')[1],'base64')); }); test('switching companies closes the preview and disabled client-offer access is respected',async({page})=>{ await boot(page);await page.locator('[data-banquet-item="menu-0"]').check();await exportMenu(page); await page.evaluate(()=>window.dispatchEvent(new Event('sun:cloud-tenant-changing')));await expect(page.locator('#ctBanquetClientDialog')).toHaveCount(0); const denied=await page.evaluate(async()=>{window.SunSaaSV16.hasFeature=()=>false;try{await CateriumBanquetClientMenu.open({catalog:boxes,draft});return false;}catch(_){return true;}});expect(denied).toBe(true); });