test_helpers/
test_helpers.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::{format_err, Result};
6use async_trait::async_trait;
7use futures::stream::{self, StreamExt, TryStreamExt};
8use futures::FutureExt;
9use {
10    fidl_fuchsia_input as input, fidl_fuchsia_ui_input as ui_input,
11    fidl_fuchsia_ui_input3 as ui_input3,
12};
13
14pub fn default_state() -> ui_input::TextInputState {
15    ui_input::TextInputState {
16        revision: 1,
17        text: "".to_string(),
18        selection: ui_input::TextSelection {
19            base: 0,
20            extent: 0,
21            affinity: ui_input::TextAffinity::Upstream,
22        },
23        composing: ui_input::TextRange { start: -1, end: -1 },
24    }
25}
26
27// Measure text in utf16 code units.
28pub fn measure_utf16(s: &str) -> usize {
29    s.chars().map(|c| c.len_utf16()).sum::<usize>()
30}
31
32// Setup IME text edit state using contents and selection indices.
33pub async fn setup_ime(
34    ime: &ui_input::InputMethodEditorProxy,
35    text: &str,
36    base: i64,
37    extent: i64,
38) -> Result<()> {
39    let mut state = default_state();
40    state.text = text.to_string();
41    state.selection.base = base;
42    state.selection.extent = extent;
43
44    ime.set_state(&state).map_err(Into::into)
45}
46
47// Bind a new IME to the service.
48pub fn bind_editor(
49    ime_service: &ui_input::ImeServiceProxy,
50) -> Result<(ui_input::InputMethodEditorProxy, ui_input::InputMethodEditorClientRequestStream)> {
51    let (ime, ime_server_end) =
52        fidl::endpoints::create_proxy::<ui_input::InputMethodEditorMarker>();
53    let (editor_client_end, editor_request_stream) = fidl::endpoints::create_request_stream();
54    ime_service.get_input_method_editor(
55        ui_input::KeyboardType::Text,
56        ui_input::InputMethodAction::Done,
57        &default_state(),
58        editor_client_end,
59        ime_server_end,
60    )?;
61
62    Ok((ime, editor_request_stream))
63}
64
65/// Provides a method for dispatching keys, in case the tests need to vary
66/// them.
67#[async_trait]
68pub trait KeyDispatcher {
69    async fn dispatch(&self, event: ui_input3::KeyEvent) -> Result<bool>;
70}
71
72/// A [KeyDispatcher] that uses `fuchsia.ui.input.InputMethodEditor` to dispatch keypresses.
73pub struct InputMethodEditorDispatcher<'a> {
74    pub ime: &'a ui_input::InputMethodEditorProxy,
75}
76
77#[async_trait]
78impl<'a> KeyDispatcher for InputMethodEditorDispatcher<'a> {
79    async fn dispatch(&self, event: ui_input3::KeyEvent) -> Result<bool> {
80        Ok(self.ime.dispatch_key3(&event).await?)
81    }
82}
83
84/// A [KeyDispatcher] that uses `fuchsia.ui.input3.KeyEventInjector` to dispatch keypresses.
85pub struct KeyEventInjectorDispatcher<'a> {
86    pub key_event_injector: &'a ui_input3::KeyEventInjectorProxy,
87}
88
89#[async_trait]
90impl<'a> KeyDispatcher for KeyEventInjectorDispatcher<'a> {
91    async fn dispatch(&self, event: ui_input3::KeyEvent) -> Result<bool> {
92        Ok(self.key_event_injector.inject(&event).await? == ui_input3::KeyEventStatus::Handled)
93    }
94}
95
96/// A fixture that can use different services to send key events.  Use [KeySimulator::new] to
97/// create a new instance.
98///
99/// # Example
100///
101/// ```ignore
102/// let ime_service = connect_to_protocol::<ui_input::ImeServiceMarker>()
103///     .context("Failed to connect to IME Service")?;
104/// let key_dispatcher = test_helpers::ImeServiceKeyDispatcher { ime_service: &ime_service };
105/// let key_simulator = test_helpers::KeySimulator::new(&key_dispatcher);
106/// // Thereafter...
107/// key_simulator.dispatch(fidl_fuchsia_ui_input3::KeyEvent{...}).await?;
108/// ```
109pub struct KeySimulator<'a> {
110    dispatcher: &'a dyn KeyDispatcher,
111}
112
113impl<'a> KeySimulator<'a> {
114    pub fn new(dispatcher: &'a dyn KeyDispatcher) -> Self {
115        KeySimulator { dispatcher }
116    }
117
118    pub async fn dispatch(&self, event: ui_input3::KeyEvent) -> Result<bool> {
119        self.dispatcher.dispatch(event).await
120    }
121
122    // Simulate a key press and release of `key`.
123    async fn simulate_keypress(&self, key: input::Key) -> Result<()> {
124        self.dispatch(ui_input3::KeyEvent {
125            timestamp: Some(0),
126            type_: Some(ui_input3::KeyEventType::Pressed),
127            key: Some(key),
128            ..Default::default()
129        })
130        .await?;
131        self.dispatch(ui_input3::KeyEvent {
132            timestamp: Some(0),
133            type_: Some(ui_input3::KeyEventType::Released),
134            key: Some(key),
135            ..Default::default()
136        })
137        .await?;
138        Ok(())
139    }
140
141    // Simulate keypress with `held_keys` pressed down.
142    async fn simulate_ime_keypress_with_held_keys(
143        &self,
144        key: input::Key,
145        held_keys: Vec<input::Key>,
146    ) {
147        let held_keys_down =
148            held_keys.iter().map(|k| (ui_input3::KeyEventType::Pressed, *k)).into_iter();
149        let held_keys_up =
150            held_keys.iter().map(|k| (ui_input3::KeyEventType::Released, *k)).into_iter();
151        let key_press_and_release =
152            vec![(ui_input3::KeyEventType::Pressed, key), (ui_input3::KeyEventType::Released, key)]
153                .into_iter();
154        let sequence = held_keys_down.chain(key_press_and_release).chain(held_keys_up);
155        stream::iter(sequence)
156            .for_each(|(type_, key)| {
157                self.dispatch(ui_input3::KeyEvent {
158                    timestamp: Some(0),
159                    type_: Some(type_),
160                    key: Some(key),
161                    ..Default::default()
162                })
163                .map(|_| ())
164            })
165            .await;
166    }
167}
168
169// Simulate keypress by injecting an event into supplied key event injector.
170pub async fn simulate_keypress(
171    key_event_injector: &ui_input3::KeyEventInjectorProxy,
172    key: input::Key,
173) -> Result<()> {
174    let key_dispatcher = KeyEventInjectorDispatcher { key_event_injector: &key_event_injector };
175    let key_simulator = KeySimulator::new(&key_dispatcher);
176    key_simulator.simulate_keypress(key).await
177}
178
179// Simulate keypress by injecting into IME.
180pub async fn simulate_ime_keypress(ime: &ui_input::InputMethodEditorProxy, key: input::Key) {
181    simulate_ime_keypress_with_held_keys(ime, key, Vec::new()).await
182}
183
184// Simulate keypress by injecting into IME, with `held_keys` pressed down.
185pub async fn simulate_ime_keypress_with_held_keys(
186    ime: &ui_input::InputMethodEditorProxy,
187    key: input::Key,
188    held_keys: Vec<input::Key>,
189) {
190    let key_dispatcher = InputMethodEditorDispatcher { ime: &ime };
191    let key_simulator = KeySimulator::new(&key_dispatcher);
192    key_simulator.simulate_ime_keypress_with_held_keys(key, held_keys).await
193}
194
195// Get next IME message, assuming it's a `InputMethodEditorClientRequest::DidUpdateState`.
196pub async fn get_state_update(
197    editor_stream: &mut ui_input::InputMethodEditorClientRequestStream,
198) -> Result<(ui_input::TextInputState, Option<ui_input::KeyboardEvent>)> {
199    editor_stream
200        .map(|request| match request {
201            Ok(ui_input::InputMethodEditorClientRequest::DidUpdateState {
202                state, event, ..
203            }) => {
204                let keyboard_event = event.map(|e| {
205                    if let ui_input::InputEvent::Keyboard(keyboard_event) = *e {
206                        keyboard_event
207                    } else {
208                        panic!("expected DidUpdateState to only send Keyboard events");
209                    }
210                });
211                Ok((state, keyboard_event))
212            }
213            Ok(msg) => Err(format_err!("request should be DidUpdateState, got {:?}", msg)),
214            Err(err) => Err(Into::into(err)),
215        })
216        .try_next()
217        .await
218        .map(|maybe_msg| maybe_msg.ok_or(format_err!("ime should have sent message")))?
219}
220
221// Get next IME message, assuming it's a `InputMethodEditorClientRequest::OnAction`.
222pub async fn get_action(
223    editor_stream: &mut ui_input::InputMethodEditorClientRequestStream,
224) -> Result<ui_input::InputMethodAction> {
225    editor_stream
226        .map(|request| match request {
227            Ok(ui_input::InputMethodEditorClientRequest::OnAction { action, .. }) => Ok(action),
228            Ok(msg) => Err(format_err!("request should be OnAction, got {:?}", msg)),
229            Err(err) => Err(Into::into(err)),
230        })
231        .try_next()
232        .await
233        .map(|maybe_msg| maybe_msg.ok_or(format_err!("ime should have sent message")))?
234}
235
236/// Used to reduce verbosity of instantiating `KeyMeaning`s.
237pub struct KeyMeaningWrapper(Option<ui_input3::KeyMeaning>);
238
239impl From<ui_input3::KeyMeaning> for KeyMeaningWrapper {
240    fn from(src: ui_input3::KeyMeaning) -> Self {
241        KeyMeaningWrapper(src.into())
242    }
243}
244
245impl From<Option<ui_input3::KeyMeaning>> for KeyMeaningWrapper {
246    fn from(src: Option<ui_input3::KeyMeaning>) -> Self {
247        KeyMeaningWrapper(src)
248    }
249}
250
251impl From<KeyMeaningWrapper> for Option<ui_input3::KeyMeaning> {
252    fn from(src: KeyMeaningWrapper) -> Self {
253        src.0
254    }
255}
256
257impl From<char> for KeyMeaningWrapper {
258    fn from(src: char) -> Self {
259        Some(ui_input3::KeyMeaning::Codepoint(src as u32)).into()
260    }
261}
262
263impl From<ui_input3::NonPrintableKey> for KeyMeaningWrapper {
264    fn from(src: ui_input3::NonPrintableKey) -> Self {
265        Some(ui_input3::KeyMeaning::NonPrintableKey(src)).into()
266    }
267}
268
269/// Creates a `KeyEvent` with the given parameters.
270pub fn create_key_event(
271    timestamp: zx::MonotonicInstant,
272    event_type: ui_input3::KeyEventType,
273    key: impl Into<Option<input::Key>>,
274    modifiers: impl Into<Option<ui_input3::Modifiers>>,
275    key_meaning: impl Into<KeyMeaningWrapper>,
276) -> ui_input3::KeyEvent {
277    let key_meaning: KeyMeaningWrapper = key_meaning.into();
278    ui_input3::KeyEvent {
279        timestamp: Some(timestamp.into_nanos()),
280        type_: Some(event_type),
281        key: key.into(),
282        modifiers: modifiers.into(),
283        key_meaning: key_meaning.into(),
284        ..Default::default()
285    }
286}