Stabilize settings updates, auth gates and modal lifecycle
This commit is contained in:
parent
47ae8df39c
commit
4f518b3663
@ -11,7 +11,7 @@
|
|||||||
"serverReady": true,
|
"serverReady": true,
|
||||||
"workspaceAutoDiscovery": true,
|
"workspaceAutoDiscovery": true,
|
||||||
"invitesTemporarilyDisabled": false,
|
"invitesTemporarilyDisabled": false,
|
||||||
"pwaCache": "v109-20260918-mobile-calendar",
|
"pwaCache": "v110-20260918-ui-stability",
|
||||||
"fullOfferDescriptions": true,
|
"fullOfferDescriptions": true,
|
||||||
"dynamicOfferRows": true,
|
"dynamicOfferRows": true,
|
||||||
"pdfOfferDescriptionFix": true,
|
"pdfOfferDescriptionFix": true,
|
||||||
@ -323,7 +323,7 @@
|
|||||||
"calendarOverflowPanel": true,
|
"calendarOverflowPanel": true,
|
||||||
"routeBaseConfigurable": true,
|
"routeBaseConfigurable": true,
|
||||||
"routeOrderPopup": true,
|
"routeOrderPopup": true,
|
||||||
"developerGateHotfixV1763": true,
|
"developerGateHotfixV1763": false,
|
||||||
"saasClickHotfixV1763": true,
|
"saasClickHotfixV1763": true,
|
||||||
"ordersAutoCompleteDelayMs": 60000,
|
"ordersAutoCompleteDelayMs": 60000,
|
||||||
"ordersAutoPayment": true,
|
"ordersAutoPayment": true,
|
||||||
@ -433,5 +433,8 @@
|
|||||||
"proposalLogoAspectRatio": true,
|
"proposalLogoAspectRatio": true,
|
||||||
"proposalLogoContrastMasthead": true,
|
"proposalLogoContrastMasthead": true,
|
||||||
"retiredCatalogItemsPreserveOrderHistory": true,
|
"retiredCatalogItemsPreserveOrderHistory": true,
|
||||||
"oneTimeTelegramArchiveUiRemoved": true
|
"oneTimeTelegramArchiveUiRemoved": true,
|
||||||
|
"settingsIdleMutationLoopRemoved": true,
|
||||||
|
"subscriptionResponseTenantIsolation": true,
|
||||||
|
"stableBackgroundUiUpdates": true
|
||||||
}
|
}
|
||||||
|
|||||||
18
docs/ui-stability-20260918.md
Normal file
18
docs/ui-stability-20260918.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# UI stability audit — 18 September 2026
|
||||||
|
|
||||||
|
## Confirmed defects and fixes
|
||||||
|
|
||||||
|
- Settings refreshed their own DOM through a MutationObserver every 20 ms. An idle fixture produced 34 child-list changes in 350 ms. Catalog summaries and tab captions now update only when their content changes. New cards receive their tab visibility before the next paint.
|
||||||
|
- Settings card ordering repeatedly moved the same nodes. Ordering now walks backwards from its anchor and leaves already ordered cards in place.
|
||||||
|
- Unchanged cloud notifications replaced theme controls and removed input focus. Theme state is compared before applying it; viewport resizing uses existing responsive CSS without replaying the theme.
|
||||||
|
- The obsolete developer hotfix replaced the unified login while company access was still loading. Removed that duplicate renderer; retained safe opening of the developer console from its explicit button.
|
||||||
|
- A pending subscription request could apply the previous account's state and restore a blocking window. Requests are scoped to user and workspace; tenant changes invalidate requests and clear subscription windows. Repeated notifications share a request. Temporary failures retain the last confirmed subscription state.
|
||||||
|
- Subscription refresh rebuilt unchanged controls and overlays. Those nodes now persist. The plans dialog opens above the subscription blocker.
|
||||||
|
- A queued catalog editor callback could move the editor into an inactive section after navigation. It now checks the active section and skips duplicate docking.
|
||||||
|
- The common modal handler overwrote higher z-index values and could move focus after its window had closed. It now preserves stacking priority, checks the active window before autofocus, respects a user's newly focused input, and releases the page scroll lock when a window is removed. Plans also open above the dialog that requested them.
|
||||||
|
|
||||||
|
## Regression coverage
|
||||||
|
|
||||||
|
`tests/ui-stability.spec.mjs` covers idle DOM stability, focus preservation, real theme changes, account response races, dialog stacking, rapid menu navigation and a complete application session with mocked server responses. It runs in desktop Chromium, mobile Chromium and iPhone WebKit. Existing login, account isolation, theme startup and PDF tests remain in the release suite.
|
||||||
|
|
||||||
|
No application data or database schema changes are needed for this release. Browser automation checks specific workflows; it does not establish the absence of every possible defect or substitute for testing on a physical iPhone.
|
||||||
@ -4608,7 +4608,8 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
|
|
||||||
let snapshot=null;
|
let snapshot=null;
|
||||||
let lastWorkspaceId='';
|
let lastWorkspaceId='';
|
||||||
let loading=false;
|
let loading=null;
|
||||||
|
let subscriptionScope='';
|
||||||
let guardedFns=new Map();
|
let guardedFns=new Map();
|
||||||
let adminRows=[];
|
let adminRows=[];
|
||||||
|
|
||||||
@ -4624,23 +4625,34 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
function endDate(){return snapshot?.status==='trialing'?snapshot?.trial_ends_at:snapshot?.current_period_end;}
|
function endDate(){return snapshot?.status==='trialing'?snapshot?.trial_ends_at:snapshot?.current_period_end;}
|
||||||
function daysLeft(){const end=endDate();if(!end)return null;return Math.ceil((new Date(end).getTime()-Date.now())/86400000);}
|
function daysLeft(){const end=endDate();if(!end)return null;return Math.ceil((new Date(end).getTime()-Date.now())/86400000);}
|
||||||
|
|
||||||
|
const currentSubscriptionScope=()=>JSON.stringify([session()?.user?.id||'',workspace()?.id||'']);
|
||||||
|
function resetSubscription(){
|
||||||
|
loading=null;snapshot=null;lastWorkspaceId='';subscriptionScope='';removeSubscriptionUi();applyReadOnlyControls();
|
||||||
|
}
|
||||||
async function refreshSnapshot(force=false){
|
async function refreshSnapshot(force=false){
|
||||||
const c=client(),ws=workspace();
|
const c=client(),ws=workspace();
|
||||||
if(!c||!ws?.id){snapshot=null;lastWorkspaceId='';removeSubscriptionUi();return null;}
|
if(!c||!ws?.id){resetSubscription();return null;}
|
||||||
if(loading)return snapshot;
|
const scope=currentSubscriptionScope();
|
||||||
|
if(subscriptionScope!==scope){resetSubscription();subscriptionScope=scope;}
|
||||||
|
if(loading)return loading.promise;
|
||||||
if(!force&&lastWorkspaceId===ws.id&&snapshot)return snapshot;
|
if(!force&&lastWorkspaceId===ws.id&&snapshot)return snapshot;
|
||||||
loading=true;
|
const request={promise:null};loading=request;
|
||||||
try{
|
const isCurrent=()=>loading===request&¤tSubscriptionScope()===scope;
|
||||||
const {data,error}=await c.rpc('sun_subscription_snapshot',{p_workspace:ws.id});
|
request.promise=(async()=>{
|
||||||
if(error)throw error;
|
try{
|
||||||
snapshot=Array.isArray(data)?(data[0]||null):data;
|
const {data,error}=await c.rpc('sun_subscription_snapshot',{p_workspace:ws.id});
|
||||||
lastWorkspaceId=ws.id;
|
if(!isCurrent())return null;
|
||||||
applyAll();
|
if(error)throw error;
|
||||||
window.dispatchEvent(new CustomEvent('sun:subscription-changed',{detail:snapshot}));
|
const next=Array.isArray(data)?(data[0]||null):data;
|
||||||
return snapshot;
|
if(!next||!['full','read_only','blocked'].includes(next.access_mode))throw new Error('Некорректный ответ о подписке');
|
||||||
}catch(err){console.error('[SaaS] subscription snapshot',err);snapshot=null;}
|
snapshot=next;lastWorkspaceId=ws.id;
|
||||||
finally{loading=false;}
|
applyAll();
|
||||||
return snapshot;
|
window.dispatchEvent(new CustomEvent('sun:subscription-changed',{detail:snapshot}));
|
||||||
|
return snapshot;
|
||||||
|
}catch(err){if(isCurrent())console.error('[SaaS] subscription snapshot',err);return null;}
|
||||||
|
finally{if(loading===request)loading=null;}
|
||||||
|
})();
|
||||||
|
return request.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function installStyle(){
|
function installStyle(){
|
||||||
@ -4655,7 +4667,7 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
.sun-saas-badge b{display:block;font-size:12px}.sun-saas-badge .warn{color:#ffd36b}
|
.sun-saas-badge b{display:block;font-size:12px}.sun-saas-badge .warn{color:#ffd36b}
|
||||||
.sun-saas-readonly-banner{position:fixed;top:10px;left:50%;transform:translateX(-50%);z-index:9800;background:#fff6df;border:1px solid #e2bd55;color:#6a5216;border-radius:12px;padding:9px 14px;box-shadow:0 8px 30px #0002;font-weight:800;max-width:min(680px,calc(100vw - 24px));text-align:center}
|
.sun-saas-readonly-banner{position:fixed;top:10px;left:50%;transform:translateX(-50%);z-index:9800;background:#fff6df;border:1px solid #e2bd55;color:#6a5216;border-radius:12px;padding:9px 14px;box-shadow:0 8px 30px #0002;font-weight:800;max-width:min(680px,calc(100vw - 24px));text-align:center}
|
||||||
.sun-saas-blocked{position:fixed;inset:0;z-index:12000;background:#122b3cf2;display:grid;place-items:center;padding:20px}.sun-saas-blocked-card{width:min(520px,100%);background:#fff;border-radius:18px;padding:24px;box-shadow:0 30px 90px #0007;text-align:center}.sun-saas-blocked-card img{width:72px;height:72px;object-fit:contain}.sun-saas-blocked-card h2{margin:8px 0;color:#17384d}.sun-saas-blocked-card p{color:#596773;line-height:1.5}
|
.sun-saas-blocked{position:fixed;inset:0;z-index:12000;background:#122b3cf2;display:grid;place-items:center;padding:20px}.sun-saas-blocked-card{width:min(520px,100%);background:#fff;border-radius:18px;padding:24px;box-shadow:0 30px 90px #0007;text-align:center}.sun-saas-blocked-card img{width:72px;height:72px;object-fit:contain}.sun-saas-blocked-card h2{margin:8px 0;color:#17384d}.sun-saas-blocked-card p{color:#596773;line-height:1.5}
|
||||||
.sun-saas-modal{position:fixed;inset:0;z-index:11500;background:#0d2130c9;display:grid;place-items:center;padding:18px}.sun-saas-modal-card{width:min(900px,100%);max-height:92vh;overflow:auto;background:#fff;border-radius:18px;padding:20px;box-shadow:0 30px 90px #0007}.sun-saas-modal-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.sun-saas-modal-head h2{margin:0;color:#17384d}.sun-saas-modal-head button{border:0;background:#eef1f2;border-radius:50%;width:34px;height:34px;font-size:20px}.sun-saas-plans{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:16px}.sun-saas-plan{border:1px solid #dfe5e8;border-radius:14px;padding:15px;background:#fafcfc}.sun-saas-plan.current{border:2px solid #ffb400;background:#fffaf0}.sun-saas-plan h3{margin:0 0 5px;color:#18384b}.sun-saas-plan ul{padding-left:18px;color:#4f5b64;font-size:12px;line-height:1.55}.sun-saas-plan .muted{color:#89939a}.sun-saas-upgrade{margin-top:12px;background:#eef4f6;border-radius:12px;padding:12px;color:#52606a}
|
.sun-saas-modal{position:fixed;inset:0;z-index:12100;background:#0d2130c9;display:grid;place-items:center;padding:18px}.sun-saas-modal-card{width:min(900px,100%);max-height:92vh;overflow:auto;background:#fff;border-radius:18px;padding:20px;box-shadow:0 30px 90px #0007}.sun-saas-modal-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.sun-saas-modal-head h2{margin:0;color:#17384d}.sun-saas-modal-head button{border:0;background:#eef1f2;border-radius:50%;width:34px;height:34px;font-size:20px}.sun-saas-plans{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:16px}.sun-saas-plan{border:1px solid #dfe5e8;border-radius:14px;padding:15px;background:#fafcfc}.sun-saas-plan.current{border:2px solid #ffb400;background:#fffaf0}.sun-saas-plan h3{margin:0 0 5px;color:#18384b}.sun-saas-plan ul{padding-left:18px;color:#4f5b64;font-size:12px;line-height:1.55}.sun-saas-plan .muted{color:#89939a}.sun-saas-upgrade{margin-top:12px;background:#eef4f6;border-radius:12px;padding:12px;color:#52606a}
|
||||||
.sun-saas-settings-card .sun-saas-status-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:9px;margin-top:12px}.sun-saas-kpi{border:1px solid #e2e7ea;border-radius:11px;padding:10px;background:#fafcfc}.sun-saas-kpi small{display:block;color:#7a858d;margin-bottom:4px}.sun-saas-kpi b{color:#203846}
|
.sun-saas-settings-card .sun-saas-status-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:9px;margin-top:12px}.sun-saas-kpi{border:1px solid #e2e7ea;border-radius:11px;padding:10px;background:#fafcfc}.sun-saas-kpi small{display:block;color:#7a858d;margin-bottom:4px}.sun-saas-kpi b{color:#203846}
|
||||||
#sun-saas-admin{padding:20px}.sun-saas-admin-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap}.sun-saas-admin-table{display:grid;gap:10px;margin-top:16px}.sun-saas-company{border:1px solid #dfe5e8;border-radius:14px;padding:14px;background:#fff}.sun-saas-company-top{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;flex-wrap:wrap}.sun-saas-company h3{margin:0;color:#18384b}.sun-saas-company small{color:#78828a}.sun-saas-company-actions{display:grid;grid-template-columns:1fr 110px auto auto;gap:8px;margin-top:11px;align-items:end}.sun-saas-company-actions label{font-size:11px;color:#64717a}.sun-saas-company-actions select{width:100%}.sun-saas-company-meta{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}.sun-saas-pill{border-radius:999px;padding:5px 8px;background:#eef3f5;font-size:11px}.sun-saas-pill.good{background:#eaf7ef;color:#347153}.sun-saas-pill.warn{background:#fff5dc;color:#8a681b}.sun-saas-pill.bad{background:#ffebe9;color:#993f3d}
|
#sun-saas-admin{padding:20px}.sun-saas-admin-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap}.sun-saas-admin-table{display:grid;gap:10px;margin-top:16px}.sun-saas-company{border:1px solid #dfe5e8;border-radius:14px;padding:14px;background:#fff}.sun-saas-company-top{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;flex-wrap:wrap}.sun-saas-company h3{margin:0;color:#18384b}.sun-saas-company small{color:#78828a}.sun-saas-company-actions{display:grid;grid-template-columns:1fr 110px auto auto;gap:8px;margin-top:11px;align-items:end}.sun-saas-company-actions label{font-size:11px;color:#64717a}.sun-saas-company-actions select{width:100%}.sun-saas-company-meta{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}.sun-saas-pill{border-radius:999px;padding:5px 8px;background:#eef3f5;font-size:11px}.sun-saas-pill.good{background:#eaf7ef;color:#347153}.sun-saas-pill.warn{background:#fff5dc;color:#8a681b}.sun-saas-pill.bad{background:#ffebe9;color:#993f3d}
|
||||||
.sun-saas-onboarding-tabs{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}.sun-saas-onboarding-choice{border:1px solid #dfe5e8;background:#fafcfc;border-radius:12px;padding:12px;text-align:left}.sun-saas-onboarding-choice b{display:block;color:#17384d;margin-bottom:3px}.sun-saas-onboarding-choice small{color:#6f7980;line-height:1.35}
|
.sun-saas-onboarding-tabs{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}.sun-saas-onboarding-choice{border:1px solid #dfe5e8;background:#fafcfc;border-radius:12px;padding:12px;text-align:left}.sun-saas-onboarding-choice b{display:block;color:#17384d;margin-bottom:3px}.sun-saas-onboarding-choice small{color:#6f7980;line-height:1.35}
|
||||||
@ -4664,7 +4676,7 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
}
|
}
|
||||||
|
|
||||||
function removeSubscriptionUi(){
|
function removeSubscriptionUi(){
|
||||||
$('sunSaaSBadgeV16')?.remove();$('sunSaaSReadonlyV16')?.remove();$('sunSaaSBlockedV16')?.remove();$('sunSaaSSettingsCardV16')?.remove();
|
$('sunSaaSBadgeV16')?.remove();$('sunSaaSReadonlyV16')?.remove();$('sunSaaSBlockedV16')?.remove();$('sunSaaSSettingsCardV16')?.remove();$('sunSaaSPlansModalV16')?.remove();
|
||||||
qa('[data-sun-plan-locked]').forEach(el=>{el.removeAttribute('data-sun-plan-locked');el.classList.remove('sun-plan-locked')});
|
qa('[data-sun-plan-locked]').forEach(el=>{el.removeAttribute('data-sun-plan-locked');el.classList.remove('sun-plan-locked')});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4711,17 +4723,23 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
if(!brand)return;
|
if(!brand)return;
|
||||||
if(!badge){badge=document.createElement('div');badge.id='sunSaaSBadgeV16';badge.className='sun-saas-badge';brand.insertAdjacentElement('afterend',badge);}
|
if(!badge){badge=document.createElement('div');badge.id='sunSaaSBadgeV16';badge.className='sun-saas-badge';brand.insertAdjacentElement('afterend',badge);}
|
||||||
const d=daysLeft(), trial=snapshot.status==='trialing';
|
const d=daysLeft(), trial=snapshot.status==='trialing';
|
||||||
badge.innerHTML=`<b>${esc(snapshot.plan_name||PLAN_LABELS[snapshot.plan_id]||'Тариф')}</b><span class="${snapshot.access_mode==='full'?'':'warn'}">${trial?`Пробный период${d!=null?` · ${Math.max(0,d)} дн.`:''}`:accessLabel(snapshot.access_mode)}</span>`;
|
const html=`<b>${esc(snapshot.plan_name||PLAN_LABELS[snapshot.plan_id]||'Тариф')}</b><span class="${snapshot.access_mode==='full'?'':'warn'}">${trial?`Пробный период${d!=null?` · ${Math.max(0,d)} дн.`:''}`:accessLabel(snapshot.access_mode)}</span>`;
|
||||||
|
if(badge.innerHTML!==html)badge.innerHTML=html;
|
||||||
badge.onclick=()=>showPlans();badge.title='Тариф и подписка';badge.style.cursor='pointer';
|
badge.onclick=()=>showPlans();badge.title='Тариф и подписка';badge.style.cursor='pointer';
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyAccessMode(){
|
function applyAccessMode(){
|
||||||
$('sunSaaSReadonlyV16')?.remove();$('sunSaaSBlockedV16')?.remove();
|
if(snapshot?.access_mode!=='read_only')$('sunSaaSReadonlyV16')?.remove();
|
||||||
|
if(snapshot?.access_mode!=='blocked')$('sunSaaSBlockedV16')?.remove();
|
||||||
if(!snapshot)return;
|
if(!snapshot)return;
|
||||||
applyReadOnlyControls();
|
applyReadOnlyControls();
|
||||||
if(snapshot.access_mode==='read_only'){
|
if(snapshot.access_mode==='read_only'){
|
||||||
const b=document.createElement('div');b.id='sunSaaSReadonlyV16';b.className='sun-saas-readonly-banner';b.textContent=`Подписка закончилась. До ${formatDate(snapshot.grace_until)} база доступна только для просмотра.`;document.body.appendChild(b);
|
let b=$('sunSaaSReadonlyV16');
|
||||||
|
if(!b){b=document.createElement('div');b.id='sunSaaSReadonlyV16';b.className='sun-saas-readonly-banner';document.body.appendChild(b);}
|
||||||
|
const text=`Подписка закончилась. До ${formatDate(snapshot.grace_until)} база доступна только для просмотра.`;
|
||||||
|
if(b.textContent!==text)b.textContent=text;
|
||||||
}else if(snapshot.access_mode==='blocked'){
|
}else if(snapshot.access_mode==='blocked'){
|
||||||
|
if($('sunSaaSBlockedV16'))return;
|
||||||
const x=document.createElement('div');x.id='sunSaaSBlockedV16';x.className='sun-saas-blocked';x.innerHTML=`<div class="sun-saas-blocked-card"><img src="caterium-mark-light.svg" alt=""><h2>Доступ к компании приостановлен</h2><p>Подписка закончилась. Все данные сохранены и будут доступны сразу после продления.</p><div class="actions" style="justify-content:center"><button class="primary" type="button" data-saas-show-plans>Посмотреть тарифы</button><button class="outline" type="button" data-saas-signout>Выйти</button></div></div>`;document.body.appendChild(x);
|
const x=document.createElement('div');x.id='sunSaaSBlockedV16';x.className='sun-saas-blocked';x.innerHTML=`<div class="sun-saas-blocked-card"><img src="caterium-mark-light.svg" alt=""><h2>Доступ к компании приостановлен</h2><p>Подписка закончилась. Все данные сохранены и будут доступны сразу после продления.</p><div class="actions" style="justify-content:center"><button class="primary" type="button" data-saas-show-plans>Посмотреть тарифы</button><button class="outline" type="button" data-saas-signout>Выйти</button></div></div>`;document.body.appendChild(x);
|
||||||
x.querySelector('[data-saas-show-plans]').onclick=()=>showPlans();x.querySelector('[data-saas-signout]').onclick=async()=>{if(window.SunCloudV2?.signOut)return window.SunCloudV2.signOut();try{await client()?.auth.signOut()}catch(_){}location.reload();};
|
x.querySelector('[data-saas-show-plans]').onclick=()=>showPlans();x.querySelector('[data-saas-signout]').onclick=async()=>{if(window.SunCloudV2?.signOut)return window.SunCloudV2.signOut();try{await client()?.auth.signOut()}catch(_){}location.reload();};
|
||||||
}
|
}
|
||||||
@ -4737,6 +4755,8 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
function showPlans(feature=null,note=''){
|
function showPlans(feature=null,note=''){
|
||||||
$('sunSaaSPlansModalV16')?.remove();
|
$('sunSaaSPlansModalV16')?.remove();
|
||||||
const m=document.createElement('div');m.id='sunSaaSPlansModalV16';m.className='sun-saas-modal';
|
const m=document.createElement('div');m.id='sunSaaSPlansModalV16';m.className='sun-saas-modal';
|
||||||
|
// A feature guard may be triggered inside another dialog, including a PDF.
|
||||||
|
m.style.zIndex=String(Math.max(12100,...qa('.modal.on,.sun-v1762-modal,.sun-dev-modal').map(el=>Number(getComputedStyle(el).zIndex)||0))+1);
|
||||||
const cards=[
|
const cards=[
|
||||||
{id:'basic',name:'Базовый',members:'1 пользователь',inc:['Заказы, календарь и клиенты','Каталог для работы с заказом','Базовая статистика и печать'],out:['Редактирование боксов','Предложения клиенту','Финансы и склад']},
|
{id:'basic',name:'Базовый',members:'1 пользователь',inc:['Заказы, календарь и клиенты','Каталог для работы с заказом','Базовая статистика и печать'],out:['Редактирование боксов','Предложения клиенту','Финансы и склад']},
|
||||||
{id:'professional',name:'Профессиональный',members:'до 3 пользователей',inc:['Всё из Базового','Производство, склад и закупки','Финансы, поставщики и маршруты','Фирменное оформление'],out:['Редактирование боксов','Предложения клиенту']},
|
{id:'professional',name:'Профессиональный',members:'до 3 пользователей',inc:['Всё из Базового','Производство, склад и закупки','Финансы, поставщики и маршруты','Фирменное оформление'],out:['Редактирование боксов','Предложения клиенту']},
|
||||||
@ -4751,7 +4771,9 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
const settings=$('enterprise-settings');if(!settings)return;
|
const settings=$('enterprise-settings');if(!settings)return;
|
||||||
let card=$('sunSaaSSettingsCardV16');if(!card){card=document.createElement('section');card.id='sunSaaSSettingsCardV16';card.className='enterprise-card wide sun-saas-settings-card';const first=settings.querySelector('.enterprise-grid')||settings;first.prepend(card);}
|
let card=$('sunSaaSSettingsCardV16');if(!card){card=document.createElement('section');card.id='sunSaaSSettingsCardV16';card.className='enterprise-card wide sun-saas-settings-card';const first=settings.querySelector('.enterprise-grid')||settings;first.prepend(card);}
|
||||||
const end=endDate(),d=daysLeft();
|
const end=endDate(),d=daysLeft();
|
||||||
card.innerHTML=`<h2>Тариф и подписка</h2><p class="hint">Подписка относится ко всей компании. Данные этой рабочей базы изолированы от других компаний.</p><div class="sun-saas-status-grid"><div class="sun-saas-kpi"><small>Тариф</small><b>${esc(snapshot.plan_name||PLAN_LABELS[snapshot.plan_id])}</b></div><div class="sun-saas-kpi"><small>Статус</small><b>${esc(statusLabel(snapshot.status))}</b></div><div class="sun-saas-kpi"><small>${snapshot.status==='trialing'?'Пробный период до':'Оплачено до'}</small><b>${formatDate(end)}${d!=null&&d>=0?` · ${d} дн.`:''}</b></div><div class="sun-saas-kpi"><small>Сотрудники</small><b>${Number(snapshot.member_count||0)} / ${snapshot.max_members==null?'∞':snapshot.max_members}</b></div></div><div class="actions" style="margin-top:12px"><button class="outline" type="button" data-saas-plans>Сравнить тарифы</button>${snapshot.platform_admin?'<button class="primary" type="button" data-saas-admin>Управление SaaS</button>':''}</div>`;
|
const html=`<h2>Тариф и подписка</h2><p class="hint">Подписка относится ко всей компании. Данные этой рабочей базы изолированы от других компаний.</p><div class="sun-saas-status-grid"><div class="sun-saas-kpi"><small>Тариф</small><b>${esc(snapshot.plan_name||PLAN_LABELS[snapshot.plan_id])}</b></div><div class="sun-saas-kpi"><small>Статус</small><b>${esc(statusLabel(snapshot.status))}</b></div><div class="sun-saas-kpi"><small>${snapshot.status==='trialing'?'Пробный период до':'Оплачено до'}</small><b>${formatDate(end)}${d!=null&&d>=0?` · ${d} дн.`:''}</b></div><div class="sun-saas-kpi"><small>Сотрудники</small><b>${Number(snapshot.member_count||0)} / ${snapshot.max_members==null?'∞':snapshot.max_members}</b></div></div><div class="actions" style="margin-top:12px"><button class="outline" type="button" data-saas-plans>Сравнить тарифы</button>${snapshot.platform_admin?'<button class="primary" type="button" data-saas-admin>Управление SaaS</button>':''}</div>`;
|
||||||
|
if(card.dataset.subscriptionHtml===html)return;
|
||||||
|
card.dataset.subscriptionHtml=html;card.innerHTML=html;
|
||||||
card.querySelector('[data-saas-plans]').onclick=()=>showPlans();card.querySelector('[data-saas-admin]')?.addEventListener('click',openPlatformAdmin);
|
card.querySelector('[data-saas-plans]').onclick=()=>showPlans();card.querySelector('[data-saas-admin]')?.addEventListener('click',openPlatformAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4842,10 +4864,16 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
const ws=workspace();
|
const ws=workspace();
|
||||||
if(ws?.id&&(ws.id!==lastWorkspaceId||!snapshot))await refreshSnapshot(true);
|
if(ws?.id&&(ws.id!==lastWorkspaceId||!snapshot))await refreshSnapshot(true);
|
||||||
else if(ws?.id&&snapshot){applyFeatureGates();guardFunctions();injectSettingsCard();ensureAdminNav();}
|
else if(ws?.id&&snapshot){applyFeatureGates();guardFunctions();injectSettingsCard();ensureAdminNav();}
|
||||||
else if(!ws){snapshot=null;lastWorkspaceId='';}
|
else if(!ws)resetSubscription();
|
||||||
},5000);
|
},5000);
|
||||||
|
window.addEventListener('sun:cloud-tenant-changing',resetSubscription);
|
||||||
window.addEventListener('sun:cloud-state-applied',()=>refreshSnapshot(true));
|
window.addEventListener('sun:cloud-state-applied',()=>refreshSnapshot(true));
|
||||||
window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(()=>{applyFeatureGates();guardFunctions();},50));
|
window.addEventListener('sun:cloud-permissions-changed',()=>{
|
||||||
|
if(subscriptionScope&&subscriptionScope!==currentSubscriptionScope())resetSubscription();
|
||||||
|
// Membership discovery can still reload the page. Fetch after cloud-state
|
||||||
|
// application (or the boot retry), not during that intermediate state.
|
||||||
|
setTimeout(()=>{applyFeatureGates();guardFunctions();},50);
|
||||||
|
});
|
||||||
document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(b&&String(b.dataset.navLabel||b.textContent||'').trim()==='Настройки')setTimeout(()=>{injectSettingsCard();applyFeatureGates();},100);},true);
|
document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(b&&String(b.dataset.navLabel||b.textContent||'').trim()==='Настройки')setTimeout(()=>{injectSettingsCard();applyFeatureGates();},100);},true);
|
||||||
setTimeout(()=>refreshSnapshot(true),1200);
|
setTimeout(()=>refreshSnapshot(true),1200);
|
||||||
}
|
}
|
||||||
@ -4917,7 +4945,8 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
|
|
||||||
// Bottom operational block: company requisites -> QR -> order blank -> cloud -> history.
|
// Bottom operational block: company requisites -> QR -> order blank -> cloud -> history.
|
||||||
// Reinsert in the desired order so dynamically injected cards settle into one stable tail block.
|
// Reinsert in the desired order so dynamically injected cards settle into one stable tail block.
|
||||||
[requisites,qr,blank,cloud].forEach(card=>{if(card&&grid.contains(card))moveBefore(card,audit)});
|
let anchor=audit;
|
||||||
|
[requisites,qr,blank,cloud].reverse().forEach(card=>{if(card&&grid.contains(card)){moveBefore(card,anchor);anchor=card;}});
|
||||||
}finally{ordering=false;}
|
}finally{ordering=false;}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5286,6 +5315,9 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
}
|
}
|
||||||
const cats=typeof window.sunCatalogCategories==='function'?window.sunCatalogCategories():[];
|
const cats=typeof window.sunCatalogCategories==='function'?window.sunCatalogCategories():[];
|
||||||
const total=Array.isArray(cats)?cats.length:0,visible=Array.isArray(cats)?cats.filter(x=>!x.hidden).length:0;
|
const total=Array.isArray(cats)?cats.length:0,visible=Array.isArray(cats)?cats.filter(x=>!x.hidden).length:0;
|
||||||
|
const summary=`${total}:${visible}`;
|
||||||
|
if(card.dataset.catalogSummary===summary)return;
|
||||||
|
card.dataset.catalogSummary=summary;
|
||||||
card.innerHTML=`<h2>Вкладки каталога</h2><p class="hint">Название, порядок, цвет и видимость разделов каталога. Скрытые разделы и их товары не удаляются.</p><div class="sun-settings-catalog-summary"><span class="sun-settings-catalog-chip">Всего: ${total}</span><span class="sun-settings-catalog-chip">Видимых: ${visible}</span></div><div class="actions"><button class="primary" id="sunSettingsCatalogTabsBtnV21" type="button">Настроить вкладки каталога</button></div>`;
|
card.innerHTML=`<h2>Вкладки каталога</h2><p class="hint">Название, порядок, цвет и видимость разделов каталога. Скрытые разделы и их товары не удаляются.</p><div class="sun-settings-catalog-summary"><span class="sun-settings-catalog-chip">Всего: ${total}</span><span class="sun-settings-catalog-chip">Видимых: ${visible}</span></div><div class="actions"><button class="primary" id="sunSettingsCatalogTabsBtnV21" type="button">Настроить вкладки каталога</button></div>`;
|
||||||
$('sunSettingsCatalogTabsBtnV21')?.addEventListener('click',()=>window.sunOpenCategoryManager?.());
|
$('sunSettingsCatalogTabsBtnV21')?.addEventListener('click',()=>window.sunOpenCategoryManager?.());
|
||||||
}
|
}
|
||||||
@ -5323,7 +5355,8 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
if(on&&wrap.dataset.lastActive!==active)setTimeout(()=>btn.scrollIntoView?.({block:'nearest',inline:'nearest',behavior:'smooth'}),0);
|
if(on&&wrap.dataset.lastActive!==active)setTimeout(()=>btn.scrollIntoView?.({block:'nearest',inline:'nearest',behavior:'smooth'}),0);
|
||||||
});
|
});
|
||||||
wrap.dataset.lastActive=active;
|
wrap.dataset.lastActive=active;
|
||||||
const context=$('sunSettingsTabContextV21');if(context)context.innerHTML=`<b>${meta.label}</b><span>${meta.hint}</span>`;
|
const context=$('sunSettingsTabContextV21'),html=`<b>${meta.label}</b><span>${meta.hint}</span>`;
|
||||||
|
if(context&&context.innerHTML!==html)context.innerHTML=html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function activate(tab,persist=false){
|
function activate(tab,persist=false){
|
||||||
@ -5344,8 +5377,9 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
|
|
||||||
function boot(){
|
function boot(){
|
||||||
const view=$('enterprise-settings');if(!view)return;
|
const view=$('enterprise-settings');if(!view)return;
|
||||||
const observer=new MutationObserver(()=>schedule(20));observer.observe(view,{childList:true,subtree:true});
|
// Classify newly inserted cards before the next paint. The refresh is idempotent.
|
||||||
document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(b&&String(b.dataset.navLabel||b.textContent||'').trim()==='Настройки'){schedule(0);schedule(90);schedule(240)}},true);
|
const observer=new MutationObserver(()=>{applyCards();schedule(0);});observer.observe(view,{childList:true,subtree:true});
|
||||||
|
document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(b&&String(b.dataset.navLabel||b.textContent||'').trim()==='Настройки')schedule(0);},true);
|
||||||
window.addEventListener('sun:catalog-categories-changed',()=>schedule(20));
|
window.addEventListener('sun:catalog-categories-changed',()=>schedule(20));
|
||||||
window.addEventListener('sun:cloud-state-applied',()=>schedule(40));
|
window.addEventListener('sun:cloud-state-applied',()=>schedule(40));
|
||||||
window.addEventListener('suncloudsync',()=>schedule(40));
|
window.addEventListener('suncloudsync',()=>schedule(40));
|
||||||
|
|||||||
@ -101,9 +101,9 @@
|
|||||||
}
|
}
|
||||||
function ensureCard(){const view=document.getElementById('enterprise-settings'),grid=view?.querySelector('.enterprise-grid');if(!grid)return;let card=document.getElementById('sunBrandThemeSettingsCard');if(card&&grid.contains(card)){if(!card.dataset.bound){rerender(card);card.dataset.bound='1'}return}card=document.createElement('section');card.id='sunBrandThemeSettingsCard';card.className='enterprise-card wide sun-brand-settings-card';card.innerHTML=cardHtml();const history=[...grid.children].find(el=>(el.querySelector(':scope > h2')?.textContent||'').trim()==='История изменений');if(history)window.SunSafe.insertBefore(grid,card,history);else grid.appendChild(card);bind(card);updatePreviewNode(card);card.dataset.bound='1'}
|
function ensureCard(){const view=document.getElementById('enterprise-settings'),grid=view?.querySelector('.enterprise-grid');if(!grid)return;let card=document.getElementById('sunBrandThemeSettingsCard');if(card&&grid.contains(card)){if(!card.dataset.bound){rerender(card);card.dataset.bound='1'}return}card=document.createElement('section');card.id='sunBrandThemeSettingsCard';card.className='enterprise-card wide sun-brand-settings-card';card.innerHTML=cardHtml();const history=[...grid.children].find(el=>(el.querySelector(':scope > h2')?.textContent||'').trim()==='История изменений');if(history)window.SunSafe.insertBefore(grid,card,history);else grid.appendChild(card);bind(card);updatePreviewNode(card);card.dataset.bound='1'}
|
||||||
function bindBrandHome(){const brand=document.querySelector('header .brand');if(!brand||brand.dataset.sunHomeBound)return;brand.dataset.sunHomeBound='1';brand.classList.add('sun-brand-home-link');brand.setAttribute('role','link');brand.setAttribute('tabindex','0');brand.setAttribute('title','Новый заказ');const go=()=>{const btn=[...document.querySelectorAll('header nav button')].find(b=>String(b.dataset.navLabel||b.textContent||'').trim()==='Новый заказ')||document.querySelector('header nav .nav-new');btn?.click()};brand.addEventListener('click',go);brand.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();go()}})}
|
function bindBrandHome(){const brand=document.querySelector('header .brand');if(!brand||brand.dataset.sunHomeBound)return;brand.dataset.sunHomeBound='1';brand.classList.add('sun-brand-home-link');brand.setAttribute('role','link');brand.setAttribute('tabindex','0');brand.setAttribute('title','Новый заказ');const go=()=>{const btn=[...document.querySelectorAll('header nav button')].find(b=>String(b.dataset.navLabel||b.textContent||'').trim()==='Новый заказ')||document.querySelector('header nav .nav-new');btn?.click()};brand.addEventListener('click',go);brand.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();go()}})}
|
||||||
function syncFromStorage(){savedState=read();if(!dirty)previewState=clone(savedState);applyActual(savedState);const card=document.getElementById('sunBrandThemeSettingsCard');if(card&&!dirty)rerender(card)}
|
function syncFromStorage(){const next=read();if(JSON.stringify(next)===JSON.stringify(savedState))return;savedState=next;if(!dirty)previewState=clone(savedState);applyActual(savedState);const card=document.getElementById('sunBrandThemeSettingsCard');if(card&&!dirty)rerender(card)}
|
||||||
applyActual(savedState);previewState=clone(savedState);
|
applyActual(savedState);previewState=clone(savedState);
|
||||||
const boot=()=>{applyActual(savedState);ensureCard();bindBrandHome();const settings=document.getElementById('enterprise-settings');if(settings)new MutationObserver(()=>queueMicrotask(()=>{if(settings.classList.contains('on')&&!document.getElementById('sunBrandThemeSettingsCard'))ensureCard();bindBrandHome()})).observe(settings,{childList:true,subtree:true});document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(!b)return;const name=String(b.dataset.navLabel||b.textContent||'').trim();if(name!=='Настройки'&&dirty){previewState=clone(savedState);dirty=false;const card=document.getElementById('sunBrandThemeSettingsCard');if(card)rerender(card)}},true);window.addEventListener('resize',()=>applyActual(savedState));window.addEventListener('storage',e=>{if(e.key===KEY)syncFromStorage()});window.addEventListener('suncloudsync',syncFromStorage);window.addEventListener('sun:cloud-state-applied',syncFromStorage)};
|
const boot=()=>{applyActual(savedState);ensureCard();bindBrandHome();const settings=document.getElementById('enterprise-settings');if(settings)new MutationObserver(()=>queueMicrotask(()=>{if(settings.classList.contains('on')&&!document.getElementById('sunBrandThemeSettingsCard'))ensureCard();bindBrandHome()})).observe(settings,{childList:true,subtree:true});document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(!b)return;const name=String(b.dataset.navLabel||b.textContent||'').trim();if(name!=='Настройки'&&dirty){previewState=clone(savedState);dirty=false;const card=document.getElementById('sunBrandThemeSettingsCard');if(card)rerender(card)}},true);window.addEventListener('storage',e=>{if(e.key===KEY)syncFromStorage()});window.addEventListener('suncloudsync',syncFromStorage);window.addEventListener('sun:cloud-state-applied',syncFromStorage)};
|
||||||
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
|
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
|
||||||
window.SunBrandTheme={get:()=>clone(savedState),preview:()=>clone(previewState),save:()=>{savedState=clone(previewState);persist();dirty=false;applyActual(savedState)},resetStandard:()=>{savedState=standardState();previewState=clone(savedState);persist();dirty=false;applyActual(savedState);ensureCard()},applyPreset:name=>{previewState=presetState(name);dirty=true;ensureCard()}};
|
window.SunBrandTheme={get:()=>clone(savedState),preview:()=>clone(previewState),save:()=>{savedState=clone(previewState);persist();dirty=false;applyActual(savedState)},resetStandard:()=>{savedState=standardState();previewState=clone(savedState);persist();dirty=false;applyActual(savedState);ensureCard()},applyPreset:name=>{previewState=presetState(name);dirty=true;ensureCard()}};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -3,10 +3,6 @@
|
|||||||
if(window.SunHotfixV1763)return;
|
if(window.SunHotfixV1763)return;
|
||||||
|
|
||||||
const VERSION='17.6.3';
|
const VERSION='17.6.3';
|
||||||
const $=id=>document.getElementById(id);
|
|
||||||
const esc=value=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(value??'')):String(value??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
||||||
let checkingGate=false;
|
|
||||||
|
|
||||||
function patchDeveloperOpen(){
|
function patchDeveloperOpen(){
|
||||||
const dev=window.SunDeveloperV22;
|
const dev=window.SunDeveloperV22;
|
||||||
if(!dev||dev.__sunV1763SafeOpen||typeof dev.open!=='function')return false;
|
if(!dev||dev.__sunV1763SafeOpen||typeof dev.open!=='function')return false;
|
||||||
@ -16,34 +12,8 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function platformAdmin(){
|
// Authentication screens belong to the unified auth gate. Never replace
|
||||||
const dev=window.SunDeveloperV22,cloud=window.SunCloudV2;
|
// its loading/error state with an old developer-only login card.
|
||||||
if(!dev||!cloud?.getSession?.()?.user)return false;
|
|
||||||
if(dev.isPlatformAdmin?.()===true)return true;
|
|
||||||
try{return await dev.checkPlatformAdmin?.(false)===true}catch(_){return false}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function enhanceDeveloperGate(){
|
|
||||||
if(checkingGate)return false;
|
|
||||||
const cloud=window.SunCloudV2,dev=window.SunDeveloperV22;
|
|
||||||
if(!cloud||!dev)return false;
|
|
||||||
const session=cloud.getSession?.(),workspace=cloud.getWorkspace?.();
|
|
||||||
if(!session?.user||workspace)return false;
|
|
||||||
checkingGate=true;
|
|
||||||
try{
|
|
||||||
if(!await platformAdmin())return false;
|
|
||||||
const gate=$('sunCloudAuthGateV3'),card=gate?.querySelector('.sun-cloud-auth-card');
|
|
||||||
if(!gate||!card)return false;
|
|
||||||
if(card.dataset.dev1763==='1')return true;
|
|
||||||
gate.dataset.saasEnhanced='1';
|
|
||||||
card.dataset.devEnhanced='1';
|
|
||||||
card.dataset.dev1763='1';
|
|
||||||
card.innerHTML=`<div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>Аккаунт разработчика</h2><div class="hint">${esc(session.user.email||'')}</div></div></div><p class="hint">Этот аккаунт управляет платформой и не обязан иметь собственную рабочую компанию.</p><div class="sun-cloud-auth-actions"><button class="primary" type="button" data-open-dev-v1763>Открыть кабинет разработчика</button><button class="outline" type="button" data-signout-v1763>Выйти</button></div><div class="sun-cloud-auth-error" id="sunDevGateStatusV1763"></div>`;
|
|
||||||
card.querySelector('[data-open-dev-v1763]')?.addEventListener('click',()=>{patchDeveloperOpen();dev.open?.();});
|
|
||||||
card.querySelector('[data-signout-v1763]')?.addEventListener('click',()=>cloud.signOut?.());
|
|
||||||
return true;
|
|
||||||
}finally{checkingGate=false;}
|
|
||||||
}
|
|
||||||
|
|
||||||
function interceptSaasAdmin(event){
|
function interceptSaasAdmin(event){
|
||||||
const button=event.target?.closest?.('[data-saas-admin]');
|
const button=event.target?.closest?.('[data-saas-admin]');
|
||||||
@ -58,7 +28,6 @@
|
|||||||
|
|
||||||
function maintain(){
|
function maintain(){
|
||||||
patchDeveloperOpen();
|
patchDeveloperOpen();
|
||||||
enhanceDeveloperGate().catch(()=>{});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('click',interceptSaasAdmin,true);
|
document.addEventListener('click',interceptSaasAdmin,true);
|
||||||
@ -67,5 +36,5 @@
|
|||||||
setTimeout(maintain,0);
|
setTimeout(maintain,0);
|
||||||
setInterval(()=>{if(!document.hidden)maintain();},10000);
|
setInterval(()=>{if(!document.hidden)maintain();},10000);
|
||||||
|
|
||||||
window.SunHotfixV1763={VERSION,patchDeveloperOpen,enhanceDeveloperGate};
|
window.SunHotfixV1763={VERSION,patchDeveloperOpen};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -130,7 +130,9 @@
|
|||||||
pane.innerHTML=`<div class="sun-menu-readonly"><h2>${esc(item.name||'Позиция')}<span class="sun-menu-readonly-badge">только просмотр</span></h2>${item.photo?`<img class="sun-menu-readonly-photo" src="${esc(item.photo)}" alt="">`:''}<div class="sun-menu-readonly-grid"><div class="sun-menu-readonly-box"><small>Категория</small><b>${esc(currentCategory().label)}</b></div><div class="sun-menu-readonly-box"><small>Цена</small><b>${esc(money(item.price||0))}</b></div><div class="sun-menu-readonly-box"><small>Вес</small><b>${esc(item.weight||'—')}</b></div><div class="sun-menu-readonly-box"><small>Количество</small><b>${Number(item.pieces||0)||'—'}${item.pieces?' шт.':''}</b></div></div><div class="sun-route-order-section"><b>Состав для клиента</b>${comp.length?`<ul class="sun-menu-readonly-list">${comp.map(x=>`<li>${esc(x)}</li>`).join('')}</ul>`:'<p class="hint">Не заполнен.</p>'}</div><div class="sun-route-order-section"><b>Состав / ТТК</b>${ingredients.length?`<ul class="sun-menu-readonly-list">${ingredients.map(x=>`<li>${esc(x?.[0]||'')} — ${esc(x?.[1]??'')} ${esc(x?.[2]||'')}</li>`).join('')}</ul>`:'<p class="hint">Не заполнен.</p>'}</div></div>`;
|
pane.innerHTML=`<div class="sun-menu-readonly"><h2>${esc(item.name||'Позиция')}<span class="sun-menu-readonly-badge">только просмотр</span></h2>${item.photo?`<img class="sun-menu-readonly-photo" src="${esc(item.photo)}" alt="">`:''}<div class="sun-menu-readonly-grid"><div class="sun-menu-readonly-box"><small>Категория</small><b>${esc(currentCategory().label)}</b></div><div class="sun-menu-readonly-box"><small>Цена</small><b>${esc(money(item.price||0))}</b></div><div class="sun-menu-readonly-box"><small>Вес</small><b>${esc(item.weight||'—')}</b></div><div class="sun-menu-readonly-box"><small>Количество</small><b>${Number(item.pieces||0)||'—'}${item.pieces?' шт.':''}</b></div></div><div class="sun-route-order-section"><b>Состав для клиента</b>${comp.length?`<ul class="sun-menu-readonly-list">${comp.map(x=>`<li>${esc(x)}</li>`).join('')}</ul>`:'<p class="hint">Не заполнен.</p>'}</div><div class="sun-route-order-section"><b>Состав / ТТК</b>${ingredients.length?`<ul class="sun-menu-readonly-list">${ingredients.map(x=>`<li>${esc(x?.[0]||'')} — ${esc(x?.[1]??'')} ${esc(x?.[2]||'')}</li>`).join('')}</ul>`:'<p class="hint">Не заполнен.</p>'}</div></div>`;
|
||||||
}
|
}
|
||||||
function dockEditor(){
|
function dockEditor(){
|
||||||
|
if(!menuActive||!menuView?.classList.contains('on'))return;
|
||||||
const pane=$('sunMenuDetailV1762'),home=$('editor'),dialog=home?.querySelector('.dialog')||editorDialog;if(!pane||!home||!dialog)return;
|
const pane=$('sunMenuDetailV1762'),home=$('editor'),dialog=home?.querySelector('.dialog')||editorDialog;if(!pane||!home||!dialog)return;
|
||||||
|
if(dialog.parentElement===pane)return;
|
||||||
editorHome=home;editorDialog=dialog;home.classList.remove('on');pane.innerHTML='';pane.classList.add('sun-menu-editor-pane');pane.appendChild(dialog);dialog.classList.add('sun-menu-editor-docked');
|
editorHome=home;editorDialog=dialog;home.classList.remove('on');pane.innerHTML='';pane.classList.add('sun-menu-editor-pane');pane.appendChild(dialog);dialog.classList.add('sun-menu-editor-docked');
|
||||||
}
|
}
|
||||||
function renderMenuList(){
|
function renderMenuList(){
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
(()=>{
|
(()=>{
|
||||||
'use strict';
|
'use strict';
|
||||||
const VERSION='17.7.3';
|
const VERSION='17.7.3';
|
||||||
const RELEASE='20260918-mobile-calendar';
|
const RELEASE='20260918-ui-stability';
|
||||||
|
|
||||||
const hasStoredSession=()=>{try{return Object.keys(localStorage).some(k=>/^sb-.*-auth-token$/i.test(k)&&String(localStorage.getItem(k)||'').length>20)}catch(_){return false}};
|
const hasStoredSession=()=>{try{return Object.keys(localStorage).some(k=>/^sb-.*-auth-token$/i.test(k)&&String(localStorage.getItem(k)||'').length>20)}catch(_){return false}};
|
||||||
function installAuthBoot(){
|
function installAuthBoot(){
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
const CACHE='sun-catering-pwa-v109-20260918-mobile-calendar';
|
const CACHE='sun-catering-pwa-v110-20260918-ui-stability';
|
||||||
const VERSION='20260918-mobile-calendar';
|
const VERSION='20260918-ui-stability';
|
||||||
const CORE=[
|
const CORE=[
|
||||||
'./vendor/supabase-2.112.4.min.js',
|
'./vendor/supabase-2.112.4.min.js',
|
||||||
`./core/help-center.js?v=${VERSION}`,`./core/help-center.css?v=${VERSION}`,`./help/knowledge-v1.json?v=${VERSION}`,
|
`./core/help-center.js?v=${VERSION}`,`./core/help-center.css?v=${VERSION}`,`./help/knowledge-v1.json?v=${VERSION}`,
|
||||||
|
|||||||
@ -80,7 +80,7 @@ test('v17.6.2 operations UX boots with menu and route features', async ({ page }
|
|||||||
expect(checks.calendarMore).toBeTruthy();
|
expect(checks.calendarMore).toBeTruthy();
|
||||||
expect(await page.locator('header nav button', {hasText:'Меню'}).count()).toBeGreaterThan(0);
|
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 }) => {
|
test('developer tools preserve the unified loading gate and SaaS click is safe', async ({ page }) => {
|
||||||
// This is a module fixture: real auth timers must not overwrite its mocked session.
|
// This is a module fixture: real auth timers must not overwrite its mocked session.
|
||||||
await page.setContent('<!doctype html><html><body></body></html>');
|
await page.setContent('<!doctype html><html><body></body></html>');
|
||||||
await page.evaluate(()=>{
|
await page.evaluate(()=>{
|
||||||
@ -100,12 +100,12 @@ test('v17.6.3 developer gate bypasses workspace loading and SaaS click is safe',
|
|||||||
});
|
});
|
||||||
const content=fs.readFileSync(path.join(process.cwd(),'public','core','hotfix-v1763.js'),'utf8');
|
const content=fs.readFileSync(path.join(process.cwd(),'public','core','hotfix-v1763.js'),'utf8');
|
||||||
await page.addScriptTag({content});
|
await page.addScriptTag({content});
|
||||||
await page.waitForFunction(()=>document.querySelector('[data-open-dev-v1763]')&&Boolean(window.SunHotfixV1763),null,{timeout:5000});
|
await page.waitForFunction(()=>Boolean(window.SunHotfixV1763),null,{timeout:5000});
|
||||||
expect(await page.locator('#sunCloudAuthGateV3 h2').textContent()).toBe('Аккаунт разработчика');
|
expect(await page.locator('#sunCloudAuthGateV3 h2').textContent()).toBe('Загружаю рабочую базу');
|
||||||
await page.locator('[data-saas-admin]').click();
|
await page.locator('[data-saas-admin]').click();
|
||||||
await page.locator('[data-open-dev-v1763]').click();
|
await expect(page.locator('[data-open-dev-v1763]')).toHaveCount(0);
|
||||||
const args=await page.evaluate(()=>window.__devOpenArgs);
|
const args=await page.evaluate(()=>window.__devOpenArgs);
|
||||||
expect(args).toEqual(['null','null']);
|
expect(args).toEqual(['null']);
|
||||||
});
|
});
|
||||||
test('v17.6.4 auto-completes and fully pays an order one minute after scheduled time', async ({ page }) => {
|
test('v17.6.4 auto-completes and fully pays an order one minute after scheduled time', async ({ page }) => {
|
||||||
await page.addInitScript(()=>{
|
await page.addInitScript(()=>{
|
||||||
|
|||||||
@ -2,13 +2,13 @@ import { defineConfig, devices } from '@playwright/test';
|
|||||||
import {fileURLToPath} from 'node:url';
|
import {fileURLToPath} from 'node:url';
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir:'.',
|
testDir:'.',
|
||||||
testMatch:['help-center.spec.mjs','app.spec.mjs','theme-startup.spec.mjs','company-branding.spec.mjs','order-import.spec.mjs','account-access.spec.mjs','banquet-menu.spec.mjs','calendar-print.spec.mjs','login-recovery.spec.mjs','trial-demo.spec.mjs','proposal-quality.spec.mjs'],
|
testMatch:['ui-stability.spec.mjs','help-center.spec.mjs','app.spec.mjs','theme-startup.spec.mjs','company-branding.spec.mjs','order-import.spec.mjs','account-access.spec.mjs','banquet-menu.spec.mjs','calendar-print.spec.mjs','login-recovery.spec.mjs','trial-demo.spec.mjs','proposal-quality.spec.mjs'],
|
||||||
timeout:30000,
|
timeout:30000,
|
||||||
use:{baseURL:'http://127.0.0.1:4173'},
|
use:{baseURL:'http://127.0.0.1:4173'},
|
||||||
webServer:{command:'npx http-server public -p 4173 -c-1',cwd:fileURLToPath(new URL('../',import.meta.url)),port:4173,reuseExistingServer:true},
|
webServer:{command:'npx http-server public -p 4173 -c-1',cwd:fileURLToPath(new URL('../',import.meta.url)),port:4173,reuseExistingServer:true},
|
||||||
projects:[
|
projects:[
|
||||||
{name:'iphone-pdf',testMatch:['proposal-quality.spec.mjs'],grep:/all six selections|transparent wide|an actual offer downloads/,use:{...devices['iPhone 13'],serviceWorkers:'block'}},
|
{name:'iphone-pdf',testMatch:['proposal-quality.spec.mjs'],grep:/all six selections|transparent wide|an actual offer downloads/,use:{...devices['iPhone 13'],serviceWorkers:'block'}},
|
||||||
{name:'iphone-webkit',testMatch:['help-center.spec.mjs','login-recovery.spec.mjs','account-access.spec.mjs','calendar-print.spec.mjs'],use:{...devices['iPhone 13'],serviceWorkers:'block'}},
|
{name:'iphone-webkit',testMatch:['ui-stability.spec.mjs','help-center.spec.mjs','login-recovery.spec.mjs','account-access.spec.mjs','calendar-print.spec.mjs'],use:{...devices['iPhone 13'],serviceWorkers:'block'}},
|
||||||
{name:'desktop',use:{...devices['Desktop Chrome']}},
|
{name:'desktop',use:{...devices['Desktop Chrome']}},
|
||||||
{name:'mobile-390',use:{viewport:{width:390,height:844},isMobile:true,hasTouch:true}}
|
{name:'mobile-390',use:{viewport:{width:390,height:844},isMobile:true,hasTouch:true}}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -14,15 +14,15 @@ check(!index.includes('offer-gallery-data.js'),'blocking Base64 gallery absent')
|
|||||||
check((runtime.match(/\/Type \/Catalog/g)||[]).length===0,'runtime contains no PDF binary writer');
|
check((runtime.match(/\/Type \/Catalog/g)||[]).length===0,'runtime contains no PDF binary writer');
|
||||||
check(read('core/pdf-engine.js').includes('595.28')&&read('core/pdf-engine.js').includes('841.89'),'PDF engine uses A4 MediaBox');
|
check(read('core/pdf-engine.js').includes('595.28')&&read('core/pdf-engine.js').includes('841.89'),'PDF engine uses A4 MediaBox');
|
||||||
check([...index.matchAll(/@page\{([^}]*)\}/g)].every(m=>/size:A4/i.test(m[1])),'compact @page rules use A4');
|
check([...index.matchAll(/@page\{([^}]*)\}/g)].every(m=>/size:A4/i.test(m[1])),'compact @page rules use A4');
|
||||||
check(sw.includes('v109-20260918-mobile-calendar')&&sw.includes('data-layer-v1773.js')&&sw.includes('server-automation-v1770.js')&&sw.includes('offer-workspace-v1769.js'),'service worker cache is v17.7.3');
|
check(sw.includes('v110-20260918-ui-stability')&&sw.includes('data-layer-v1773.js')&&sw.includes('server-automation-v1770.js')&&sw.includes('offer-workspace-v1769.js'),'service worker cache is v17.7.3');
|
||||||
check(index.includes('20260918-mobile-calendar')&&index.includes('classic-offer-pdf-v1767.js')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.7.3');
|
check(index.includes('20260918-ui-stability')&&index.includes('classic-offer-pdf-v1767.js')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.7.3');
|
||||||
check(performance.includes('SunAttachmentGuard')&&performance.includes('TARGET=2*1024*1024'),'chat photo auto-compression is versioned');
|
check(performance.includes('SunAttachmentGuard')&&performance.includes('TARGET=2*1024*1024'),'chat photo auto-compression is versioned');
|
||||||
check(performance.includes("rpc('sun_dev_dashboard')")&&performance.includes('server_size')&&performance.includes('storage_size'),'Developer Console server/storage counters are versioned');
|
check(performance.includes("rpc('sun_dev_dashboard')")&&performance.includes('server_size')&&performance.includes('storage_size'),'Developer Console server/storage counters are versioned');
|
||||||
check(performance.includes('MEMORY_REFRESH_MS=30000')&&performance.includes('MEMORY_TIMEOUT_MS=8000')&&performance.includes('memoryPromise'),'Developer Console memory refresh is bounded');
|
check(performance.includes('MEMORY_REFRESH_MS=30000')&&performance.includes('MEMORY_TIMEOUT_MS=8000')&&performance.includes('memoryPromise'),'Developer Console memory refresh is bounded');
|
||||||
check(!performance.includes('records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory()'),'Developer Console refresh is not mutation-driven');
|
check(!performance.includes('records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory()'),'Developer Console refresh is not mutation-driven');
|
||||||
check(performance.includes('ux-fixes-v1764.js')&&performance.includes('SunUXFixV1764'),'v17.6.4 UX module is loaded');
|
check(performance.includes('ux-fixes-v1764.js')&&performance.includes('SunUXFixV1764'),'v17.6.4 UX module is loaded');
|
||||||
check(performance.includes('hotfix-v1763.js')&&performance.includes('SunHotfixV1763'),'developer/SaaS hotfix is loaded');
|
check(performance.includes('hotfix-v1763.js')&&performance.includes('SunHotfixV1763'),'developer/SaaS hotfix is loaded');
|
||||||
check(hotfix.includes('patchDeveloperOpen')&&hotfix.includes('enhanceDeveloperGate')&&hotfix.includes('data-saas-admin'),'developer gate and SaaS click hotfix is versioned');
|
check(hotfix.includes('patchDeveloperOpen')&&!hotfix.includes('card.innerHTML')&&hotfix.includes('data-saas-admin'),'SaaS click hotfix preserves the unified auth gate');
|
||||||
check(hotfix.includes('source instanceof HTMLElement')&&hotfix.includes('stopImmediatePropagation'),'SaaS event object cannot reach Developer Console as a nav button');
|
check(hotfix.includes('source instanceof HTMLElement')&&hotfix.includes('stopImmediatePropagation'),'SaaS event object cannot reach Developer Console as a nav button');
|
||||||
check(performance.includes('ops-ux-v1762.js'),'operations UX module is loaded by performance core');
|
check(performance.includes('ops-ux-v1762.js'),'operations UX module is loaded by performance core');
|
||||||
check(ops.includes('supportReadPermission')&&ops.includes('SUPPORT_POLL_MS=20000')&&ops.includes('refreshSupportWorkspace'),'developer support read-only refresh is lightweight and bounded');
|
check(ops.includes('supportReadPermission')&&ops.includes('SUPPORT_POLL_MS=20000')&&ops.includes('refreshSupportWorkspace'),'developer support read-only refresh is lightweight and bounded');
|
||||||
@ -39,12 +39,12 @@ check(!/sb_secret_[A-Za-z0-9_-]{20,}|service_role\s*[:=]\s*["'][A-Za-z0-9._-]{30
|
|||||||
check(lock.version===pkg.version&&lock.packages?.['']?.version===pkg.version,'package.json and package-lock.json versions match');
|
check(lock.version===pkg.version&&lock.packages?.['']?.version===pkg.version,'package.json and package-lock.json versions match');
|
||||||
check(releaseManifest.version===`v${pkg.version}`,'release manifest version matches package.json');
|
check(releaseManifest.version===`v${pkg.version}`,'release manifest version matches package.json');
|
||||||
check(releaseManifest.channel==='production','release manifest channel is production');
|
check(releaseManifest.channel==='production','release manifest channel is production');
|
||||||
check(String(releaseManifest.pwaCache||'').includes('v109-20260918-mobile-calendar'),'release manifest points to current PWA cache');
|
check(String(releaseManifest.pwaCache||'').includes('v110-20260918-ui-stability'),'release manifest points to current PWA cache');
|
||||||
check(['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7','17.6.8','17.6.9','17.7.0','17.7.1','17.7.2','17.7.3'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.7.3');
|
check(['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7','17.6.8','17.6.9','17.7.0','17.7.1','17.7.2','17.7.3'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.7.3');
|
||||||
check(runtime.includes('CLOUD_RPC_TIMEOUT_MS=45000')&&runtime.includes('CLOUD_CONFLICT_MAX_RETRIES=4')&&runtime.includes('retryCount'),'cloud sync has timeout and capped exponential conflict retries');
|
check(runtime.includes('CLOUD_RPC_TIMEOUT_MS=45000')&&runtime.includes('CLOUD_CONFLICT_MAX_RETRIES=4')&&runtime.includes('retryCount'),'cloud sync has timeout and capped exponential conflict retries');
|
||||||
check(runtime.includes("const VERSION = '17.7.3'")&&runtime.includes('ERROR_DEDUPE_MS=5*60*1000')&&runtime.includes('mirrorBusy=false')&&runtime.includes('backupBusy=false'),'stability logger uses current version, dedupe and single-flight guards');
|
check(runtime.includes("const VERSION = '17.7.3'")&&runtime.includes('ERROR_DEDUPE_MS=5*60*1000')&&runtime.includes('mirrorBusy=false')&&runtime.includes('backupBusy=false'),'stability logger uses current version, dedupe and single-flight guards');
|
||||||
check(runtime.includes('refreshSupportWorkspace')&&runtime.includes('sun_dev_support_snapshot'),'cloud exposes lightweight read-only support refresh');
|
check(runtime.includes('refreshSupportWorkspace')&&runtime.includes('sun_dev_support_snapshot'),'cloud exposes lightweight read-only support refresh');
|
||||||
check(runtime.includes('DEV_ADMIN_TTL_MS=30000')&&hotfix.includes('checkPlatformAdmin?.(false)')&&hotfix.includes('},10000);'),'developer access checks are throttled');
|
check(runtime.includes('DEV_ADMIN_TTL_MS=30000')&&!hotfix.includes('checkPlatformAdmin'),'developer access checks are throttled');
|
||||||
check(performance.includes('pendingImageRoots')&&performance.includes('queueImageScan')&&ux.includes('},5000);'),'background DOM maintenance is batched/throttled');
|
check(performance.includes('pendingImageRoots')&&performance.includes('queueImageScan')&&ux.includes('},5000);'),'background DOM maintenance is batched/throttled');
|
||||||
check(runtime.includes('explicitTemplate')&&runtime.includes("OFFER_TEMPLATE_IDS.has(explicitTemplate)"),'per-order proposal template survives render and PDF');
|
check(runtime.includes('explicitTemplate')&&runtime.includes("OFFER_TEMPLATE_IDS.has(explicitTemplate)"),'per-order proposal template survives render and PDF');
|
||||||
check(runtime.includes("else if(id==='midnight-glass')")&&runtime.includes("else if(id==='emerald-gold')")&&runtime.includes("if(id==='editorial-grid')"),'existing proposal layout sequences remain available');
|
check(runtime.includes("else if(id==='midnight-glass')")&&runtime.includes("else if(id==='emerald-gold')")&&runtime.includes("if(id==='editorial-grid')"),'existing proposal layout sequences remain available');
|
||||||
@ -65,8 +65,8 @@ check(offerWorkspace.includes('PDF и предпросмотр')&&offerWorkspace
|
|||||||
check(offerWorkspace.includes('SunClassicOfferPDFV1767')&&offerWorkspace.includes('finalGallery=galleryFor'),'custom gallery is injected into PDF renderer');
|
check(offerWorkspace.includes('SunClassicOfferPDFV1767')&&offerWorkspace.includes('finalGallery=galleryFor'),'custom gallery is injected into PDF renderer');
|
||||||
check(releaseManifest.offerWorkspaceTabs===true&&releaseManifest.offerTemplatesSeparateTab===true&&releaseManifest.offerTwoCustomGalleryPhotos===true,'release manifest records offer workspace changes');
|
check(releaseManifest.offerWorkspaceTabs===true&&releaseManifest.offerTemplatesSeparateTab===true&&releaseManifest.offerTwoCustomGalleryPhotos===true,'release manifest records offer workspace changes');
|
||||||
check(pkg.version==='17.7.3','package version is v17.7.3');
|
check(pkg.version==='17.7.3','package version is v17.7.3');
|
||||||
check(index.includes('20260918-mobile-calendar'),'index cache bust is v17.7.3');
|
check(index.includes('20260918-ui-stability'),'index cache bust is v17.7.3');
|
||||||
check(sw.includes('v109-20260918-mobile-calendar')&&sw.includes('data-layer-v1773.js')&&sw.includes('server-automation-v1770.js'),'PWA caches v17.7.3 client foundation modules');
|
check(sw.includes('v110-20260918-ui-stability')&&sw.includes('data-layer-v1773.js')&&sw.includes('server-automation-v1770.js'),'PWA caches v17.7.3 client foundation modules');
|
||||||
check(fs.existsSync(path.join(root,'public/core/data-layer-v1773.js'))&&fs.existsSync(path.join(root,'public/core/server-automation-v1770.js')),'data layer and server automation modules exist');
|
check(fs.existsSync(path.join(root,'public/core/data-layer-v1773.js'))&&fs.existsSync(path.join(root,'public/core/server-automation-v1770.js')),'data layer and server automation modules exist');
|
||||||
check(ux.includes('CateriumServerAutomationV1770?.enabled'),'cloud browser auto completion is disabled when server automation is active');
|
check(ux.includes('CateriumServerAutomationV1770?.enabled'),'cloud browser auto completion is disabled when server automation is active');
|
||||||
check(runtime.includes("const VERSION = '17.7.3'")&&runtime.includes("v17.7.3 Clients Server Read"),'stability logger reports v17.7.3');
|
check(runtime.includes("const VERSION = '17.7.3'")&&runtime.includes("v17.7.3 Clients Server Read"),'stability logger reports v17.7.3');
|
||||||
|
|||||||
@ -28,8 +28,8 @@ if(current!==113)fail(`current catalog photo count ${current}, expected 113`);el
|
|||||||
if(legacyCount!==60)fail(`legacy catalog photo count ${legacyCount}, expected 60`);else ok('60 legacy catalog photos');
|
if(legacyCount!==60)fail(`legacy catalog photo count ${legacyCount}, expected 60`);else ok('60 legacy catalog photos');
|
||||||
const gallery=fs.readdirSync(path.join(pub,'offer-gallery')).filter(x=>/\.jpg$/i.test(x));
|
const gallery=fs.readdirSync(path.join(pub,'offer-gallery')).filter(x=>/\.jpg$/i.test(x));
|
||||||
if(gallery.length!==2)fail(`offer gallery contains ${gallery.length} jpg files, expected 2`);else ok('offer gallery trimmed');
|
if(gallery.length!==2)fail(`offer gallery contains ${gallery.length} jpg files, expected 2`);else ok('offer gallery trimmed');
|
||||||
if(!sw.includes('20260918-mobile-calendar')||!sw.includes('login-signature-v1776.js')||!sw.includes('data-layer-v1773.js')||!sw.includes('server-automation-v1770.js')||!sw.includes('offer-workspace-v1769.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated for login refresh');
|
if(!sw.includes('20260918-ui-stability')||!sw.includes('login-signature-v1776.js')||!sw.includes('data-layer-v1773.js')||!sw.includes('server-automation-v1770.js')||!sw.includes('offer-workspace-v1769.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated for login refresh');
|
||||||
if(html.includes('20260907-v17-6-0-stability-security')||html.includes('20260909-v17-7-3-clients-server-read')||!html.includes('20260918-mobile-calendar')||!html.includes('classic-offer-pdf-v1767.js'))fail('index still serves stale core asset version');else ok('index cache-busting is current');
|
if(html.includes('20260907-v17-6-0-stability-security')||html.includes('20260909-v17-7-3-clients-server-read')||!html.includes('20260918-ui-stability')||!html.includes('classic-offer-pdf-v1767.js'))fail('index still serves stale core asset version');else ok('index cache-busting is current');
|
||||||
if(!performance.includes('SunAttachmentGuard')||!performance.includes('MAX_SIDE=2048'))fail('chat photo compression guard missing');else ok('chat photo compression guard present');
|
if(!performance.includes('SunAttachmentGuard')||!performance.includes('MAX_SIDE=2048'))fail('chat photo compression guard missing');else ok('chat photo compression guard present');
|
||||||
if(!performance.includes("rpc('sun_dev_dashboard')")||!performance.includes('storage_size')||!performance.includes('server_size'))fail('Developer Console memory counters missing');else ok('Developer Console memory counters present');
|
if(!performance.includes("rpc('sun_dev_dashboard')")||!performance.includes('storage_size')||!performance.includes('server_size'))fail('Developer Console memory counters missing');else ok('Developer Console memory counters present');
|
||||||
if(performance.includes('records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory()'))fail('Developer Console memory refresh is still coupled to MutationObserver');else ok('Developer Console memory refresh loop removed');
|
if(performance.includes('records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory()'))fail('Developer Console memory refresh is still coupled to MutationObserver');else ok('Developer Console memory refresh loop removed');
|
||||||
@ -41,11 +41,11 @@ if(!serverAutomation.includes("const VERSION='17.7.0'")||!serverAutomation.inclu
|
|||||||
if(!performance.includes('ux-fixes-v1764.js')||!performance.includes('SunUXFixV1764'))fail('UX fix loader missing');else ok('UX fix loader present');
|
if(!performance.includes('ux-fixes-v1764.js')||!performance.includes('SunUXFixV1764'))fail('UX fix loader missing');else ok('UX fix loader present');
|
||||||
if(!performance.includes('hotfix-v1763.js')||!performance.includes('SunHotfixV1763'))fail('v17.6.3 hotfix loader missing');else ok('v17.6.3 hotfix loader present');
|
if(!performance.includes('hotfix-v1763.js')||!performance.includes('SunHotfixV1763'))fail('v17.6.3 hotfix loader missing');else ok('v17.6.3 hotfix loader present');
|
||||||
if(!performance.includes('ops-ux-v1762.js')||!performance.includes('SunOpsUXV1762'))fail('ops UX loader missing');else ok('ops UX loader present');
|
if(!performance.includes('ops-ux-v1762.js')||!performance.includes('SunOpsUXV1762'))fail('ops UX loader missing');else ok('ops UX loader present');
|
||||||
for(const marker of ['patchDeveloperOpen','enhanceDeveloperGate','data-saas-admin','stopImmediatePropagation','instanceof HTMLElement']) if(!hotfix.includes(marker))fail(`developer/SaaS hotfix marker missing: ${marker}`);else ok(`developer/SaaS hotfix marker: ${marker}`);
|
for(const marker of ['patchDeveloperOpen','data-saas-admin','stopImmediatePropagation','instanceof HTMLElement']) if(!hotfix.includes(marker))fail(`developer/SaaS hotfix marker missing: ${marker}`);else ok(`developer/SaaS hotfix marker: ${marker}`);
|
||||||
for(const marker of ['SUPPORT_POLL_MS=20000','supportReadPermission','refreshSupportWorkspace','sun-menu-editor-v1762','showCalendarDay',"ROUTE_BASE_KEY='sunRouteBaseV1'",'showRouteOrder','routeOpenYandex']) if(!ops.includes(marker)) fail(`ops UX marker missing: ${marker}`); else ok(`ops UX marker: ${marker}`);
|
for(const marker of ['SUPPORT_POLL_MS=20000','supportReadPermission','refreshSupportWorkspace','sun-menu-editor-v1762','showCalendarDay',"ROUTE_BASE_KEY='sunRouteBaseV1'",'showRouteOrder','routeOpenYandex']) if(!ops.includes(marker)) fail(`ops UX marker missing: ${marker}`); else ok(`ops UX marker: ${marker}`);
|
||||||
for(const marker of ["AUTO_DELAY_MS=60*1000","order.prepayment=total","order.status='Отдан заказчику'",'sunAutoCompletedAt','classificationDate','persistOfferTemplate','clientOfferTemplateId','offerTemplateId','sun-v1764-menu-icon','CateriumServerAutomationV1770?.enabled']) if(!ux.includes(marker))fail(`UX compatibility marker missing: ${marker}`);else ok(`UX compatibility marker: ${marker}`);
|
for(const marker of ["AUTO_DELAY_MS=60*1000","order.prepayment=total","order.status='Отдан заказчику'",'sunAutoCompletedAt','classificationDate','persistOfferTemplate','clientOfferTemplateId','offerTemplateId','sun-v1764-menu-icon','CateriumServerAutomationV1770?.enabled']) if(!ux.includes(marker))fail(`UX compatibility marker missing: ${marker}`);else ok(`UX compatibility marker: ${marker}`);
|
||||||
for(const marker of ['CLOUD_RPC_TIMEOUT_MS=45000','CLOUD_CONFLICT_MAX_RETRIES=4','refreshSupportWorkspace',"const VERSION = '17.7.3'",'ERROR_DEDUPE_MS=5*60*1000','DEV_ADMIN_TTL_MS=30000']) if(!runtime.includes(marker))fail(`stability marker missing: ${marker}`);else ok(`stability marker: ${marker}`);
|
for(const marker of ['CLOUD_RPC_TIMEOUT_MS=45000','CLOUD_CONFLICT_MAX_RETRIES=4','refreshSupportWorkspace',"const VERSION = '17.7.3'",'ERROR_DEDUPE_MS=5*60*1000','DEV_ADMIN_TTL_MS=30000']) if(!runtime.includes(marker))fail(`stability marker missing: ${marker}`);else ok(`stability marker: ${marker}`);
|
||||||
if(!hotfix.includes('checkPlatformAdmin?.(false)')||!hotfix.includes('},10000);'))fail('Developer fallback polling is still aggressive');else ok('Developer fallback polling is throttled');
|
if(hotfix.includes('card.innerHTML')||hotfix.includes('checkPlatformAdmin'))fail('Legacy developer login override returned');else ok('Developer hotfix does not replace the unified login');
|
||||||
if(!ux.includes('sunMenuIconV1766')||!ux.includes('sun-offer-template-mini-editorial-grid')||!runtime.includes('explicitTemplate'))fail('v17.6.6 proposal/menu markers missing');else ok('v17.6.6 proposal/menu markers present');
|
if(!ux.includes('sunMenuIconV1766')||!ux.includes('sun-offer-template-mini-editorial-grid')||!runtime.includes('explicitTemplate'))fail('v17.6.6 proposal/menu markers missing');else ok('v17.6.6 proposal/menu markers present');
|
||||||
if(!classic.includes("const VERSION='17.6.7'")||!classic.includes('CLASSIC_IDS')||!classic.includes('ARCHIVE_IDS')||!classic.includes('renderPages'))fail('v17.6.7 classic PDF module missing');else ok('v17.6.7 classic PDF module present');
|
if(!classic.includes("const VERSION='17.6.7'")||!classic.includes('CLASSIC_IDS')||!classic.includes('ARCHIVE_IDS')||!classic.includes('renderPages'))fail('v17.6.7 classic PDF module missing');else ok('v17.6.7 classic PDF module present');
|
||||||
if(!developerUX.includes("const VERSION='17.6.8'")||!developerUX.includes('sun_dev_delete_company_v1768')||!developerUX.includes('sun_dev_error_groups_v1768')||!developerUX.includes('selectedAccountsWorkspace')||!developerUX.includes('sun-dev-plan-matrix')||!developerUX.includes('KNOWN_DOM_RACE'))fail('v17.6.8 Developer Console UX module missing');else ok('v17.6.8 Developer Console UX module present');
|
if(!developerUX.includes("const VERSION='17.6.8'")||!developerUX.includes('sun_dev_delete_company_v1768')||!developerUX.includes('sun_dev_error_groups_v1768')||!developerUX.includes('selectedAccountsWorkspace')||!developerUX.includes('sun-dev-plan-matrix')||!developerUX.includes('KNOWN_DOM_RACE'))fail('v17.6.8 Developer Console UX module missing');else ok('v17.6.8 Developer Console UX module present');
|
||||||
|
|||||||
164
tests/ui-stability.spec.mjs
Normal file
164
tests/ui-stability.spec.mjs
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import {test,expect} from '@playwright/test';
|
||||||
|
|
||||||
|
const runtime=fs.readFileSync('public/app-runtime.js','utf8');
|
||||||
|
function moduleSource(name){const start=runtime.indexOf(`/* ===== MODULE: ${name} ===== */`),end=runtime.indexOf('/* ===== MODULE:',start+1);return runtime.slice(start,end<0?undefined:end);}
|
||||||
|
async function fixture(page,body){
|
||||||
|
await page.route('**/index.html',r=>r.fulfill({contentType:'text/html; charset=utf-8',body:`<!doctype html><html><head><meta charset="utf-8"></head><body>${body}</body></html>`}));
|
||||||
|
await page.goto('/index.html');
|
||||||
|
await page.evaluate(()=>window.SunSafe={escapeHTML:v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])),insertBefore:(p,n,a)=>p.insertBefore(n,a)});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('modal stacking respects existing layers and closing never steals input focus',async({page})=>{
|
||||||
|
await fixture(page,'<style>.modal{display:none;position:fixed;inset:0}.modal.on{display:block}</style><button id="opener">Открыть</button><input id="nextInput"><div id="first" class="modal" style="z-index:100050"><div class="dialog"><button>Первое окно</button></div></div><div id="second" class="modal"><div class="dialog"><button>Второе окно</button></div></div>');
|
||||||
|
await page.evaluate(()=>window.closeModal=id=>document.getElementById(id).classList.remove('on'));
|
||||||
|
const html=fs.readFileSync('public/index.html','utf8');
|
||||||
|
await page.addScriptTag({content:html.match(/<script id="sun-stability-motion-script">([\s\S]*?)<\/script>/)[1]});
|
||||||
|
await page.locator('#opener').focus();
|
||||||
|
await page.evaluate(()=>document.getElementById('first').classList.add('on'));
|
||||||
|
await expect(page.locator('#first button')).toBeFocused();
|
||||||
|
expect(await page.locator('#first').evaluate(el=>Number(getComputedStyle(el).zIndex))).toBeGreaterThanOrEqual(100050);
|
||||||
|
await page.evaluate(()=>document.getElementById('second').classList.add('on'));
|
||||||
|
await expect(page.locator('#second button')).toBeFocused();
|
||||||
|
expect(await page.evaluate(()=>Number(getComputedStyle(document.getElementById('second')).zIndex)>Number(getComputedStyle(document.getElementById('first')).zIndex))).toBe(true);
|
||||||
|
await page.evaluate(()=>{closeModal('second');closeModal('first');});
|
||||||
|
await page.locator('#nextInput').focus();
|
||||||
|
await page.waitForTimeout(250);
|
||||||
|
await expect(page.locator('#nextInput')).toBeFocused();
|
||||||
|
await page.evaluate(()=>document.getElementById('second').classList.add('on'));
|
||||||
|
await expect(page.locator('body')).toHaveClass(/sun-modal-open/);
|
||||||
|
await page.evaluate(()=>document.getElementById('second').remove());
|
||||||
|
await expect(page.locator('body')).not.toHaveClass(/sun-modal-open/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('settings settle without repeated DOM replacement and preserve focused controls',async({page})=>{
|
||||||
|
await fixture(page,`<header><nav><button>Настройки</button></nav></header><section id="enterprise-settings" class="on"><div class="catalog-head"></div><div class="enterprise-grid">
|
||||||
|
<section id="sunReceiptSettingsCard" class="enterprise-card"><h2>Настройки реквизитов товарного чека</h2><input value="Черновик"></section>
|
||||||
|
<section id="sunDefaultQrCard" class="enterprise-card"><h2>QR-код для бланка заказа</h2></section><section id="sunBlankSettingsCardV3" class="enterprise-card"><h2>Настройки бланка</h2></section>
|
||||||
|
<section id="sunCloudV2Card" class="enterprise-card"><h2>Профиль и аккаунт</h2></section><section class="enterprise-card"><h2>История изменений</h2></section></div></section>`);
|
||||||
|
await page.evaluate(()=>{window.sunOpenCategoryManager=()=>{};window.sunCatalogCategories=()=>[{id:0}];});
|
||||||
|
await page.addScriptTag({content:moduleSource('settings-polish-v1.js')+moduleSource('settings-tabs-v21.js')});
|
||||||
|
await page.getByRole('tab',{name:'Документы',exact:true}).click();
|
||||||
|
await page.locator('#sunReceiptSettingsCard input').focus();
|
||||||
|
await page.waitForTimeout(400);
|
||||||
|
await page.evaluate(()=>{window.changes=0;new MutationObserver(records=>window.changes+=records.length).observe(document.querySelector('#enterprise-settings'),{childList:true,subtree:true});});
|
||||||
|
await page.waitForTimeout(350);
|
||||||
|
expect(await page.evaluate(()=>window.changes)).toBe(0);
|
||||||
|
await expect(page.locator('#sunReceiptSettingsCard input')).toBeFocused();
|
||||||
|
await expect(page.locator('#sunReceiptSettingsCard input')).toHaveValue('Черновик');
|
||||||
|
await page.evaluate(()=>{window.sunCatalogCategories=()=>[{id:0},{id:1}];window.dispatchEvent(new Event('sun:catalog-categories-changed'));});
|
||||||
|
await expect(page.locator('.sun-settings-catalog-chip').first()).toHaveText('Всего: 2');
|
||||||
|
await expect(page.locator('#sunCatalogTabsSettingsCardV21')).toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unchanged cloud notifications preserve the theme input and do not repaint the theme',async({page})=>{
|
||||||
|
await fixture(page,'<section id="enterprise-settings" class="on"><div class="enterprise-grid"></div></section>');
|
||||||
|
await page.addScriptTag({url:'/core/brand-theme.js'});
|
||||||
|
const input=page.locator('[data-color-scope="sidebar"][data-color-key="background"] input[type="text"]');
|
||||||
|
await input.focus();
|
||||||
|
await page.evaluate(()=>{window.themeChanges=0;window.addEventListener('sunbrandthemechange',()=>window.themeChanges++);window.dispatchEvent(new Event('suncloudsync'));window.dispatchEvent(new Event('sun:cloud-state-applied'));window.dispatchEvent(new Event('resize'));});
|
||||||
|
await expect(input).toBeFocused();
|
||||||
|
expect(await page.evaluate(()=>window.themeChanges)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function subscriptionFixture(page){
|
||||||
|
await fixture(page,'<header><div class="brand"></div><nav></nav></header><section id="enterprise-settings"><div class="enterprise-grid"></div></section>');
|
||||||
|
await page.clock.install();
|
||||||
|
await page.evaluate(()=>{
|
||||||
|
window.pending=[];window.company='a';window.user='user';window.subscriptionEvents=[];
|
||||||
|
const client={rpc:(_,args)=>new Promise(resolve=>pending.push({id:args.p_workspace,resolve}))};
|
||||||
|
window.SunCloudV2={getClient:()=>client,getWorkspace:()=>company?{id:company}:null,getSession:()=>user?{user:{id:user}}:null};
|
||||||
|
window.addEventListener('sun:subscription-changed',e=>subscriptionEvents.push(e.detail.plan_name));
|
||||||
|
});
|
||||||
|
await page.addScriptTag({content:moduleSource('saas-v16.js')});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('subscription responses from a previous account cannot restore its blocking window',async({page})=>{
|
||||||
|
await subscriptionFixture(page);
|
||||||
|
const result=await page.evaluate(async()=>{
|
||||||
|
const old=SunSaaSV16.refresh();company='b';window.dispatchEvent(new Event('sun:cloud-tenant-changing'));const next=SunSaaSV16.refresh();
|
||||||
|
const requested=pending.map(x=>x.id);
|
||||||
|
pending.find(x=>x.id==='b')?.resolve({data:{plan_name:'Company B',access_mode:'full',features:{}}});await next;
|
||||||
|
pending.find(x=>x.id==='a').resolve({data:{plan_name:'Company A',access_mode:'blocked',features:{}}});await old;
|
||||||
|
return {requested,name:SunSaaSV16.getSnapshot()?.plan_name,blocked:Boolean(document.getElementById('sunSaaSBlockedV16')),events:subscriptionEvents};
|
||||||
|
});
|
||||||
|
expect(result).toEqual({requested:['a','b'],name:'Company B',blocked:false,events:['Company B']});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('subscription refresh preserves controls and its plans dialog is above the blocker',async({page})=>{
|
||||||
|
await subscriptionFixture(page);
|
||||||
|
await page.evaluate(async()=>{const p=SunSaaSV16.refresh();pending.shift().resolve({data:{plan_name:'Full',access_mode:'blocked',features:{}}});await p;});
|
||||||
|
await page.locator('[data-saas-show-plans]').click();
|
||||||
|
expect(await page.evaluate(()=>{const b=document.querySelector('#sunSaaSPlansModalV16 [data-close]'),r=b.getBoundingClientRect();return b.contains(document.elementFromPoint(r.x+r.width/2,r.y+r.height/2));})).toBe(true);
|
||||||
|
await page.locator('#sunSaaSPlansModalV16 [data-close]').click();
|
||||||
|
await page.evaluate(()=>{
|
||||||
|
const overlay=document.createElement('div');overlay.id='testSourceDialog';overlay.className='modal on';overlay.style.cssText='position:fixed;inset:0;z-index:100200;background:white';document.body.appendChild(overlay);SunSaaSV16.showPlans('client_offers');
|
||||||
|
});
|
||||||
|
await page.locator('#sunSaaSPlansModalV16 [data-close]').click();
|
||||||
|
await page.evaluate(()=>document.getElementById('testSourceDialog').remove());
|
||||||
|
await page.locator('[data-saas-show-plans]').focus();
|
||||||
|
await page.evaluate(async()=>{const p=SunSaaSV16.refresh();pending.shift().resolve({data:{plan_name:'Full',access_mode:'blocked',features:{}}});await p;});
|
||||||
|
await expect(page.locator('[data-saas-show-plans]')).toBeFocused();
|
||||||
|
await page.evaluate(()=>{company=null;user=null;window.dispatchEvent(new Event('sun:cloud-tenant-changing'));});
|
||||||
|
await expect(page.locator('#sunSaaSBlockedV16')).toHaveCount(0);
|
||||||
|
await expect(page.locator('#sunSaaSSettingsCardV16')).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a delayed menu editor cannot move back into a section that was already left',async({page})=>{
|
||||||
|
await fixture(page,'<header><nav><button id="elsewhere">Заказы</button></nav></header><div id="editor" class="modal"><div class="dialog"><input id="name"></div></div>');
|
||||||
|
await page.evaluate(()=>{
|
||||||
|
localStorage.setItem('sunBoxes',JSON.stringify([{id:'one',name:'Тестовый бокс',price:100}]));
|
||||||
|
window.SunCloudV2={getSession:()=>({user:{id:'test'}}),hasPermission:()=>true};
|
||||||
|
window.modal=id=>document.getElementById(id).classList.add('on');window.closeModal=id=>document.getElementById(id).classList.remove('on');window.editBox=()=>window.modal('editor');
|
||||||
|
});
|
||||||
|
await page.addScriptTag({url:'/core/ops-ux-v1762.js'});
|
||||||
|
await page.evaluate(()=>{SunOpsUXV1762.openMenu();document.querySelector('[data-menu-item-v1762]').click();document.getElementById('elsewhere').click();});
|
||||||
|
await page.waitForTimeout(100);
|
||||||
|
await expect(page.locator('#editor > .dialog')).toHaveCount(1);
|
||||||
|
await expect(page.locator('#editor')).not.toHaveClass(/\bon\b/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('full app stays stable across settings, menu, calendar and delayed background refreshes',async({page})=>{
|
||||||
|
const errors=[];page.on('pageerror',e=>errors.push(String(e)));
|
||||||
|
const expires=Math.floor(Date.now()/1000)+3600,user={id:'11111111-1111-4111-8111-111111111111',aud:'authenticated',role:'authenticated',email:'ui-test@example.invalid',app_metadata:{provider:'email'},user_metadata:{}};
|
||||||
|
const token=[{alg:'HS256',typ:'JWT'},{sub:user.id,role:'authenticated',aud:'authenticated',exp:expires,iat:expires-3600,aal:'aal1'},'test'].map(x=>typeof x==='string'?x:Buffer.from(JSON.stringify(x)).toString('base64url')).join('.');
|
||||||
|
const session={access_token:token,refresh_token:'test-refresh',token_type:'bearer',expires_in:3600,expires_at:expires,user};
|
||||||
|
await page.addInitScript(value=>localStorage.setItem('sb-usfjwhztqoopzzfmfbis-auth-token',JSON.stringify(value)),session);
|
||||||
|
await page.route('https://**',r=>r.abort());
|
||||||
|
const handle=async route=>{
|
||||||
|
const url=new URL(route.request().url()),path=url.searchParams.get('__caterium_path')||url.pathname;
|
||||||
|
let data=null;
|
||||||
|
if(path==='/auth/v1/user')data=user;
|
||||||
|
else if(path.endsWith('/sun_my_workspaces'))data=[{id:'22222222-2222-4222-8222-222222222222',name:'Тестовая компания',role:'admin',is_active:true,permissions:{}}];
|
||||||
|
else if(path.endsWith('/sun_is_platform_admin'))data=false;
|
||||||
|
else if(path.endsWith('/sun_subscription_snapshot'))data={plan_id:'full',plan_name:'Полный',status:'active',access_mode:'full',features:Object.fromEntries('orders calendar clients catalog_view catalog_edit production shopping stock routes mailings money stats_basic stats_advanced team suppliers print settings branding client_offers offer_templates backups audit users_manage'.split(' ').map(k=>[k,true]))};
|
||||||
|
else if(path.endsWith('/sun_fetch_app_state'))data=url.searchParams.get('select')==='revision'?{revision:1}:[{revision:1,payload:{format:'sun-cloud-v2',version:2,storage:{sunOrders:{t:'j',v:[]},sunBoxes:{t:'j',v:[]}}}}];
|
||||||
|
return route.fulfill({contentType:'application/json',body:JSON.stringify(data)});
|
||||||
|
};
|
||||||
|
await page.route('**/api/index.php?**',handle);
|
||||||
|
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
|
||||||
|
await expect(page.locator('body > header')).toBeVisible({timeout:20000});
|
||||||
|
await page.waitForFunction(()=>window.SunOpsUXV1762&&window.SunSaaSV16?.getSnapshot());
|
||||||
|
const nav=page.locator('header nav');
|
||||||
|
for(const name of ['Настройки','Меню','Календарь','Заказы','Настройки']){
|
||||||
|
await nav.getByRole('button',{name,exact:true}).click();
|
||||||
|
await expect(page.locator('body > .view.on')).toHaveCount(1);
|
||||||
|
await expect(page.locator('#sunCloudAuthGateV3')).toHaveCount(0);
|
||||||
|
await expect(page.locator('.modal.on')).toHaveCount(0);
|
||||||
|
}
|
||||||
|
await page.getByRole('tab',{name:'Оформление',exact:true}).click();
|
||||||
|
const input=page.locator('[data-color-scope="sidebar"][data-color-key="background"] input[type="text"]');
|
||||||
|
await input.focus();
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
await page.evaluate(()=>{
|
||||||
|
window.settingsMutations=[];
|
||||||
|
new MutationObserver(records=>{for(const r of records)settingsMutations.push(r.target.id||r.target.parentElement?.id||r.target.nodeName)}).observe(document.querySelector('#enterprise-settings'),{childList:true,subtree:true});
|
||||||
|
});
|
||||||
|
await page.waitForTimeout(1100);
|
||||||
|
expect(await page.evaluate(()=>settingsMutations)).toEqual([]);
|
||||||
|
await page.evaluate(()=>window.dispatchEvent(new Event('sun:cloud-state-applied')));
|
||||||
|
await page.waitForTimeout(600);
|
||||||
|
await expect(input).toBeFocused();
|
||||||
|
await expect(page.locator('.sun-settings-tab.on')).toHaveText('Оформление');
|
||||||
|
expect(errors).toEqual([]);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user