Fix Developer Console memory refresh freeze, auto-complete and fully pay orders one minute after scheduled time, persist offer template per client proposal, and add styled Menu SVG icon. Includes PWA cache update and regression coverage.
165 lines
11 KiB
JavaScript
165 lines
11 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { test, expect } from '@playwright/test';
|
|
async function loadModule(page,file,globalName){
|
|
await page.goto('/index.html', { waitUntil:'domcontentloaded' });
|
|
const content=fs.readFileSync(path.join(process.cwd(),'public','core',file),'utf8');
|
|
await page.addScriptTag({content});
|
|
expect(await page.evaluate(name=>Boolean(window[name]),globalName)).toBeTruthy();
|
|
}
|
|
async function injectCore(page,file,globalName){
|
|
const content=fs.readFileSync(path.join(process.cwd(),'public','core',file),'utf8');
|
|
await page.addScriptTag({content});
|
|
if(globalName)expect(await page.evaluate(name=>Boolean(window[name]),globalName)).toBeTruthy();
|
|
}
|
|
test('legacy localStorage payload cannot execute XSS', async ({ page }) => {
|
|
const pageErrors=[]; page.on('pageerror',e=>pageErrors.push(String(e)));
|
|
await page.addInitScript(() => {
|
|
localStorage.setItem('sunBoxes', JSON.stringify([{id:'1',name:'<img src=x onerror="window.__cateriumXss=1">',price:100,photo:'javascript:alert(1)',ingredients:[['<svg onload="window.__cateriumXss=2">',1,'шт.']]}]));
|
|
localStorage.setItem('sunOrders', JSON.stringify([{id:1,event:'<img src=x onerror="window.__cateriumXss=3">',date:'2026-09-07',time:'12:00',address:'<svg onload="window.__cateriumXss=4">',status:'Новый',lines:[{id:'1',qty:1}]}]));
|
|
});
|
|
await page.goto('/index.html', { waitUntil:'domcontentloaded' }); await page.waitForTimeout(300);
|
|
expect(await page.evaluate(()=>window.__cateriumXss)).toBeUndefined();
|
|
expect(pageErrors.filter(x=>!x.includes('supabase'))).toEqual([]);
|
|
});
|
|
test('safe insert helper handles foreign reference node', async ({ page }) => {
|
|
await loadModule(page,'sun-safe.js','SunSafe');
|
|
const result=await page.evaluate(()=>{const a=document.createElement('div'),b=document.createElement('div'),n=document.createElement('span'),foreign=document.createElement('i');a.appendChild(foreign);document.body.append(a,b);try{window.SunSafe.insertBefore(b,n,foreign);return {ok:true,parent:n.parentNode===b}}catch(e){return {ok:false,error:String(e)}}});
|
|
expect(result).toEqual({ok:true,parent:true});
|
|
});
|
|
test('shared PDF engine produces A4 PDF blob', async ({ page }) => {
|
|
await loadModule(page,'pdf-engine.js','SunPdfEngine');
|
|
const result=await page.evaluate(async()=>{const fake=new Uint8Array([255,216,255,217]);const blob=window.SunPdfEngine.fromJpegs([{width:1000,height:1414,bytes:fake}]);const head=new TextDecoder().decode(new Uint8Array(await blob.arrayBuffer()).slice(0,8));return {type:blob.type,size:blob.size,head,w:window.SunPdfEngine.PAGE_W,h:window.SunPdfEngine.PAGE_H}});
|
|
expect(result.type).toBe('application/pdf'); expect(result.size).toBeGreaterThan(150); expect(result.head.startsWith('%PDF-1.4')).toBeTruthy(); expect(result.w/result.h).toBeCloseTo(1/Math.sqrt(2),3);
|
|
});
|
|
test('chat photo guard compresses a large camera image', async ({ page }) => {
|
|
await loadModule(page,'performance.js','SunAttachmentGuard');
|
|
const result=await page.evaluate(async()=>{
|
|
const c=document.createElement('canvas');c.width=3000;c.height=2200;const x=c.getContext('2d');
|
|
const g=x.createLinearGradient(0,0,c.width,c.height);g.addColorStop(0,'#172a3a');g.addColorStop(.5,'#d79a4a');g.addColorStop(1,'#f1e3ca');x.fillStyle=g;x.fillRect(0,0,c.width,c.height);
|
|
for(let i=0;i<1200;i++){x.fillStyle=`rgba(${i%255},${(i*7)%255},${(i*13)%255},.55)`;x.fillRect((i*31)%3000,(i*47)%2200,90,55)}
|
|
const blob=await new Promise(r=>c.toBlob(r,'image/jpeg',1));const input=new File([blob],'camera-photo.jpg',{type:'image/jpeg'});const out=await window.SunAttachmentGuard.prepareAttachment(input);const bm=await createImageBitmap(out.file);const data={original:input.size,size:out.file.size,width:bm.width,height:bm.height,type:out.file.type,name:out.file.name};bm.close();return data;
|
|
});
|
|
expect(Math.max(result.width,result.height)).toBeLessThanOrEqual(2048);expect(result.size).toBeLessThan(result.original);expect(result.size).toBeLessThanOrEqual(15*1024*1024);expect(result.type).toBe('image/jpeg');expect(result.name.endsWith('.jpg')).toBeTruthy();
|
|
});
|
|
test('v17.6.2 operations UX boots with menu and route features', async ({ page }) => {
|
|
await page.goto('/index.html', { waitUntil:'domcontentloaded' });
|
|
await page.evaluate(()=>{
|
|
if(!document.querySelector('header nav')){
|
|
const header=document.createElement('header');
|
|
const nav=document.createElement('nav');
|
|
header.appendChild(nav);
|
|
document.body.appendChild(header);
|
|
}
|
|
window.SunCloudV2={
|
|
getSupportMode:()=>null,
|
|
isSupportMode:()=>false,
|
|
hasPermission:()=>true,
|
|
getSession:()=>({user:{id:'e2e'}})
|
|
};
|
|
window.editBox=()=>{};
|
|
});
|
|
const content=fs.readFileSync(path.join(process.cwd(),'public','core','ops-ux-v1762.js'),'utf8');
|
|
await page.addScriptTag({content});
|
|
await page.waitForFunction(()=>Boolean(window.SunOpsUXV1762),null,{timeout:5000});
|
|
const checks=await page.evaluate(()=>window.SunOpsUXV1762.checks());
|
|
expect(checks.version).toBe('17.6.2');
|
|
expect(checks.menuPage).toBeTruthy();
|
|
expect(checks.routeStartKey).toBe('sunRouteBaseV1');
|
|
expect(checks.routeOrderPopup).toBeTruthy();
|
|
expect(checks.calendarMore).toBeTruthy();
|
|
expect(await page.locator('header nav button', {hasText:'Меню'}).count()).toBeGreaterThan(0);
|
|
});
|
|
test('v17.6.3 developer gate bypasses workspace loading and SaaS click is safe', async ({ page }) => {
|
|
await page.goto('/index.html', { waitUntil:'domcontentloaded' });
|
|
await page.evaluate(()=>{
|
|
document.body.innerHTML='<div id="sunCloudAuthGateV3"><div class="sun-cloud-auth-card"><h2>Загружаю рабочую базу</h2></div></div><button type="button" data-saas-admin>Управление SaaS</button>';
|
|
window.__devOpenArgs=[];
|
|
window.SunCloudV2={
|
|
getSession:()=>({user:{id:'dev',email:'developer@example.com'}}),
|
|
getWorkspace:()=>null,
|
|
status:()=>({membershipsLoading:true,membershipsLoaded:false}),
|
|
signOut:()=>{}
|
|
};
|
|
window.SunDeveloperV22={
|
|
open:arg=>{window.__devOpenArgs.push(arg===null?'null':arg?.constructor?.name||typeof arg)},
|
|
isPlatformAdmin:()=>true,
|
|
checkPlatformAdmin:async()=>true
|
|
};
|
|
});
|
|
const content=fs.readFileSync(path.join(process.cwd(),'public','core','hotfix-v1763.js'),'utf8');
|
|
await page.addScriptTag({content});
|
|
await page.waitForFunction(()=>document.querySelector('[data-open-dev-v1763]')&&Boolean(window.SunHotfixV1763),null,{timeout:5000});
|
|
expect(await page.locator('#sunCloudAuthGateV3 h2').textContent()).toBe('Аккаунт разработчика');
|
|
await page.locator('[data-saas-admin]').click();
|
|
await page.locator('[data-open-dev-v1763]').click();
|
|
const args=await page.evaluate(()=>window.__devOpenArgs);
|
|
expect(args).toEqual(['null','null']);
|
|
});
|
|
test('v17.6.4 auto-completes and fully pays an order one minute after scheduled time', async ({ page }) => {
|
|
await page.addInitScript(()=>{
|
|
localStorage.setItem('sunOrders',JSON.stringify([{id:764,event:'Тест авто-завершения',date:'2099-09-07',time:'12:00',status:'Новый',prepayment:100,total:1500,lines:[]}]))
|
|
});
|
|
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
|
|
await injectCore(page,'ux-fixes-v1764.js','SunUXFixV1764');
|
|
const result=await page.evaluate(()=>{
|
|
window.orderTotalValue=()=>1500;
|
|
const run=window.SunUXFixV1764.autoCompleteOrders(new Date('2099-09-07T12:01:01').getTime(),{silent:true});
|
|
const order=JSON.parse(localStorage.getItem('sunOrders')||'[]').find(x=>String(x.id)==='764');
|
|
return {run,prepayment:order?.prepayment,balance:order?.balance,status:order?.status,auto:Boolean(order?.sunAutoCompletedAt),classDate:window.SunUXFixV1764.classificationDate(order,'2099-09-07')};
|
|
});
|
|
expect(result.run.changed).toBe(1);
|
|
expect(result.prepayment).toBe(1500);
|
|
expect(result.balance).toBe(0);
|
|
expect(result.status).toBe('Отдан заказчику');
|
|
expect(result.auto).toBeTruthy();
|
|
expect(result.classDate).toBe('0001-01-01');
|
|
});
|
|
test('v17.6.4 keeps proposal designs per order and renders a styled menu icon', async ({ page }) => {
|
|
await page.addInitScript(()=>{
|
|
localStorage.setItem('sunOrders',JSON.stringify([
|
|
{id:801,event:'Editorial',date:'2099-09-08',time:'12:00',clientOfferSnapshot:{version:8,offerTemplateId:'editorial-grid'}},
|
|
{id:802,event:'Emerald',date:'2099-09-08',time:'13:00',clientOfferSnapshot:{version:8,offerTemplateId:'emerald-gold'}}
|
|
]));
|
|
});
|
|
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
|
|
await page.evaluate(()=>{
|
|
let nav=document.querySelector('header nav');
|
|
if(!nav){const header=document.createElement('header');nav=document.createElement('nav');header.appendChild(nav);document.body.prepend(header)}
|
|
let menu=nav.querySelector('.nav-menu');if(!menu){menu=document.createElement('button');menu.type='button';menu.className='sun-nav-button nav-menu';menu.dataset.navLabel='Меню';menu.textContent='Меню';nav.appendChild(menu)}
|
|
});
|
|
await injectCore(page,'ux-fixes-v1764.js','SunUXFixV1764');
|
|
await page.waitForFunction(()=>Boolean(document.querySelector('header nav .nav-menu .sun-v1764-menu-icon')),null,{timeout:5000});
|
|
const result=await page.evaluate(()=>{
|
|
window.SunUXFixV1764.persistOfferTemplate('801','midnight-glass');
|
|
const list=JSON.parse(localStorage.getItem('sunOrders')||'[]');
|
|
const one=list.find(x=>String(x.id)==='801'),two=list.find(x=>String(x.id)==='802');
|
|
return {one:one?.clientOfferTemplateId,snapOne:one?.clientOfferSnapshot?.offerTemplateId,two:two?.clientOfferSnapshot?.offerTemplateId,icon:Boolean(document.querySelector('header nav .nav-menu .sun-v1764-menu-icon'))};
|
|
});
|
|
expect(result.one).toBe('midnight-glass');
|
|
expect(result.snapOne).toBe('midnight-glass');
|
|
expect(result.two).toBe('emerald-gold');
|
|
expect(result.icon).toBeTruthy();
|
|
});
|
|
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.6.4');
|
|
});
|
|
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);
|
|
});
|