caterium-app/tests/proposal-quality.spec.mjs

93 lines
7.5 KiB
JavaScript
Raw 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';
async function boot(page){
await page.route('https://**',r=>r.abort());
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
await page.waitForFunction(()=>Boolean(window.sunClientOfferDebugPdfPages&&window.SunOfferTemplate));
}
const base={brandName:'Компания Лист',brandCity:'Казань',client:'Анна и Михаил',event:'Ужин для друзей',date:'2026-10-24',guests:20,foodGrams:8000,pricing:{base:24000,manual:2000,promo:0,discount:2000,itemsTotal:22000,delivery:1000,total:23000},items:[{id:'fixture',name:'Брускетта с печёными овощами, творожным сыром и зеленью',categoryId:0,categoryName:'Боксы',qty:2,unitPrice:12000,sum:24000,weight:'1200 г',composition:['Томаты, баклажаны, перец, сливочный сыр и зелень'],photoData:''}]};
function validLayout(pages){
return pages.flatMap((p,index)=>p.__proposalLayout.filter(b=>b.x<35||b.x+b.w>965||b.y<0||b.y+b.h>1400).map(b=>({page:index+1,...b})));
}
test('all 21 selections render their actual distinct covers and local Cyrillic fonts',async({page})=>{
test.setTimeout(90000);await boot(page);
const result=await page.evaluate(async s=>{
const out=[];
for(const id of CateriumProposalPDF.IDS){
const pages=await sunClientOfferDebugPdfPages({...s,offerTemplateId:id});
const text=pages.flatMap(p=>p.__proposalLayout).map(x=>x.text).join('\n');
out.push({id,cover:pages[0].dataset.sunProposalTemplate,width:pages[0].width,pages:pages.length,text,pixel:pages[0].toDataURL('image/jpeg',.1)});
pages.forEach(c=>{c.width=0;c.height=0});
}
return {out,fonts:[...document.fonts].filter(f=>f.family.includes('Caterium')).map(f=>f.status),choices:SunOfferTemplate.list().map(t=>t.id)};
},base);
expect(result.out).toHaveLength(21);expect(result.choices).toHaveLength(21);
expect(result.fonts).toEqual(['loaded','loaded','loaded']);
expect(new Set(result.out.map(r=>r.pixel)).size).toBe(21);
for(const row of result.out){expect(row.cover).toBe(row.id);expect(row.width).toBe(2400);expect(row.text.replaceAll('\n',' ')).toContain(base.items[0].name);expect(row.text).toContain('23');expect(row.text).not.toMatch(/Солнце|SUN \/ CATERING/)}
});
test('flow preserves long Cyrillic names and compositions across pages without text collisions',async({page})=>{
test.setTimeout(90000);await boot(page);
const results=await page.evaluate(async({s,validator})=>{
const valid=eval('('+validator+')'),out=[];
const long='Очень длинное название блюда с запечённым баклажаном и сливочным сыром ';
const composition='НачалоСостава '+('Томаты и печёный перец, сливочный сыр; '.repeat(130))+' КонецСостава';
const sample={...s,event:long.repeat(3),client:long.repeat(2),items:[{...s.items[0],name:long.repeat(4),composition:[composition]}]};
for(const id of ['light','editorial-grid','neon-menu','premium-emerald']){
const pages=await sunClientOfferDebugPdfPages({...sample,offerTemplateId:id});
const layout=pages.flatMap(p=>p.__proposalLayout),text=layout.map(b=>b.text).join(' ').replace(/\s+/g,' ');
const collisions=[];
pages.slice(1).forEach((p,index)=>{const list=p.__proposalLayout;for(let i=0;i<list.length;i++)for(let j=i+1;j<list.length;j++){const a=list[i],b=list[j];if(Math.min(a.x+a.w,b.x+b.w)-Math.max(a.x,b.x)>2&&Math.min(a.y+a.h,b.y+b.h)-Math.max(a.y,b.y)>2)collisions.push({page:index+2,a:a.role,b:b.role})}});
out.push({id,bounds:valid(pages),collisions,text});pages.forEach(c=>{c.width=0;c.height=0});
}return out;
},{s:base,validator:validLayout.toString()});
for(const r of results){expect(r.bounds).toEqual([]);expect(r.collisions).toEqual([]);expect(r.text).toContain('НачалоСостава');expect(r.text).toContain('КонецСостава');expect(r.text).not.toContain('…')}
});
test('offer settings hide personal data and optional blocks; custom gallery reaches the PDF',async({page})=>{
await boot(page);
const result=await page.evaluate(async s=>{
const c=document.createElement('canvas');c.width=40;c.height=40;const ctx=c.getContext('2d');ctx.fillStyle='#891552';ctx.fillRect(0,0,40,40);const gallery=c.toDataURL();
window.SunOfferWorkspaceV1769={galleryFor:()=>[gallery]};
window.SunClientOfferSettings={get:()=>({showClientName:false,showDate:false,showGuests:false,showGallery:true,showBoxCount:false,showPriceBreakdown:false,showAmountPerGuest:false,showControl:false,showExtras:false,showFooterNote:false,texts:{menuTitle:'Специальное меню'}})};
const draws=[],draw=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(image,...a){draws.push(image.src);return draw.call(this,image,...a)};
try{
const pages=await sunClientOfferDebugPdfPages({...s,offerTemplateId:'light',orderId:'local-fixture'}),text=pages.flatMap(p=>p.__proposalLayout).map(b=>b.text).join(' ');
return {text,gallery:draws.includes(gallery),bounds:pages.flatMap(p=>p.__proposalLayout.filter(b=>b.y+b.h>1400))};
}finally{CanvasRenderingContext2D.prototype.drawImage=draw}
},base);
expect(result.text).toContain('Специальное меню');expect(result.text).not.toContain(base.client);expect(result.text).not.toContain('октября');expect(result.text).not.toContain('Скидка');expect(result.text).not.toContain('гостей');expect(result.gallery).toBe(true);expect(result.bounds).toEqual([]);
});
test('per-order picker retains the classic, modern and additional collections together',async({page})=>{
await boot(page);
await page.evaluate(()=>{
const grid=document.createElement('div');grid.className='sun-v1764-offer-template-grid';
for(const id of CateriumProposalPDF.IDS){const b=document.createElement('button');b.dataset.v1764OfferTemplate=id;grid.appendChild(b)}document.body.appendChild(grid);
});
await expect(page.locator('.sun-v1764-offer-template-grid button')).toHaveCount(21);
await expect(page.locator('.sun-v1767-group-label')).toHaveCount(3);
});
test('an actual offer downloads the same A4 pages shown in its preview',async({page})=>{
test.setTimeout(60000);await boot(page);
await page.evaluate(async s=>{
boxes=[{id:'pdf-local',name:s.items[0].name,price:12000,category:0,weight:'1200 г',composition:s.items[0].composition,photo:''}];
orders=[{id:'pdf-local-order',event:s.event,contact:s.client,date:s.date,time:'18:00',guestsCount:20,lines:[{id:'pdf-local',qty:2}],total:24000,status:'Новый',clientOfferTemplateId:'event-ticket'}];
await window.sunOpenClientOffer('pdf-local-order');
},base);
await expect(page.locator('#sunClientOfferPreview canvas').first()).toHaveAttribute('data-sun-proposal-template','event-ticket');
const pageCount=await page.locator('#sunClientOfferPreview canvas').count();
const downloadPromise=page.waitForEvent('download');
await page.evaluate(()=>window.sunClientOfferDownload());
const download=await downloadPromise,stream=await download.createReadStream(),chunks=[];
for await(const chunk of stream)chunks.push(chunk);
const body=Buffer.concat(chunks).toString('latin1');
expect(body.startsWith('%PDF-1.4')).toBe(true);
expect((body.match(/\/Type \/Page\b/g)||[]).length).toBe(pageCount);
expect((body.match(/\/MediaBox \[0 0 595\.28 841\.89\]/g)||[]).length).toBe(pageCount);
expect(body).toContain('/Width 2400');
});