example_tests.rs

  1//! Tests for the `ExampleEditor` entity.
  2//!
  3//! These use GPUI's test infrastructure which requires the `test-support` feature:
  4//!
  5//! ```sh
  6//! cargo test --example view_example -p gpui --features test-support
  7//! ```
  8
  9#[cfg(test)]
 10mod tests {
 11    use std::time::Duration;
 12
 13    use gpui::{Context, Entity, KeyBinding, TestAppContext, Window, prelude::*};
 14
 15    use crate::example_editor::ExampleEditor;
 16    use crate::example_input::{ExampleInput, ExampleInputState};
 17    use crate::example_render_log::RenderLog;
 18    use crate::example_text_area::ExampleTextArea;
 19    use crate::{Backspace, Delete, End, Enter, Home, Left, Right};
 20
 21    struct InputWrapper {
 22        input_state: Entity<ExampleInputState>,
 23        render_log: Entity<RenderLog>,
 24    }
 25
 26    impl InputWrapper {
 27        fn editor(&self, cx: &gpui::App) -> Entity<ExampleEditor> {
 28            self.input_state.read(cx).editor.clone()
 29        }
 30    }
 31
 32    impl Render for InputWrapper {
 33        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
 34            ExampleInput::new(self.input_state.clone(), self.render_log.clone())
 35        }
 36    }
 37
 38    struct TextAreaWrapper {
 39        editor: Entity<ExampleEditor>,
 40        render_log: Entity<RenderLog>,
 41    }
 42
 43    impl Render for TextAreaWrapper {
 44        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
 45            ExampleTextArea::new(self.editor.clone(), self.render_log.clone(), 5)
 46        }
 47    }
 48
 49    fn bind_keys(cx: &mut TestAppContext) {
 50        cx.update(|cx| {
 51            cx.bind_keys([
 52                KeyBinding::new("backspace", Backspace, None),
 53                KeyBinding::new("delete", Delete, None),
 54                KeyBinding::new("left", Left, None),
 55                KeyBinding::new("right", Right, None),
 56                KeyBinding::new("home", Home, None),
 57                KeyBinding::new("end", End, None),
 58                KeyBinding::new("enter", Enter, None),
 59            ]);
 60        });
 61    }
 62
 63    fn init_input(
 64        cx: &mut TestAppContext,
 65    ) -> (Entity<ExampleEditor>, &mut gpui::VisualTestContext) {
 66        bind_keys(cx);
 67
 68        let (wrapper, cx) = cx.add_window_view(|window, cx| {
 69            let render_log = cx.new(|cx| RenderLog::new(cx));
 70            let input_state = cx.new(|cx| ExampleInputState::new(render_log.clone(), window, cx));
 71            InputWrapper {
 72                input_state,
 73                render_log,
 74            }
 75        });
 76
 77        let editor = cx.read_entity(&wrapper, |wrapper, cx| wrapper.editor(cx));
 78
 79        cx.update(|window, cx| {
 80            let focus_handle = editor.read(cx).focus_handle.clone();
 81            window.focus(&focus_handle, cx);
 82        });
 83
 84        (editor, cx)
 85    }
 86
 87    fn init_textarea(
 88        cx: &mut TestAppContext,
 89    ) -> (Entity<ExampleEditor>, &mut gpui::VisualTestContext) {
 90        bind_keys(cx);
 91
 92        let (wrapper, cx) = cx.add_window_view(|window, cx| {
 93            let editor = cx.new(|cx| ExampleEditor::new(window, cx));
 94            let render_log = cx.new(|cx| RenderLog::new(cx));
 95            TextAreaWrapper { editor, render_log }
 96        });
 97
 98        let editor = cx.read_entity(&wrapper, |wrapper, _cx| wrapper.editor.clone());
 99
100        cx.update(|window, cx| {
101            let focus_handle = editor.read(cx).focus_handle.clone();
102            window.focus(&focus_handle, cx);
103        });
104
105        (editor, cx)
106    }
107
108    #[gpui::test]
109    fn test_typing_and_cursor(cx: &mut TestAppContext) {
110        let (editor, cx) = init_input(cx);
111
112        cx.simulate_input("hello");
113
114        cx.read_entity(&editor, |editor, _cx| {
115            assert_eq!(editor.content, "hello");
116            assert_eq!(editor.cursor, 5);
117        });
118
119        cx.simulate_keystrokes("left left");
120
121        cx.read_entity(&editor, |editor, _cx| {
122            assert_eq!(editor.cursor, 3);
123        });
124
125        cx.simulate_input(" world");
126
127        cx.read_entity(&editor, |editor, _cx| {
128            assert_eq!(editor.content, "hel worldlo");
129            assert_eq!(editor.cursor, 9);
130        });
131    }
132
133    #[gpui::test]
134    fn test_backspace_and_delete(cx: &mut TestAppContext) {
135        let (editor, cx) = init_input(cx);
136
137        cx.simulate_input("abcde");
138
139        cx.simulate_keystrokes("backspace");
140        cx.read_entity(&editor, |editor, _cx| {
141            assert_eq!(editor.content, "abcd");
142            assert_eq!(editor.cursor, 4);
143        });
144
145        cx.simulate_keystrokes("home delete");
146        cx.read_entity(&editor, |editor, _cx| {
147            assert_eq!(editor.content, "bcd");
148            assert_eq!(editor.cursor, 0);
149        });
150
151        // Boundary no-ops
152        cx.simulate_keystrokes("backspace");
153        cx.read_entity(&editor, |editor, _cx| {
154            assert_eq!(editor.content, "bcd");
155        });
156
157        cx.simulate_keystrokes("end delete");
158        cx.read_entity(&editor, |editor, _cx| {
159            assert_eq!(editor.content, "bcd");
160        });
161    }
162
163    #[gpui::test]
164    fn test_cursor_blink(cx: &mut TestAppContext) {
165        let (editor, cx) = init_input(cx);
166
167        // Typing calls reset_blink(), which makes cursor visible and
168        // restarts the blink timer.
169        cx.simulate_input("a");
170
171        cx.read_entity(&editor, |editor, _cx| {
172            assert!(
173                editor.cursor_visible,
174                "cursor should be visible after typing"
175            );
176        });
177
178        // After 500ms the blink task toggles it off.
179        cx.background_executor
180            .advance_clock(Duration::from_millis(500));
181        cx.run_until_parked();
182
183        cx.read_entity(&editor, |editor, _cx| {
184            assert!(!editor.cursor_visible, "cursor should have blinked off");
185        });
186
187        // Typing again resets the blink.
188        cx.simulate_input("b");
189
190        cx.read_entity(&editor, |editor, _cx| {
191            assert!(
192                editor.cursor_visible,
193                "cursor should be visible after typing again"
194            );
195        });
196    }
197
198    #[gpui::test]
199    fn test_enter_does_not_insert_in_input(cx: &mut TestAppContext) {
200        let (editor, cx) = init_input(cx);
201
202        cx.simulate_input("hello");
203        cx.simulate_keystrokes("enter");
204
205        cx.read_entity(&editor, |editor, _cx| {
206            assert_eq!(
207                editor.content, "hello",
208                "Enter should not insert text in Input"
209            );
210            assert_eq!(editor.cursor, 5);
211        });
212    }
213
214    #[gpui::test]
215    fn test_enter_inserts_newline_in_textarea(cx: &mut TestAppContext) {
216        let (editor, cx) = init_textarea(cx);
217
218        cx.simulate_input("ab");
219        cx.simulate_keystrokes("enter");
220        cx.simulate_input("cd");
221
222        cx.read_entity(&editor, |editor, _cx| {
223            assert_eq!(editor.content, "ab\ncd");
224            assert_eq!(editor.cursor, 5);
225        });
226    }
227
228    #[gpui::test]
229    fn test_enter_at_start_of_textarea(cx: &mut TestAppContext) {
230        let (editor, cx) = init_textarea(cx);
231
232        cx.simulate_keystrokes("enter");
233        cx.simulate_input("hello");
234
235        cx.read_entity(&editor, |editor, _cx| {
236            assert_eq!(editor.content, "\nhello");
237            assert_eq!(editor.cursor, 6);
238        });
239    }
240}