86 lines
3.6 KiB
Python
86 lines
3.6 KiB
Python
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
MENTIONS = ROOT / "frontend" / "mention-composer.js"
|
|
|
|
|
|
def run_node(script: str) -> dict:
|
|
completed = subprocess.run(
|
|
["node", "-e", script],
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
return json.loads(completed.stdout)
|
|
|
|
|
|
def test_active_mention_replaces_only_the_token_at_the_caret():
|
|
script = f"""
|
|
const mentions=require({json.dumps(str(MENTIONS))});
|
|
const text='Thanks @al for this; keep @casey informed';
|
|
const caret=text.indexOf(' for');
|
|
const active=mentions.activeMention(text, caret);
|
|
const inserted=mentions.insertMention(text, active, 'alex');
|
|
process.stdout.write(JSON.stringify({{active,inserted}}));
|
|
"""
|
|
|
|
result = run_node(script)
|
|
|
|
assert result["active"] == {"start": 7, "end": 10, "query": "al"}
|
|
assert result["inserted"] == {
|
|
"value": "Thanks @alex for this; keep @casey informed",
|
|
"caret": 13,
|
|
}
|
|
|
|
|
|
def test_controller_ignores_stale_results_and_keyboard_selects_a_teammate():
|
|
script = f"""
|
|
const mentions=require({json.dumps(str(MENTIONS))});
|
|
class Element {{
|
|
constructor() {{ this.listeners={{}}; this.children=[]; this.attrs={{}}; this.dataset={{}}; this.hidden=true; this.value=''; this.selectionStart=0; this.selectionEnd=0; this.textContent=''; }}
|
|
addEventListener(type, fn) {{ (this.listeners[type] ||= []).push(fn); }}
|
|
dispatch(type, extra={{}}) {{ const event={{preventDefault(){{ this.prevented=true; }}, ...extra}}; (this.listeners[type] || []).forEach(fn=>fn(event)); return event; }}
|
|
replaceChildren(...children) {{ this.children=children; }}
|
|
appendChild(child) {{ this.children.push(child); }}
|
|
setAttribute(name, value) {{ this.attrs[name]=String(value); }}
|
|
removeAttribute(name) {{ delete this.attrs[name]; }}
|
|
setSelectionRange(start, end) {{ this.selectionStart=start; this.selectionEnd=end; }}
|
|
focus() {{ this.focused=true; }}
|
|
}}
|
|
const textarea=new Element(), listbox=new Element(), status=new Element();
|
|
const pending=[];
|
|
const controller=mentions.create({{
|
|
textarea,listbox,status,getRepository:()=> 'stackchain/api',
|
|
loadCandidates:(repository, query)=>new Promise(resolve=>pending.push({{repository,query,resolve}})),
|
|
setTimer:fn=>{{ fn(); return 1; }}, clearTimer:()=>{{}},
|
|
createOption:()=>new Element(),
|
|
}});
|
|
controller.start();
|
|
(async()=>{{
|
|
textarea.value='Ping @al'; textarea.selectionStart=textarea.value.length; textarea.dispatch('input');
|
|
textarea.value='Ping @alex'; textarea.selectionStart=textarea.value.length; textarea.dispatch('input');
|
|
pending[1].resolve([{{login:'alex',name:'Alexander'}}]); await Promise.resolve(); await Promise.resolve();
|
|
pending[0].resolve([{{login:'alice',name:'Alice'}}]); await Promise.resolve(); await Promise.resolve();
|
|
const before={{queries:pending.map(item=>item.query), options:listbox.children.map(item=>item.dataset.login), expanded:textarea.attrs['aria-expanded']}};
|
|
textarea.dispatch('keydown', {{key:'ArrowDown'}});
|
|
const enter=textarea.dispatch('keydown', {{key:'Enter'}});
|
|
process.stdout.write(JSON.stringify({{before,value:textarea.value,caret:textarea.selectionStart,focused:textarea.focused,prevented:enter.prevented,hidden:listbox.hidden}}));
|
|
}})().catch(error=>{{ console.error(error); process.exit(1); }});
|
|
"""
|
|
|
|
result = run_node(script)
|
|
|
|
assert result["before"] == {
|
|
"queries": ["al", "alex"],
|
|
"options": ["alex"],
|
|
"expanded": "true",
|
|
}
|
|
assert result["value"] == "Ping @alex "
|
|
assert result["caret"] == 11
|
|
assert result["focused"] is True
|
|
assert result["prevented"] is True
|
|
assert result["hidden"] is True
|