char-slop/berry
multipurpose self-modifiable agentic harness
git clone https://git.t4t.associates/char-slop/berry
1c0a335
main
1import { assertEquals } from "@std/assert" ; 2import { press , withDom } from "@berry/util/dom.ts" ; 3import { conversationName } from "./conversation-name.ts" ; 4 5Deno . test ( "sidebar names edit inline, save deliberately, and retain failed edits" , async ( t ) => { 6await withDom ( async ( window ) => { 7const row = document . createElement ( "div" ); 8const link = document . createElement ( "a" ); 9link . href = "#/conversation" ; 10const label = document . createElement ( "span" ); 11link . append ( label ); 12const saved :( string | null )[] = []; 13let save = ( _title :string | null ) => Promise . resolve (); 14const name = conversationName ( link , label , ( title ) => { 15saved . push ( title ); 16return save ( title ); 17}); 18row . append ( name . editor , link ); 19document . body . append ( row ); 20const start = () => 21link . dispatchEvent ( 22new window . MouseEvent ( "dblclick" , { cancelable :true }) as unknown as MouseEvent , 23); 24const key = ( value :string ) => ! press ( name . editor , value , { bubbles :false }); 25const settle = () => window . happyDOM . waitUntilComplete (); 26try { 27await t . step ( "double-click opens an input holding the shown name" , () => { 28name . update ( undefined , "first message preview" ); 29assertEquals ( label . textContent , "first message preview" ); 30assertEquals ( name . editor . hidden , true ); 31assertEquals ( start (), false ); 32assertEquals ( link . hidden , true ); 33assertEquals ( name . editor . hidden , false ); 34assertEquals ( name . editor . value , "first message preview" ); 35assertEquals ( document . activeElement , name . editor ); 36}); 37await t . step ( "Escape cancels without saving and restores keyboard focus" , async () => { 38name . editor . value = "cancel this" ; 39key ( "Escape" ); 40await settle (); 41assertEquals ( saved , []); 42assertEquals ( link . hidden , false ); 43assertEquals ( name . editor . hidden , true ); 44assertEquals ( label . textContent , "first message preview" ); 45assertEquals ( document . activeElement , link ); 46}); 47await t . step ( 48"Enter trims and saves once even though restoring focus blurs the input" , 49async () => { 50start (); 51name . editor . value = " renamed conversation " ; 52key ( "Enter" ); 53await settle (); 54assertEquals ( saved , [ "renamed conversation" ]); 55assertEquals ( label . textContent , "renamed conversation" ); 56assertEquals ( name . editor . hidden , true ); 57assertEquals ( link . hidden , false ); 58}, 59); 60await t . step ( "blur saves changes without stealing focus from another control" , async () => { 61start (); 62name . editor . value = "saved on blur" ; 63const elsewhere = document . createElement ( "input" ); 64document . body . append ( elsewhere ); 65elsewhere . focus (); 66await settle (); 67assertEquals ( saved , [ "renamed conversation" , "saved on blur" ]); 68assertEquals ( document . activeElement , elsewhere ); 69assertEquals ( label . textContent , "saved on blur" ); 70}); 71await t . step ( 72"leaving the generated preview unchanged does not create a custom name" , 73async () => { 74name . update ( undefined , "automatic preview" ); 75start (); 76key ( "Enter" ); 77await settle (); 78assertEquals ( saved . length , 2 ); 79name . update ( undefined , "updated automatic preview" ); 80assertEquals ( label . textContent , "updated automatic preview" ); 81}, 82); 83await t . step ( "clearing a custom name returns to the message preview" , async () => { 84name . update ( "custom" , "automatic preview" ); 85start (); 86name . editor . value = " " ; 87key ( "Enter" ); 88await settle (); 89assertEquals ( saved . at ( - 1 ), null ); 90assertEquals ( label . textContent , "automatic preview" ); 91}); 92await t . step ( "live sidebar updates do not overwrite an unfinished edit" , async () => { 93name . update ( "before" , "preview" ); 94start (); 95name . editor . value = "my unfinished name" ; 96name . update ( "renamed on another device" , "changed preview" ); 97assertEquals ( name . editor . value , "my unfinished name" ); 98key ( "Escape" ); 99await settle (); 100assertEquals ( label . textContent , "renamed on another device" ); 101assertEquals ( saved . length , 3 ); 102}); 103await t . step ( "failed saves keep the input editable and can be retried" , async () => { 104save = () => Promise . reject ( new Error ( "offline" )); 105start (); 106name . editor . value = "retry this" ; 107key ( "Enter" ); 108await settle (); 109assertEquals ( name . editor . hidden , false ); 110assertEquals ( name . editor . value , "retry this" ); 111assertEquals ( name . editor . readOnly , false ); 112assertEquals ( name . editor . getAttribute ( "aria-invalid" ), "true" ); 113assertEquals ( name . editor . title , "offline" ); 114save = () => Promise . resolve (); 115key ( "Enter" ); 116await settle (); 117assertEquals ( name . editor . hidden , true ); 118assertEquals ( label . textContent , "retry this" ); 119}); 120await t . step ( "F2 provides a keyboard equivalent for double-click" , async () => { 121link . dispatchEvent ( 122new window . KeyboardEvent ( "keydown" , { key :"F2" }) as unknown as KeyboardEvent , 123); 124assertEquals ( name . editor . hidden , false ); 125assertEquals ( document . activeElement , name . editor ); 126key ( "Escape" ); 127await settle (); 128}); 129await t . step ( 130"a pending save cannot overwrite a newer name received from another device" , 131async () => { 132const pending = Promise . withResolvers < void >(); 133save = () => pending . promise ; 134start (); 135name . editor . value = "earlier request" ; 136key ( "Enter" ); 137const count = saved . length ; 138key ( "Enter" ); 139assertEquals ( saved . length , count ); 140name . update ( "newer remote name" , "preview" ); 141pending . resolve (); 142await settle (); 143assertEquals ( label . textContent , "newer remote name" ); 144}, 145); 146} finally { 147name . dispose (); 148} 149}); 150});