/ tests / e2e / inlay_hints_test.rs
inlay_hints_test.rs
  1  use crate::harness::{LspTestClient, TempWorkspace};
  2  use std::thread;
  3  use std::time::Duration;
  4  
  5  #[test]
  6  fn test_inlay_hints_direct_reference() {
  7      let workspace = TempWorkspace::new();
  8      workspace.create_config("[features]\ninlay_hints = true");
  9      let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP");
 10      client.initialize().expect("Initialize failed");
 11  
 12      let uri = workspace.file_uri("test.js");
 13      let content = "const url = process.env.DB_URL;";
 14      workspace.create_file("test.js", content);
 15  
 16      client
 17          .open_document(&uri, "javascript", content)
 18          .expect("Failed to open document");
 19  
 20      thread::sleep(Duration::from_millis(300));
 21  
 22      let hints = client
 23          .inlay_hint(&uri, 0, 0, 0, 50)
 24          .expect("Inlay hint request failed");
 25  
 26      // Should return an array of hints
 27      assert!(hints.is_array(), "Expected array of inlay hints");
 28  
 29      let hints_arr = hints.as_array().unwrap();
 30      assert!(
 31          !hints_arr.is_empty(),
 32          "Expected at least one inlay hint for DB_URL"
 33      );
 34  
 35      // Check that the hint contains expected content
 36      let first_hint = &hints_arr[0];
 37      let label = first_hint.get("label").expect("Missing label");
 38      let label_str = label.as_str().expect("Label not a string");
 39      assert!(label_str.contains('"'), "Hint should contain quoted value");
 40  
 41      client.shutdown().expect("Shutdown failed");
 42  }
 43  
 44  #[test]
 45  fn test_inlay_hints_destructuring() {
 46      let workspace = TempWorkspace::new();
 47      workspace.create_config("[features]\ninlay_hints = true");
 48      let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP");
 49      client.initialize().expect("Initialize failed");
 50  
 51      let uri = workspace.file_uri("test.js");
 52      let content = "const { PORT, API_KEY } = process.env;";
 53      workspace.create_file("test.js", content);
 54  
 55      client
 56          .open_document(&uri, "javascript", content)
 57          .expect("Failed to open document");
 58  
 59      thread::sleep(Duration::from_millis(300));
 60  
 61      let hints = client
 62          .inlay_hint(&uri, 0, 0, 0, 50)
 63          .expect("Inlay hint request failed");
 64  
 65      assert!(hints.is_array(), "Expected array of inlay hints");
 66  
 67      let hints_arr = hints.as_array().unwrap();
 68      // Should have hints for both PORT and API_KEY
 69      assert!(
 70          hints_arr.len() >= 2,
 71          "Expected at least two inlay hints for destructuring"
 72      );
 73  
 74      client.shutdown().expect("Shutdown failed");
 75  }
 76  
 77  #[test]
 78  #[ignore = "Alias property access not yet tracked in env_var_index for inlay hints"]
 79  fn test_inlay_hints_alias_property_access() {
 80      let workspace = TempWorkspace::new();
 81      workspace.create_config("[features]\ninlay_hints = true");
 82      let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP");
 83      client.initialize().expect("Initialize failed");
 84  
 85      let uri = workspace.file_uri("test.js");
 86      let content = "const e = process.env; const port = e.PORT;";
 87      workspace.create_file("test.js", content);
 88  
 89      client
 90          .open_document(&uri, "javascript", content)
 91          .expect("Failed to open document");
 92  
 93      thread::sleep(Duration::from_millis(300));
 94  
 95      let hints = client
 96          .inlay_hint(&uri, 0, 0, 0, 60)
 97          .expect("Inlay hint request failed");
 98  
 99      assert!(hints.is_array(), "Expected array of inlay hints");
100  
101      let hints_arr = hints.as_array().unwrap();
102      // Should have hint for PORT via property access on alias
103      assert!(
104          !hints_arr.is_empty(),
105          "Expected inlay hint for property access via alias"
106      );
107  
108      client.shutdown().expect("Shutdown failed");
109  }
110  
111  #[test]
112  fn test_inlay_hints_empty_for_no_env_vars() {
113      let workspace = TempWorkspace::new();
114      workspace.create_config("[features]\ninlay_hints = true");
115      let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP");
116      client.initialize().expect("Initialize failed");
117  
118      let uri = workspace.file_uri("test.js");
119      let content = "const x = 42; function foo() { return x * 2; }";
120      workspace.create_file("test.js", content);
121  
122      client
123          .open_document(&uri, "javascript", content)
124          .expect("Failed to open document");
125  
126      thread::sleep(Duration::from_millis(300));
127  
128      let hints = client
129          .inlay_hint(&uri, 0, 0, 0, 60)
130          .expect("Inlay hint request failed");
131  
132      assert!(hints.is_array(), "Expected array of inlay hints");
133  
134      let hints_arr = hints.as_array().unwrap();
135      assert!(
136          hints_arr.is_empty(),
137          "Expected no inlay hints for code without env vars"
138      );
139  
140      client.shutdown().expect("Shutdown failed");
141  }
142  
143  #[test]
144  fn test_inlay_hints_tooltip_shows_source() {
145      let workspace = TempWorkspace::new();
146      workspace.create_config("[features]\ninlay_hints = true");
147      let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP");
148      client.initialize().expect("Initialize failed");
149  
150      let uri = workspace.file_uri("test.js");
151      let content = "const port = process.env.PORT;";
152      workspace.create_file("test.js", content);
153  
154      client
155          .open_document(&uri, "javascript", content)
156          .expect("Failed to open document");
157  
158      thread::sleep(Duration::from_millis(300));
159  
160      let hints = client
161          .inlay_hint(&uri, 0, 0, 0, 50)
162          .expect("Inlay hint request failed");
163  
164      assert!(hints.is_array(), "Expected array of inlay hints");
165  
166      let hints_arr = hints.as_array().unwrap();
167      assert!(!hints_arr.is_empty(), "Expected at least one inlay hint");
168  
169      // Check that the tooltip contains source info
170      let first_hint = &hints_arr[0];
171      let tooltip = first_hint.get("tooltip").expect("Missing tooltip");
172      let tooltip_str = tooltip.as_str().expect("Tooltip not a string");
173      assert!(
174          tooltip_str.contains("Source:"),
175          "Tooltip should indicate source. Got: {}",
176          tooltip_str
177      );
178  
179      client.shutdown().expect("Shutdown failed");
180  }
181  
182  #[test]
183  fn test_inlay_hints_typescript() {
184      let workspace = TempWorkspace::new();
185      workspace.create_config("[features]\ninlay_hints = true");
186      let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP");
187      client.initialize().expect("Initialize failed");
188  
189      let uri = workspace.file_uri("test.ts");
190      let content = "const port: string = process.env.PORT!;";
191      workspace.create_file("test.ts", content);
192  
193      client
194          .open_document(&uri, "typescript", content)
195          .expect("Failed to open document");
196  
197      thread::sleep(Duration::from_millis(300));
198  
199      let hints = client
200          .inlay_hint(&uri, 0, 0, 0, 50)
201          .expect("Inlay hint request failed");
202  
203      assert!(
204          hints.is_array(),
205          "Expected array of inlay hints for TypeScript"
206      );
207  
208      let hints_arr = hints.as_array().unwrap();
209      assert!(
210          !hints_arr.is_empty(),
211          "Expected at least one inlay hint for TypeScript"
212      );
213  
214      client.shutdown().expect("Shutdown failed");
215  }
216  
217  #[test]
218  fn test_inlay_hints_python() {
219      let workspace = TempWorkspace::new();
220      workspace.create_config("[features]\ninlay_hints = true");
221      let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP");
222      client.initialize().expect("Initialize failed");
223  
224      let uri = workspace.file_uri("test.py");
225      let content = "import os\ndb = os.environ['DB_URL']";
226      workspace.create_file("test.py", content);
227  
228      client
229          .open_document(&uri, "python", content)
230          .expect("Failed to open document");
231  
232      thread::sleep(Duration::from_millis(300));
233  
234      let hints = client
235          .inlay_hint(&uri, 0, 0, 2, 0)
236          .expect("Inlay hint request failed");
237  
238      assert!(hints.is_array(), "Expected array of inlay hints for Python");
239  
240      let hints_arr = hints.as_array().unwrap();
241      assert!(
242          !hints_arr.is_empty(),
243          "Expected at least one inlay hint for Python"
244      );
245  
246      client.shutdown().expect("Shutdown failed");
247  }
248  
249  #[test]
250  fn test_inlay_hints_range_filtering() {
251      let workspace = TempWorkspace::new();
252      workspace.create_config("[features]\ninlay_hints = true");
253      let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP");
254      client.initialize().expect("Initialize failed");
255  
256      let uri = workspace.file_uri("test.js");
257      // Multiple env vars on different lines
258      let content = "const a = process.env.PORT;\nconst b = process.env.DEBUG;";
259      workspace.create_file("test.js", content);
260  
261      client
262          .open_document(&uri, "javascript", content)
263          .expect("Failed to open document");
264  
265      thread::sleep(Duration::from_millis(300));
266  
267      // Request hints only for line 0
268      let hints = client
269          .inlay_hint(&uri, 0, 0, 0, 50)
270          .expect("Inlay hint request failed");
271  
272      assert!(hints.is_array(), "Expected array of inlay hints");
273  
274      let hints_arr = hints.as_array().unwrap();
275      // Should only have hint for PORT (line 0), not DEBUG (line 1)
276      for hint in hints_arr {
277          let position = hint.get("position").expect("Missing position");
278          let line = position
279              .get("line")
280              .expect("Missing line")
281              .as_u64()
282              .unwrap();
283          assert_eq!(
284              line, 0,
285              "All hints should be on line 0 given the requested range"
286          );
287      }
288  
289      client.shutdown().expect("Shutdown failed");
290  }