Skip to main content

gpui/platform/test/
platform.rs

1use crate::{
2    ActivityGuard, AnyWindowHandle, AppLifecyclePhase, BackgroundExecutor, ClipboardItem,
3    CursorStyle, DevicePixels, DisplayId, DummyKeyboardMapper, ForegroundExecutor, Keymap,
4    OwnedMenu, Platform, PlatformDisplay, PlatformHeadlessRenderer, PlatformKeyboardLayout,
5    PlatformKeyboardMapper, PlatformTextSystem, PromptButton, ScreenCaptureFrame,
6    ScreenCaptureSource, ScreenCaptureStream, SharedString, SourceMetadata, SystemNotification,
7    SystemNotificationResponse, Task, TestDisplay, TestWindow, ThermalState, WindowAppearance,
8    WindowParams, size,
9};
10#[cfg(any(test, feature = "test-support"))]
11use crate::{PathPromptOptions, TestTextSystem};
12use anyhow::Result;
13#[cfg(any(test, feature = "test-support"))]
14use collections::VecDeque;
15use futures::channel::oneshot;
16use parking_lot::Mutex;
17use std::{
18    cell::{Cell, RefCell},
19    path::{Path, PathBuf},
20    rc::{Rc, Weak},
21    sync::{
22        Arc,
23        atomic::{AtomicUsize, Ordering},
24    },
25    time::Duration,
26};
27
28/// TestPlatform implements the Platform trait for use in tests.
29pub(crate) struct TestPlatform {
30    background_executor: BackgroundExecutor,
31    foreground_executor: ForegroundExecutor,
32
33    pub(crate) active_window: RefCell<Option<TestWindow>>,
34    displays: RefCell<Vec<TestDisplay>>,
35    displays_changed_callback: RefCell<Option<Box<dyn FnMut()>>>,
36    active_cursor: Mutex<CursorStyle>,
37    current_clipboard_item: Mutex<Option<ClipboardItem>>,
38    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
39    current_primary_item: Mutex<Option<ClipboardItem>>,
40    #[cfg(target_os = "macos")]
41    current_find_pasteboard_item: Mutex<Option<ClipboardItem>>,
42    #[cfg(any(test, feature = "test-support"))]
43    pub(crate) prompts: RefCell<TestPrompts>,
44    screen_capture_sources: RefCell<Vec<TestScreenCaptureSource>>,
45    pub opened_url: RefCell<Option<String>>,
46    pub(crate) system_notifications: RefCell<TestSystemNotifications>,
47    pub text_system: Arc<dyn PlatformTextSystem>,
48    pub expect_restart:
49        RefCell<Option<oneshot::Sender<(Option<PathBuf>, Vec<std::ffi::OsString>)>>>,
50    idle_sleep_prevention_count: Arc<AtomicUsize>,
51    idle_sleep_prevention_delay: Cell<Duration>,
52    idle_sleep_prevention_fails: Cell<bool>,
53    headless_renderer_factory:
54        Option<Box<dyn Fn() -> anyhow::Result<Option<Box<dyn PlatformHeadlessRenderer>>>>>,
55    weak: Weak<Self>,
56    menus: RefCell<Vec<OwnedMenu>>,
57    pub(crate) app_lifecycle_callback: RefCell<Option<Box<dyn FnMut(AppLifecyclePhase)>>>,
58    pub(crate) memory_warning_callback: RefCell<Option<Box<dyn FnMut()>>>,
59}
60
61/// A fake screen capture source for tests. It reports a 1x1 resolution and sends no frames.
62#[derive(Clone)]
63pub struct TestScreenCaptureSource {}
64
65/// A fake screen capture stream for tests. It reports the metadata of [`TestScreenCaptureSource`].
66pub struct TestScreenCaptureStream {}
67
68impl ScreenCaptureSource for TestScreenCaptureSource {
69    fn metadata(&self) -> Result<SourceMetadata> {
70        Ok(SourceMetadata {
71            id: 0,
72            is_main: None,
73            label: None,
74            resolution: size(DevicePixels(1), DevicePixels(1)),
75        })
76    }
77
78    fn stream(
79        &self,
80        _foreground_executor: &ForegroundExecutor,
81        _frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
82    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
83        let (mut tx, rx) = oneshot::channel();
84        let stream = TestScreenCaptureStream {};
85        tx.send(Ok(Box::new(stream) as Box<dyn ScreenCaptureStream>))
86            .ok();
87        rx
88    }
89}
90
91impl ScreenCaptureStream for TestScreenCaptureStream {
92    fn metadata(&self) -> Result<SourceMetadata> {
93        TestScreenCaptureSource {}.metadata()
94    }
95}
96
97#[cfg(any(test, feature = "test-support"))]
98struct TestPrompt {
99    msg: String,
100    detail: Option<String>,
101    answers: Vec<String>,
102    tx: oneshot::Sender<usize>,
103}
104
105#[derive(Default)]
106pub(crate) struct TestSystemNotifications {
107    pub(crate) app_identity: Option<(SharedString, SharedString)>,
108    pub(crate) shown: Vec<SystemNotification>,
109    pub(crate) delivered: Vec<SystemNotification>,
110    pub(crate) dismissed: Vec<SharedString>,
111    response_callback: Option<Box<dyn FnMut(SystemNotificationResponse)>>,
112}
113
114#[cfg(any(test, feature = "test-support"))]
115#[derive(Default)]
116pub(crate) struct TestPrompts {
117    multiple_choice: VecDeque<TestPrompt>,
118    new_path: VecDeque<(PathBuf, oneshot::Sender<Result<Option<PathBuf>>>)>,
119    paths: VecDeque<(
120        PathPromptOptions,
121        oneshot::Sender<Result<Option<Vec<PathBuf>>>>,
122    )>,
123}
124
125impl TestPlatform {
126    pub(crate) fn cursor_style(&self) -> CursorStyle {
127        *self.active_cursor.lock()
128    }
129
130    #[cfg(any(test, feature = "test-support"))]
131    pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Rc<Self> {
132        Self::with_platform(
133            executor,
134            foreground_executor,
135            Arc::new(TestTextSystem),
136            None,
137        )
138    }
139
140    #[cfg(any(test, feature = "test-support"))]
141    pub fn with_text_system(
142        executor: BackgroundExecutor,
143        foreground_executor: ForegroundExecutor,
144        text_system: Arc<dyn PlatformTextSystem>,
145    ) -> Rc<Self> {
146        Self::with_platform(executor, foreground_executor, text_system, None)
147    }
148
149    pub(crate) fn simulate_display_added(
150        &self,
151        display_id: DisplayId,
152        refresh_interval: Option<Duration>,
153    ) {
154        self.connect_display(display_id, refresh_interval);
155        self.report_displays_changed();
156    }
157
158    /// Adds a display without telling App yet, as when the platform's
159    /// notifications about a window arrive before those about displays.
160    pub(crate) fn connect_display(
161        &self,
162        display_id: DisplayId,
163        refresh_interval: Option<Duration>,
164    ) {
165        let mut display = TestDisplay::with_id(display_id);
166        display.refresh_interval = refresh_interval;
167        self.displays.borrow_mut().push(display);
168    }
169
170    pub(crate) fn simulate_display_removed(&self, display_id: DisplayId) {
171        self.displays
172            .borrow_mut()
173            .retain(|display| display.id() != display_id);
174        self.report_displays_changed();
175    }
176
177    pub(crate) fn simulate_display_refresh_interval_change(
178        &self,
179        display_id: DisplayId,
180        refresh_interval: Option<Duration>,
181    ) {
182        for display in self.displays.borrow_mut().iter_mut() {
183            if display.id() == display_id {
184                display.refresh_interval = refresh_interval;
185            }
186        }
187        self.report_displays_changed();
188    }
189
190    fn report_displays_changed(&self) {
191        let callback = self.displays_changed_callback.borrow_mut().take();
192        if let Some(mut callback) = callback {
193            callback();
194            self.displays_changed_callback
195                .borrow_mut()
196                .get_or_insert(callback);
197        }
198    }
199
200    pub fn with_platform(
201        executor: BackgroundExecutor,
202        foreground_executor: ForegroundExecutor,
203        text_system: Arc<dyn PlatformTextSystem>,
204        headless_renderer_factory: Option<
205            Box<dyn Fn() -> anyhow::Result<Option<Box<dyn PlatformHeadlessRenderer>>>>,
206        >,
207    ) -> Rc<Self> {
208        Rc::new_cyclic(|weak| TestPlatform {
209            background_executor: executor,
210            foreground_executor,
211            #[cfg(any(test, feature = "test-support"))]
212            prompts: Default::default(),
213            screen_capture_sources: Default::default(),
214            active_cursor: Default::default(),
215            displays: RefCell::new(vec![TestDisplay::new()]),
216            displays_changed_callback: Default::default(),
217            active_window: Default::default(),
218            expect_restart: Default::default(),
219            current_clipboard_item: Mutex::new(None),
220            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
221            current_primary_item: Mutex::new(None),
222            #[cfg(target_os = "macos")]
223            current_find_pasteboard_item: Mutex::new(None),
224            idle_sleep_prevention_count: Arc::new(AtomicUsize::new(0)),
225            idle_sleep_prevention_delay: Cell::new(Duration::ZERO),
226            idle_sleep_prevention_fails: Cell::new(false),
227            weak: weak.clone(),
228            opened_url: Default::default(),
229            system_notifications: Default::default(),
230            text_system,
231            headless_renderer_factory,
232            menus: Default::default(),
233            app_lifecycle_callback: Default::default(),
234            memory_warning_callback: Default::default(),
235        })
236    }
237
238    #[cfg(any(test, feature = "test-support"))]
239    pub(crate) fn simulate_new_path_selection(
240        &self,
241        select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
242    ) {
243        let (path, tx) = self
244            .prompts
245            .borrow_mut()
246            .new_path
247            .pop_front()
248            .expect("no pending new path prompt");
249        tx.send(Ok(select_path(&path))).ok();
250    }
251
252    #[cfg(any(test, feature = "test-support"))]
253    pub(crate) fn simulate_path_prompt_response(
254        &self,
255        select_paths: impl FnOnce(&PathPromptOptions) -> Option<Vec<std::path::PathBuf>>,
256    ) {
257        let (options, tx) = self
258            .prompts
259            .borrow_mut()
260            .paths
261            .pop_front()
262            .expect("no pending paths prompt");
263        let selection = select_paths(&options);
264        if let Some(paths) = &selection
265            && !options.multiple
266            && paths.len() > 1
267        {
268            panic!(
269                "selected {} paths for a prompt that does not allow multiple selection",
270                paths.len()
271            );
272        }
273        tx.send(Ok(selection)).ok();
274    }
275
276    #[cfg(any(test, feature = "test-support"))]
277    pub(crate) fn did_prompt_for_paths(&self) -> bool {
278        !self.prompts.borrow().paths.is_empty()
279    }
280
281    #[cfg(any(test, feature = "test-support"))]
282    #[track_caller]
283    pub(crate) fn simulate_prompt_answer(&self, response: &str) {
284        let prompt = self
285            .prompts
286            .borrow_mut()
287            .multiple_choice
288            .pop_front()
289            .expect("no pending multiple choice prompt");
290        let Some(ix) = prompt.answers.iter().position(|a| a == response) else {
291            panic!(
292                "PROMPT: {}\n{:?}\n{:?}\nCannot respond with {}",
293                prompt.msg, prompt.detail, prompt.answers, response
294            )
295        };
296        prompt.tx.send(ix).ok();
297    }
298
299    #[cfg(any(test, feature = "test-support"))]
300    pub(crate) fn has_pending_prompt(&self) -> bool {
301        !self.prompts.borrow().multiple_choice.is_empty()
302    }
303
304    #[cfg(any(test, feature = "test-support"))]
305    pub(crate) fn pending_prompt(&self) -> Option<(String, String)> {
306        let prompts = self.prompts.borrow();
307        let prompt = prompts.multiple_choice.front()?;
308        Some((
309            prompt.msg.clone(),
310            prompt.detail.clone().unwrap_or_default(),
311        ))
312    }
313
314    #[cfg(any(test, feature = "test-support"))]
315    pub(crate) fn set_screen_capture_sources(&self, sources: Vec<TestScreenCaptureSource>) {
316        *self.screen_capture_sources.borrow_mut() = sources;
317    }
318
319    /// Queues the prompt so a test can later inspect or answer it through
320    /// [`Self::pending_prompt`] and [`Self::simulate_prompt_answer`].
321    #[cfg(any(test, feature = "test-support"))]
322    pub(crate) fn prompt(
323        &self,
324        msg: &str,
325        detail: Option<&str>,
326        answers: &[PromptButton],
327    ) -> oneshot::Receiver<usize> {
328        let (tx, rx) = oneshot::channel();
329        let answers: Vec<String> = answers.iter().map(|s| s.label().to_string()).collect();
330        self.prompts
331            .borrow_mut()
332            .multiple_choice
333            .push_back(TestPrompt {
334                msg: msg.to_string(),
335                detail: detail.map(|s| s.to_string()),
336                answers,
337                tx,
338            });
339        rx
340    }
341
342    /// Benchmarks have no API to answer a prompt, so this doesn't retain it
343    /// for later inspection; dropping the sender immediately cancels the
344    /// returned receiver instead of leaving it pending indefinitely.
345    #[cfg(not(any(test, feature = "test-support")))]
346    pub(crate) fn prompt(
347        &self,
348        _msg: &str,
349        _detail: Option<&str>,
350        _answers: &[PromptButton],
351    ) -> oneshot::Receiver<usize> {
352        oneshot::channel().1
353    }
354
355    pub(crate) fn set_active_window(&self, window: Option<TestWindow>) {
356        let executor = self.foreground_executor();
357        let previous_window = self.active_window.borrow_mut().take();
358        self.active_window.borrow_mut().clone_from(&window);
359
360        executor
361            .spawn(async move {
362                if let Some(previous_window) = previous_window {
363                    if let Some(window) = window.as_ref()
364                        && Rc::ptr_eq(&previous_window.0, &window.0)
365                    {
366                        return;
367                    }
368                    previous_window.simulate_active_status_change(false);
369                }
370                if let Some(window) = window {
371                    window.simulate_active_status_change(true);
372                }
373            })
374            .detach();
375    }
376
377    #[cfg(any(test, feature = "test-support"))]
378    pub(crate) fn did_prompt_for_new_path(&self) -> bool {
379        !self.prompts.borrow().new_path.is_empty()
380    }
381
382    #[cfg(any(test, feature = "test-support"))]
383    pub(crate) fn active_idle_sleep_preventions(&self) -> usize {
384        self.idle_sleep_prevention_count.load(Ordering::SeqCst)
385    }
386
387    #[cfg(any(test, feature = "test-support"))]
388    pub(crate) fn set_idle_sleep_prevention_delay(&self, delay: Duration) {
389        self.idle_sleep_prevention_delay.set(delay);
390    }
391
392    #[cfg(any(test, feature = "test-support"))]
393    pub(crate) fn set_idle_sleep_prevention_fails(&self, fails: bool) {
394        self.idle_sleep_prevention_fails.set(fails);
395    }
396
397    #[cfg(any(test, feature = "test-support"))]
398    pub(crate) fn app_identity(&self) -> Option<(SharedString, SharedString)> {
399        self.system_notifications.borrow().app_identity.clone()
400    }
401
402    #[cfg(any(test, feature = "test-support"))]
403    pub(crate) fn shown_system_notifications(&self) -> Vec<SystemNotification> {
404        self.system_notifications.borrow().shown.clone()
405    }
406
407    #[cfg(any(test, feature = "test-support"))]
408    pub(crate) fn delivered_system_notifications(&self) -> Vec<SystemNotification> {
409        self.system_notifications.borrow().delivered.clone()
410    }
411
412    #[cfg(any(test, feature = "test-support"))]
413    pub(crate) fn dismissed_system_notifications(&self) -> Vec<SharedString> {
414        self.system_notifications.borrow().dismissed.clone()
415    }
416
417    #[cfg(any(test, feature = "test-support"))]
418    pub(crate) fn simulate_system_notification_response(
419        &self,
420        response: SystemNotificationResponse,
421    ) {
422        let callback = self
423            .system_notifications
424            .borrow_mut()
425            .response_callback
426            .take();
427        if let Some(mut callback) = callback {
428            callback(response);
429            self.system_notifications
430                .borrow_mut()
431                .response_callback
432                .get_or_insert(callback);
433        }
434    }
435}
436
437impl Platform for TestPlatform {
438    fn background_executor(&self) -> BackgroundExecutor {
439        self.background_executor.clone()
440    }
441
442    fn foreground_executor(&self) -> ForegroundExecutor {
443        self.foreground_executor.clone()
444    }
445
446    fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
447        self.text_system.clone()
448    }
449
450    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
451        Box::new(TestKeyboardLayout)
452    }
453
454    fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
455        Rc::new(DummyKeyboardMapper)
456    }
457
458    fn on_keyboard_layout_change(&self, _: Box<dyn FnMut()>) {}
459
460    fn on_thermal_state_change(&self, _: Box<dyn FnMut()>) {}
461
462    fn thermal_state(&self) -> ThermalState {
463        ThermalState::Nominal
464    }
465
466    fn prevent_idle_sleep(&self, reason: &str) -> Task<Result<ActivityGuard>> {
467        let count = self.idle_sleep_prevention_count.clone();
468        let fails = self.idle_sleep_prevention_fails.get();
469        let reason = reason.to_owned();
470        let acquire = move || {
471            if fails {
472                anyhow::bail!("Idle sleep prevention for {reason:?} is set to fail in this test");
473            }
474            count.fetch_add(1, Ordering::SeqCst);
475            Ok(ActivityGuard::new(move || {
476                count.fetch_sub(1, Ordering::SeqCst);
477            }))
478        };
479
480        let delay = self.idle_sleep_prevention_delay.get();
481        if delay.is_zero() {
482            Task::ready(acquire())
483        } else {
484            let delay = self.background_executor.timer(delay);
485            self.foreground_executor.spawn(async move {
486                delay.await;
487                acquire()
488            })
489        }
490    }
491
492    fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
493        unimplemented!()
494    }
495
496    fn quit(&self) {}
497
498    fn restart(&self, path: Option<PathBuf>, arguments: Vec<std::ffi::OsString>) {
499        if let Some(tx) = self.expect_restart.take() {
500            tx.send((path, arguments)).unwrap();
501        }
502    }
503
504    fn activate(&self, _ignoring_other_apps: bool) {
505        //
506    }
507
508    fn hide(&self) {
509        unimplemented!()
510    }
511
512    fn hide_other_apps(&self) {
513        unimplemented!()
514    }
515
516    fn unhide_other_apps(&self) {
517        unimplemented!()
518    }
519
520    fn displays(&self) -> Vec<std::rc::Rc<dyn crate::PlatformDisplay>> {
521        self.displays
522            .borrow()
523            .iter()
524            .map(|display| Rc::new(display.clone()) as Rc<dyn PlatformDisplay>)
525            .collect()
526    }
527
528    fn primary_display(&self) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
529        self.displays
530            .borrow()
531            .first()
532            .map(|display| Rc::new(display.clone()) as Rc<dyn PlatformDisplay>)
533    }
534
535    fn on_displays_changed(&self, callback: Box<dyn FnMut()>) {
536        *self.displays_changed_callback.borrow_mut() = Some(callback);
537    }
538
539    fn is_screen_capture_supported(&self) -> bool {
540        true
541    }
542
543    fn screen_capture_sources(
544        &self,
545    ) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
546        let (mut tx, rx) = oneshot::channel();
547        tx.send(Ok(self
548            .screen_capture_sources
549            .borrow()
550            .iter()
551            .map(|source| Rc::new(source.clone()) as Rc<dyn ScreenCaptureSource>)
552            .collect()))
553            .ok();
554        rx
555    }
556
557    fn active_window(&self) -> Option<crate::AnyWindowHandle> {
558        self.active_window
559            .borrow()
560            .as_ref()
561            .map(|window| window.0.lock().handle)
562    }
563
564    fn open_window(
565        &self,
566        handle: AnyWindowHandle,
567        params: WindowParams,
568    ) -> anyhow::Result<Box<dyn crate::PlatformWindow>> {
569        let renderer = match self.headless_renderer_factory.as_ref() {
570            Some(factory) => factory()?,
571            None => None,
572        };
573        let window = TestWindow::new(
574            handle,
575            params,
576            self.weak.clone(),
577            self.primary_display()
578                .unwrap_or_else(|| Rc::new(TestDisplay::new())),
579            renderer,
580        );
581        Ok(Box::new(window))
582    }
583
584    fn window_appearance(&self) -> WindowAppearance {
585        WindowAppearance::Light
586    }
587
588    fn open_url(&self, url: &str) {
589        *self.opened_url.borrow_mut() = Some(url.to_string())
590    }
591
592    fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {
593        unimplemented!()
594    }
595
596    /// Queues the prompt so a test can later answer it through
597    /// [`Self::simulate_path_prompt_response`].
598    #[cfg(any(test, feature = "test-support"))]
599    fn prompt_for_paths(
600        &self,
601        options: crate::PathPromptOptions,
602    ) -> oneshot::Receiver<Result<Option<Vec<std::path::PathBuf>>>> {
603        let (tx, rx) = oneshot::channel();
604        self.prompts.borrow_mut().paths.push_back((options, tx));
605        rx
606    }
607
608    /// Benchmarks have no API to answer a path prompt, so this doesn't
609    /// retain it for later inspection; dropping the sender immediately
610    /// cancels the returned receiver instead of leaving it pending
611    /// indefinitely.
612    #[cfg(not(any(test, feature = "test-support")))]
613    fn prompt_for_paths(
614        &self,
615        _options: crate::PathPromptOptions,
616    ) -> oneshot::Receiver<Result<Option<Vec<std::path::PathBuf>>>> {
617        oneshot::channel().1
618    }
619
620    /// Queues the prompt so a test can later answer it through
621    /// [`Self::simulate_new_path_selection`].
622    #[cfg(any(test, feature = "test-support"))]
623    fn prompt_for_new_path(
624        &self,
625        directory: &std::path::Path,
626        _suggested_name: Option<&str>,
627    ) -> oneshot::Receiver<Result<Option<std::path::PathBuf>>> {
628        let (tx, rx) = oneshot::channel();
629        self.prompts
630            .borrow_mut()
631            .new_path
632            .push_back((directory.to_path_buf(), tx));
633        rx
634    }
635
636    /// Benchmarks have no API to answer a new-path prompt, so this doesn't
637    /// retain it for later inspection; dropping the sender immediately
638    /// cancels the returned receiver instead of leaving it pending
639    /// indefinitely.
640    #[cfg(not(any(test, feature = "test-support")))]
641    fn prompt_for_new_path(
642        &self,
643        _directory: &std::path::Path,
644        _suggested_name: Option<&str>,
645    ) -> oneshot::Receiver<Result<Option<std::path::PathBuf>>> {
646        oneshot::channel().1
647    }
648
649    fn can_select_mixed_files_and_dirs(&self) -> bool {
650        true
651    }
652
653    fn reveal_path(&self, _path: &std::path::Path) {
654        unimplemented!()
655    }
656
657    fn on_quit(&self, _callback: Box<dyn FnMut() -> bool>) {}
658
659    fn on_reopen(&self, _callback: Box<dyn FnMut()>) {
660        unimplemented!()
661    }
662
663    fn on_system_sleep(&self, _callback: Box<dyn FnMut()>) {}
664
665    fn on_system_wake(&self, _callback: Box<dyn FnMut()>) {}
666
667    fn on_app_lifecycle(&self, callback: Box<dyn FnMut(AppLifecyclePhase)>) {
668        *self.app_lifecycle_callback.borrow_mut() = Some(callback);
669    }
670
671    fn on_memory_warning(&self, callback: Box<dyn FnMut()>) {
672        *self.memory_warning_callback.borrow_mut() = Some(callback);
673    }
674
675    fn set_app_identity(&self, identifier: &str, name: &str) {
676        self.system_notifications.borrow_mut().app_identity =
677            Some((identifier.to_string().into(), name.to_string().into()));
678    }
679
680    fn show_system_notification(&self, notification: SystemNotification) {
681        let mut system_notifications = self.system_notifications.borrow_mut();
682        if system_notifications.app_identity.is_none() {
683            return;
684        }
685
686        let delivered = system_notifications
687            .delivered
688            .iter_mut()
689            .find(|delivered| delivered.tag == notification.tag);
690        if let Some(delivered) = delivered {
691            *delivered = notification.clone();
692        } else {
693            system_notifications.delivered.push(notification.clone());
694        }
695        system_notifications.shown.push(notification);
696    }
697
698    fn dismiss_system_notification(&self, tag: &str) {
699        let mut system_notifications = self.system_notifications.borrow_mut();
700        system_notifications
701            .delivered
702            .retain(|notification| notification.tag != tag);
703        system_notifications
704            .dismissed
705            .push(SharedString::from(tag.to_string()));
706    }
707
708    fn on_system_notification_response(
709        &self,
710        callback: Box<dyn FnMut(SystemNotificationResponse)>,
711    ) {
712        self.system_notifications.borrow_mut().response_callback = Some(callback);
713    }
714
715    fn set_menus(&self, menus: Vec<crate::Menu>, _keymap: &Keymap) {
716        *self.menus.borrow_mut() = menus.into_iter().map(|menu| menu.owned()).collect()
717    }
718
719    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
720        Some(self.menus.borrow().clone())
721    }
722
723    fn set_dock_menu(&self, _menu: Vec<crate::MenuItem>, _keymap: &Keymap) {}
724
725    fn add_recent_document(&self, _paths: &Path) {}
726
727    fn on_app_menu_action(&self, _callback: Box<dyn FnMut(&dyn crate::Action)>) {}
728
729    fn on_will_open_app_menu(&self, _callback: Box<dyn FnMut()>) {}
730
731    fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {}
732
733    fn app_path(&self) -> Result<std::path::PathBuf> {
734        unimplemented!()
735    }
736
737    fn path_for_auxiliary_executable(&self, _name: &str) -> Result<std::path::PathBuf> {
738        unimplemented!()
739    }
740
741    fn set_cursor_style(&self, style: crate::CursorStyle) {
742        *self.active_cursor.lock() = style;
743    }
744
745    fn hide_cursor_until_mouse_moves(&self) {}
746
747    fn is_cursor_visible(&self) -> bool {
748        true
749    }
750
751    fn should_auto_hide_scrollbars(&self) -> bool {
752        false
753    }
754
755    fn read_from_clipboard(&self) -> Option<ClipboardItem> {
756        self.current_clipboard_item.lock().clone()
757    }
758
759    fn write_to_clipboard(&self, item: ClipboardItem) {
760        *self.current_clipboard_item.lock() = Some(item);
761    }
762
763    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
764    fn read_from_primary(&self) -> Option<ClipboardItem> {
765        self.current_primary_item.lock().clone()
766    }
767
768    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
769    fn write_to_primary(&self, item: ClipboardItem) {
770        *self.current_primary_item.lock() = Some(item);
771    }
772
773    #[cfg(target_os = "macos")]
774    fn read_from_find_pasteboard(&self) -> Option<ClipboardItem> {
775        self.current_find_pasteboard_item.lock().clone()
776    }
777
778    #[cfg(target_os = "macos")]
779    fn write_to_find_pasteboard(&self, item: ClipboardItem) {
780        *self.current_find_pasteboard_item.lock() = Some(item);
781    }
782
783    fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Task<Result<()>> {
784        Task::ready(Ok(()))
785    }
786
787    fn read_credentials(&self, _url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
788        Task::ready(Ok(None))
789    }
790
791    fn delete_credentials(&self, _url: &str) -> Task<Result<()>> {
792        Task::ready(Ok(()))
793    }
794
795    fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
796        unimplemented!()
797    }
798
799    fn open_with_system(&self, _path: &Path) {
800        unimplemented!()
801    }
802}
803
804impl TestScreenCaptureSource {
805    /// Creates a fake screen capture source.
806    #[cfg(any(test, feature = "test-support"))]
807    pub fn new() -> Self {
808        Self {}
809    }
810}
811
812struct TestKeyboardLayout;
813
814impl PlatformKeyboardLayout for TestKeyboardLayout {
815    fn id(&self) -> &str {
816        "zed.keyboard.example"
817    }
818
819    fn name(&self) -> &str {
820        "zed.keyboard.example"
821    }
822}