extension_store_test.rs

  1use crate::{
  2    ExtensionStore, GrammarManifestEntry, LanguageManifestEntry, Manifest, ThemeManifestEntry,
  3};
  4use fs::FakeFs;
  5use gpui::{Context, TestAppContext};
  6use language::{LanguageMatcher, LanguageRegistry};
  7use serde_json::json;
  8use std::{path::PathBuf, sync::Arc};
  9use theme::ThemeRegistry;
 10
 11#[gpui::test]
 12async fn test_extension_store(cx: &mut TestAppContext) {
 13    let fs = FakeFs::new(cx.executor());
 14
 15    fs.insert_tree(
 16        "/the-extension-dir",
 17        json!({
 18            "installed": {
 19                "zed-monokai": {
 20                    "themes": {
 21                        "monokai.json": r#"{
 22                            "name": "Monokai",
 23                            "author": "Someone",
 24                            "themes": [
 25                                {
 26                                    "name": "Monokai Dark",
 27                                    "appearance": "dark",
 28                                    "style": {}
 29                                },
 30                                {
 31                                    "name": "Monokai Light",
 32                                    "appearance": "light",
 33                                    "style": {}
 34                                }
 35                            ]
 36                        }"#,
 37                        "monokai-pro.json": r#"{
 38                            "name": "Monokai Pro",
 39                            "author": "Someone",
 40                            "themes": [
 41                                {
 42                                    "name": "Monokai Pro Dark",
 43                                    "appearance": "dark",
 44                                    "style": {}
 45                                },
 46                                {
 47                                    "name": "Monokai Pro Light",
 48                                    "appearance": "light",
 49                                    "style": {}
 50                                }
 51                            ]
 52                        }"#,
 53                    }
 54                },
 55                "zed-ruby": {
 56                    "grammars": {
 57                        "ruby.wasm": "",
 58                        "embedded_template.wasm": "",
 59                    },
 60                    "languages": {
 61                        "ruby": {
 62                            "config.toml": r#"
 63                                name = "Ruby"
 64                                grammar = "ruby"
 65                                path_suffixes = ["rb"]
 66                            "#,
 67                            "highlights.scm": "",
 68                        },
 69                        "erb": {
 70                            "config.toml": r#"
 71                                name = "ERB"
 72                                grammar = "embedded_template"
 73                                path_suffixes = ["erb"]
 74                            "#,
 75                            "highlights.scm": "",
 76                        }
 77                    },
 78                }
 79            }
 80        }),
 81    )
 82    .await;
 83
 84    let mut expected_manifest = Manifest {
 85        grammars: [
 86            (
 87                "embedded_template".into(),
 88                GrammarManifestEntry {
 89                    extension: "zed-ruby".into(),
 90                    path: "grammars/embedded_template.wasm".into(),
 91                },
 92            ),
 93            (
 94                "ruby".into(),
 95                GrammarManifestEntry {
 96                    extension: "zed-ruby".into(),
 97                    path: "grammars/ruby.wasm".into(),
 98                },
 99            ),
100        ]
101        .into_iter()
102        .collect(),
103        languages: [
104            (
105                "ERB".into(),
106                LanguageManifestEntry {
107                    extension: "zed-ruby".into(),
108                    path: "languages/erb".into(),
109                    grammar: Some("embedded_template".into()),
110                    matcher: LanguageMatcher {
111                        path_suffixes: vec!["erb".into()],
112                        first_line_pattern: None,
113                    },
114                },
115            ),
116            (
117                "Ruby".into(),
118                LanguageManifestEntry {
119                    extension: "zed-ruby".into(),
120                    path: "languages/ruby".into(),
121                    grammar: Some("ruby".into()),
122                    matcher: LanguageMatcher {
123                        path_suffixes: vec!["rb".into()],
124                        first_line_pattern: None,
125                    },
126                },
127            ),
128        ]
129        .into_iter()
130        .collect(),
131        themes: [
132            (
133                "Monokai Dark".into(),
134                ThemeManifestEntry {
135                    extension: "zed-monokai".into(),
136                    path: "themes/monokai.json".into(),
137                },
138            ),
139            (
140                "Monokai Light".into(),
141                ThemeManifestEntry {
142                    extension: "zed-monokai".into(),
143                    path: "themes/monokai.json".into(),
144                },
145            ),
146            (
147                "Monokai Pro Dark".into(),
148                ThemeManifestEntry {
149                    extension: "zed-monokai".into(),
150                    path: "themes/monokai-pro.json".into(),
151                },
152            ),
153            (
154                "Monokai Pro Light".into(),
155                ThemeManifestEntry {
156                    extension: "zed-monokai".into(),
157                    path: "themes/monokai-pro.json".into(),
158                },
159            ),
160        ]
161        .into_iter()
162        .collect(),
163    };
164
165    let language_registry = Arc::new(LanguageRegistry::test());
166    let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
167
168    let store = cx.new_model(|cx| {
169        ExtensionStore::new(
170            PathBuf::from("/the-extension-dir"),
171            fs.clone(),
172            language_registry.clone(),
173            theme_registry.clone(),
174            cx,
175        )
176    });
177
178    cx.executor().run_until_parked();
179    store.read_with(cx, |store, _| {
180        let manifest = store.manifest.read();
181        assert_eq!(manifest.grammars, expected_manifest.grammars);
182        assert_eq!(manifest.languages, expected_manifest.languages);
183        assert_eq!(manifest.themes, expected_manifest.themes);
184
185        assert_eq!(
186            language_registry.language_names(),
187            ["ERB", "Plain Text", "Ruby"]
188        );
189        assert_eq!(
190            theme_registry.list_names(false),
191            [
192                "Monokai Dark",
193                "Monokai Light",
194                "Monokai Pro Dark",
195                "Monokai Pro Light",
196                "One Dark",
197            ]
198        );
199    });
200
201    fs.insert_tree(
202        "/the-extension-dir/installed/zed-gruvbox",
203        json!({
204            "themes": {
205                "gruvbox.json": r#"{
206                    "name": "Gruvbox",
207                    "author": "Someone Else",
208                    "themes": [
209                        {
210                            "name": "Gruvbox",
211                            "appearance": "dark",
212                            "style": {}
213                        }
214                    ]
215                }"#,
216            }
217        }),
218    )
219    .await;
220
221    expected_manifest.themes.insert(
222        "Gruvbox".into(),
223        ThemeManifestEntry {
224            extension: "zed-gruvbox".into(),
225            path: "themes/gruvbox.json".into(),
226        },
227    );
228
229    store
230        .update(cx, |store, cx| store.reload(cx))
231        .await
232        .unwrap();
233
234    cx.executor().run_until_parked();
235    store.read_with(cx, |store, _| {
236        let manifest = store.manifest.read();
237        assert_eq!(manifest.grammars, expected_manifest.grammars);
238        assert_eq!(manifest.languages, expected_manifest.languages);
239        assert_eq!(manifest.themes, expected_manifest.themes);
240
241        assert_eq!(
242            theme_registry.list_names(false),
243            [
244                "Gruvbox",
245                "Monokai Dark",
246                "Monokai Light",
247                "Monokai Pro Dark",
248                "Monokai Pro Light",
249                "One Dark",
250            ]
251        );
252    });
253
254    let prev_fs_metadata_call_count = fs.metadata_call_count();
255    let prev_fs_read_dir_call_count = fs.read_dir_call_count();
256
257    // Create new extension store, as if Zed were restarting.
258    drop(store);
259    let store = cx.new_model(|cx| {
260        ExtensionStore::new(
261            PathBuf::from("/the-extension-dir"),
262            fs.clone(),
263            language_registry.clone(),
264            theme_registry.clone(),
265            cx,
266        )
267    });
268
269    cx.executor().run_until_parked();
270    store.read_with(cx, |store, _| {
271        let manifest = store.manifest.read();
272        assert_eq!(manifest.grammars, expected_manifest.grammars);
273        assert_eq!(manifest.languages, expected_manifest.languages);
274        assert_eq!(manifest.themes, expected_manifest.themes);
275
276        assert_eq!(
277            language_registry.language_names(),
278            ["ERB", "Plain Text", "Ruby"]
279        );
280        assert_eq!(
281            theme_registry.list_names(false),
282            [
283                "Gruvbox",
284                "Monokai Dark",
285                "Monokai Light",
286                "Monokai Pro Dark",
287                "Monokai Pro Light",
288                "One Dark",
289            ]
290        );
291
292        // The on-disk manifest limits the number of FS calls that need to be made
293        // on startup.
294        assert_eq!(fs.read_dir_call_count(), prev_fs_read_dir_call_count);
295        assert_eq!(fs.metadata_call_count(), prev_fs_metadata_call_count + 2);
296    });
297}