112 lines
11 KiB
JavaScript
112 lines
11 KiB
JavaScript
import {test,expect} from '@playwright/test';
|
||
import fs from 'node:fs';
|
||
import {demo} from '../ops/demo/trial-demo-data.mjs';
|
||
import {banquetDemo} from '../ops/demo/trial-banquet-data.mjs';
|
||
const completeDemo={...demo,boxes:[...demo.boxes,...banquetDemo.banquet],stock:[...demo.stock,...banquetDemo.stock]};
|
||
|
||
test('installed app serves old trial PNG links as cached lightweight images even offline',async({page,context})=>{
|
||
await page.route('**://api.caterium.ru/**',r=>r.abort());await page.route('**://*.supabase.co/**',r=>r.abort());
|
||
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
|
||
await page.waitForFunction(()=>Boolean(navigator.serviceWorker.controller));
|
||
const load=()=>page.evaluate(async d=>Promise.all(d.boxes.map(async b=>{const r=await fetch('/'+b.photo);return {type:r.headers.get('content-type'),bytes:(await r.arrayBuffer()).byteLength}})),demo);
|
||
const online=await load();expect(online.every(r=>r.type.includes('webp'))).toBe(true);expect(online.reduce((s,r)=>s+r.bytes,0)).toBe(1343650);
|
||
await expect.poll(()=>page.evaluate(async d=>(await Promise.all(d.boxes.map(b=>caches.match('/'+b.photo.replace('.png','.webp'))))).filter(Boolean).length,demo)).toBe(10);
|
||
await context.setOffline(true);expect(await load()).toEqual(online);await context.setOffline(false);
|
||
});
|
||
|
||
async function ready(page){
|
||
page.on('dialog',d=>d.accept());
|
||
await page.route('**://api.caterium.ru/**',r=>r.abort());await page.route('**://*.supabase.co/**',r=>r.abort());
|
||
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
|
||
await page.waitForFunction(()=>window.CateriumDataV1773&&window.CateriumTrialDemo&&window.SunAdminRBACV3);
|
||
await page.evaluate(d=>{
|
||
window.SunCloudV2={getSession:()=>({user:{id:'test'}}),getWorkspace:()=>({id:'test',role:'admin',permissions:{}}),getClient:()=>({rpc:async(name)=>({data:name==='sun_subscription_snapshot'?{access_mode:'full',status:'trialing',plan_name:'Полный',features:Object.fromEntries(['orders','shopping','stock','catalog_view','catalog_edit','suppliers','settings','clients','calendar','print'].map(k=>[k,true]))}:name==='caterium_trial_demo_status'?{eligible:true,installed:true,empty:false,canInstall:false}:null})}),status:()=>({connected:true,signedIn:true,membershipsLoaded:true}),hasPermission:()=>true};
|
||
// Hydrate AFTER stock UI boot: the real cloud path must update its closure.
|
||
for(const [key,value] of Object.entries({sunBoxes:d.boxes,sunStock:d.stock,sunSuppliers:d.suppliers,sunStockMoves:[],sunOrders:[],sunTrialDemoV1:{version:1,batch:d.batch,scenario:d.scenario}}))localStorage.setItem(key,JSON.stringify(value));
|
||
boxes=structuredClone(d.boxes);orders=[];window.dispatchEvent(new Event('sun:cloud-state-applied'));window.dispatchEvent(new Event('sun:cloud-permissions-changed'));window.render();window.CateriumTrialDemo.render();
|
||
},completeDemo);
|
||
await expect(page.locator('#ctTrialDemo')).toBeVisible();
|
||
}
|
||
test('demo TTK, order, shortages, receipt and one-time write-off work in the full app',async({page})=>{
|
||
await ready(page);
|
||
await page.locator('#ctTrialDemo summary').click();await page.locator('[data-demo-ttk]').click();
|
||
await expect(page.locator('#ctDemoTtk')).toBeVisible();await expect(page.locator('#ctDemoTtk')).toContainText('840 г');await expect(page.locator('#ctDemoTtk')).toContainText('Брутто');
|
||
expect(await page.locator('#ctDemoTtk').evaluate(el=>el.getBoundingClientRect().width<=innerWidth)).toBe(true);
|
||
await page.getByRole('button',{name:'Закрыть ТТК',exact:true}).click();
|
||
await page.locator('[data-demo-order]').click();await expect(page.locator('#ctDemoMessage')).toContainText('9 700 ₽');
|
||
await page.locator('[data-demo-order]').click();expect(await page.evaluate(()=>orders.length)).toBe(1);
|
||
expect(await page.evaluate(()=>orders[0].autoCompletionDisabled)).toBe(true);
|
||
await page.locator('[data-demo-go="shopping"]').click();
|
||
await expect(page.locator('[data-shop-key="багет|кг"]')).toContainText('0,96');
|
||
await expect(page.locator('[data-shop-key="багет|кг"]')).toContainText('230');
|
||
await page.getByRole('button',{name:'Склад',exact:true}).first().click();
|
||
await page.evaluate(()=>window.stockMovement('demo-v1-stock-baguette','in'));
|
||
await page.locator('#stockMoveQty').fill('2');await page.locator('#stockSaveMove').click();
|
||
expect(await page.evaluate(()=>JSON.parse(localStorage.sunStock).find(p=>p.id==='demo-v1-stock-baguette').qty)).toBe(2);
|
||
expect(await page.evaluate(()=>JSON.parse(localStorage.sunStock).find(p=>p.id==='demo-v1-stock-baguette').cost)).toBe(240);
|
||
await page.getByRole('button',{name:'Закупки',exact:true}).first().click();await expect(page.locator('[data-shop-key="багет|кг"]')).toHaveCount(0);
|
||
await page.evaluate(()=>window.writeOffOrderStock(orders[0].id));
|
||
const written=await page.evaluate(()=>({qty:JSON.parse(localStorage.sunStock).find(p=>p.id==='demo-v1-stock-baguette').qty,moves:JSON.parse(localStorage.sunStockMoves).length,done:orders[0].stockWrittenOff}));
|
||
expect(written.qty).toBeCloseTo(1.04,3);expect(written.done).toBe(true);expect(written.moves).toBeGreaterThan(5);
|
||
await page.evaluate(()=>window.writeOffOrderStock(orders[0].id));expect(await page.evaluate(()=>JSON.parse(localStorage.sunStockMoves).length)).toBe(written.moves);
|
||
});
|
||
test('demo photo assets load and company changes remove the trial guide',async({page})=>{
|
||
await ready(page);
|
||
expect(await page.evaluate(async d=>{const images=await Promise.all(d.boxes.map(b=>new Promise(resolve=>{const i=new Image();i.onload=()=>resolve(i.naturalWidth===1024);i.onerror=()=>resolve(false);i.src=SunSafe.imageAssetSrc(b.photo)})));return images.every(Boolean)},demo)).toBe(true);
|
||
const sources=await page.locator('#tiles img[src*="demo/images/"]').evaluateAll(images=>images.map(i=>i.getAttribute('src')));
|
||
expect(sources).toHaveLength(10);expect(sources.every(s=>s.endsWith('.webp'))).toBe(true);
|
||
const bytes=demo.boxes.reduce((sum,b)=>sum+fs.statSync('public/'+b.photo.replace('.png','.webp')).size,0);expect(bytes).toBeLessThan(1500000);
|
||
expect(await page.evaluate(()=>['catalog/001.jpg','data:image/png;base64,AAAA','https://other.example/demo/images/caprese.png','demo/images/custom.png'].map(SunSafe.imageAssetSrc))).toEqual(['catalog/001.jpg','data:image/png;base64,AAAA','https://other.example/demo/images/caprese.png','demo/images/custom.png']);
|
||
await page.evaluate(()=>{window.dispatchEvent(new Event('sun:cloud-tenant-changing'));window.SunCloudV2.getSession=()=>null;localStorage.removeItem('sunTrialDemoV1');window.CateriumTrialDemo.render()});
|
||
await expect(page.locator('#ctTrialDemo')).toHaveCount(0);
|
||
});
|
||
|
||
test('trial banquet categories, per-guest TTK, shopping and stock work together',async({page})=>{
|
||
await ready(page);
|
||
await page.getByRole('button',{name:'Банкетное меню',exact:true}).click();
|
||
await expect(page.locator('[data-banquet-row]')).toHaveCount(18);
|
||
await page.locator('[data-banquet-group="sides"]').click();await expect(page.locator('[data-banquet-row]')).toHaveCount(2);
|
||
await page.locator('[data-banquet-ttk="demo-banquet-v1-mash"]').click();
|
||
await expect(page.locator('#ctDemoTtk')).toContainText('На 1 порцию / 1 гостя');
|
||
await expect(page.locator('#ctDemoTtk')).toContainText('150 г');await expect(page.locator('#ctDemoTtk')).toContainText('Цена порции: 160 ₽');
|
||
await page.getByRole('button',{name:'Закрыть ТТК',exact:true}).click();
|
||
await page.locator('[data-banquet-item="demo-banquet-v1-mash"]').check();
|
||
await page.locator('[data-banquet-group="desserts"]').click();await expect(page.locator('[data-banquet-row]')).toHaveCount(2);
|
||
await page.locator('[data-banquet-group="main"]').click();await page.locator('[data-banquet-item="demo-banquet-v1-chicken"]').check();
|
||
await page.locator('[data-banquet-guests]').fill('4');await expect(page.locator('[data-banquet-total]')).toHaveText('2 600 ₽');
|
||
await page.locator('[data-banquet-apply]').click();
|
||
await page.evaluate(()=>{const day=new Date();day.setDate(day.getDate()+1);draft.event='Пробный банкет';draft.date=`${day.getFullYear()}-${String(day.getMonth()+1).padStart(2,'0')}-${String(day.getDate()).padStart(2,'0')}`;draft.time='12:00';draft.autoCompletionDisabled=true;document.getElementById('date').value=draft.date;document.getElementById('time').value=draft.time;window.saveOrder()});
|
||
expect(await page.evaluate(()=>orders.at(-1).lines.map(l=>({id:l.id,qty:l.qty})))).toEqual([{id:'demo-banquet-v1-chicken',qty:4},{id:'demo-banquet-v1-mash',qty:4}]);
|
||
await page.getByRole('button',{name:'Закупки',exact:true}).first().click();
|
||
await expect(page.locator('[data-shop-key="картофель|кг"]')).toContainText('0,68');
|
||
await expect(page.locator('[data-shop-key="филе куриное сырое|кг"]')).toContainText('0,72');
|
||
await page.getByRole('button',{name:'Склад',exact:true}).first().click();
|
||
await page.evaluate(()=>window.stockMovement('demo-banquet-v1-stock-potato','in'));
|
||
await page.locator('#stockMoveQty').fill('1');await page.locator('#stockSaveMove').click();
|
||
await page.evaluate(()=>window.writeOffOrderStock(orders.at(-1).id));
|
||
expect(await page.evaluate(()=>JSON.parse(localStorage.sunStock).find(p=>p.id==='demo-banquet-v1-stock-potato').qty)).toBeCloseTo(.32,3);
|
||
});
|
||
|
||
test('owner onboarding preserves server demo when saving the company profile',async({page})=>{
|
||
await page.route('**/index.html',r=>r.fulfill({contentType:'text/html',body:'<!doctype html><html><body></body></html>'}));await page.goto('/index.html');
|
||
await page.addScriptTag({url:'/core/sun-safe.js'});await page.addScriptTag({url:'/core/cloud-transport.js'});
|
||
const runtime=fs.readFileSync('public/app-runtime.js','utf8');let source=runtime.slice(runtime.indexOf('/* ===== MODULE: cloud-sync-v2.js'),runtime.indexOf('/* ===== MODULE: admin-rbac-v3.js'));
|
||
source=source.replace('async function boot(){','async function boot(){return;').replace('window.SunCloudV2={',`window.SunCloudV2={testInit:c=>{client=c;session={user:{id:'test-user'}};config.tenantStorageReady=true;config.localWorkspaceId='';config.workspaceId=''},`);
|
||
await page.addScriptTag({content:'var boxes=[],orders=[];async function compactSunBoxesForStorage(x){return x};'+source});
|
||
const result=await page.evaluate(async d=>{
|
||
let created=false,saved=null;const payload={format:'sun-cloud-v2',version:2,storage:{sunBoxes:{t:'j',v:d.boxes},sunStock:{t:'j',v:d.stock},sunSuppliers:{t:'j',v:d.suppliers},sunTrialDemoV1:{t:'j',v:{version:1,scenario:d.scenario}},sunCompanyProfileV1:{t:'j',v:{email:'owner@example.invalid',name:''}}}};
|
||
const row={payload,revision:1,updated_at:new Date().toISOString()};
|
||
window.SunCloudV2.testInit({rpc:async(name,args)=>{
|
||
if(name==='sun_my_workspaces')return {data:created?[{id:'company',name:'Test',role:'admin',is_active:true}]:[]};
|
||
if(name==='sun_create_workspace'){created=true;return {data:'company'}}
|
||
if(name==='sun_fetch_app_state')return {data:[row]};
|
||
if(name==='sun_save_app_state_v17'){saved=structuredClone(args.p_payload);return {data:[{...row,payload:saved,revision:2}]}}
|
||
return {data:null};
|
||
},channel:()=>({on(){return this},subscribe(){return this}})});
|
||
await window.SunCloudV2.createTrialWorkspace('My trial company');
|
||
return {saved,local:JSON.parse(localStorage.sunBoxes),workspace:window.SunCloudV2.getWorkspace()};
|
||
},demo);
|
||
expect(result.saved.storage.sunBoxes.v).toHaveLength(10);expect(result.local).toHaveLength(10);
|
||
expect(result.saved.storage.sunStock.v).toEqual(demo.stock);expect(result.saved.storage.sunCompanyProfileV1.v).toMatchObject({name:'My trial company',email:'owner@example.invalid'});
|
||
});
|