Skip to content

Commit aeb438a

Browse files
claudesinelaw
authored andcommitted
flash tests: cover space/tab/EOL/EOB/wrap edge cases for label rendering
Defensive coverage for the conceal-replacement code path in the view renderer. Of the four new tests, only one (space-after-match, already covered by the regression test from the previous commit's fix) actually fails without the renderer fix — the others document edge-case behaviour that already works correctly today and protect against future regressions. - flash_label_on_match_at_end_of_line: pattern matches right before `\n`. Plugin's `nextCharByteLen` returns 0 for newlines so we fall back to inline virtual text; this test verifies the line content survives that path. - flash_label_on_match_at_end_of_buffer: pattern matches the last char of a buffer with no trailing newline. Same fallback, same content-survival assertion. - flash_label_does_not_eat_tab_after_match: pattern matches a word followed by a literal tab. Verifies the renderer doesn't swallow the tab cell. (Currently passes even without the Space/Newline/Break fix because tabs hit a different code path.) - flash_label_at_wrap_boundary_does_not_corrupt_text: pattern matches at end of a soft-wrapped visual row. Documented cosmetic limitation: the label letter ends up at the START of the next visual row rather than the END of the current one. Test asserts text content survives; the visual-placement quirk is a separate, larger renderer change. Run history with renderer fix reverted (HEAD~1): flash_label_does_not_eat_space_after_match → FAILED ← reproducer flash_label_does_not_eat_tab_after_match → ok flash_label_at_wrap_boundary_… → ok flash_label_on_match_at_end_of_line → ok flash_label_on_match_at_end_of_buffer → ok All 11 flash e2e tests pass with the fix in place.
1 parent e2eeed0 commit aeb438a

1 file changed

Lines changed: 141 additions & 0 deletions

File tree

crates/fresh-editor/tests/e2e/flash.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,147 @@ fn flash_label_does_not_eat_space_after_match() {
381381
);
382382
}
383383

384+
/// Edge: pattern matches right at end of line (next char is `\n`).
385+
/// My plugin falls back to inline virtual text in that case
386+
/// because conceal-substituting a newline would corrupt line
387+
/// layout. Verify the line content remains visible and intact.
388+
#[test]
389+
fn flash_label_on_match_at_end_of_line() {
390+
let (mut harness, _temp, project_root) = flash_harness(120, 24);
391+
let path = write_fixture(
392+
&project_root,
393+
"test.txt",
394+
"first line ends with x\nsecond line ends with y\nthird line\n",
395+
);
396+
harness.open_file(&path).unwrap();
397+
harness.render().unwrap();
398+
399+
arm_flash(&mut harness);
400+
type_pattern(&mut harness, "x");
401+
harness.render().unwrap();
402+
403+
// Pattern `x` matches `x` at end of line 1 (next char is `\n`).
404+
// Conceal can't substitute `\n` — would break line layout.
405+
// Plugin falls back to inline virtual text. The match `x`
406+
// must remain on screen, AND no part of the line content
407+
// should be lost.
408+
let screen = harness.screen_to_string();
409+
assert!(
410+
screen.contains("first line ends with x"),
411+
"line 1 content lost when match was at end of line. Screen:\n{}",
412+
screen,
413+
);
414+
assert!(
415+
screen.contains("second line ends with y"),
416+
"subsequent line corrupted. Screen:\n{}",
417+
screen,
418+
);
419+
}
420+
421+
/// Edge: pattern matches at end of buffer with no trailing
422+
/// newline. `nextCharByteLen` returns 0 (charEnd >= text.length),
423+
/// plugin falls back to inline virtual text.
424+
#[test]
425+
fn flash_label_on_match_at_end_of_buffer() {
426+
let (mut harness, _temp, project_root) = flash_harness(120, 24);
427+
// No trailing newline. Pattern matches the very last char.
428+
let path = write_fixture(&project_root, "test.txt", "alpha bravo charlie");
429+
harness.open_file(&path).unwrap();
430+
harness.render().unwrap();
431+
432+
arm_flash(&mut harness);
433+
type_pattern(&mut harness, "ie");
434+
harness.render().unwrap();
435+
436+
let screen = harness.screen_to_string();
437+
// Buffer content should still be visible — no crash, no
438+
// missing chars at the end.
439+
assert!(
440+
screen.contains("alpha bravo charl"),
441+
"buffer prefix lost after end-of-buffer match. Screen:\n{}",
442+
screen,
443+
);
444+
}
445+
446+
/// Edge: a token boundary issue — pattern that ends right before
447+
/// a Tab character. Tabs are tokenized as Space-class tokens in
448+
/// fresh. The conceal-replacement-on-Space-tokens fix should
449+
/// cover this too; verify by typing a pattern in a buffer that
450+
/// has tab-separated columns.
451+
#[test]
452+
fn flash_label_does_not_eat_tab_after_match() {
453+
let (mut harness, _temp, project_root) = flash_harness(120, 24);
454+
let path = write_fixture(
455+
&project_root,
456+
"test.txt",
457+
"key\tvalue\nname\talice\nrole\tadmin\n",
458+
);
459+
harness.open_file(&path).unwrap();
460+
harness.render().unwrap();
461+
462+
arm_flash(&mut harness);
463+
type_pattern(&mut harness, "key");
464+
harness.render().unwrap();
465+
466+
let screen = harness.screen_to_string();
467+
// The cell after `key` would have rendered the tab indicator.
468+
// After conceal substitution it should show a label letter,
469+
// and `value` (the next column) should remain readable.
470+
assert!(
471+
screen.contains("value"),
472+
"next column lost after tab. Screen:\n{}",
473+
screen,
474+
);
475+
assert!(
476+
!screen.contains("keyvalue"),
477+
"tab cell collapsed — `key value` rendered as `keyvalue`. Screen:\n{}",
478+
screen,
479+
);
480+
}
481+
482+
/// Edge: pattern at end of a soft-wrapped visual row — the cell
483+
/// that should receive the label is at the wrap point. Documented
484+
/// limitation: the label letter ends up at the START of the next
485+
/// visual row instead of the end of the current one. Test asserts
486+
/// content survives; the visual placement quirk is noted in the
487+
/// plugin's redraw comment.
488+
#[test]
489+
fn flash_label_at_wrap_boundary_does_not_corrupt_text() {
490+
// Width 100 keeps status bar room for `Flash[w]` (without it
491+
// type_pattern's screen-text wait would never converge), but
492+
// the long line below still has to wrap because it's >100
493+
// chars.
494+
let (mut harness, _temp, project_root) = flash_harness(100, 24);
495+
let path = write_fixture(
496+
&project_root,
497+
"test.txt",
498+
"this is a very long first line that wraps around the viewport edge \
499+
because it is too long to fit on a single visual row even at width \
500+
one hundred which is what we are using here today\n",
501+
);
502+
harness.open_file(&path).unwrap();
503+
harness.render().unwrap();
504+
505+
arm_flash(&mut harness);
506+
type_pattern(&mut harness, "wraps");
507+
harness.render().unwrap();
508+
509+
// Whatever the visual placement of the label, the buffer text
510+
// must be intact: every word from the long line should still
511+
// appear on screen somewhere, in the right order.
512+
let screen = harness.screen_to_string();
513+
for word in [
514+
"this", "very", "long", "first", "line", "that", "around", "because",
515+
] {
516+
assert!(
517+
screen.contains(word),
518+
"word `{}` was eaten by the wrap-point label. Screen:\n{}",
519+
word,
520+
screen,
521+
);
522+
}
523+
}
524+
384525
#[test]
385526
fn flash_jumps_across_splits() {
386527
// Two vertical splits, each with a different buffer that contains

0 commit comments

Comments
 (0)