2026-09-07 15:16:52 -04:00
import { test , expect , type Page } from '@playwright/test' ;
// Exercises the real Monaco/UI with a deterministic IPC boundary. Native file
// operations and reset safety are covered independently by the Rust tests.
async function mockDesktop ( page : Page ) {
page . on ( 'pageerror' , error = > { throw error ; } ) ;
await page . addInitScript ( ( ) = > {
const state = { version : 1 , build_directory : '/sample/build' , last_file : 'main.cpp' , search_exclusions : [ ] as string [ ] , clangd_path : 'clangd' } ;
const analysisStatus = { phase : 'ready' , message : 'Indexing idle.' , server : '22' , indexing : false , diagnostics : { } } ;
const content = '#include <concepts>\n\ntemplate<class T>\nconcept Number = requires(T value) {\n value + value;\n};\n\nint main() {\n return 0;\n}\n' ;
let context : Record < string , any > | null = null ;
let llm = { base_url : 'http://localhost:1234/v1' , model : 'test-model' , api_key_env : '' , context_bytes : 65536 , max_output_tokens : 4096 , token_parameter : 'max_completion_tokens' , stream : true , selected_audience : 'expert' , audiences : [ { id : 'expert' , name : 'Expert' , instructions : 'Explain precisely.' } , { id : 'custom' , name : 'Custom' , instructions : 'Custom instructions.' } ] } ;
Object . assign ( window , {
isTauri : true ,
calls : [ ] as unknown [ ] ,
__TAURI_INTERNALS__ : {
transformCallback : ( ) = > 1 ,
unregisterCallback : ( ) = > { } ,
invoke : async ( command : string , args : Record < string , unknown > ) = > {
( window as unknown as { calls : unknown [ ] } ) . calls . push ( { command , args } ) ;
switch ( command ) {
case 'load_theme' : return localStorage . getItem ( 'test-theme' ) || 'system' ;
case 'save_theme' : localStorage . setItem ( 'test-theme' , args . theme as string ) ; return null ;
case 'recent_projects' : return ( window as unknown as { recentProjects? : unknown [ ] } ) . recentProjects ? ? [ ] ;
case 'remember_recent' : return null ;
case 'remove_recent' : Object . assign ( window , { recentProjects : [ ] } ) ; return null ;
case 'inspect_workspace' : if ( ( window as unknown as { missingProject? : boolean } ) . missingProject ) throw new Error ( 'Source directory does not exist.' ) ; return { root : '/sample' , settings : state , warning : null , can_reset : true } ;
2026-09-07 15:52:46 -04:00
case 'close_workspace' : return null ;
2026-09-07 15:16:52 -04:00
case 'open_workspace' : return { root : '/sample' , settings : { . . . state , build_directory : args.build , last_file : args.reset ? null : state . last_file } } ;
case 'list_directory' : return args . path ? [ { name : 'concepts.hpp' , path : 'include/concepts.hpp' , directory : false } ] : [ { name : 'include' , path : 'include' , directory : true } , { name : 'main.cpp' , path : 'main.cpp' , directory : false } ] ;
case 'read_file' : return { path : args.path , content } ;
case 'remember_file' : state . last_file = args . path as string ; return null ;
case 'search' : return { hits : [ { path : 'main.cpp' , line : 4 , column : 9 , end_column : 15 , preview : 'concept Number = requires(T value) {' } ] , truncated : false , cancelled : false , skipped_files : 0 } ;
case 'cancel_search' : return null ;
case 'save_search_exclusions' : state . search_exclusions = args . exclusions as string [ ] ; return null ;
case 'start_analysis' :
case 'analysis_status' : return ( window as unknown as { failAnalysis? : boolean } ) . failAnalysis ? { . . . analysisStatus , phase : 'unavailable' , message : 'Cannot start clangd: executable not found.' } : analysisStatus ;
case 'save_graph_options' : return null ;
case 'graph_request' :
case 'analysis_request' :
if ( command === 'graph_request' || args . kind === 'conceptgraph' ) {
2026-09-07 15:52:46 -04:00
if ( ( window as unknown as { slowGraph? :boolean } ) . slowGraph ) await new Promise ( resolve = > setTimeout ( resolve , 900 ) ) ;
2026-09-07 15:16:52 -04:00
if ( ( window as unknown as { failGraph? : boolean } ) . failGraph ) throw new Error ( 'Select a concept or function name to view its graph.' ) ;
const location = ( line : number , label : string ) = > ( { path : 'main.cpp' , range : { start : { line , character : 8 } , end : { line , character : 14 } } , label } ) ;
const graph = { title : 'Number' , nodes : [
{ id : 'selected' , label : 'Number' , kind : 'selected' , detail : 'concept Number = requires(T value) { value + value; };' , location : location ( 3 , 'Number' ) } ,
{ id : 'requirement' , label : 'requires(T value) { value + value; }' , kind : 'requires' , detail : 'requires(T value) { value + value; }' , location : location ( 3 , 'requires' ) } ,
{ id : 'concept' , label : 'Base<T>' , kind : 'concept' , detail : 'Base<T>' , location : location ( 2 , 'Base' ) } ,
{ id : 'use' , label : 'main' , kind : 'usage' , detail : 'used by main.cpp:8' , location : location ( 7 , 'main' ) } ,
2026-09-07 15:52:46 -04:00
] , edges : [ { from : 'concept' , to : 'requirement' , label : '' } , { from : 'requirement' , to : 'selected' , label : '' } , { from : 'selected' , to : 'use' , label : 'used by' } ] , warnings : [ 'Active build only.' ] } ;
if ( ( window as unknown as { functionGraph? :boolean } ) . functionGraph ) { graph . title = 'target' ; graph . nodes [ 0 ] . label = 'target' ; graph . nodes [ 1 ] . kind = 'function' ; graph . nodes [ 1 ] . label = 'helper' ; graph . nodes [ 2 ] . kind = 'function' ; graph . nodes [ 2 ] . label = 'leaf' ; graph . nodes [ 3 ] . kind = 'function' ; graph . nodes [ 3 ] . label = 'caller' ; }
if ( ( window as unknown as { typeGraph? :boolean } ) . typeGraph ) {
graph . title = 'demo::Box' ; graph . nodes [ 0 ] . label = 'demo::Box' ;
graph . nodes [ 1 ] . kind = 'template-parameter' ; graph . nodes [ 1 ] . label = 'template T' ;
graph . nodes [ 2 ] . kind = 'type' ; graph . nodes [ 2 ] . label = 'demo::Base' ;
graph . nodes [ 3 ] . kind = 'function' ; graph . nodes [ 3 ] . label = 'demo::Box::run' ;
}
return command === 'graph_request' ? graph : { graph } ;
2026-09-07 15:16:52 -04:00
}
return {
locations : ( window as unknown as { multipleLocations? : boolean } ) . multipleLocations && [ 'callers' , 'references' ] . includes ( args . kind as string ) ? [ { path : 'first.cpp' , range : { start : { line : 3 , character : 8 } , end : { line : 3 , character : 14 } } , label : 'first' } , { path : 'second.cpp' , range : { start : { line : 7 , character : 4 } , end : { line : 7 , character : 8 } } , label : 'second' } ] : args . kind === 'folding' || args . kind === 'hover' ? [ ] : [ { path : args.kind === 'definition' ? 'lib.cpp' : 'main.cpp' , range : { start : { line : 3 , character : 8 } , end : { line : 3 , character : 14 } } , label : args.kind === 'callers' ? 'main' : '' } ] ,
hover : 'concept Number' , folds : [ { startLine : 3 , endLine : 4 } , { startLine : 7 , endLine : 8 } ] ,
evidence : { engine : 'clangd 22' , build_directory : '/sample/build' , coverage : 'Active build configuration only.' } ,
} ;
case 'get_llm_settings' : return { settings : structuredClone ( llm ) , has_session_key : false } ;
case 'save_llm_settings' : llm = structuredClone ( args . settings ) as typeof llm ; return null ;
case 'prepare_explanation' : context = { available_callees : [ { id : 'C1' , location : { path : 'a.cpp' , label : 'helper_a' , range : { start : { line : 1 , character : 0 } } } } , { id : 'C2' , location : { path : 'b.cpp' , label : 'helper_b' , range : { start : { line : 1 , character : 0 } } } } ] , selected_callees : [ ] , recursion_depth : 0 , prompt_budget : 65536 , id : 1 , endpoint : ` ${ llm . base_url } /chat/completions ` , model : llm.model , audience : llm.selected_audience , prompt_bytes : 1024 , messages : [ { role : 'system' , content : 'Use source citations.' } , { role : 'user' , content : ` Reviewed evidence: ${ args . question || 'Explain Number' } ` } ] , bundle : { selected_path : 'main.cpp' , sources : [ { id : 'S1' , role : 'selected' , path : 'main.cpp' , start_line : 4 , end_line : 6 , enclosing : 'Number' , code : content.split ( '\n' ) . slice ( 3 , 6 ) . join ( '\n' ) , truncated : false } ] , usages : [ ] , references_found : 2 , callers_found : 0 , callees_found : 0 , omissions : [ 'Active build only.' ] , diagnostics : [ ] } } ;
if ( ( window as unknown as { conceptMode? : boolean } ) . conceptMode ) {
context . concept_dependencies = true ; context . include_standard = false ;
context . available_callees = [ { id : 'C1' , is_concept : true , is_standard : false , location : { path : 'concept.hpp' , label : 'app::Composed' , range : { start : { line : 1 , character : 0 } } } } , { id : 'C2' , is_concept : true , is_standard : true , location : { path : '/usr/include/concepts' , label : 'std::copyable' , range : { start : { line : 10 , character : 0 } } } } ] ;
}
return structuredClone ( context ) ;
case 'update_explanation_question' : context ! . messages [ 1 ] . content = ` Reviewed evidence: ${ args . question } ` ; return structuredClone ( context ) ;
case 'expand_explanation' : context ! . include_standard = args . includeStandard ? ? false ; context ! . selected_callees = args . selected ; context ! . recursion_depth = args . depth ; context ! . prompt_bytes = 50000 ; return structuredClone ( context ) ;
case 'send_explanation' : {
const channel = args . onDelta as { onmessage : ( value : string ) = > void } ;
channel . onmessage ( 'Partial explanation ' ) ;
await new Promise ( resolve = > setTimeout ( resolve , ( window as unknown as { slowModel? : boolean } ) . slowModel ? 1000 : 50 ) ) ;
const text = '## Number\nAdds values [S1]. Unknown [S99]. <img src="https://invalid.example/image" onerror="alert(1)"> [external](https://invalid.example)' ;
channel . onmessage ( text ) ; return { text , finish_reason : 'stop' } ;
}
case 'cancel_explanation' : return null ;
case 'plugin:dialog|message' : return 'Ok' ;
default : throw new Error ( ` Unexpected command: ${ command } ` ) ;
}
} ,
} ,
} ) ;
} ) ;
}
async function openProject ( page : Page ) {
await page . goto ( '/' ) ;
await page . getByRole ( 'button' , { name : 'Open a project' , exact : true } ) . click ( ) ;
await page . getByLabel ( 'Source directory' , { exact : true } ) . fill ( '/sample' ) ;
await page . getByLabel ( 'Source directory' , { exact : true } ) . press ( 'Tab' ) ;
await expect ( page . locator ( '#saved-state' ) ) . toContainText ( 'Saved workspace found' ) ;
await expect ( page . locator ( '#build' ) ) . toHaveValue ( '/sample/build' ) ;
await page . locator ( '#setup-submit' ) . click ( ) ;
await expect ( page . locator ( '#setup' ) ) . not . toBeVisible ( ) ;
}
test ( 'restores a workspace, browses lazily, and keeps Monaco read-only' , async ( { page } ) = > {
const errors : string [ ] = [ ] ;
page . on ( 'pageerror' , error = > errors . push ( error . message ) ) ;
await mockDesktop ( page ) ; await openProject ( page ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'main.cpp' ) ;
await expect ( page . locator ( '.view-lines' ) ) . toContainText ( 'concept Number' ) ;
await expect ( page . locator ( '.monaco-editor .inputarea' ) ) . toHaveAttribute ( 'readonly' , 'true' ) ;
await page . getByRole ( 'button' , { name : '▸ include' , exact : true } ) . click ( ) ;
await page . getByRole ( 'button' , { name : '· concepts.hpp' , exact : true } ) . click ( ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'include/concepts.hpp' ) ;
const fold = page . locator ( '.codicon-folding-expanded' ) . first ( ) ;
await expect ( fold ) . toBeVisible ( ) ;
await fold . click ( ) ;
await expect ( page . locator ( '.view-lines' ) ) . not . toContainText ( 'value + value;' ) ;
await page . locator ( '.codicon-folding-collapsed' ) . first ( ) . click ( ) ;
await expect ( page . locator ( '.view-lines' ) ) . toContainText ( 'value + value;' ) ;
await page . getByRole ( 'button' , { name : 'Find in file' } ) . click ( ) ;
await expect ( page . locator ( '.find-widget' ) ) . toHaveClass ( /visible/ ) ;
await page . screenshot ( { path : 'test-results/browser.png' } ) ;
expect ( errors ) . toEqual ( [ ] ) ;
} ) ;
test ( 'project search jumps to a matching source location' , async ( { page } ) = > {
await mockDesktop ( page ) ; await openProject ( page ) ;
await page . getByRole ( 'button' , { name : 'Search' , exact : true } ) . click ( ) ;
await page . getByLabel ( 'Search project text' ) . fill ( 'Number' ) ;
await expect ( page . locator ( '#search-summary' ) ) . toHaveText ( '1 matches' ) ;
await page . locator ( '.result' ) . click ( ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'main.cpp' ) ;
await expect ( page . locator ( '#cursor' ) ) . toContainText ( 'Ln 4' ) ;
} ) ;
test ( 'force reset passes an explicit reset and clears the old file' , async ( { page } ) = > {
await mockDesktop ( page ) ; await openProject ( page ) ;
await page . getByRole ( 'button' , { name : 'Workspace…' } ) . click ( ) ;
await expect ( page . locator ( '#saved-state' ) ) . toContainText ( 'Saved workspace found' ) ;
await page . locator ( '#reset' ) . check ( ) ;
await page . locator ( '#setup-submit' ) . click ( ) ;
await expect ( page . locator ( '#welcome' ) ) . toBeVisible ( ) ;
const calls = await page . evaluate ( ( ) = > ( window as unknown as { calls : { command : string ; args : { reset? : boolean } } [ ] } ) . calls ) ;
expect ( calls . filter ( call = > call . command === 'open_workspace' ) . at ( - 1 ) ? . args . reset ) . toBe ( true ) ;
} ) ;
test ( 'search exclusions are saved and restored per workspace' , async ( { page } ) = > {
await mockDesktop ( page ) ; await openProject ( page ) ;
await page . getByRole ( 'button' , { name : 'Search' , exact : true } ) . click ( ) ;
await page . locator ( '#exclusions' ) . fill ( 'docs/\n**/generated/**' ) ;
await page . getByRole ( 'button' , { name : 'Apply exclusions' } ) . click ( ) ;
await expect ( page . locator ( '#exclusion-status' ) ) . toContainText ( 'saved' ) ;
await page . getByRole ( 'button' , { name : 'Workspace…' } ) . click ( ) ;
await expect ( page . locator ( '#saved-state' ) ) . toContainText ( 'Saved workspace found' ) ;
await page . locator ( '#setup-submit' ) . click ( ) ;
await page . getByRole ( 'button' , { name : 'Search' , exact : true } ) . click ( ) ;
await expect ( page . locator ( '#exclusions' ) ) . toHaveValue ( 'docs/\n**/generated/**' ) ;
} ) ;
test ( 'definition, back, references, callers, and type information use semantic results' , async ( { page } ) = > {
await mockDesktop ( page ) ; await openProject ( page ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'main.cpp' ) ;
await page . locator ( '#nav-definition' ) . click ( ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'lib.cpp' ) ;
await page . locator ( '#nav-back' ) . click ( ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'main.cpp' ) ;
await page . locator ( '#nav-references' ) . click ( ) ;
await expect ( page . locator ( '#navigation-summary' ) ) . toHaveText ( '1 location' ) ;
await expect ( page . locator ( '#navigation-evidence' ) ) . toContainText ( 'clangd 22' ) ;
await page . locator ( '#nav-callers' ) . click ( ) ;
await expect ( page . locator ( '#navigation-results strong' ) ) . toHaveText ( 'main' ) ;
await page . locator ( '#navigation-results .result' ) . click ( ) ;
await expect ( page . locator ( '#cursor' ) ) . toContainText ( 'Ln 4' ) ;
await page . locator ( '#nav-hover' ) . click ( ) ;
await expect ( page . locator ( '.type-info' ) ) . toHaveText ( 'concept Number' ) ;
await page . screenshot ( { path : 'test-results/navigation.png' } ) ;
} ) ;
test ( 'missing clangd leaves browsing usable and explains recovery' , async ( { page } ) = > {
await mockDesktop ( page ) ;
await page . addInitScript ( ( ) = > Object . assign ( window , { failAnalysis : true } ) ) ;
await openProject ( page ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'main.cpp' ) ;
await page . locator ( '#nav-definition' ) . click ( ) ;
await expect ( page . locator ( '#navigation-summary' ) ) . toContainText ( 'Cannot start clangd' ) ;
await expect ( page . locator ( '#restart-analysis' ) ) . toBeEnabled ( ) ;
} ) ;
test ( 'explanations require reviewed context and render only verified source links' , async ( { page } ) = > {
await mockDesktop ( page ) ; await openProject ( page ) ;
await page . locator ( '#nav-explain' ) . click ( ) ;
await expect ( page . locator ( '#explain-status' ) ) . toContainText ( 'Context ready' ) ;
await expect ( page . locator ( '#context-summary' ) ) . toContainText ( 'Active build only' ) ;
expect ( await page . evaluate ( ( ) = > ( window as unknown as { calls : { command : string } [ ] } ) . calls . some ( c = > c . command === 'send_explanation' ) ) ) . toBe ( false ) ;
await page . locator ( '#send-explanation' ) . click ( ) ;
await expect ( page . locator ( '#explain-status' ) ) . toContainText ( 'Explanation complete' ) ;
await expect ( page . locator ( '.source-citation' ) ) . toHaveCount ( 1 ) ;
await expect ( page . locator ( '#explanation-answer' ) ) . toContainText ( '[S99] (unknown source)' ) ;
await expect ( page . locator ( '#explanation-answer img, #explanation-answer a' ) ) . toHaveCount ( 0 ) ;
await page . locator ( '.source-citation' ) . click ( ) ;
await expect ( page . locator ( '#cursor' ) ) . toContainText ( 'Ln 4' ) ;
await expect ( page . locator ( '#citation-status' ) ) . toContainText ( 'Opened main.cpp:4' ) ;
await page . screenshot ( { path : 'test-results/explanation.png' } ) ;
await page . locator ( '#explain-question' ) . fill ( 'How is this constrained?' ) ;
await expect ( page . locator ( '#send-explanation' ) ) . toBeEnabled ( ) ;
await expect ( page . locator ( '#context-messages' ) ) . toContainText ( 'How is this constrained?' ) ;
expect ( await page . evaluate ( ( ) = > ( window as unknown as { calls : { command : string } [ ] } ) . calls . filter ( c = > c . command === 'prepare_explanation' ) . length ) ) . toBe ( 1 ) ;
} ) ;
test ( 'model settings save custom audiences and keep credentials separate' , async ( { page } ) = > {
await mockDesktop ( page ) ; await openProject ( page ) ; await page . locator ( '#nav-explain' ) . click ( ) ;
await expect ( page . locator ( '#explain-status' ) ) . toContainText ( 'Context ready' ) ;
await page . locator ( '#model-settings' ) . click ( ) ;
await page . locator ( '#llm-audience' ) . selectOption ( 'custom' ) ;
await page . locator ( '#llm-instructions' ) . fill ( 'Explain to a scientist.' ) ;
await page . locator ( '#llm-key' ) . fill ( 'session-secret' ) ;
await page . locator ( '#llm-save' ) . click ( ) ;
await expect ( page . locator ( '#llm-settings' ) ) . not . toBeVisible ( ) ;
await expect ( page . locator ( '#send-explanation' ) ) . toBeDisabled ( ) ;
const saved = await page . evaluate ( ( ) = > ( window as unknown as { calls : { command : string ; args : { settings : unknown ; apiKey : string } } [ ] } ) . calls . find ( c = > c . command === 'save_llm_settings' ) ! . args ) ;
expect ( saved . apiKey ) . toBe ( 'session-secret' ) ;
expect ( JSON . stringify ( saved . settings ) ) . not . toContain ( 'session-secret' ) ;
await page . locator ( '#model-settings' ) . click ( ) ;
await expect ( page . locator ( '#llm-audience' ) ) . toHaveValue ( 'custom' ) ;
await expect ( page . locator ( '#llm-instructions' ) ) . toHaveValue ( 'Explain to a scientist.' ) ;
await expect ( page . locator ( '#llm-key' ) ) . toHaveValue ( '' ) ;
} ) ;
test ( 'cancelled model responses cannot overwrite the partial explanation' , async ( { page } ) = > {
await mockDesktop ( page ) ; await page . addInitScript ( ( ) = > Object . assign ( window , { slowModel : true } ) ) ;
await openProject ( page ) ; await page . locator ( '#nav-explain' ) . click ( ) ;
await expect ( page . locator ( '#explain-status' ) ) . toContainText ( 'Context ready' ) ;
await page . locator ( '#send-explanation' ) . click ( ) ;
await expect ( page . locator ( '#explanation-answer' ) ) . toContainText ( 'Partial explanation' ) ;
await page . locator ( '#cancel-explanation' ) . click ( ) ;
await page . waitForTimeout ( 1200 ) ;
await expect ( page . locator ( '#explain-status' ) ) . toContainText ( 'Cancelled' ) ;
await expect ( page . locator ( '#explanation-answer' ) ) . toHaveText ( 'Partial explanation' ) ;
} ) ;
test ( 'called bodies support individual selection, select all, and bounded recursion with size warnings' , async ( { page } ) = > {
await mockDesktop ( page ) ; await openProject ( page ) ; await page . locator ( '#nav-explain' ) . click ( ) ;
await expect ( page . locator ( '#callee-list input' ) ) . toHaveCount ( 2 ) ;
await page . locator ( '#callee-list input' ) . first ( ) . check ( ) ;
await expect ( page . locator ( '#send-explanation' ) ) . toBeDisabled ( ) ;
await page . locator ( '#apply-callees' ) . click ( ) ;
await expect ( page . locator ( '#send-explanation' ) ) . toBeEnabled ( ) ;
await expect ( page . locator ( '#context-size-warning' ) ) . toContainText ( 'Large context' ) ;
await page . locator ( '#callees-all' ) . click ( ) ;
await page . locator ( '#callee-depth' ) . selectOption ( '2' ) ;
await page . locator ( '#apply-callees' ) . click ( ) ;
await expect ( page . locator ( '#explain-status' ) ) . toContainText ( 'Body selection applied' ) ;
const calls = await page . evaluate ( ( ) = > ( window as unknown as { calls : { command : string ; args : { selected : string [ ] ; depth : number } } [ ] } ) . calls . filter ( c = > c . command === 'expand_explanation' ) ) ;
expect ( calls [ 0 ] . args . selected ) . toEqual ( [ 'C1' ] ) ;
expect ( calls [ 0 ] . args . depth ) . toBe ( 1 ) ;
expect ( calls [ 1 ] . args . selected ) . toEqual ( [ 'C1' , 'C2' ] ) ;
expect ( calls [ 1 ] . args . depth ) . toBe ( 2 ) ;
expect ( await page . locator ( '#callee-depth option' ) . evaluateAll ( options = > options . map ( o = > ( o as HTMLOptionElement ) . value ) ) ) . toEqual ( [ '0' , '1' , '2' ] ) ;
await page . locator ( '#callees-none' ) . click ( ) ; await page . locator ( '#apply-callees' ) . click ( ) ;
await expect ( page . locator ( '#callee-list input:checked' ) ) . toHaveCount ( 0 ) ;
} ) ;
test ( 'right-click on a symbol opens inline semantic navigation and explanation actions' , async ( { page } ) = > {
await mockDesktop ( page ) ; await openProject ( page ) ;
await page . locator ( '.view-line' ) . filter ( { hasText : 'concept Number' } ) . click ( { button : 'right' , position : { x : 95 , y : 10 } } ) ;
for ( const name of [ 'Go to Definition' , 'Find References' , 'Find Callers' , 'Show Type Information' , 'Explain Selected Code…' ] ) {
await expect ( page . getByRole ( 'menuitem' , { name , exact : false } ) ) . toBeVisible ( ) ;
}
await page . getByRole ( 'menuitem' , { name : 'Find Callers' , exact : false } ) . click ( { delay : 150 } ) ; // Monaco enables menu mouse-up after 100 ms.
await expect ( page . locator ( '#navigation-title' ) ) . toContainText ( 'Callers' ) ;
} ) ;
test ( 'Vim motions, counts, definition and back preserve read-only source' , async ( { page } ) = > {
await mockDesktop ( page ) ; await openProject ( page ) ;
await page . keyboard . type ( 'gg3j' ) ;
await expect ( page . locator ( '#cursor' ) ) . toContainText ( 'Ln 4' ) ;
await page . keyboard . type ( '0w' ) ;
await expect ( page . locator ( '#cursor' ) ) . toContainText ( 'Col 9' ) ;
await page . keyboard . type ( 'gd' ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'lib.cpp' ) ;
await page . keyboard . press ( 'Control+o' ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'main.cpp' ) ;
await page . keyboard . type ( 'iaodcx' ) ;
await expect ( page . locator ( '.monaco-editor .inputarea' ) ) . toHaveAttribute ( 'readonly' , 'true' ) ;
await page . keyboard . type ( 'G' ) ;
await expect ( page . locator ( '#cursor' ) ) . toContainText ( 'Ln 11' ) ;
await page . keyboard . type ( '4gg' ) ;
await expect ( page . locator ( '#cursor' ) ) . toContainText ( 'Ln 4' ) ;
await page . keyboard . type ( 'zc' ) ;
await expect ( page . locator ( '.view-lines' ) ) . not . toContainText ( 'value + value;' ) ;
await page . keyboard . type ( 'zo' ) ;
await expect ( page . locator ( '.view-lines' ) ) . toContainText ( 'value + value;' ) ;
} ) ;
test ( 'Vim gc and gr cycle cross-file results, while slash switches n to file search' , async ( { page } ) = > {
await mockDesktop ( page ) ; await page . addInitScript ( ( ) = > Object . assign ( window , { multipleLocations : true } ) ) ;
await openProject ( page ) ; await page . keyboard . type ( 'gc' ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'first.cpp' ) ;
await page . keyboard . type ( 'n' ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'second.cpp' ) ;
await page . keyboard . type ( 'N' ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'first.cpp' ) ;
await page . keyboard . type ( 'gr' ) ;
await expect ( page . locator ( '#navigation-title' ) ) . toContainText ( 'References' ) ;
await page . keyboard . type ( 'n' ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'second.cpp' ) ;
await page . keyboard . type ( '/' ) ;
const find = page . getByRole ( 'textbox' , { name : 'Find' , exact : true } ) ;
await expect ( find ) . toBeFocused ( ) ;
await find . fill ( 'value' ) ;
await page . keyboard . press ( 'Enter' ) ;
await expect ( page . locator ( '.find-widget' ) ) . not . toHaveClass ( /visible/ ) ;
await expect ( page . locator ( '#vim-status' ) ) . toContainText ( 'n: search' ) ;
await page . keyboard . type ( 'n' ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'second.cpp' ) ;
await expect ( page . locator ( '#cursor' ) ) . toContainText ( 'Ln 5' ) ;
await page . getByRole ( 'button' , { name : 'Search' , exact : true } ) . click ( ) ;
await page . getByLabel ( 'Search project text' ) . fill ( 'hjkl gd gc' ) ;
await expect ( page . getByLabel ( 'Search project text' ) ) . toHaveValue ( 'hjkl gd gc' ) ;
} ) ;
test ( 'landing page restores a recent project and records successful opens' , async ( { page } ) = > {
await mockDesktop ( page ) ;
await page . addInitScript ( ( ) = > Object . assign ( window , { recentProjects : [ { root : '/sample' , last_opened : 1788800000 } ] } ) ) ;
await page . goto ( '/' ) ;
await expect ( page . getByRole ( 'heading' , { name : 'Open Recent' } ) ) . toBeVisible ( ) ;
await page . screenshot ( { path : 'test-results/recents.png' } ) ;
await page . locator ( '.recent-project' ) . click ( ) ;
await expect ( page . locator ( '#landing' ) ) . not . toBeVisible ( ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'main.cpp' ) ;
await expect . poll ( ( ) = > page . evaluate ( ( ) = > ( window as unknown as { calls : { command : string } [ ] } ) . calls . some ( c = > c . command === 'remember_recent' ) ) ) . toBe ( true ) ;
} ) ;
test ( 'recent entries can be removed without opening or resetting a workspace' , async ( { page } ) = > {
await mockDesktop ( page ) ;
await page . addInitScript ( ( ) = > Object . assign ( window , { recentProjects : [ { root : '/sample' , last_opened : 1788800000 } ] } ) ) ;
await page . goto ( '/' ) ;
await page . getByRole ( 'button' , { name : 'Remove sample from recent projects' } ) . click ( ) ;
await expect ( page . locator ( '#recent-status' ) ) . toContainText ( 'No recent projects yet' ) ;
expect ( await page . evaluate ( ( ) = > ( window as unknown as { calls : { command : string } [ ] } ) . calls . some ( c = > c . command === 'open_workspace' ) ) ) . toBe ( false ) ;
} ) ;
test ( 'missing recent projects leave setup available and do not record a successful open' , async ( { page } ) = > {
await mockDesktop ( page ) ;
await page . addInitScript ( ( ) = > Object . assign ( window , { missingProject : true , recentProjects : [ { root : '/missing' , last_opened : 1788800000 } ] } ) ) ;
await page . goto ( '/' ) ; await page . locator ( '.recent-project' ) . click ( ) ;
await expect ( page . locator ( '#setup' ) ) . toBeVisible ( ) ;
await expect ( page . locator ( '#source' ) ) . toHaveValue ( '/missing' ) ;
await expect ( page . locator ( '#saved-state' ) ) . toContainText ( 'Source directory does not exist' ) ;
expect ( await page . evaluate ( ( ) = > ( window as unknown as { calls : { command : string } [ ] } ) . calls . some ( c = > c . command === 'remember_recent' ) ) ) . toBe ( false ) ;
} ) ;
test ( 'concept expansion excludes standard concepts by default, including Select all' , async ( { page } ) = > {
await mockDesktop ( page ) ; await page . addInitScript ( ( ) = > Object . assign ( window , { conceptMode : true } ) ) ;
await openProject ( page ) ; await page . locator ( '#nav-explain' ) . click ( ) ;
await expect ( page . locator ( '#dependency-heading' ) ) . toHaveText ( 'Concept dependencies' ) ;
await expect ( page . locator ( '#include-standard' ) ) . not . toBeChecked ( ) ;
await expect ( page . locator ( '#callee-list input' ) . nth ( 1 ) ) . toBeDisabled ( ) ;
await page . locator ( '#callees-all' ) . click ( ) ; await page . locator ( '#callee-depth' ) . selectOption ( '2' ) ;
await page . locator ( '#apply-callees' ) . click ( ) ;
await expect ( page . locator ( '#send-explanation' ) ) . toBeEnabled ( ) ;
await page . locator ( '#include-standard' ) . check ( ) ;
await expect ( page . locator ( '#send-explanation' ) ) . toBeDisabled ( ) ;
await page . locator ( '#callees-all' ) . click ( ) ; await page . locator ( '#apply-callees' ) . click ( ) ;
await expect ( page . locator ( '#send-explanation' ) ) . toBeEnabled ( ) ;
const calls = await page . evaluate ( ( ) = > ( window as unknown as { calls : { command : string ; args : { selected : string [ ] ; includeStandard : boolean ; depth : number } } [ ] } ) . calls . filter ( c = > c . command === 'expand_explanation' ) ) ;
expect ( calls [ 0 ] . args . selected ) . toEqual ( [ 'C1' ] ) ; expect ( calls [ 0 ] . args . includeStandard ) . toBe ( false ) ; expect ( calls [ 0 ] . args . depth ) . toBe ( 2 ) ;
expect ( calls [ 1 ] . args . selected ) . toEqual ( [ 'C1' , 'C2' ] ) ; expect ( calls [ 1 ] . args . includeStandard ) . toBe ( true ) ;
await page . locator ( '#include-standard' ) . uncheck ( ) ;
await expect ( page . locator ( '#callee-list input' ) . nth ( 1 ) ) . not . toBeChecked ( ) ;
await expect ( page . locator ( '#send-explanation' ) ) . toBeDisabled ( ) ;
} ) ;
test ( 'concept graph shares a collapsible tabbed sidebar with chat and opens source nodes' , async ( { page } ) = > {
await mockDesktop ( page ) ; await openProject ( page ) ;
await page . locator ( '#nav-explain' ) . click ( ) ;
await page . locator ( '#explain-question' ) . fill ( 'Keep this chat question' ) ;
await expect ( page . locator ( '#send-explanation' ) ) . toBeEnabled ( ) ;
await page . getByRole ( 'tab' , { name : 'Graph' , exact : true } ) . click ( ) ;
await expect ( page . locator ( '#graph-title' ) ) . toHaveText ( 'Number' ) ;
await expect ( page . locator ( '#graph-canvas [role=button]' ) ) . toHaveCount ( 4 ) ;
const positions = await page . locator ( '#graph-canvas .graph-node' ) . evaluateAll ( nodes = > nodes . map ( n = > ( { kind :n.getAttribute ( 'class' ) , y :n.getBoundingClientRect ( ) . y } ) ) ) ;
expect ( positions . find ( n = > n . kind ? . includes ( 'requires' ) ) ! . y ) . toBeLessThan ( positions . find ( n = > n . kind ? . includes ( 'selected' ) ) ! . y ) ;
expect ( positions . find ( n = > n . kind ? . includes ( 'usage' ) ) ! . y ) . toBeGreaterThan ( positions . find ( n = > n . kind ? . includes ( 'selected' ) ) ! . y ) ;
await page . locator ( '#graph-canvas .usage' ) . click ( ) ;
await page . getByRole ( 'button' , { name : 'Open source' , exact : true } ) . click ( ) ;
await expect ( page . locator ( '#cursor' ) ) . toContainText ( 'Ln 8' ) ;
await page . getByRole ( 'button' , { name : 'Collapse right sidebar' } ) . click ( ) ;
await expect ( page . locator ( '#explanation-pane' ) ) . not . toBeVisible ( ) ;
await page . locator ( '#toggle-sidebar' ) . click ( ) ;
await expect ( page . getByRole ( 'tab' , { name : 'Graph' , exact : true } ) ) . toHaveAttribute ( 'aria-selected' , 'true' ) ;
await page . screenshot ( { path : 'test-results/graph.png' } ) ;
await page . getByRole ( 'tab' , { name : 'Chat' , exact : true } ) . click ( ) ;
await expect ( page . locator ( '#explain-question' ) ) . toHaveValue ( 'Keep this chat question' ) ;
await page . getByRole ( 'tab' , { name : 'Graph' , exact : true } ) . click ( ) ;
await page . locator ( '#graph-canvas .concept' ) . click ( ) ;
await page . getByRole ( 'button' , { name : 'Focus this concept' } ) . click ( ) ;
await expect . poll ( ( ) = > page . evaluate ( ( ) = > ( window as unknown as { calls : { command :string ; args : { kind? :string ; position ? : { line :number } } } [ ] } ) . calls . filter ( c = > c . command === 'graph_request' ) . at ( - 1 ) ? . args . position ? . line ) ) . toBe ( 2 ) ;
} ) ;
test ( 'graph reports unsupported selections and leaves chat available' , async ( { page } ) = > {
await mockDesktop ( page ) ; await page . addInitScript ( ( ) = > Object . assign ( window , { failGraph :true } ) ) ;
await openProject ( page ) ; await page . locator ( '#nav-graph' ) . click ( ) ;
await expect ( page . locator ( '#graph-status' ) ) . toContainText ( 'Select a concept or function' ) ;
await expect ( page . locator ( '#graph-refresh' ) ) . toBeEnabled ( ) ;
await page . getByRole ( 'tab' , { name : 'Chat' , exact :true } ) . click ( ) ;
await expect ( page . locator ( '#explain-question' ) ) . toBeVisible ( ) ;
} ) ;
test ( 'themes follow the system, persist overrides, and update the editor and graph' , async ( { page } ) = > {
await page . emulateMedia ( { colorScheme : 'dark' } ) ;
await mockDesktop ( page ) ; await openProject ( page ) ;
await expect ( page . locator ( 'html' ) ) . toHaveAttribute ( 'data-theme' , 'dark' ) ;
await page . getByLabel ( 'Color theme' ) . selectOption ( 'light' ) ;
await expect ( page . locator ( 'html' ) ) . toHaveAttribute ( 'data-theme' , 'light' ) ;
await expect ( page . locator ( '.monaco-editor' ) . first ( ) ) . toHaveCSS ( 'background-color' , 'rgb(255, 255, 255)' ) ;
await page . locator ( '#nav-graph' ) . click ( ) ;
await expect ( page . locator ( '.graph-node.selected rect' ) ) . toHaveCSS ( 'fill' , 'rgb(220, 238, 229)' ) ;
await page . screenshot ( { path : 'test-results/theme-light.png' , animations : 'disabled' } ) ;
await page . getByLabel ( 'Color theme' ) . selectOption ( 'dark' ) ;
await expect ( page . locator ( '.monaco-editor' ) . first ( ) ) . toHaveCSS ( 'background-color' , 'rgb(23, 28, 35)' ) ;
await page . screenshot ( { path : 'test-results/theme-dark.png' , animations : 'disabled' } ) ;
await page . getByLabel ( 'Color theme' ) . selectOption ( 'light' ) ;
await page . reload ( ) ;
await expect ( page . getByLabel ( 'Color theme' ) ) . toHaveValue ( 'light' ) ;
await page . getByLabel ( 'Color theme' ) . selectOption ( 'system' ) ;
await expect ( page . locator ( 'html' ) ) . toHaveAttribute ( 'data-theme' , 'dark' ) ;
await page . emulateMedia ( { colorScheme : 'light' } ) ;
await expect ( page . locator ( 'html' ) ) . toHaveAttribute ( 'data-theme' , 'light' ) ;
await page . getByLabel ( 'Color theme' ) . selectOption ( 'dark' ) ;
await expect ( page . locator ( 'html' ) ) . toHaveAttribute ( 'data-theme' , 'dark' ) ;
await expect ( page . locator ( '#recent-open' ) ) . toHaveCSS ( 'background-color' , 'rgb(131, 216, 189)' ) ;
await page . screenshot ( { path : 'test-results/theme-dark-landing.png' , animations : 'disabled' } ) ;
} ) ;
test ( 'graph options are separate from context and navigation supports history and zoom' , async ( { page } ) = > {
await mockDesktop ( page ) ; await openProject ( page ) ; await page . locator ( '#nav-graph' ) . click ( ) ;
await page . locator ( '#graph-options summary' ) . click ( ) ;
await expect ( page . getByRole ( 'button' , { name : 'Remove namespace std' , exact :true } ) ) . toBeVisible ( ) ;
await page . getByLabel ( 'Graph depth' , { exact :true } ) . selectOption ( '3' ) ;
await page . getByLabel ( 'Excluded namespaces' , { exact :true } ) . fill ( 'vendor::detail' ) ;
await page . locator ( '#graph-add' ) . click ( ) ;
await page . getByRole ( 'button' , { name : 'Remove namespace std' , exact :true } ) . click ( ) ;
await page . locator ( '#graph-apply' ) . click ( ) ;
await expect ( page . locator ( '#graph-options-status' ) ) . toHaveText ( 'Saved for this project.' ) ;
const options = await page . evaluate ( ( ) = > ( window as unknown as { calls : { command :string ; args :Record < string , unknown > } [ ] } ) . calls . filter ( c = > c . command === 'graph_request' ) . at ( - 1 ) ? . args . options ) ;
expect ( options ) . toEqual ( { depth :3 , excluded_namespaces : [ 'vendor::detail' ] } ) ;
await page . locator ( '#graph-options summary' ) . click ( ) ;
await page . locator ( '.graph-node.concept' ) . dblclick ( ) ;
await expect ( page . locator ( '#graph-back' ) ) . toBeEnabled ( ) ;
await page . locator ( '#graph-back' ) . click ( ) ;
await expect ( page . locator ( '#graph-forward' ) ) . toBeEnabled ( ) ;
await page . locator ( '#graph-center' ) . click ( ) ;
await expect ( page . locator ( '#graph-zoom' ) ) . toHaveText ( '100%' ) ;
await page . locator ( '#graph-in' ) . click ( ) ;
await expect ( page . locator ( '#graph-zoom' ) ) . toHaveText ( '125%' ) ;
await page . locator ( '#graph-expand' ) . click ( ) ;
await expect ( page . locator ( '#explanation-pane' ) ) . toHaveClass ( /graph-expanded/ ) ;
await page . locator ( '#graph-fit' ) . click ( ) ;
await page . screenshot ( { path : 'test-results/graph-navigation.png' } ) ;
} ) ;
2026-09-07 15:52:46 -04:00
test ( 'function graphs navigate callees above and callers below using nodes and keyboard' , async ( { page } ) = > {
await mockDesktop ( page ) ; await page . addInitScript ( ( ) = > Object . assign ( window , { functionGraph :true } ) ) ;
await openProject ( page ) ; await page . locator ( '#nav-graph' ) . click ( ) ;
await expect ( page . locator ( '#graph-title' ) ) . toHaveText ( 'target' ) ;
const root = page . getByRole ( 'button' , { name : 'selected: target' , exact :true } ) ;
const caller = page . getByRole ( 'button' , { name : 'function: caller' , exact :true } ) ;
const helper = page . getByRole ( 'button' , { name : 'function: helper' , exact :true } ) ;
expect ( ( await helper . boundingBox ( ) ) ! . y ) . toBeLessThan ( ( await root . boundingBox ( ) ) ! . y ) ;
expect ( ( await caller . boundingBox ( ) ) ! . y ) . toBeGreaterThan ( ( await root . boundingBox ( ) ) ! . y ) ;
await root . focus ( ) ; await root . press ( 'ArrowUp' ) ;
await expect ( helper ) . toBeFocused ( ) ;
await page . getByRole ( 'button' , { name : 'Focus this symbol' , exact :true } ) . click ( ) ;
await expect ( page . locator ( '#graph-back' ) ) . toBeEnabled ( ) ;
} ) ;
test ( 'graph building indicator covers success and failure and clears on return to welcome' , async ( { page } ) = > {
await mockDesktop ( page ) ; await page . addInitScript ( ( ) = > Object . assign ( window , { slowGraph :true } ) ) ;
await openProject ( page ) ; await page . locator ( '#nav-graph' ) . click ( ) ;
await expect ( page . locator ( '#graph-progress' ) ) . toBeVisible ( ) ;
await expect ( page . locator ( '#graph-canvas' ) ) . toHaveAttribute ( 'aria-busy' , 'true' ) ;
await expect ( page . locator ( '#graph-progress' ) ) . toBeHidden ( ) ;
await page . evaluate ( ( ) = > Object . assign ( window , { failGraph :true } ) ) ;
await page . locator ( '#graph-refresh' ) . click ( ) ;
await expect ( page . locator ( '#graph-progress' ) ) . toBeVisible ( ) ;
await expect ( page . locator ( '#graph-status' ) ) . toContainText ( 'Select a concept or function' ) ;
await expect ( page . locator ( '#graph-progress' ) ) . toBeHidden ( ) ;
await page . evaluate ( ( ) = > Object . assign ( window , { failGraph :false } ) ) ;
await page . locator ( '#graph-refresh' ) . click ( ) ;
await expect ( page . locator ( '#graph-progress' ) ) . toBeVisible ( ) ;
await page . locator ( '#file-menu > summary' ) . click ( ) ;
await page . getByRole ( 'button' , { name : 'Return to Welcome' , exact :true } ) . click ( ) ;
await expect ( page . locator ( '#landing' ) ) . toBeVisible ( ) ;
await expect ( page . locator ( '#graph-progress' ) ) . toBeHidden ( ) ;
await expect ( page . locator ( '#explanation-pane' ) ) . toBeHidden ( ) ;
} ) ;
test ( 'File menu opens fresh projects, recent projects, and returns to Welcome' , async ( { page } ) = > {
await mockDesktop ( page ) ;
await page . addInitScript ( ( ) = > Object . assign ( window , { recentProjects : [ { root : '/sample' , last_opened :1700000000 } ] } ) ) ;
await openProject ( page ) ;
await page . locator ( '#file-menu > summary' ) . click ( ) ;
await page . getByRole ( 'button' , { name : 'Open Project…' , exact :true } ) . click ( ) ;
await expect ( page . locator ( '#source' ) ) . toHaveValue ( '' ) ;
await expect ( page . locator ( '#build' ) ) . toHaveValue ( '' ) ;
await page . locator ( '#setup-cancel' ) . click ( ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'main.cpp' ) ;
await page . locator ( '#file-menu > summary' ) . click ( ) ;
await page . locator ( '#file-recents > summary' ) . click ( ) ;
await expect ( page . locator ( '#file-recent-list button' ) ) . toHaveCount ( 1 ) ;
await page . locator ( '#file-recent-list button' ) . click ( ) ;
await expect ( page . locator ( '#setup' ) ) . toBeHidden ( ) ;
await expect ( page . locator ( '#file-menu' ) ) . not . toHaveAttribute ( 'open' , '' ) ;
await page . locator ( '#file-menu > summary' ) . click ( ) ;
await page . getByRole ( 'button' , { name : 'Return to Welcome' , exact :true } ) . click ( ) ;
await expect ( page . locator ( '#landing' ) ) . toBeVisible ( ) ;
await expect ( page . locator ( '#project-name' ) ) . toHaveText ( 'No project open' ) ;
await expect ( page . locator ( '#toggle-sidebar' ) ) . toBeDisabled ( ) ;
expect ( await page . evaluate ( ( ) = > ( window as unknown as { calls : { command :string ; args :unknown } [ ] } ) . calls . filter ( c = > c . command === 'close_workspace' ) . at ( - 1 ) ? . args ) ) . toEqual ( { root : '/sample' } ) ;
await page . locator ( '.recent-project' ) . click ( ) ;
await expect ( page . locator ( '#landing' ) ) . toBeHidden ( ) ;
await expect ( page . locator ( '#file-path' ) ) . toHaveText ( 'main.cpp' ) ;
} ) ;
test ( 'both sidebars resize with pointer and keyboard and keep widths across collapse' , async ( { page } ) = > {
await mockDesktop ( page ) ; await openProject ( page ) ; await page . locator ( '#nav-graph' ) . click ( ) ;
const left = page . getByRole ( 'separator' , { name : 'Resize left sidebar' } ) , right = page . getByRole ( 'separator' , { name : 'Resize right sidebar' } ) ;
const initialLeft = ( await page . locator ( 'main > aside' ) . boundingBox ( ) ) ! . width ;
const handle = ( await left . boundingBox ( ) ) ! ;
await page . mouse . move ( handle . x + 4 , handle . y + 150 ) ; await page . mouse . down ( ) ; await page . mouse . move ( handle . x + 64 , handle . y + 150 ) ; await page . mouse . up ( ) ;
await expect . poll ( async ( ) = > ( await page . locator ( 'main > aside' ) . boundingBox ( ) ) ! . width ) . toBeGreaterThan ( initialLeft + 45 ) ;
const initialRight = ( await page . locator ( '#explanation-pane' ) . boundingBox ( ) ) ! . width ;
const r = ( await right . boundingBox ( ) ) ! ;
await page . mouse . move ( r . x + 4 , r . y + 150 ) ; await page . mouse . down ( ) ; await page . mouse . move ( r . x - 46 , r . y + 150 ) ; await page . mouse . up ( ) ;
await expect . poll ( async ( ) = > ( await page . locator ( '#explanation-pane' ) . boundingBox ( ) ) ! . width ) . toBeGreaterThan ( initialRight + 35 ) ;
await right . focus ( ) ; await right . press ( 'ArrowLeft' ) ;
const width = ( await page . locator ( '#explanation-pane' ) . boundingBox ( ) ) ! . width ;
await page . getByRole ( 'button' , { name : 'Collapse right sidebar' } ) . click ( ) ; await page . locator ( '#toggle-sidebar' ) . click ( ) ;
expect ( ( await page . locator ( '#explanation-pane' ) . boundingBox ( ) ) ! . width ) . toBeCloseTo ( width , 0 ) ;
await page . locator ( '#graph-expand' ) . click ( ) ;
expect ( ( await page . locator ( '#explanation-pane' ) . boundingBox ( ) ) ! . width ) . toBeGreaterThan ( width + 100 ) ;
await page . locator ( '#graph-expand' ) . click ( ) ;
expect ( ( await page . locator ( '#explanation-pane' ) . boundingBox ( ) ) ! . width ) . toBeCloseTo ( width , 0 ) ;
await page . setViewportSize ( { width :950 , height :850 } ) ;
await expect ( page . locator ( '#explanation-pane' ) ) . toHaveCSS ( 'position' , 'fixed' ) ;
await right . focus ( ) ; await right . press ( 'ArrowRight' ) ;
expect ( ( await page . locator ( '#explanation-pane' ) . boundingBox ( ) ) ! . width ) . toBeLessThan ( width ) ;
} ) ;
test ( 'type graph nodes can be refocused and member nodes remain navigable' , async ( { page } ) = > {
await mockDesktop ( page ) ; await page . addInitScript ( ( ) = > Object . assign ( window , { typeGraph :true } ) ) ;
await openProject ( page ) ; await page . locator ( '#nav-graph' ) . click ( ) ;
await expect ( page . locator ( '#graph-title' ) ) . toHaveText ( 'demo::Box' ) ;
await page . getByRole ( 'button' , { name : 'type: demo::Base' , exact :true } ) . click ( ) ;
await page . getByRole ( 'button' , { name : 'Focus this symbol' , exact :true } ) . click ( ) ;
await expect ( page . locator ( '#graph-back' ) ) . toBeEnabled ( ) ;
await page . getByRole ( 'button' , { name : 'function: demo::Box::run' , exact :true } ) . click ( ) ;
await expect ( page . getByRole ( 'button' , { name : 'Open source' , exact :true } ) ) . toBeVisible ( ) ;
} ) ;