caterium-app/tests/app.spec.mjs
2026-09-09 02:37:25 +03:00

115 lines
14 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';
import fs from 'node:fs';
import path from 'node:path';
const injectCore=async(page,file,marker='')=>{
const source=fs.readFileSync(path.join(process.cwd(),'public','core',file),'utf8');
await page.addScriptTag({content:source});
if(marker)await page.waitForFunction(m=>Boolean(window[m]),marker,{timeout:5000});
};
test('app boots without page errors', async ({ page }) => {
const errors=[];page.on('pageerror',e=>errors.push(String(e)));
await page.goto('/index.html',{waitUntil:'domcontentloaded'});await page.waitForTimeout(1200);
expect(errors).toEqual([]);
});
test('official catalog and photos remain intact', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
const result=await page.evaluate(()=>({catalog:Array.isArray(window.SUN_OFFICIAL_CATALOG)?window.SUN_OFFICIAL_CATALOG.length:0,version:window.SUN_OFFICIAL_CATALOG_VERSION||'',photos:[...document.querySelectorAll('img[src*="catalog/"]')].length}));
expect(result.catalog).toBeGreaterThanOrEqual(100);expect(result.version).toBeTruthy();
});
test('mobile body does not overflow viewport', async ({ page }, testInfo) => {
test.skip(testInfo.project.name!=='mobile-390'); await page.goto('/index.html', { waitUntil:'domcontentloaded' }); await page.waitForTimeout(500);
const dims=await page.evaluate(()=>({innerWidth,scrollWidth:document.documentElement.scrollWidth,bodyWidth:document.body.scrollWidth}));
expect(dims.scrollWidth).toBeLessThanOrEqual(dims.innerWidth+2); expect(dims.bodyWidth).toBeLessThanOrEqual(dims.innerWidth+2);
});
test('Developer Console memory refresh is bounded and does not react to its own DOM update', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
const performance=fs.readFileSync(path.join(process.cwd(),'public','core','performance.js'),'utf8');
await page.evaluate(()=>{
document.body.innerHTML='<section id="sun-developer-console-v22" class="on"><div id="sunDevBody"></div><span id="sunDevReleaseVersion"></span></section>';
window.__rpcCount=0;
window.SunCloudV2={getClient:()=>({rpc:async()=>{window.__rpcCount++;return {data:{server_size:'35 MB',database_size:'21 MB',storage_size:'14 MB',storage_objects:12}}}})};
});
await page.addScriptTag({content:performance});
await page.waitForFunction(()=>Boolean(window.SunPerformance),null,{timeout:5000});
await page.evaluate(async()=>{await window.SunPerformance.refreshDeveloperMemory(true);await new Promise(r=>setTimeout(r,250));});
const result=await page.evaluate(()=>({count:window.__rpcCount,box:document.querySelectorAll('#sunDevMemoryV1761').length,version:document.getElementById('sunDevReleaseVersion')?.textContent}));
expect(result.count).toBeLessThanOrEqual(2);expect(result.box).toBe(1);expect(result.version).toBe('17.7.1');
});
test('v17.7.1 stays free of timer page errors during idle', async ({ page }, testInfo) => {
test.skip(testInfo.project.name!=='desktop');
const errors=[];page.on('pageerror',e=>errors.push(String(e)));
await page.goto('/index.html',{waitUntil:'domcontentloaded'});await page.waitForTimeout(5500);
expect(errors).toEqual([]);
const runtimeSource=fs.readFileSync(path.join(process.cwd(),'public','app-runtime.js'),'utf8');
expect(runtimeSource).toContain("const VERSION = '17.7.1'");
});
test('support refresh uses lightweight cloud API', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
await page.evaluate(()=>{try{window.SunOpsUXV1762?.disconnect?.()}catch(_){}document.getElementById('sunOpsUXV1762Script')?.remove();window.SunOpsUXV1762=undefined;window.__lightRefresh=0;window.__fullEnter=0;let nav=document.querySelector('header nav');if(!nav){const header=document.createElement('header');nav=document.createElement('nav');header.appendChild(nav);document.body.prepend(header)}window.SunCloudV2={getSupportMode:()=>({workspaceId:'support-test',name:'Тест'}),isSupportMode:()=>true,hasPermission:()=>false,getSession:()=>({user:{id:'dev'}}),refreshSupportWorkspace:async()=>{window.__lightRefresh++;return true},enterSupportWorkspace:async()=>{window.__fullEnter++;return true}};window.editBox=window.editBox||(()=>{});});
await injectCore(page,'ops-ux-v1762.js');await page.waitForFunction(()=>Boolean(window.SunOpsUXV1762),null,{timeout:5000});
await page.evaluate(()=>window.SunOpsUXV1762.refreshSupport(true));
const counts=await page.evaluate(()=>({light:window.__lightRefresh,full:window.__fullEnter}));expect(counts.light).toBe(1);expect(counts.full).toBe(0);
});
test('developer hotfix does not poll admin every second', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
await page.evaluate(()=>{window.SunHotfixV1763=undefined;window.__devChecks=0;window.SunCloudV2={getSession:()=>({user:{id:'dev'}}),getWorkspace:()=>null};window.SunDeveloperV22={open:()=>{},isPlatformAdmin:()=>false,checkPlatformAdmin:async()=>{window.__devChecks++;return false}};});
await injectCore(page,'hotfix-v1763.js','SunHotfixV1763');await page.waitForTimeout(2600);expect(await page.evaluate(()=>window.__devChecks)).toBeLessThanOrEqual(2);
});
test('Menu sidebar icon uses native mask and has no duplicate inline SVG', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
await page.evaluate(()=>{let nav=document.querySelector('header nav');if(!nav){const h=document.createElement('header');nav=document.createElement('nav');h.appendChild(nav);document.body.prepend(h)}let b=nav.querySelector('.nav-menu');if(!b){b=document.createElement('button');b.className='sun-nav-button nav-menu';b.dataset.navLabel='Меню';b.textContent='Меню';nav.appendChild(b)}});
await injectCore(page,'ux-fixes-v1764.js','SunUXFixV1764');await page.waitForFunction(()=>document.querySelector('.nav-menu')?.dataset?.sunMenuIconV1766==='1',null,{timeout:5000});
const r=await page.evaluate(()=>{const b=document.querySelector('.nav-menu');return {inline:b.querySelectorAll('.sun-v1764-menu-icon').length,marker:b.dataset.sunMenuIconV1766,label:b.textContent.trim()}});
const uxSource=fs.readFileSync(path.join(process.cwd(),'public','core','ux-fixes-v1764.js'),'utf8');expect(r.inline).toBe(0);expect(r.marker).toBe('1');expect(r.label).toContain('Меню');expect(uxSource).toContain('--sun-icon:url(');
});
test('proposal templates preserve selection and expose structural previews', async () => {
const runtimeSource=fs.readFileSync(path.join(process.cwd(),'public','app-runtime.js'),'utf8');const uxSource=fs.readFileSync(path.join(process.cwd(),'public','core','ux-fixes-v1764.js'),'utf8');
expect(runtimeSource).toContain("OFFER_TEMPLATE_IDS.has(explicitTemplate)");for(const marker of ['mini-light-hero','mini-editorial-hero','mini-midnight-total','mini-emerald-gold'])expect(runtimeSource+uxSource).toContain(marker);
});
test('classic client PDF renders distinctive cover and keeps preview/download page parity', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});const source=fs.readFileSync(path.join(process.cwd(),'public','core','classic-offer-pdf-v1767.js'),'utf8');await page.addScriptTag({content:source});await page.waitForFunction(()=>Boolean(window.SunClassicOfferPDFV1767),null,{timeout:5000});
const result=await page.evaluate(async()=>{const c=document.createElement('canvas');c.width=1600;c.height=1000;const x=c.getContext('2d');x.fillStyle='#d9a757';x.fillRect(0,0,c.width,c.height);x.fillStyle='#173a2d';x.fillRect(500,120,700,700);const photo=c.toDataURL('image/jpeg',.8);const snapshot={id:'1767',client:'Анна',event:'День рождения',date:'2026-09-20',time:'18:30',guests:24,offerTemplateId:'event-ticket',pricing:{base:23517,itemsTotal:23517,total:23517},items:[{id:'a',name:'Фуршетный бокс',qty:3,categoryId:0,photoData:photo,weight:'1500 г'}]};const fakeBase=async()=>{const base=document.createElement('canvas');base.width=1600;base.height=2262;return [base]};const pages=await window.SunClassicOfferPDFV1767.renderPages(snapshot,fakeBase);return {count:pages.length,id:pages[0].dataset.sunClassicTemplate,cover:pages[0].dataset.sunClassicCover,classic:window.SunClassicOfferPDFV1767.CLASSIC_IDS.length,archive:window.SunClassicOfferPDFV1767.ARCHIVE_IDS.length};});
expect(result.count).toBe(2);expect(result.id).toBe('event-ticket');expect(result.cover).toBe('1');expect(result.classic).toBe(10);expect(result.archive).toBe(3);
});
test('Developer Console UX module boots', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});const source=fs.readFileSync(path.join(process.cwd(),'public','core','developer-console-v1768.js'),'utf8');
await page.evaluate(()=>{document.body.innerHTML='<section id="sun-developer-console-v22" class="view on"><div class="sun-dev-tabs"><button data-dev-tab="overview" class="on">Обзор</button><button data-dev-tab="companies">Компании</button><button data-dev-tab="accounts">Аккаунты</button><button data-dev-tab="plans">Тарифы</button><button data-dev-tab="journal">Журнал</button></div><div id="sunDevBody"></div></section>';window.SunDeveloperV22={open:async()=>true,activateTab:async()=>true};window.SunCloudV2={getClient:()=>null};window.SunEnterprise={toast:()=>null};});
await page.addScriptTag({content:source});await page.waitForFunction(()=>Boolean(window.SunDeveloperUXV1768),null,{timeout:5000});const result=await page.evaluate(()=>window.SunDeveloperUXV1768.checks());expect(result.version).toBe('17.6.8');
});
test('offer workspace module boots', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});const source=fs.readFileSync(path.join(process.cwd(),'public','core','offer-workspace-v1769.js'),'utf8');
await page.evaluate(()=>{document.body.innerHTML='<div id="sunClientOfferModal" class="modal on"><div class="dialog"><div class="sun-offer-modal-head"><button id="sunOfferPdf">Скачать PDF</button><button id="sunOfferEdit">Редактировать</button></div><section id="sunOfferEditor"><div class="sun-offer-editor-gallery"><div class="sun-offer-editor-gallery-images"><img src="offer-gallery/001.jpg"><img src="offer-gallery/002.jpg"></div></div></section><section id="sunOfferTemplateV1764"></section><div class="sun-offer-frame-wrap"><div id="sunClientOfferPreview"></div></div></div></div>';window.sunOpenClientOffer=async()=>true;window.sunOpenCurrentClientOffer=async()=>true;window.SunClassicOfferPDFV1767={renderPages:async()=>[]};window.SunEnterprise={toast:()=>null};});
await page.addScriptTag({content:source});await page.waitForFunction(()=>Boolean(window.SunOfferWorkspaceV1769),null,{timeout:5000});const result=await page.evaluate(()=>window.SunOfferWorkspaceV1769.checks());expect(result.version).toBe('17.6.9');expect(result.tabs).toBe(true);expect(await page.locator('[data-offer-gallery-slot]').count()).toBe(2);
});
test('v17.7.1 data layer can update orders without direct UI globals', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});await page.waitForFunction(()=>Boolean(window.CateriumDataV1771),null,{timeout:5000});
const result=await page.evaluate(()=>{const d=window.CateriumDataV1771;d.orders.replace([{id:77,status:'Новый',total:1000}],{persistLocal:false,reason:'e2e-seed'});const before=d.orders.get(77);d.orders.update(77,o=>({...o,status:'Тест'}),{persistLocal:false});return {before:before.status,after:d.orders.get(77).status,version:d.VERSION,alias:window.CateriumDataV1770===d}});
expect(result).toEqual({before:'Новый',after:'Тест',version:'17.7.1',alias:true});
});
test('server automation applies returned normalized orders through current data layer alias', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
await page.evaluate(()=>{window.__applied=[];window.__signed=false;window.CateriumDataV1770={isSupportReadOnly:()=>false,diagnostics:{measure:async(_n,fn)=>fn()},orders:{applyServerChanges:(orders)=>window.__applied.push(...orders)}};window.CateriumData=window.CateriumDataV1770;window.SunCloudV2={getSession:()=>window.__signed?({user:{id:'u'}}):null,getWorkspace:()=>({id:'w'}),getClient:()=>({rpc:async()=>({data:{changed:1,orders:[{id:8,status:'Отдан заказчику'}]},error:null})})};});
const source=fs.readFileSync(path.join(process.cwd(),'public','core','server-automation-v1770.js'),'utf8');await page.addScriptTag({content:source});await page.waitForFunction(()=>Boolean(window.CateriumServerAutomationV1770));await page.evaluate(async()=>{window.__signed=true;await window.CateriumServerAutomationV1770.run({force:true})});expect(await page.evaluate(()=>window.__applied)).toEqual([{id:8,status:'Отдан заказчику'}]);
});
test('v17.7.1 data layer serves orders and catalog with v17.7.0 compatibility alias', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});await page.waitForFunction(()=>Boolean(window.CateriumDataV1771),null,{timeout:5000});
const result=await page.evaluate(()=>{const d=window.CateriumDataV1771,beforeOrders=d.orders.list(),beforeCatalog=d.catalog.list();d.orders.replace([...beforeOrders,{id:'qa-v1771',event:'QA',lines:[]}],{persistLocal:false,reason:'qa-order'});d.catalog.replace([...beforeCatalog,{id:'qa-v1771',name:'QA item',price:0,ingredients:[]}],{persistLocal:false,reason:'qa-catalog'});const hasOrder=Boolean(d.orders.get('qa-v1771')),hasCatalog=Boolean(d.catalog.get('qa-v1771'));d.orders.replace(beforeOrders,{persistLocal:false,reason:'qa-restore'});d.catalog.replace(beforeCatalog,{persistLocal:false,reason:'qa-restore'});return {version:d.VERSION,alias:window.CateriumDataV1770===d,hasOrder,hasCatalog};});
expect(result).toEqual({version:'17.7.1',alias:true,hasOrder:true,hasCatalog:true});
});