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}