Add a single-page A4 preview and PDF for all selected banquet dishes or a ready-menu selection, with company branding and optional costs. Preserve draft/order state and saved line prices, enforce access and tenant scope, and fail clearly rather than clipping oversized menus. Nineteen targeted browser scenarios passed twice and the generated iPhone PDF was visually checked. Full main QA must pass before the existing automatic production promotion; production verification includes the new renderer and real-asset client menu export.
82 lines
7.2 KiB
JavaScript
82 lines
7.2 KiB
JavaScript
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);
|
||
});
|