@@ -316,3 +316,70 @@ describe("insertPaste + expandPastes", () => {
316316 expect ( after ) . toEqual ( before ) ;
317317 } ) ;
318318} ) ;
319+
320+ describe ( "atomic placeholder deletion" , ( ) => {
321+ it ( "backspace at the right edge of a placeholder removes the whole placeholder" , ( ) => {
322+ let s = insertPaste ( initialInputState ( ) , "abc\ndef\nghi" ) ;
323+ const before = s . buffer ; // "[Pasted #1 · 3 lines]"
324+ s = backspace ( s ) ;
325+ expect ( s . buffer ) . toBe ( "" ) ;
326+ expect ( s . cursor ) . toBe ( 0 ) ;
327+ expect ( s . pastedContents [ 1 ] ) . toBeUndefined ( ) ;
328+ // Sanity: prior state actually held the placeholder
329+ expect ( before ) . toBe ( "[Pasted #1 · 3 lines]" ) ;
330+ } ) ;
331+
332+ it ( "backspace garbage-collects the side entry for the deleted placeholder" , ( ) => {
333+ let s = insertPaste ( initialInputState ( ) , "x" . repeat ( 500 ) ) ;
334+ expect ( Object . keys ( s . pastedContents ) ) . toHaveLength ( 1 ) ;
335+ s = backspace ( s ) ;
336+ expect ( Object . keys ( s . pastedContents ) ) . toHaveLength ( 0 ) ;
337+ } ) ;
338+
339+ it ( "backspace deletes only the adjacent placeholder, leaving siblings intact" , ( ) => {
340+ let s = insertPaste ( initialInputState ( ) , "first one" ) ;
341+ for ( const ch of " then " ) s = insertChar ( s , ch ) ;
342+ s = insertPaste ( s , "second one is longer than the first paste here" ) ;
343+ // Cursor is at the end, right after the second placeholder.
344+ s = backspace ( s ) ;
345+ // Second placeholder removed; first remains intact.
346+ expect ( s . buffer ) . toBe ( "[Pasted #1 · 9 chars] then " ) ;
347+ expect ( s . pastedContents [ 1 ] ) . toBeDefined ( ) ;
348+ expect ( s . pastedContents [ 2 ] ) . toBeUndefined ( ) ;
349+ } ) ;
350+
351+ it ( "deleteForward at the left edge of a placeholder removes the whole placeholder" , ( ) => {
352+ let s = insertPaste ( initialInputState ( ) , "foo\nbar" ) ;
353+ s = moveStart ( s ) ;
354+ s = deleteForward ( s ) ;
355+ expect ( s . buffer ) . toBe ( "" ) ;
356+ expect ( s . pastedContents [ 1 ] ) . toBeUndefined ( ) ;
357+ } ) ;
358+
359+ it ( "backspace in the middle of a placeholder still chips one char (non-edge)" , ( ) => {
360+ // Cursor inside the placeholder, not at the right edge — fall back
361+ // to normal char-by-char behavior. The placeholder is now broken
362+ // but expandPastes will leave the fragment as-is on submit.
363+ let s = insertPaste ( initialInputState ( ) , "hello" ) ;
364+ s = moveLeft ( s ) ; // cursor now between "]" and end? no, one before end
365+ s = backspace ( s ) ;
366+ // We removed one char from the middle, breaking the placeholder.
367+ expect ( s . buffer . length ) . toBe ( s . buffer . length ) ;
368+ expect ( s . buffer ) . not . toBe ( "[Pasted #1 · 5 chars]" ) ; // broken
369+ expect ( s . pastedContents [ 1 ] ) . toBeDefined ( ) ; // entry NOT garbage-collected
370+ } ) ;
371+
372+ it ( "backspace at a position where the buffer looks like a placeholder but isn't ours just chips a char" , ( ) => {
373+ // User literally typed [Pasted #999 · 5 chars] — we have no id 999,
374+ // but we still detect placeholder shape via the regex. Decision:
375+ // trust the shape, garbage-collect won't find an entry to remove
376+ // (no-op on the side map), but the visible delete is atomic.
377+ // That's defensible: shape matches user intent.
378+ let s = initialInputState ( ) ;
379+ for ( const ch of "[Pasted #999 · 5 chars]" ) s = insertChar ( s , ch ) ;
380+ s = backspace ( s ) ;
381+ expect ( s . buffer ) . toBe ( "" ) ;
382+ // pastedContents was empty — nothing to drop.
383+ expect ( s . pastedContents ) . toEqual ( { } ) ;
384+ } ) ;
385+ } ) ;
0 commit comments