Match Help icon to native sidebar styling (#30)
Add a question-circle SVG mask to Help using the existing sidebar pseudo-element. Preserve shared icon geometry, theme colors and navigation behavior. Add full-app icon regression and correct the previous loading-recovery fixture. Production promotion remains gated by full main QA.
This commit is contained in:
parent
b2bcb0f4db
commit
15ab908836
@ -1,3 +1,5 @@
|
|||||||
|
/* Use the shared sidebar pseudo-element, sizing and theme colors; no extra icon node. */
|
||||||
|
.sun-enterprise-sidebar header nav #ctHelpNav{--sun-icon:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.9%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%229%22%2F%3E%3Cpath%20d%3D%22M9.5%209a2.5%202.5%200%200%201%205%200c0%201.7-2.5%202-2.5%204M12%2017h.01%22%2F%3E%3C%2Fsvg%3E")}
|
||||||
#ctHelpDialog{box-sizing:border-box;width:min(980px,calc(100vw - 24px));max-width:calc(100vw - 24px);height:min(820px,calc(100dvh - 24px));max-height:calc(100dvh - 24px);padding:0;border:1px solid #d9ddda;border-radius:20px;background:#fafaf7;color:#263d31;font:15px/1.55 Arial,sans-serif;overflow:hidden;margin:auto}
|
#ctHelpDialog{box-sizing:border-box;width:min(980px,calc(100vw - 24px));max-width:calc(100vw - 24px);height:min(820px,calc(100dvh - 24px));max-height:calc(100dvh - 24px);padding:0;border:1px solid #d9ddda;border-radius:20px;background:#fafaf7;color:#263d31;font:15px/1.55 Arial,sans-serif;overflow:hidden;margin:auto}
|
||||||
#ctHelpDialog::backdrop{background:#152c27a6}
|
#ctHelpDialog::backdrop{background:#152c27a6}
|
||||||
#ctHelpDialog[open]{display:flex;flex-direction:column}
|
#ctHelpDialog[open]{display:flex;flex-direction:column}
|
||||||
|
|||||||
@ -46,3 +46,36 @@ test('full app exposes help on login without exposing the workspace',async({page
|
|||||||
await expect(page.locator('[data-article="iphone"]')).toBeVisible();await expect(page.locator('body > header')).toBeHidden();
|
await expect(page.locator('[data-article="iphone"]')).toBeVisible();await expect(page.locator('body > header')).toBeHidden();
|
||||||
await page.getByRole('button',{name:'Закрыть помощь',exact:true}).click();await expect(page.locator('#sunGateEmailV3')).toBeVisible();
|
await page.getByRole('button',{name:'Закрыть помощь',exact:true}).click();await expect(page.locator('#sunGateEmailV3')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Help uses a question-circle mask with the native sidebar icon geometry and colors',async({page})=>{
|
||||||
|
// No production account or network is needed to inspect the real navigation styles.
|
||||||
|
await page.route('https://**',r=>r.abort());
|
||||||
|
await page.route('**/api/index.php*',r=>r.fulfill({status:503,contentType:'application/json',body:'{}'}));
|
||||||
|
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
|
||||||
|
const help=page.locator('#ctHelpNav');
|
||||||
|
await expect(help).toHaveClass(/sun-nav-button/);
|
||||||
|
await page.locator('header nav .nav-settings').waitFor({state:'attached'});
|
||||||
|
await expect.poll(()=>help.evaluate(el=>getComputedStyle(el,'::before').getPropertyValue('mask-image')||getComputedStyle(el,'::before').getPropertyValue('-webkit-mask-image'))).toContain('data:image/svg+xml');
|
||||||
|
const result=await help.evaluate(el=>{
|
||||||
|
const peer=document.querySelector('header nav .nav-settings');
|
||||||
|
const pseudo=node=>getComputedStyle(node,'::before');
|
||||||
|
const mask=pseudo(el).getPropertyValue('mask-image')||pseudo(el).getPropertyValue('-webkit-mask-image');
|
||||||
|
const dataUrl=mask.match(/url\(["']?(data:image\/svg\+xml,[^"')]+)["']?\)/)?.[1];
|
||||||
|
const svg=dataUrl?new DOMParser().parseFromString(decodeURIComponent(dataUrl.slice(dataUrl.indexOf(',')+1)),'image/svg+xml'):null;
|
||||||
|
const geometry=node=>{
|
||||||
|
const s=pseudo(node);
|
||||||
|
return {width:s.width,height:s.height,marginRight:s.marginRight,background:s.backgroundColor,opacity:s.opacity,maskSize:s.getPropertyValue('mask-size')||s.getPropertyValue('-webkit-mask-size')};
|
||||||
|
};
|
||||||
|
const normal={help:geometry(el),peer:geometry(peer)};
|
||||||
|
const hadHelp=el.classList.contains('on'),hadPeer=peer.classList.contains('on');
|
||||||
|
el.classList.add('on');peer.classList.add('on');
|
||||||
|
const active={help:geometry(el),peer:geometry(peer)};
|
||||||
|
el.classList.toggle('on',hadHelp);peer.classList.toggle('on',hadPeer);
|
||||||
|
return {normal,active,viewBox:svg?.documentElement.getAttribute('viewBox'),fill:svg?.documentElement.getAttribute('fill'),stroke:svg?.documentElement.getAttribute('stroke-width'),circle:svg?.querySelector('circle')?.getAttribute('r'),question:svg?.querySelector('path')?.getAttribute('d')};
|
||||||
|
});
|
||||||
|
expect(result.viewBox).toBe('0 0 24 24');expect(result.fill).toBe('none');expect(result.stroke).toBe('1.9');expect(result.circle).toBe('9');expect(result.question).toContain('M12 17h.01');
|
||||||
|
expect(result.normal.help).toEqual(result.normal.peer);expect(result.active.help).toEqual(result.active.peer);
|
||||||
|
expect(result.normal.help.width).toBe('20px');expect(result.normal.help.height).toBe('20px');
|
||||||
|
await expect(help.locator('svg,img')).toHaveCount(0);
|
||||||
|
await expect(help).toHaveText('Помощь');
|
||||||
|
});
|
||||||
|
|||||||
@ -7,12 +7,14 @@ async function startWorkspaceLookup(page,{withoutDecoration=false}={}){
|
|||||||
if(withoutDecoration)await page.route('**/core/login-signature-v1776.js*',route=>route.abort());
|
if(withoutDecoration)await page.route('**/core/login-signature-v1776.js*',route=>route.abort());
|
||||||
await page.addInitScript(()=>{
|
await page.addInitScript(()=>{
|
||||||
localStorage.setItem('sunCloudV2Config',JSON.stringify({workspaceId:'startup-company',localWorkspaceId:'startup-company',tenantStorageReady:true,autoSync:false}));
|
localStorage.setItem('sunCloudV2Config',JSON.stringify({workspaceId:'startup-company',localWorkspaceId:'startup-company',tenantStorageReady:true,autoSync:false}));
|
||||||
|
const recovered=sessionStorage.getItem('testWorkspaceRecovered')==='1';
|
||||||
window.workspaceLookupCount=0;
|
window.workspaceLookupCount=0;
|
||||||
window.supabase={createClient:()=>({
|
window.supabase={createClient:()=>({
|
||||||
auth:{onAuthStateChange:()=>({data:{subscription:{unsubscribe(){}}}}),getSession:()=>new Promise(resolve=>{window.finishSessionRestore=resolve}),getUser:async()=>({data:{user:null}})},
|
auth:{onAuthStateChange:()=>({data:{subscription:{unsubscribe(){}}}}),getSession:()=>recovered?Promise.resolve({data:{session:{user:{id:'startup-user',email:'test@example.invalid'}}},error:null}):new Promise(resolve=>{window.finishSessionRestore=resolve}),getUser:async()=>({data:{user:null}})},
|
||||||
rpc:async name=>{
|
rpc:async name=>{
|
||||||
if(name==='sun_my_workspaces'){
|
if(name==='sun_my_workspaces'){
|
||||||
window.workspaceLookupCount++;
|
window.workspaceLookupCount++;
|
||||||
|
if(recovered)return {data:[{id:'startup-company',name:'Test company',role:'admin',is_active:true}],error:null};
|
||||||
return new Promise(resolve=>{window.finishWorkspaceLookup=resolve});
|
return new Promise(resolve=>{window.finishWorkspaceLookup=resolve});
|
||||||
}
|
}
|
||||||
return {data:null,error:null};
|
return {data:null,error:null};
|
||||||
@ -63,10 +65,20 @@ test('failed workspace lookup retains recovery controls and retry returns to qui
|
|||||||
await expect(page.locator('#sunGateRetryWorkspaceV3')).toBeVisible();
|
await expect(page.locator('#sunGateRetryWorkspaceV3')).toBeVisible();
|
||||||
await expect(page.locator('#sunGateSignOutV3')).toBeVisible();
|
await expect(page.locator('#sunGateSignOutV3')).toBeVisible();
|
||||||
await expect(page.locator('#sunGateErrorV3')).toBeVisible();
|
await expect(page.locator('#sunGateErrorV3')).toBeVisible();
|
||||||
expect(await gate.locator('.sun-cloud-auth-brand').evaluate(el=>getComputedStyle(el,'::after').content)).toBe('none');
|
// Error markup intentionally has no .sun-cloud-auth-brand loading wrapper.
|
||||||
|
expect(await gate.evaluate(el=>[...el.querySelectorAll('*')].some(node=>getComputedStyle(node,'::after').animationName==='caterium-workspace-loading'))).toBe(false);
|
||||||
await page.locator('#sunGateRetryWorkspaceV3').click();
|
await page.locator('#sunGateRetryWorkspaceV3').click();
|
||||||
await page.waitForFunction(()=>window.workspaceLookupCount===2);
|
await page.waitForFunction(()=>window.workspaceLookupCount===2);
|
||||||
await expect(gate).toHaveAttribute('data-auth-state','loading');
|
await expect(gate).toHaveAttribute('data-auth-state','loading');
|
||||||
await expectQuietLoading(page);
|
await expectQuietLoading(page);
|
||||||
await resolveWorkspace(page);
|
// A successful recovery deliberately reloads the document. Restore the mock
|
||||||
|
// session/workspace on that new document rather than pausing it a second time.
|
||||||
|
const reloaded=page.waitForEvent('framenavigated',frame=>frame===page.mainFrame());
|
||||||
|
await page.evaluate(()=>{
|
||||||
|
sessionStorage.setItem('testWorkspaceRecovered','1');
|
||||||
|
finishWorkspaceLookup({data:[{id:'startup-company',name:'Test company',role:'admin',is_active:true}],error:null});
|
||||||
|
});
|
||||||
|
await reloaded;
|
||||||
|
await expect(page.locator(gateSelector)).toHaveCount(0);
|
||||||
|
await expect(page.locator('body > header')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user