tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

definition-names-probe.mjs (5930B)


      1 // Compiled-definition inspection through the shipped editor controls.
      2 import {el,input,change,status,wait,inspect,card,result,pause,sourceEditor} from './source-probe-helpers.mjs';
      3 const assert=(ok,message)=>{if(!ok)throw Error(message);};
      4 export async function run(control,backend='haskell-wasm'){
      5  await status('Ready');
      6  document.querySelector('[data-compiler-example=identity]').click();
      7  const long='aVeryLongDefinitionName'+ 'Extended'.repeat(9);
      8  const source=`-- λ🙂 positions stay native UTF-16 offsets
      9 !export answer
     10 answer = 42
     11 identity x = x
     12 ${long} = 9
     13 text = "fake = 1"
     14 outer x = let
     15   local = x
     16   in local
     17 select [] = 0
     18 select xs = 1
     19 -- comment = 1`;
     20  const editor=sourceEditor,wrapper=editor.dom;
     21  input('source-editor',source);await status('Compiled');editor.view.scrollDOM.scrollTop=0;
     22  await pause(0);
     23  const target=name=>wrapper.querySelector(`[data-definition="${name}"]`);
     24  assert(!wrapper.querySelector('.definition-gutter'),'No gutter');
     25  assert(parseFloat(getComputedStyle(wrapper).paddingLeft)<=20,'No reserved horizontal inspection column');
     26  const names=[...new Set([...wrapper.querySelectorAll('[data-definition]')].map(button=>button.dataset.definition))];
     27  assert(JSON.stringify(names)===JSON.stringify(['answer','identity',long,'text','outer','select']),'Only unique top-level declaration heads are interactive');
     28  assert(!target('local')&&!target('x')&&!target('fake')&&!target('comment'),'No local variables, references, comments or string lookalikes');
     29  assert(!target('answer').hidden&&target('answer').getAttribute('aria-disabled')==='false'&&target('answer').tabIndex===0,'Compiled names are keyboard accessible');
     30  assert(await inspect('answer')==='42','Name opens the actual retained definition');
     31  assert(target('answer').getAttribute('aria-expanded')==='true','Open name highlighted');
     32  const close=card().querySelector('[aria-label="Close definition inspection"]');
     33  const bounds=card().getBoundingClientRect(),buttonBounds=close.getBoundingClientRect();
     34  assert(Math.abs(bounds.right-buttonBounds.right-13)<=2&&Math.abs(buttonBounds.top-bounds.top-9)<=2,'Close is in the top-right corner');
     35  assert(buttonBounds.width>=44&&buttonBounds.height>=44,'Close has a comfortable touch target');
     36  card().querySelector('.edit-definition-name').click();
     37  const offset=source.indexOf('answer =');
     38  assert(card().hidden&&document.activeElement===editor.view.contentDOM&&editor.getSelection().from===offset&&editor.getSelection().to===offset+6,'Edit name selects exactly the declaration, not its export or references');
     39  assert(editor.getSource()===source,'Opening/closing and Edit name do not mutate source');
     40  // Selection, hold and drag checks complement trusted mouse/touch/keyboard input.
     41  const button=target('answer');
     42  const down=()=>button.dispatchEvent(new PointerEvent('pointerdown',{clientX:10,clientY:10,pointerId:1,bubbles:true}));
     43  const click=()=>button.dispatchEvent(new MouseEvent('click',{detail:1,bubbles:true}));
     44  down();await pause(550);click();assert(card().hidden,'Holding is not a tap');
     45  down();button.dispatchEvent(new PointerEvent('pointermove',{clientX:30,clientY:10,pointerId:1,bubbles:true}));click();assert(card().hidden,'Dragging is not a tap');
     46  down();const menu=new MouseEvent('contextmenu',{bubbles:true,cancelable:true});button.dispatchEvent(menu);click();assert(!menu.defaultPrevented&&card().hidden,'Native context menu is not intercepted');
     47  down();document.getSelection().selectAllChildren(button);click();assert(card().hidden,'Selecting the identifier does not inspect');document.getSelection().removeAllRanges();
     48  wrapper.dispatchEvent(new CompositionEvent('compositionstart'));
     49  assert(getComputedStyle(target('answer')).pointerEvents==='none','IME disables name pointer activation');
     50  wrapper.dispatchEvent(new CompositionEvent('compositionend'));
     51  input('source-editor',source.replaceAll('answer','renamed'));await pause(0);
     52  assert([...wrapper.querySelectorAll('[data-definition]')].every(button=>button.getAttribute('aria-disabled')==='true'||button.hidden),'Dirty names lose clickability immediately');
     53  await status('Compiled');assert(await inspect('renamed')==='42','Renamed definition resolves from the new compiled revision');
     54  input('source-editor','!export renamed\nrenamed = missing');await status('Compilation failed');
     55  assert([...wrapper.querySelectorAll('[data-definition]')].every(button=>button.getAttribute('aria-disabled')==='true'&&button.hidden),'Failed compilation leaves no clickable underline');
     56  input('source-editor',source);await status('Compiled');editor.view.scrollDOM.scrollTop=0;await pause(0);
     57  // Wrapped declaration fragments retain their inline text hit areas.
     58  const span=target(long);span.scrollIntoView({block:'center',behavior:'instant'});await new Promise(r=>requestAnimationFrame(()=>requestAnimationFrame(r)));const boxes=[...span.getClientRects()];
     59  assert(boxes.length>0&&span.tabIndex===0,'Wrapped inline name has one keyboard stop');
     60  for(const box of boxes){
     61   const hit=document.elementFromPoint(box.left+Math.min(3,box.width/2),box.top+box.height/2);
     62   assert(hit&&span.contains(hit),'Every visible name fragment has its own native inline hit area: '+JSON.stringify({box,hit:hit?.outerHTML.slice(0,180),window:innerHeight}));
     63  }
     64  assert(!wrapper.querySelector('.definition-targets'),'No manual overlay rectangles');
     65  assert(await inspect(long)==='9','Wrapped private definition is inspectable');
     66  card().querySelector('[aria-label="Close definition inspection"]').click();
     67  assert(document.activeElement===target(long),'Closing returns focus to the name');
     68  editor.view.scrollDOM.scrollTop=0;
     69  return {userAgent:navigator.userAgent,checks:['underlined definition names without a gutter; exact wrapped hit areas; unique heads only; retained/private inspection; Edit name and focus; dirty/error invalidation; native context menus; hold/drag/selection suppression; IME; one keyboard stop per name']};
     70 }