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}