Ensure the SettingsStore global is added in tests

Max Brunsfeld created

Change summary

crates/client/src/client.rs                             | 25 ++++++----
crates/collab/src/tests.rs                              |  5 +
crates/collab/src/tests/randomized_integration_tests.rs |  7 ++
crates/editor/src/test/editor_lsp_test_context.rs       |  4 
crates/gpui/src/app.rs                                  | 11 ++++
crates/settings/src/settings_store.rs                   | 23 +++++++---
crates/vim/src/test/vim_test_context.rs                 |  5 +
crates/workspace/src/workspace.rs                       |  7 ++
crates/zed/src/main.rs                                  |  2 
crates/zed/src/zed.rs                                   |  4 -
10 files changed, 61 insertions(+), 32 deletions(-)

Detailed changes

crates/client/src/client.rs 🔗

@@ -70,27 +70,30 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
 
 actions!(client, [SignIn, SignOut]);
 
-pub fn init(client: Arc<Client>, cx: &mut AppContext) {
+pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
+    let client = Arc::downgrade(client);
     settings::register_setting::<TelemetrySettings>(cx);
 
     cx.add_global_action({
         let client = client.clone();
         move |_: &SignIn, cx| {
-            let client = client.clone();
-            cx.spawn(
-                |cx| async move { client.authenticate_and_connect(true, &cx).log_err().await },
-            )
-            .detach();
+            if let Some(client) = client.upgrade() {
+                cx.spawn(
+                    |cx| async move { client.authenticate_and_connect(true, &cx).log_err().await },
+                )
+                .detach();
+            }
         }
     });
     cx.add_global_action({
         let client = client.clone();
         move |_: &SignOut, cx| {
-            let client = client.clone();
-            cx.spawn(|cx| async move {
-                client.disconnect(&cx);
-            })
-            .detach();
+            if let Some(client) = client.upgrade() {
+                cx.spawn(|cx| async move {
+                    client.disconnect(&cx);
+                })
+                .detach();
+            }
         }
     });
 }

crates/collab/src/tests.rs 🔗

@@ -19,7 +19,7 @@ use gpui::{
 use language::LanguageRegistry;
 use parking_lot::Mutex;
 use project::{Project, WorktreeId};
-use settings::Settings;
+use settings::{Settings, SettingsStore};
 use std::{
     cell::{Ref, RefCell, RefMut},
     env,
@@ -102,6 +102,7 @@ impl TestServer {
 
     async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient {
         cx.update(|cx| {
+            cx.set_global(SettingsStore::test(cx));
             cx.set_global(Settings::test(cx));
         });
 
@@ -185,6 +186,8 @@ impl TestServer {
                 })
             });
 
+        cx.update(|cx| client::init(&client, cx));
+
         let fs = FakeFs::new(cx.background());
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
         let app_state = Arc::new(workspace::AppState {

crates/collab/src/tests/randomized_integration_tests.rs 🔗

@@ -20,7 +20,7 @@ use rand::{
     prelude::*,
 };
 use serde::{Deserialize, Serialize};
-use settings::Settings;
+use settings::{Settings, SettingsStore};
 use std::{
     env,
     ops::Range,
@@ -148,8 +148,11 @@ async fn test_random_collaboration(
 
     for (client, mut cx) in clients {
         cx.update(|cx| {
+            let store = cx.remove_global::<SettingsStore>();
+            let settings = cx.remove_global::<Settings>();
             cx.clear_globals();
-            cx.set_global(Settings::test(cx));
+            cx.set_global(store);
+            cx.set_global(settings);
             drop(client);
         });
     }

crates/editor/src/test/editor_lsp_test_context.rs 🔗

@@ -34,13 +34,13 @@ impl<'a> EditorLspTestContext<'a> {
     ) -> EditorLspTestContext<'a> {
         use json::json;
 
+        let app_state = cx.update(AppState::test);
+
         cx.update(|cx| {
             crate::init(cx);
             pane::init(cx);
         });
 
-        let app_state = cx.update(AppState::test);
-
         let file_name = format!(
             "file.{}",
             language

crates/gpui/src/app.rs 🔗

@@ -1174,7 +1174,7 @@ impl AppContext {
             this.notify_global(type_id);
             result
         } else {
-            panic!("No global added for {}", std::any::type_name::<T>());
+            panic!("no global added for {}", std::any::type_name::<T>());
         }
     }
 
@@ -1182,6 +1182,15 @@ impl AppContext {
         self.globals.clear();
     }
 
+    pub fn remove_global<T: 'static>(&mut self) -> T {
+        *self
+            .globals
+            .remove(&TypeId::of::<T>())
+            .unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::<T>()))
+            .downcast()
+            .unwrap()
+    }
+
     pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
     where
         T: Entity,

crates/settings/src/settings_store.rs 🔗

@@ -122,11 +122,11 @@ impl SettingsStore {
     /// Add a new type of setting to the store.
     pub fn register_setting<T: Setting>(&mut self, cx: &AppContext) {
         let setting_type_id = TypeId::of::<T>();
-
         let entry = self.setting_values.entry(setting_type_id);
         if matches!(entry, hash_map::Entry::Occupied(_)) {
-            panic!("duplicate setting type: {}", type_name::<T>());
+            return;
         }
+
         let setting_value = entry.or_insert(Box::new(SettingValue::<T> {
             global_value: None,
             local_values: Vec::new(),
@@ -142,6 +142,7 @@ impl SettingsStore {
                     user_values_stack = vec![user_value];
                 }
             }
+
             if let Some(default_deserialized_value) = default_settings.typed.get(&setting_type_id) {
                 setting_value.set_global_value(setting_value.load_setting(
                     default_deserialized_value,
@@ -159,7 +160,7 @@ impl SettingsStore {
     pub fn get<T: Setting>(&self, path: Option<&Path>) -> &T {
         self.setting_values
             .get(&TypeId::of::<T>())
-            .expect("unregistered setting type")
+            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
             .value_for_path(path)
             .downcast_ref::<T>()
             .expect("no default value for setting type")
@@ -175,6 +176,14 @@ impl SettingsStore {
             .map_or(&serde_json::Value::Null, |s| &s.untyped)
     }
 
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn test(cx: &AppContext) -> Self {
+        let mut this = Self::default();
+        this.set_default_settings(&crate::test_settings(), cx)
+            .unwrap();
+        this
+    }
+
     /// Override the global value for a particular setting.
     ///
     /// This is only for tests. Normally, settings are only loaded from
@@ -183,7 +192,7 @@ impl SettingsStore {
     pub fn replace_value<T: Setting>(&mut self, value: T) {
         self.setting_values
             .get_mut(&TypeId::of::<T>())
-            .expect("unregistered setting type")
+            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
             .set_global_value(Box::new(value))
     }
 
@@ -268,7 +277,7 @@ impl SettingsStore {
     pub fn set_default_settings(
         &mut self,
         default_settings_content: &str,
-        cx: &mut AppContext,
+        cx: &AppContext,
     ) -> Result<()> {
         let deserialized_setting_map = self.load_setting_map(default_settings_content)?;
         if deserialized_setting_map.typed.len() != self.setting_values.len() {
@@ -290,7 +299,7 @@ impl SettingsStore {
     pub fn set_user_settings(
         &mut self,
         user_settings_content: &str,
-        cx: &mut AppContext,
+        cx: &AppContext,
     ) -> Result<()> {
         let user_settings = self.load_setting_map(user_settings_content)?;
         let old_user_settings =
@@ -304,7 +313,7 @@ impl SettingsStore {
         &mut self,
         path: Arc<Path>,
         settings_content: Option<&str>,
-        cx: &mut AppContext,
+        cx: &AppContext,
     ) -> Result<()> {
         let removed_map = if let Some(settings_content) = settings_content {
             self.local_deserialized_settings

crates/vim/src/test/vim_test_context.rs 🔗

@@ -18,11 +18,12 @@ impl<'a> VimTestContext<'a> {
     pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
         let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
         cx.update(|cx| {
+            search::init(cx);
+            crate::init(cx);
+
             cx.update_global(|store: &mut SettingsStore, _| {
                 store.replace_value(VimModeSetting(enabled));
             });
-            search::init(cx);
-            crate::init(cx);
 
             settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
         });

crates/workspace/src/workspace.rs 🔗

@@ -369,8 +369,8 @@ pub struct AppState {
 impl AppState {
     #[cfg(any(test, feature = "test-support"))]
     pub fn test(cx: &mut AppContext) -> Arc<Self> {
-        let settings = Settings::test(cx);
-        cx.set_global(settings);
+        cx.set_global(settings::SettingsStore::test(cx));
+        cx.set_global(Settings::test(cx));
 
         let fs = fs::FakeFs::new(cx.background().clone());
         let languages = Arc::new(LanguageRegistry::test());
@@ -378,6 +378,9 @@ impl AppState {
         let client = Client::new(http_client.clone(), cx);
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
         let themes = ThemeRegistry::new((), cx.font_cache().clone());
+
+        client::init(&client, cx);
+
         Arc::new(Self {
             client,
             themes,

crates/zed/src/main.rs 🔗

@@ -158,7 +158,7 @@ fn main() {
 
         context_menu::init(cx);
         project::Project::init(&client);
-        client::init(client.clone(), cx);
+        client::init(&client, cx);
         command_palette::init(cx);
         editor::init(cx);
         go_to_line::init(cx);

crates/zed/src/zed.rs 🔗

@@ -1283,9 +1283,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_pane_actions(cx: &mut TestAppContext) {
-        init(cx);
-
-        let app_state = cx.update(AppState::test);
+        let app_state = init(cx);
         app_state
             .fs
             .as_fake()