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                    matcher: LanguageMatcher {
110                        path_suffixes: vec!["erb".into()],
111                        first_line_pattern: None,
112                    },
113                },
114            ),
115            (
116                "Ruby".into(),
117                LanguageManifestEntry {
118                    extension: "zed-ruby".into(),
119                    path: "languages/ruby".into(),
120                    matcher: LanguageMatcher {
121                        path_suffixes: vec!["rb".into()],
122                        first_line_pattern: None,
123                    },
124                },
125            ),
126        ]
127        .into_iter()
128        .collect(),
129        themes: [
130            (
131                "Monokai Dark".into(),
132                ThemeManifestEntry {
133                    extension: "zed-monokai".into(),
134                    path: "themes/monokai.json".into(),
135                },
136            ),
137            (
138                "Monokai Light".into(),
139                ThemeManifestEntry {
140                    extension: "zed-monokai".into(),
141                    path: "themes/monokai.json".into(),
142                },
143            ),
144            (
145                "Monokai Pro Dark".into(),
146                ThemeManifestEntry {
147                    extension: "zed-monokai".into(),
148                    path: "themes/monokai-pro.json".into(),
149                },
150            ),
151            (
152                "Monokai Pro Light".into(),
153                ThemeManifestEntry {
154                    extension: "zed-monokai".into(),
155                    path: "themes/monokai-pro.json".into(),
156                },
157            ),
158        ]
159        .into_iter()
160        .collect(),
161    };
162
163    let language_registry = Arc::new(LanguageRegistry::test());
164    let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
165
166    let store = cx.new_model(|cx| {
167        ExtensionStore::new(
168            PathBuf::from("/the-extension-dir"),
169            fs.clone(),
170            language_registry.clone(),
171            theme_registry.clone(),
172            cx,
173        )
174    });
175
176    cx.executor().run_until_parked();
177    store.read_with(cx, |store, _| {
178        let manifest = store.manifest.read();
179        assert_eq!(manifest.grammars, expected_manifest.grammars);
180        assert_eq!(manifest.languages, expected_manifest.languages);
181        assert_eq!(manifest.themes, expected_manifest.themes);
182
183        assert_eq!(
184            language_registry.language_names(),
185            ["ERB", "Plain Text", "Ruby"]
186        );
187        assert_eq!(
188            theme_registry.list_names(false),
189            [
190                "Monokai Dark",
191                "Monokai Light",
192                "Monokai Pro Dark",
193                "Monokai Pro Light",
194                "One Dark",
195            ]
196        );
197    });
198
199    fs.insert_tree(
200        "/the-extension-dir/installed/zed-gruvbox",
201        json!({
202            "themes": {
203                "gruvbox.json": r#"{
204                    "name": "Gruvbox",
205                    "author": "Someone Else",
206                    "themes": [
207                        {
208                            "name": "Gruvbox",
209                            "appearance": "dark",
210                            "style": {}
211                        }
212                    ]
213                }"#,
214            }
215        }),
216    )
217    .await;
218
219    expected_manifest.themes.insert(
220        "Gruvbox".into(),
221        ThemeManifestEntry {
222            extension: "zed-gruvbox".into(),
223            path: "themes/gruvbox.json".into(),
224        },
225    );
226
227    store
228        .update(cx, |store, cx| store.reload(cx))
229        .await
230        .unwrap();
231
232    cx.executor().run_until_parked();
233    store.read_with(cx, |store, _| {
234        let manifest = store.manifest.read();
235        assert_eq!(manifest.grammars, expected_manifest.grammars);
236        assert_eq!(manifest.languages, expected_manifest.languages);
237        assert_eq!(manifest.themes, expected_manifest.themes);
238
239        assert_eq!(
240            theme_registry.list_names(false),
241            [
242                "Gruvbox",
243                "Monokai Dark",
244                "Monokai Light",
245                "Monokai Pro Dark",
246                "Monokai Pro Light",
247                "One Dark",
248            ]
249        );
250    });
251
252    let prev_fs_metadata_call_count = fs.metadata_call_count();
253    let prev_fs_read_dir_call_count = fs.read_dir_call_count();
254
255    // Create new extension store, as if Zed were restarting.
256    drop(store);
257    let store = cx.new_model(|cx| {
258        ExtensionStore::new(
259            PathBuf::from("/the-extension-dir"),
260            fs.clone(),
261            language_registry.clone(),
262            theme_registry.clone(),
263            cx,
264        )
265    });
266
267    cx.executor().run_until_parked();
268    store.read_with(cx, |store, _| {
269        let manifest = store.manifest.read();
270        assert_eq!(manifest.grammars, expected_manifest.grammars);
271        assert_eq!(manifest.languages, expected_manifest.languages);
272        assert_eq!(manifest.themes, expected_manifest.themes);
273
274        assert_eq!(
275            language_registry.language_names(),
276            ["ERB", "Plain Text", "Ruby"]
277        );
278        assert_eq!(
279            theme_registry.list_names(false),
280            [
281                "Gruvbox",
282                "Monokai Dark",
283                "Monokai Light",
284                "Monokai Pro Dark",
285                "Monokai Pro Light",
286                "One Dark",
287            ]
288        );
289
290        // The on-disk manifest limits the number of FS calls that need to be made
291        // on startup.
292        assert_eq!(fs.read_dir_call_count(), prev_fs_read_dir_call_count);
293        assert_eq!(fs.metadata_call_count(), prev_fs_metadata_call_count + 2);
294    });
295}