test.rs

  1mod neovim_backed_binding_test_context;
  2mod neovim_backed_test_context;
  3mod neovim_connection;
  4mod vim_test_context;
  5
  6use std::sync::Arc;
  7
  8use command_palette::CommandPalette;
  9use editor::DisplayPoint;
 10pub use neovim_backed_binding_test_context::*;
 11pub use neovim_backed_test_context::*;
 12pub use vim_test_context::*;
 13
 14use indoc::indoc;
 15use search::BufferSearchBar;
 16
 17use crate::{state::Mode, ModeIndicator};
 18
 19#[gpui::test]
 20async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
 21    let mut cx = VimTestContext::new(cx, false).await;
 22    cx.simulate_keystrokes(["h", "j", "k", "l"]);
 23    cx.assert_editor_state("hjklˇ");
 24}
 25
 26#[gpui::test]
 27async fn test_neovim(cx: &mut gpui::TestAppContext) {
 28    let mut cx = NeovimBackedTestContext::new(cx).await;
 29
 30    cx.simulate_shared_keystroke("i").await;
 31    cx.assert_state_matches().await;
 32    cx.simulate_shared_keystrokes([
 33        "shift-T", "e", "s", "t", " ", "t", "e", "s", "t", "escape", "0", "d", "w",
 34    ])
 35    .await;
 36    cx.assert_state_matches().await;
 37    cx.assert_editor_state("ˇtest");
 38}
 39
 40#[gpui::test]
 41async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
 42    let mut cx = VimTestContext::new(cx, true).await;
 43
 44    cx.simulate_keystroke("i");
 45    assert_eq!(cx.mode(), Mode::Insert);
 46
 47    // Editor acts as though vim is disabled
 48    cx.disable_vim();
 49    cx.simulate_keystrokes(["h", "j", "k", "l"]);
 50    cx.assert_editor_state("hjklˇ");
 51
 52    // Selections aren't changed if editor is blurred but vim-mode is still disabled.
 53    cx.set_state("«hjklˇ»", Mode::Normal);
 54    cx.assert_editor_state("«hjklˇ»");
 55    cx.update_editor(|_, cx| cx.blur());
 56    cx.assert_editor_state("«hjklˇ»");
 57    cx.update_editor(|_, cx| cx.focus_self());
 58    cx.assert_editor_state("«hjklˇ»");
 59
 60    // Enabling dynamically sets vim mode again and restores normal mode
 61    cx.enable_vim();
 62    assert_eq!(cx.mode(), Mode::Normal);
 63    cx.simulate_keystrokes(["h", "h", "h", "l"]);
 64    assert_eq!(cx.buffer_text(), "hjkl".to_owned());
 65    cx.assert_editor_state("hˇjkl");
 66    cx.simulate_keystrokes(["i", "T", "e", "s", "t"]);
 67    cx.assert_editor_state("hTestˇjkl");
 68
 69    // Disabling and enabling resets to normal mode
 70    assert_eq!(cx.mode(), Mode::Insert);
 71    cx.disable_vim();
 72    cx.enable_vim();
 73    assert_eq!(cx.mode(), Mode::Normal);
 74}
 75
 76#[gpui::test]
 77async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
 78    let mut cx = VimTestContext::new(cx, true).await;
 79
 80    cx.set_state(
 81        indoc! {"
 82            The quick brown
 83            fox juˇmps over
 84            the lazy dog"},
 85        Mode::Normal,
 86    );
 87    cx.simulate_keystroke("/");
 88
 89    let search_bar = cx.workspace(|workspace, cx| {
 90        workspace
 91            .active_pane()
 92            .read(cx)
 93            .toolbar()
 94            .read(cx)
 95            .item_of_type::<BufferSearchBar>()
 96            .expect("Buffer search bar should be deployed")
 97    });
 98
 99    search_bar.read_with(cx.cx, |bar, cx| {
100        assert_eq!(bar.query(cx), "");
101    })
102}
103
104#[gpui::test]
105async fn test_count_down(cx: &mut gpui::TestAppContext) {
106    let mut cx = VimTestContext::new(cx, true).await;
107
108    cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
109    cx.simulate_keystrokes(["2", "down"]);
110    cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
111    cx.simulate_keystrokes(["9", "down"]);
112    cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
113}
114
115#[gpui::test]
116async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
117    let mut cx = VimTestContext::new(cx, true).await;
118
119    // goes to end by default
120    cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
121    cx.simulate_keystrokes(["shift-g"]);
122    cx.assert_editor_state("aa\nbb\ncˇc");
123
124    // can go to line 1 (https://github.com/zed-industries/community/issues/710)
125    cx.simulate_keystrokes(["1", "shift-g"]);
126    cx.assert_editor_state("aˇa\nbb\ncc");
127}
128
129#[gpui::test]
130async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
131    let mut cx = VimTestContext::new(cx, true).await;
132
133    // works in normal mode
134    cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
135    cx.simulate_keystrokes([">", ">"]);
136    cx.assert_editor_state("aa\n    bˇb\ncc");
137    cx.simulate_keystrokes(["<", "<"]);
138    cx.assert_editor_state("aa\nbˇb\ncc");
139
140    // works in visuial mode
141    cx.simulate_keystrokes(["shift-v", "down", ">"]);
142    cx.assert_editor_state("aa\n    b«b\n    ccˇ»");
143}
144
145#[gpui::test]
146async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
147    let mut cx = VimTestContext::new(cx, true).await;
148
149    cx.set_state("aˇbc\n", Mode::Normal);
150    cx.simulate_keystrokes(["i", "cmd-shift-p"]);
151
152    assert!(cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
153    cx.simulate_keystroke("escape");
154    assert!(!cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
155    cx.assert_state("aˇbc\n", Mode::Insert);
156}
157
158#[gpui::test]
159async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
160    let mut cx = VimTestContext::new(cx, true).await;
161
162    cx.set_state("aˇbˇc", Mode::Normal);
163    cx.simulate_keystrokes(["escape"]);
164
165    cx.assert_state("aˇbc", Mode::Normal);
166}
167
168#[gpui::test]
169async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
170    let mut cx = VimTestContext::new(cx, true).await;
171
172    cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
173    cx.simulate_keystrokes(["/", "c", "c"]);
174
175    let search_bar = cx.workspace(|workspace, cx| {
176        workspace
177            .active_pane()
178            .read(cx)
179            .toolbar()
180            .read(cx)
181            .item_of_type::<BufferSearchBar>()
182            .expect("Buffer search bar should be deployed")
183    });
184
185    search_bar.read_with(cx.cx, |bar, cx| {
186        assert_eq!(bar.query(cx), "cc");
187    });
188
189    // wait for the query editor change event to fire.
190    search_bar.next_notification(&cx).await;
191
192    cx.update_editor(|editor, cx| {
193        let highlights = editor.all_text_background_highlights(cx);
194        assert_eq!(3, highlights.len());
195        assert_eq!(
196            DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
197            highlights[0].0
198        )
199    });
200    cx.simulate_keystrokes(["enter"]);
201
202    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
203    cx.simulate_keystrokes(["n"]);
204    cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
205    cx.simulate_keystrokes(["shift-n"]);
206    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
207}
208
209#[gpui::test]
210async fn test_status_indicator(
211    cx: &mut gpui::TestAppContext,
212    deterministic: Arc<gpui::executor::Deterministic>,
213) {
214    let mut cx = VimTestContext::new(cx, true).await;
215    deterministic.run_until_parked();
216
217    let mode_indicator = cx.workspace(|workspace, cx| {
218        let status_bar = workspace.status_bar().read(cx);
219        let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
220        assert!(mode_indicator.is_some());
221        mode_indicator.unwrap()
222    });
223
224    assert_eq!(
225        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
226        Some(Mode::Normal)
227    );
228
229    // shows the correct mode
230    cx.simulate_keystrokes(["i"]);
231    deterministic.run_until_parked();
232    assert_eq!(
233        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
234        Some(Mode::Insert)
235    );
236
237    // shows even in search
238    cx.simulate_keystrokes(["escape", "v", "/"]);
239    deterministic.run_until_parked();
240    assert_eq!(
241        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
242        Some(Mode::Visual)
243    );
244
245    // hides if vim mode is disabled
246    cx.disable_vim();
247    deterministic.run_until_parked();
248    cx.workspace(|workspace, cx| {
249        let status_bar = workspace.status_bar().read(cx);
250        let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
251        assert!(mode_indicator.read(cx).mode.is_none());
252    });
253
254    cx.enable_vim();
255    deterministic.run_until_parked();
256    cx.workspace(|workspace, cx| {
257        let status_bar = workspace.status_bar().read(cx);
258        let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
259        assert!(mode_indicator.read(cx).mode.is_some());
260    });
261}
262
263#[gpui::test]
264async fn test_word_characters(cx: &mut gpui::TestAppContext) {
265    let mut cx = VimTestContext::new_typescript(cx).await;
266    cx.set_state(
267        indoc! { "
268        class A {
269            #ˇgoop = 99;
270            $ˇgoop () { return this.#gˇoop };
271        };
272        console.log(new A().$gooˇp())
273    "},
274        Mode::Normal,
275    );
276    cx.simulate_keystrokes(["v", "i", "w"]);
277    cx.assert_state(
278        indoc! {"
279        class A {
280            «#goopˇ» = 99;
281            «$goopˇ» () { return this.«#goopˇ» };
282        };
283        console.log(new A().«$goopˇ»())
284    "},
285        Mode::Visual,
286    )
287}
288
289#[gpui::test]
290async fn test_join_lines(cx: &mut gpui::TestAppContext) {
291    let mut cx = NeovimBackedTestContext::new(cx).await;
292
293    cx.set_shared_state(indoc! {"
294      ˇone
295      two
296      three
297      four
298      five
299      six
300      "})
301        .await;
302    cx.simulate_shared_keystrokes(["shift-j"]).await;
303    cx.assert_shared_state(indoc! {"
304          oneˇ two
305          three
306          four
307          five
308          six
309          "})
310        .await;
311    cx.simulate_shared_keystrokes(["3", "shift-j"]).await;
312    cx.assert_shared_state(indoc! {"
313          one two threeˇ four
314          five
315          six
316          "})
317        .await;
318
319    cx.set_shared_state(indoc! {"
320      ˇone
321      two
322      three
323      four
324      five
325      six
326      "})
327        .await;
328    cx.simulate_shared_keystrokes(["j", "v", "3", "j", "shift-j"])
329        .await;
330    cx.assert_shared_state(indoc! {"
331      one
332      two three fourˇ five
333      six
334      "})
335        .await;
336}
337
338#[gpui::test]
339async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
340    let mut cx = NeovimBackedTestContext::new(cx).await;
341
342    cx.set_shared_wrap(12).await;
343    // tests line wrap as follows:
344    //  1: twelve char
345    //     twelve char
346    //  2: twelve char
347    cx.set_shared_state(indoc! { "
348        tˇwelve char twelve char
349        twelve char
350    "})
351        .await;
352    cx.simulate_shared_keystrokes(["j"]).await;
353    cx.assert_shared_state(indoc! { "
354        twelve char twelve char
355        tˇwelve char
356    "})
357        .await;
358    cx.simulate_shared_keystrokes(["k"]).await;
359    cx.assert_shared_state(indoc! { "
360        tˇwelve char twelve char
361        twelve char
362    "})
363        .await;
364    cx.simulate_shared_keystrokes(["g", "j"]).await;
365    cx.assert_shared_state(indoc! { "
366        twelve char tˇwelve char
367        twelve char
368    "})
369        .await;
370    cx.simulate_shared_keystrokes(["g", "j"]).await;
371    cx.assert_shared_state(indoc! { "
372        twelve char twelve char
373        tˇwelve char
374    "})
375        .await;
376
377    cx.simulate_shared_keystrokes(["g", "k"]).await;
378    cx.assert_shared_state(indoc! { "
379        twelve char tˇwelve char
380        twelve char
381    "})
382        .await;
383
384    cx.simulate_shared_keystrokes(["g", "^"]).await;
385    cx.assert_shared_state(indoc! { "
386        twelve char ˇtwelve char
387        twelve char
388    "})
389        .await;
390
391    cx.simulate_shared_keystrokes(["^"]).await;
392    cx.assert_shared_state(indoc! { "
393        ˇtwelve char twelve char
394        twelve char
395    "})
396        .await;
397
398    cx.simulate_shared_keystrokes(["g", "$"]).await;
399    cx.assert_shared_state(indoc! { "
400        twelve charˇ twelve char
401        twelve char
402    "})
403        .await;
404    cx.simulate_shared_keystrokes(["$"]).await;
405    cx.assert_shared_state(indoc! { "
406        twelve char twelve chaˇr
407        twelve char
408    "})
409        .await;
410
411    cx.set_shared_state(indoc! { "
412        tˇwelve char twelve char
413        twelve char
414    "})
415        .await;
416    cx.simulate_shared_keystrokes(["enter"]).await;
417    cx.assert_shared_state(indoc! { "
418            twelve char twelve char
419            ˇtwelve char
420        "})
421        .await;
422
423    cx.set_shared_state(indoc! { "
424        twelve char
425        tˇwelve char twelve char
426        twelve char
427    "})
428        .await;
429    cx.simulate_shared_keystrokes(["o", "o", "escape"]).await;
430    cx.assert_shared_state(indoc! { "
431        twelve char
432        twelve char twelve char
433        ˇo
434        twelve char
435    "})
436        .await;
437
438    cx.set_shared_state(indoc! { "
439        twelve char
440        tˇwelve char twelve char
441        twelve char
442    "})
443        .await;
444    cx.simulate_shared_keystrokes(["shift-a", "a", "escape"])
445        .await;
446    cx.assert_shared_state(indoc! { "
447        twelve char
448        twelve char twelve charˇa
449        twelve char
450    "})
451        .await;
452    cx.simulate_shared_keystrokes(["shift-i", "i", "escape"])
453        .await;
454    cx.assert_shared_state(indoc! { "
455        twelve char
456        ˇitwelve char twelve chara
457        twelve char
458    "})
459        .await;
460    cx.simulate_shared_keystrokes(["shift-d"]).await;
461    cx.assert_shared_state(indoc! { "
462        twelve char
463        ˇ
464        twelve char
465    "})
466        .await;
467
468    cx.set_shared_state(indoc! { "
469        twelve char
470        twelve char tˇwelve char
471        twelve char
472    "})
473        .await;
474    cx.simulate_shared_keystrokes(["shift-o", "o", "escape"])
475        .await;
476    cx.assert_shared_state(indoc! { "
477        twelve char
478        ˇo
479        twelve char twelve char
480        twelve char
481    "})
482        .await;
483
484    // line wraps as:
485    // fourteen ch
486    // ar
487    // fourteen ch
488    // ar
489    cx.set_shared_state(indoc! { "
490        fourteen chaˇr
491        fourteen char
492    "})
493        .await;
494
495    cx.simulate_shared_keystrokes(["d", "i", "w"]).await;
496    cx.assert_shared_state(indoc! {"
497        fourteenˇ•
498        fourteen char
499    "})
500        .await;
501    cx.simulate_shared_keystrokes(["j", "shift-f", "e", "f", "r"])
502        .await;
503    cx.assert_shared_state(indoc! {"
504        fourteen•
505        fourteen chaˇr
506    "})
507        .await;
508}
509
510#[gpui::test]
511async fn test_folds(cx: &mut gpui::TestAppContext) {
512    let mut cx = NeovimBackedTestContext::new(cx).await;
513    cx.set_neovim_option("foldmethod=manual").await;
514
515    cx.set_shared_state(indoc! { "
516        fn boop() {
517          ˇbarp()
518          bazp()
519        }
520    "})
521        .await;
522    cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
523        .await;
524
525    // visual display is now:
526    // fn boop () {
527    //  [FOLDED]
528    // }
529
530    // TODO: this should not be needed but currently zf does not
531    // return to normal mode.
532    cx.simulate_shared_keystrokes(["escape"]).await;
533
534    // skip over fold downward
535    cx.simulate_shared_keystrokes(["g", "g"]).await;
536    cx.assert_shared_state(indoc! { "
537        ˇfn boop() {
538          barp()
539          bazp()
540        }
541    "})
542        .await;
543
544    cx.simulate_shared_keystrokes(["j", "j"]).await;
545    cx.assert_shared_state(indoc! { "
546        fn boop() {
547          barp()
548          bazp()
549        ˇ}
550    "})
551        .await;
552
553    // skip over fold upward
554    cx.simulate_shared_keystrokes(["2", "k"]).await;
555    cx.assert_shared_state(indoc! { "
556        ˇfn boop() {
557          barp()
558          bazp()
559        }
560    "})
561        .await;
562
563    // yank the fold
564    cx.simulate_shared_keystrokes(["down", "y", "y"]).await;
565    cx.assert_shared_clipboard("  barp()\n  bazp()\n").await;
566
567    // re-open
568    cx.simulate_shared_keystrokes(["z", "o"]).await;
569    cx.assert_shared_state(indoc! { "
570        fn boop() {
571        ˇ  barp()
572          bazp()
573        }
574    "})
575        .await;
576}
577
578#[gpui::test]
579async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
580    let mut cx = NeovimBackedTestContext::new(cx).await;
581    cx.set_neovim_option("foldmethod=manual").await;
582
583    cx.set_shared_state(indoc! { "
584        fn boop() {
585          ˇbarp()
586          bazp()
587        }
588    "})
589        .await;
590    cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
591        .await;
592    cx.simulate_shared_keystrokes(["escape"]).await;
593    cx.simulate_shared_keystrokes(["g", "g"]).await;
594    cx.simulate_shared_keystrokes(["5", "d", "j"]).await;
595    cx.assert_shared_state(indoc! { "ˇ"}).await;
596}
597
598#[gpui::test]
599async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
600    let mut cx = NeovimBackedTestContext::new(cx).await;
601
602    cx.set_shared_state(indoc! {"
603        The quick brown
604        fox juˇmps over
605        the lazy dog"})
606        .await;
607
608    cx.simulate_shared_keystrokes(["4", "escape", "3", "d", "l"])
609        .await;
610    cx.assert_shared_state(indoc! {"
611        The quick brown
612        fox juˇ over
613        the lazy dog"})
614        .await;
615}
616
617#[gpui::test]
618async fn test_zero(cx: &mut gpui::TestAppContext) {
619    let mut cx = NeovimBackedTestContext::new(cx).await;
620
621    cx.set_shared_state(indoc! {"
622        The quˇick brown
623        fox jumps over
624        the lazy dog"})
625        .await;
626
627    cx.simulate_shared_keystrokes(["0"]).await;
628    cx.assert_shared_state(indoc! {"
629        ˇThe quick brown
630        fox jumps over
631        the lazy dog"})
632        .await;
633
634    cx.simulate_shared_keystrokes(["1", "0", "l"]).await;
635    cx.assert_shared_state(indoc! {"
636        The quick ˇbrown
637        fox jumps over
638        the lazy dog"})
639        .await;
640}