Add test that JSON schema generation works + actions build from no input (#23049)

Michael Sloan created

Release Notes:

- N/A

Change summary

Cargo.lock                         |  1 
crates/languages/Cargo.toml        |  1 
crates/languages/src/json.rs       | 14 -----
crates/settings/src/keymap_file.rs | 14 +++++
crates/zed/src/zed.rs              | 67 ++++++++++++++++++++++++++++++++
5 files changed, 81 insertions(+), 16 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -6819,7 +6819,6 @@ dependencies = [
  "regex",
  "rope",
  "rust-embed",
- "schemars",
  "serde",
  "serde_json",
  "settings",

crates/languages/Cargo.toml 🔗

@@ -56,7 +56,6 @@ project.workspace = true
 regex.workspace = true
 rope.workspace = true
 rust-embed.workspace = true
-schemars.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 settings.workspace = true

crates/languages/src/json.rs 🔗

@@ -10,7 +10,6 @@ use language::{LanguageRegistry, LanguageToolchainStore, LspAdapter, LspAdapterD
 use lsp::{LanguageServerBinary, LanguageServerName};
 use node_runtime::NodeRuntime;
 use project::{lsp_store::language_server_settings, ContextProviderWithTasks};
-use schemars::gen::SchemaSettings;
 use serde_json::{json, Value};
 use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
 use smol::{
@@ -76,6 +75,7 @@ impl JsonLspAdapter {
     }
 
     fn get_workspace_config(language_names: Vec<String>, cx: &mut AppContext) -> Value {
+        let keymap_schema = KeymapFile::generate_json_schema_for_registered_actions(cx);
         let font_names = &cx.text_system().all_font_names();
         let settings_schema = cx.global::<SettingsStore>().json_schema(
             &SettingsJsonSchemaParams {
@@ -115,7 +115,7 @@ impl JsonLspAdapter {
                     },
                     {
                         "fileMatch": [schema_file_match(paths::keymap_file())],
-                        "schema": Self::generate_keymap_schema(cx),
+                        "schema": keymap_schema,
                     },
                     {
                         "fileMatch": [
@@ -129,16 +129,6 @@ impl JsonLspAdapter {
             }
         })
     }
-
-    fn generate_keymap_schema(cx: &mut AppContext) -> Value {
-        let mut generator = SchemaSettings::draft07()
-            .with(|settings| settings.option_add_null_type = false)
-            .into_generator();
-
-        let action_schemas = cx.action_schemas(&mut generator);
-        let deprecations = cx.action_deprecations();
-        KeymapFile::generate_json_schema(generator, action_schemas, deprecations)
-    }
 }
 
 #[async_trait(?Send)]

crates/settings/src/keymap_file.rs 🔗

@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result};
 use collections::{BTreeMap, HashMap};
 use gpui::{Action, AppContext, KeyBinding, SharedString};
 use schemars::{
-    gen::SchemaGenerator,
+    gen::{SchemaGenerator, SchemaSettings},
     schema::{ArrayValidation, InstanceType, Metadata, Schema, SchemaObject, SubschemaValidation},
     JsonSchema,
 };
@@ -139,7 +139,17 @@ impl KeymapFile {
         Ok(())
     }
 
-    pub fn generate_json_schema(
+    pub fn generate_json_schema_for_registered_actions(cx: &mut AppContext) -> Value {
+        let mut generator = SchemaSettings::draft07()
+            .with(|settings| settings.option_add_null_type = false)
+            .into_generator();
+
+        let action_schemas = cx.action_schemas(&mut generator);
+        let deprecations = cx.action_deprecations();
+        KeymapFile::generate_json_schema(generator, action_schemas, deprecations)
+    }
+
+    fn generate_json_schema(
         generator: SchemaGenerator,
         action_schemas: Vec<(SharedString, Option<Schema>)>,
         deprecations: &HashMap<SharedString, SharedString>,

crates/zed/src/zed.rs 🔗

@@ -3502,6 +3502,73 @@ mod tests {
         assert_key_bindings_for(workspace.into(), cx, vec![("6", &Deploy)], line!());
     }
 
+    #[gpui::test]
+    async fn test_generate_keymap_json_schema_for_registered_actions(
+        cx: &mut gpui::TestAppContext,
+    ) {
+        init_keymap_test(cx);
+        cx.update(|cx| {
+            // Make sure it doesn't panic.
+            KeymapFile::generate_json_schema_for_registered_actions(cx);
+        });
+    }
+
+    /// Actions that don't build from empty input won't work from command palette invocation.
+    #[gpui::test]
+    async fn test_actions_build_with_empty_input(cx: &mut gpui::TestAppContext) {
+        init_keymap_test(cx);
+        cx.update(|cx| {
+            let all_actions = cx.all_action_names();
+            let mut failing_names = Vec::new();
+            let mut errors = Vec::new();
+            for action in all_actions {
+                match action.to_string().as_str() {
+                    "vim::FindCommand"
+                    | "vim::Literal"
+                    | "vim::ResizePane"
+                    | "vim::SwitchMode"
+                    | "vim::PushOperator"
+                    | "vim::Number"
+                    | "vim::SelectRegister"
+                    | "terminal::SendText"
+                    | "terminal::SendKeystroke"
+                    | "app_menu::OpenApplicationMenu"
+                    | "app_menu::NavigateApplicationMenuInDirection"
+                    | "picker::ConfirmInput"
+                    | "editor::HandleInput"
+                    | "editor::FoldAtLevel"
+                    | "pane::ActivateItem"
+                    | "workspace::ActivatePane"
+                    | "workspace::ActivatePaneInDirection"
+                    | "workspace::MoveItemToPane"
+                    | "workspace::MoveItemToPaneInDirection"
+                    | "workspace::OpenTerminal"
+                    | "workspace::SwapPaneInDirection"
+                    | "workspace::SendKeystrokes"
+                    | "zed::OpenBrowser"
+                    | "zed::OpenZedUrl" => {}
+                    _ => {
+                        let result = cx.build_action(action, None);
+                        match &result {
+                            Ok(_) => {}
+                            Err(err) => {
+                                failing_names.push(action);
+                                errors.push(format!("{action} failed to build: {err:?}"));
+                            }
+                        }
+                    }
+                }
+            }
+            if errors.len() > 0 {
+                panic!(
+                    "Failed to build actions using {{}} as input: {:?}. Errors:\n{}",
+                    failing_names,
+                    errors.join("\n")
+                );
+            }
+        });
+    }
+
     #[gpui::test]
     fn test_bundled_settings_and_themes(cx: &mut AppContext) {
         cx.text_system()