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