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 text_views -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;
17 use crate::example_text_area::ExampleTextArea;
18 use crate::{Backspace, Delete, End, Enter, Home, Left, Right};
19
20 struct InputWrapper {
21 editor: Entity<ExampleEditor>,
22 }
23
24 impl Render for InputWrapper {
25 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
26 ExampleInput::new(self.editor.clone())
27 }
28 }
29
30 struct TextAreaWrapper {
31 editor: Entity<ExampleEditor>,
32 }
33
34 impl Render for TextAreaWrapper {
35 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
36 ExampleTextArea::new(self.editor.clone(), 5)
37 }
38 }
39
40 fn bind_keys(cx: &mut TestAppContext) {
41 cx.update(|cx| {
42 cx.bind_keys([
43 KeyBinding::new("backspace", Backspace, None),
44 KeyBinding::new("delete", Delete, None),
45 KeyBinding::new("left", Left, None),
46 KeyBinding::new("right", Right, None),
47 KeyBinding::new("home", Home, None),
48 KeyBinding::new("end", End, None),
49 KeyBinding::new("enter", Enter, None),
50 ]);
51 });
52 }
53
54 fn init_input(
55 cx: &mut TestAppContext,
56 ) -> (Entity<ExampleEditor>, &mut gpui::VisualTestContext) {
57 bind_keys(cx);
58
59 let (wrapper, cx) = cx.add_window_view(|_window, cx| {
60 let editor = cx.new(|cx| ExampleEditor::new(cx));
61 InputWrapper { editor }
62 });
63
64 let editor = cx.read_entity(&wrapper, |wrapper, _cx| wrapper.editor.clone());
65
66 cx.update(|window, cx| {
67 let focus_handle = editor.read(cx).focus_handle.clone();
68 window.focus(&focus_handle, cx);
69 });
70
71 (editor, cx)
72 }
73
74 fn init_textarea(
75 cx: &mut TestAppContext,
76 ) -> (Entity<ExampleEditor>, &mut gpui::VisualTestContext) {
77 bind_keys(cx);
78
79 let (wrapper, cx) = cx.add_window_view(|_window, cx| {
80 let editor = cx.new(|cx| ExampleEditor::new(cx));
81 TextAreaWrapper { editor }
82 });
83
84 let editor = cx.read_entity(&wrapper, |wrapper, _cx| wrapper.editor.clone());
85
86 cx.update(|window, cx| {
87 let focus_handle = editor.read(cx).focus_handle.clone();
88 window.focus(&focus_handle, cx);
89 });
90
91 (editor, cx)
92 }
93
94 #[gpui::test]
95 fn test_typing_and_cursor(cx: &mut TestAppContext) {
96 let (editor, cx) = init_input(cx);
97
98 cx.simulate_input("hello");
99
100 cx.read_entity(&editor, |editor, _cx| {
101 assert_eq!(editor.content, "hello");
102 assert_eq!(editor.cursor, 5);
103 });
104
105 cx.simulate_keystrokes("left left");
106
107 cx.read_entity(&editor, |editor, _cx| {
108 assert_eq!(editor.cursor, 3);
109 });
110
111 cx.simulate_input(" world");
112
113 cx.read_entity(&editor, |editor, _cx| {
114 assert_eq!(editor.content, "hel worldlo");
115 assert_eq!(editor.cursor, 9);
116 });
117 }
118
119 #[gpui::test]
120 fn test_backspace_and_delete(cx: &mut TestAppContext) {
121 let (editor, cx) = init_input(cx);
122
123 cx.simulate_input("abcde");
124
125 cx.simulate_keystrokes("backspace");
126 cx.read_entity(&editor, |editor, _cx| {
127 assert_eq!(editor.content, "abcd");
128 assert_eq!(editor.cursor, 4);
129 });
130
131 cx.simulate_keystrokes("home delete");
132 cx.read_entity(&editor, |editor, _cx| {
133 assert_eq!(editor.content, "bcd");
134 assert_eq!(editor.cursor, 0);
135 });
136
137 // Boundary no-ops
138 cx.simulate_keystrokes("backspace");
139 cx.read_entity(&editor, |editor, _cx| {
140 assert_eq!(editor.content, "bcd");
141 });
142
143 cx.simulate_keystrokes("end delete");
144 cx.read_entity(&editor, |editor, _cx| {
145 assert_eq!(editor.content, "bcd");
146 });
147 }
148
149 #[gpui::test]
150 fn test_cursor_blink(cx: &mut TestAppContext) {
151 let (editor, cx) = init_input(cx);
152
153 cx.read_entity(&editor, |editor, _cx| {
154 assert!(editor.cursor_visible);
155 });
156
157 cx.background_executor
158 .advance_clock(Duration::from_millis(500));
159 cx.run_until_parked();
160
161 cx.read_entity(&editor, |editor, _cx| {
162 assert!(!editor.cursor_visible);
163 });
164
165 // Typing resets the blink.
166 cx.simulate_input("a");
167
168 cx.read_entity(&editor, |editor, _cx| {
169 assert!(editor.cursor_visible);
170 });
171 }
172
173 #[gpui::test]
174 fn test_enter_does_not_insert_in_input(cx: &mut TestAppContext) {
175 let (editor, cx) = init_input(cx);
176
177 cx.simulate_input("hello");
178 cx.simulate_keystrokes("enter");
179
180 cx.read_entity(&editor, |editor, _cx| {
181 assert_eq!(
182 editor.content, "hello",
183 "Enter should not insert text in Input"
184 );
185 assert_eq!(editor.cursor, 5);
186 });
187 }
188
189 #[gpui::test]
190 fn test_enter_inserts_newline_in_textarea(cx: &mut TestAppContext) {
191 let (editor, cx) = init_textarea(cx);
192
193 cx.simulate_input("ab");
194 cx.simulate_keystrokes("enter");
195 cx.simulate_input("cd");
196
197 cx.read_entity(&editor, |editor, _cx| {
198 assert_eq!(editor.content, "ab\ncd");
199 assert_eq!(editor.cursor, 5);
200 });
201 }
202
203 #[gpui::test]
204 fn test_enter_at_start_of_textarea(cx: &mut TestAppContext) {
205 let (editor, cx) = init_textarea(cx);
206
207 cx.simulate_keystrokes("enter");
208 cx.simulate_input("hello");
209
210 cx.read_entity(&editor, |editor, _cx| {
211 assert_eq!(editor.content, "\nhello");
212 assert_eq!(editor.cursor, 6);
213 });
214 }
215}