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}