@neo4j-cypher/react-codemirror 2.0.0-next.16 → 2.0.0-next.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/CypherEditor.d.ts +5 -1
  3. package/dist/CypherEditor.js +20 -0
  4. package/dist/CypherEditor.js.map +1 -1
  5. package/dist/e2e_tests/autoCompletion.spec.js +80 -35
  6. package/dist/e2e_tests/autoCompletion.spec.js.map +1 -1
  7. package/dist/e2e_tests/e2eUtils.d.ts +1 -0
  8. package/dist/e2e_tests/e2eUtils.js +13 -2
  9. package/dist/e2e_tests/e2eUtils.js.map +1 -1
  10. package/dist/e2e_tests/performanceTest.spec.js +1 -1
  11. package/dist/e2e_tests/performanceTest.spec.js.map +1 -1
  12. package/dist/e2e_tests/signatureHelp.spec.js +59 -13
  13. package/dist/e2e_tests/signatureHelp.spec.js.map +1 -1
  14. package/dist/e2e_tests/snippets.spec.js +2 -2
  15. package/dist/e2e_tests/snippets.spec.js.map +1 -1
  16. package/dist/e2e_tests/syntaxValidation.spec.js +25 -10
  17. package/dist/e2e_tests/syntaxValidation.spec.js.map +1 -1
  18. package/dist/lang-cypher/autocomplete.js +9 -4
  19. package/dist/lang-cypher/autocomplete.js.map +1 -1
  20. package/dist/lang-cypher/langCypher.d.ts +1 -0
  21. package/dist/lang-cypher/langCypher.js +1 -8
  22. package/dist/lang-cypher/langCypher.js.map +1 -1
  23. package/dist/lang-cypher/lintWorker.d.ts +8 -4
  24. package/dist/lang-cypher/lintWorker.js +12 -2
  25. package/dist/lang-cypher/lintWorker.js.map +1 -1
  26. package/dist/lang-cypher/syntaxValidation.d.ts +0 -1
  27. package/dist/lang-cypher/syntaxValidation.js +2 -25
  28. package/dist/lang-cypher/syntaxValidation.js.map +1 -1
  29. package/dist/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +2 -2
  31. package/src/CypherEditor.tsx +32 -4
  32. package/src/e2e_tests/autoCompletion.spec.tsx +119 -54
  33. package/src/e2e_tests/e2eUtils.ts +14 -2
  34. package/src/e2e_tests/performanceTest.spec.tsx +1 -1
  35. package/src/e2e_tests/signatureHelp.spec.tsx +74 -13
  36. package/src/e2e_tests/snippets.spec.tsx +2 -2
  37. package/src/e2e_tests/syntaxValidation.spec.tsx +58 -17
  38. package/src/lang-cypher/autocomplete.ts +13 -7
  39. package/src/lang-cypher/langCypher.ts +3 -12
  40. package/src/lang-cypher/lintWorker.ts +24 -7
  41. package/src/lang-cypher/syntaxValidation.ts +2 -39
@@ -194,14 +194,60 @@ test('can complete rel types', async ({ page, mount }) => {
194
194
  await expect(component).toContainText('MATCH (n)-[:KNOWS');
195
195
  });
196
196
 
197
+ test('can complete YIELD clauses without manual trigger', async ({
198
+ page,
199
+ mount,
200
+ }) => {
201
+ const component = await mount(
202
+ <CypherEditor
203
+ schema={{
204
+ procedures: testData.mockSchema.procedures,
205
+ }}
206
+ />,
207
+ );
208
+
209
+ const textField = page.getByRole('textbox');
210
+
211
+ await textField.fill('CALL dbms.components() YIELD ');
212
+
213
+ await page.locator('.cm-tooltip-autocomplete').getByText('edition').click();
214
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
215
+
216
+ await expect(component).toContainText('CALL dbms.components() YIELD edition');
217
+ });
218
+
219
+ test('automatic yield trigger is not case sensitive', async ({
220
+ page,
221
+ mount,
222
+ }) => {
223
+ const component = await mount(
224
+ <CypherEditor
225
+ schema={{
226
+ procedures: testData.mockSchema.procedures,
227
+ }}
228
+ />,
229
+ );
230
+
231
+ const textField = page.getByRole('textbox');
232
+
233
+ await textField.fill('CALL dbms.components() yIeLd ');
234
+
235
+ await page.locator('.cm-tooltip-autocomplete').getByText('edition').click();
236
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
237
+
238
+ await expect(component).toContainText('CALL dbms.components() yIeLd edition');
239
+ });
240
+
197
241
  test('can complete functions', async ({ page, mount }) => {
198
242
  const component = await mount(
199
243
  <CypherEditor
200
244
  schema={{
201
245
  functions: {
202
- function123: {
203
- ...testData.emptyFunction,
204
- name: 'function123',
246
+ 'CYPHER 5': {
247
+ function123: {
248
+ ...testData.emptyFunction,
249
+ name: 'function123',
250
+ },
205
251
  },
206
252
  },
207
253
  }}
@@ -227,7 +273,9 @@ test('can complete procedures', async ({ page, mount }) => {
227
273
  <CypherEditor
228
274
  schema={{
229
275
  procedures: {
230
- 'db.ping': { ...testData.emptyProcedure, name: 'db.ping' },
276
+ 'CYPHER 5': {
277
+ 'db.ping': { ...testData.emptyProcedure, name: 'db.ping' },
278
+ },
231
279
  },
232
280
  }}
233
281
  />,
@@ -308,16 +356,9 @@ test('shows signature help information on auto-completion for procedures', async
308
356
  page,
309
357
  mount,
310
358
  }) => {
311
- await mount(
312
- <CypherEditor
313
- schema={testData.mockSchema}
314
- featureFlags={{
315
- signatureInfoOnAutoCompletions: true,
316
- }}
317
- />,
318
- );
359
+ await mount(<CypherEditor schema={testData.mockSchema} />);
319
360
  const procName = 'apoc.periodic.iterate';
320
- const procedure = testData.mockSchema.procedures[procName];
361
+ const procedure = testData.mockSchema.procedures['CYPHER 5'][procName];
321
362
 
322
363
  const textField = page.getByRole('textbox');
323
364
  await textField.fill('CALL apoc.periodic.');
@@ -333,16 +374,9 @@ test('shows signature help information on auto-completion for functions', async
333
374
  page,
334
375
  mount,
335
376
  }) => {
336
- await mount(
337
- <CypherEditor
338
- schema={testData.mockSchema}
339
- featureFlags={{
340
- signatureInfoOnAutoCompletions: true,
341
- }}
342
- />,
343
- );
377
+ await mount(<CypherEditor schema={testData.mockSchema} />);
344
378
  const fnName = 'apoc.coll.combinations';
345
- const fn = testData.mockSchema.functions[fnName];
379
+ const fn = testData.mockSchema.functions['CYPHER 5'][fnName];
346
380
 
347
381
  const textField = page.getByRole('textbox');
348
382
  await textField.fill('RETURN apoc.coll.');
@@ -363,10 +397,11 @@ test('shows deprecated procedures as strikethrough on auto-completion', async ({
363
397
  await mount(
364
398
  <CypherEditor
365
399
  schema={{
366
- procedures: { [procName]: testData.mockSchema.procedures[procName] },
367
- }}
368
- featureFlags={{
369
- signatureInfoOnAutoCompletions: true,
400
+ procedures: {
401
+ 'CYPHER 5': {
402
+ [procName]: testData.mockSchema.procedures['CYPHER 5'][procName],
403
+ },
404
+ },
370
405
  }}
371
406
  />,
372
407
  );
@@ -387,10 +422,11 @@ test('shows deprecated function as strikethrough on auto-completion', async ({
387
422
  await mount(
388
423
  <CypherEditor
389
424
  schema={{
390
- functions: { [fnName]: testData.mockSchema.functions[fnName] },
391
- }}
392
- featureFlags={{
393
- signatureInfoOnAutoCompletions: true,
425
+ functions: {
426
+ 'CYPHER 5': {
427
+ [fnName]: testData.mockSchema.functions['CYPHER 5'][fnName],
428
+ },
429
+ },
394
430
  }}
395
431
  />,
396
432
  );
@@ -406,14 +442,7 @@ test('does not signature help information on auto-completion if docs and signatu
406
442
  page,
407
443
  mount,
408
444
  }) => {
409
- await mount(
410
- <CypherEditor
411
- schema={testData.mockSchema}
412
- featureFlags={{
413
- signatureInfoOnAutoCompletions: true,
414
- }}
415
- />,
416
- );
445
+ await mount(<CypherEditor schema={testData.mockSchema} />);
417
446
 
418
447
  const textField = page.getByRole('textbox');
419
448
  await textField.fill('C');
@@ -430,17 +459,16 @@ test('shows signature help information on auto-completion if description is not
430
459
  <CypherEditor
431
460
  schema={{
432
461
  procedures: {
433
- 'db.ping': {
434
- ...testData.emptyProcedure,
435
- description: 'foo',
436
- signature: '',
437
- name: 'db.ping',
462
+ 'CYPHER 5': {
463
+ 'db.ping': {
464
+ ...testData.emptyProcedure,
465
+ description: 'foo',
466
+ signature: '',
467
+ name: 'db.ping',
468
+ },
438
469
  },
439
470
  },
440
471
  }}
441
- featureFlags={{
442
- signatureInfoOnAutoCompletions: true,
443
- }}
444
472
  />,
445
473
  );
446
474
 
@@ -459,17 +487,16 @@ test('shows signature help information on auto-completion if signature is not em
459
487
  <CypherEditor
460
488
  schema={{
461
489
  procedures: {
462
- 'db.ping': {
463
- ...testData.emptyProcedure,
464
- description: '',
465
- signature: 'foo',
466
- name: 'db.ping',
490
+ 'CYPHER 5': {
491
+ 'db.ping': {
492
+ ...testData.emptyProcedure,
493
+ description: '',
494
+ signature: 'foo',
495
+ name: 'db.ping',
496
+ },
467
497
  },
468
498
  },
469
499
  }}
470
- featureFlags={{
471
- signatureInfoOnAutoCompletions: true,
472
- }}
473
500
  />,
474
501
  );
475
502
 
@@ -479,3 +506,41 @@ test('shows signature help information on auto-completion if signature is not em
479
506
  await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible();
480
507
  await expect(page.locator('.cm-completionInfo')).toBeVisible();
481
508
  });
509
+
510
+ test('completions depend on the Cypher version', async ({ page, mount }) => {
511
+ await mount(
512
+ <CypherEditor
513
+ schema={{
514
+ functions: {
515
+ 'CYPHER 5': {
516
+ cypher5Function: {
517
+ ...testData.emptyFunction,
518
+ name: 'cypher5Function',
519
+ },
520
+ },
521
+ 'CYPHER 25': {
522
+ cypher25Function: {
523
+ ...testData.emptyFunction,
524
+ name: 'cypher25Function',
525
+ },
526
+ },
527
+ },
528
+ }}
529
+ featureFlags={{ cypher25: true }}
530
+ />,
531
+ );
532
+
533
+ const textField = page.getByRole('textbox');
534
+
535
+ await textField.fill('CYPHER 5 RETURN cypher');
536
+
537
+ await expect(
538
+ page.locator('.cm-tooltip-autocomplete').getByText('cypher5Function'),
539
+ ).toBeVisible();
540
+
541
+ await textField.fill('CYPHER 25 RETURN cypher');
542
+
543
+ await expect(
544
+ page.locator('.cm-tooltip-autocomplete').getByText('cypher25Function'),
545
+ ).toBeVisible();
546
+ });
@@ -59,17 +59,29 @@ export class CypherEditorPage {
59
59
  return this.checkNotificationMessage('warning', queryChunk, expectedMsg);
60
60
  }
61
61
 
62
+ async checkNoNotificationMessage(type: 'error' | 'warning') {
63
+ await this.page.waitForTimeout(10000);
64
+ await expect(this.page.locator('.cm-lintRange-' + type)).toHaveCount(0, {
65
+ timeout: 10000,
66
+ });
67
+ await expect(this.page.locator('.cm-lintPoint-' + type)).toHaveCount(0, {
68
+ timeout: 10000,
69
+ });
70
+ }
71
+
62
72
  private async checkNotificationMessage(
63
73
  type: 'error' | 'warning',
64
74
  queryChunk: string,
65
75
  expectedMsg: string,
66
76
  ) {
67
77
  await expect(this.page.locator('.cm-lintRange-' + type).last()).toBeVisible(
68
- { timeout: 3000 },
78
+ { timeout: 10000 },
69
79
  );
70
80
 
71
81
  await this.page.getByText(queryChunk, { exact: true }).hover();
72
- await expect(this.page.locator('.cm-tooltip-hover').last()).toBeVisible();
82
+ await expect(this.page.locator('.cm-tooltip-hover').last()).toBeVisible({
83
+ timeout: 10000,
84
+ });
73
85
  await expect(this.page.getByText(expectedMsg)).toBeVisible();
74
86
  /* Return the mouse to the beginning of the query and
75
87
  This is because if for example we have an overlay with a
@@ -100,7 +100,7 @@ test('benchmarking & performance test session', async ({
100
100
 
101
101
  await editorPage.checkErrorMessage(
102
102
  'RETRN',
103
- 'Unexpected token. Did you mean RETURN?',
103
+ `Invalid input 'RETRN': expected a graph pattern, 'FOREACH', ',', 'ORDER BY', 'CALL', 'CREATE', 'LOAD CSV', 'DELETE', 'DETACH', 'FINISH', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REMOVE', 'RETURN', 'SET', 'SKIP', 'UNION', 'UNWIND', 'USE', 'USING', 'WHERE', 'WITH' or <EOF>`,
104
104
  );
105
105
 
106
106
  await editorPage
@@ -11,7 +11,8 @@ type TooltipExpectations = {
11
11
  excludes?: string[];
12
12
  };
13
13
 
14
- const importCsvProc = testData.mockSchema.procedures['apoc.import.csv'];
14
+ const importCsvProc =
15
+ testData.mockSchema.procedures['CYPHER 5']['apoc.import.csv'];
15
16
 
16
17
  function testTooltip(tooltip: Locator, expectations: TooltipExpectations) {
17
18
  const includes = expectations.includes ?? [];
@@ -20,7 +21,7 @@ function testTooltip(tooltip: Locator, expectations: TooltipExpectations) {
20
21
  const included = Promise.all(
21
22
  includes.map((text) => {
22
23
  return expect(tooltip).toContainText(text, {
23
- timeout: 2000,
24
+ timeout: 10000,
24
25
  });
25
26
  }),
26
27
  );
@@ -28,7 +29,7 @@ function testTooltip(tooltip: Locator, expectations: TooltipExpectations) {
28
29
  const excluded = Promise.all(
29
30
  excludes.map((text) => {
30
31
  return expect(tooltip).not.toContainText(text, {
31
- timeout: 2000,
32
+ timeout: 10000,
32
33
  });
33
34
  }),
34
35
  );
@@ -48,7 +49,7 @@ test('Signature help works for functions', async ({ page, mount }) => {
48
49
  );
49
50
 
50
51
  await expect(page.locator('.cm-signature-help-panel')).toBeVisible({
51
- timeout: 2000,
52
+ timeout: 10000,
52
53
  });
53
54
  });
54
55
 
@@ -64,7 +65,7 @@ test('Signature help works for procedures', async ({ page, mount }) => {
64
65
  );
65
66
 
66
67
  await expect(page.locator('.cm-signature-help-panel')).toBeVisible({
67
- timeout: 2000,
68
+ timeout: 10000,
68
69
  });
69
70
  });
70
71
 
@@ -86,9 +87,9 @@ test('Signature help shows the description for the first argument', async ({
86
87
 
87
88
  await testTooltip(tooltip, {
88
89
  includes: [
89
- testData.mockSchema.procedures['apoc.import.csv'].argumentDescription[0]
90
- .description,
91
- testData.mockSchema.procedures['apoc.import.csv'].description,
90
+ testData.mockSchema.procedures['CYPHER 5']['apoc.import.csv']
91
+ .argumentDescription[0].description,
92
+ testData.mockSchema.procedures['CYPHER 5']['apoc.import.csv'].description,
92
93
  ],
93
94
  });
94
95
  });
@@ -289,7 +290,7 @@ test('Signature help does not show any help when method finished', async ({
289
290
  );
290
291
 
291
292
  await expect(page.locator('.cm-signature-help-panel')).not.toBeVisible({
292
- timeout: 2000,
293
+ timeout: 10000,
293
294
  });
294
295
  });
295
296
 
@@ -308,7 +309,7 @@ test('Signature help does not blow up on empty query', async ({
308
309
  );
309
310
 
310
311
  await expect(page.locator('.cm-signature-help-panel')).not.toBeVisible({
311
- timeout: 2000,
312
+ timeout: 10000,
312
313
  });
313
314
  });
314
315
 
@@ -331,7 +332,7 @@ test('Signature help is shown below the text by default', async ({
331
332
  await expect(
332
333
  page.locator('.cm-signature-help-panel.cm-tooltip-below'),
333
334
  ).toBeVisible({
334
- timeout: 2000,
335
+ timeout: 10000,
335
336
  });
336
337
  });
337
338
 
@@ -355,7 +356,7 @@ test('Setting showSignatureTooltipBelow to true shows the signature help above t
355
356
  await expect(
356
357
  page.locator('.cm-signature-help-panel.cm-tooltip-below'),
357
358
  ).toBeVisible({
358
- timeout: 2000,
359
+ timeout: 10000,
359
360
  });
360
361
  });
361
362
 
@@ -379,6 +380,66 @@ test('Setting showSignatureTooltipBelow to false shows the signature help above
379
380
  await expect(
380
381
  page.locator('.cm-signature-help-panel.cm-tooltip-above'),
381
382
  ).toBeVisible({
382
- timeout: 2000,
383
+ timeout: 10000,
384
+ });
385
+ });
386
+
387
+ test('Signature help depends on the Cypher version', async ({
388
+ page,
389
+ mount,
390
+ }) => {
391
+ const cypher5ArgDescription = 'The Cypher 5 statement to run.';
392
+ const cypher25ArgDescription = 'The Cypher 25 statement to run.';
393
+
394
+ await mount(
395
+ <CypherEditor
396
+ schema={{
397
+ functions: {
398
+ 'CYPHER 5': {
399
+ cypher5Function: {
400
+ ...testData.emptyFunction,
401
+ argumentDescription: [
402
+ {
403
+ isDeprecated: false,
404
+ description: cypher5ArgDescription,
405
+ name: 'statement',
406
+ type: 'STRING',
407
+ },
408
+ ],
409
+ name: 'cypher5Function',
410
+ },
411
+ },
412
+ 'CYPHER 25': {
413
+ cypher25Function: {
414
+ ...testData.emptyFunction,
415
+ argumentDescription: [
416
+ {
417
+ isDeprecated: false,
418
+ description: cypher25ArgDescription,
419
+ name: 'statement',
420
+ type: 'STRING',
421
+ },
422
+ ],
423
+ name: 'cypher25Function',
424
+ },
425
+ },
426
+ },
427
+ }}
428
+ featureFlags={{ cypher25: true }}
429
+ />,
430
+ );
431
+
432
+ const textField = page.getByRole('textbox');
433
+ await textField.fill('CYPHER 5 RETURN cypher5Function(');
434
+ const tooltip = page.locator('.cm-signature-help-panel');
435
+
436
+ await testTooltip(tooltip, {
437
+ includes: [cypher5ArgDescription],
438
+ });
439
+
440
+ await textField.fill('CYPHER 25 RETURN cypher25Function(');
441
+
442
+ await testTooltip(tooltip, {
443
+ includes: [cypher25ArgDescription],
383
444
  });
384
445
  });
@@ -65,11 +65,11 @@ test('can accept completion inside pattern snippet', async ({
65
65
  page.locator('.cm-tooltip-autocomplete').getByText('City'),
66
66
  ).toBeVisible();
67
67
 
68
- await textField.press('Tab');
68
+ await textField.press('Tab', { delay: 300 });
69
69
  await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
70
70
 
71
71
  // tab out of the snippet
72
- await textField.press('Tab');
72
+ await textField.press('Tab', { delay: 300 });
73
73
 
74
74
  await expect(textField).toHaveText('MATCH ()-[]->()-[ ]->(:City)');
75
75
  });
@@ -13,7 +13,7 @@ test('Prop lint set to false disables syntax validation', async ({
13
13
  await mount(<CypherEditor value={query} lint={false} />);
14
14
 
15
15
  await expect(page.locator('.cm-lintRange-error').last()).not.toBeVisible({
16
- timeout: 2000,
16
+ timeout: 10000,
17
17
  });
18
18
  });
19
19
 
@@ -24,7 +24,7 @@ test('Can turn linting back on', async ({ page, mount }) => {
24
24
  const component = await mount(<CypherEditor value={query} lint={false} />);
25
25
 
26
26
  await expect(page.locator('.cm-lintRange-error').last()).not.toBeVisible({
27
- timeout: 2000,
27
+ timeout: 10000,
28
28
  });
29
29
 
30
30
  await component.update(<CypherEditor value={query} lint={true} />);
@@ -33,7 +33,7 @@ test('Can turn linting back on', async ({ page, mount }) => {
33
33
 
34
34
  await editorPage.checkErrorMessage(
35
35
  'METCH',
36
- 'Unrecognized keyword. Did you mean MATCH?',
36
+ `Invalid input 'METCH': expected 'FOREACH', 'ALTER', 'ORDER BY', 'CALL', 'USING PERIODIC COMMIT', 'CREATE', 'LOAD CSV', 'START DATABASE', 'STOP DATABASE', 'DEALLOCATE', 'DELETE', 'DENY', 'DETACH', 'DROP', 'DRYRUN', 'FINISH', 'GRANT', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REALLOCATE', 'REMOVE', 'RENAME', 'RETURN', 'REVOKE', 'ENABLE SERVER', 'SET', 'SHOW', 'SKIP', 'TERMINATE', 'UNWIND', 'USE' or 'WITH'`,
37
37
  );
38
38
  });
39
39
 
@@ -45,10 +45,22 @@ test('Syntactic errors are surfaced', async ({ page, mount }) => {
45
45
 
46
46
  await editorPage.checkErrorMessage(
47
47
  'METCH',
48
- 'Unrecognized keyword. Did you mean MATCH?',
48
+ `Invalid input 'METCH': expected 'FOREACH', 'ALTER', 'ORDER BY', 'CALL', 'USING PERIODIC COMMIT', 'CREATE', 'LOAD CSV', 'START DATABASE', 'STOP DATABASE', 'DEALLOCATE', 'DELETE', 'DENY', 'DETACH', 'DROP', 'DRYRUN', 'FINISH', 'GRANT', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REALLOCATE', 'REMOVE', 'RENAME', 'RETURN', 'REVOKE', 'ENABLE SERVER', 'SET', 'SHOW', 'SKIP', 'TERMINATE', 'UNWIND', 'USE' or 'WITH'`,
49
49
  );
50
50
  });
51
51
 
52
+ test('Does not trigger syntax errors for backticked parameters in parameter creation', async ({
53
+ page,
54
+ mount,
55
+ }) => {
56
+ const editorPage = new CypherEditorPage(page);
57
+
58
+ const query = ':param x => "abc"';
59
+ await mount(<CypherEditor value={query} />);
60
+
61
+ await editorPage.checkNoNotificationMessage('error');
62
+ });
63
+
52
64
  test('Errors for undefined labels are surfaced', async ({ page, mount }) => {
53
65
  const editorPage = new CypherEditorPage(page);
54
66
  const query = 'MATCH (n: Person) RETURN n';
@@ -170,33 +182,62 @@ test('Validation errors are correctly overlapped', async ({ page, mount }) => {
170
182
  );
171
183
  });
172
184
 
173
- test('Strikethroughs are shown for deprecated functions', async ({ page, mount }) => {
185
+ test('Strikethroughs are shown for deprecated functions', async ({
186
+ page,
187
+ mount,
188
+ }) => {
174
189
  const editorPage = new CypherEditorPage(page);
175
190
  const query = `RETURN id()`;
176
191
 
177
192
  await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
178
- await expect(editorPage.page.locator('.cm-deprecated-element').last()).toBeVisible(
179
- { timeout: 3000 },
180
- );
181
- await editorPage.checkWarningMessage(
182
- 'id',
183
- "Function id is deprecated.",
184
- );
185
-
193
+ await expect(
194
+ editorPage.page.locator('.cm-deprecated-element').last(),
195
+ ).toBeVisible({ timeout: 10000 });
196
+ await editorPage.checkWarningMessage('id', 'Function id is deprecated.');
186
197
  });
187
198
 
188
- test('Strikethroughs are shown for deprecated procedures', async ({ page, mount }) => {
199
+ test('Strikethroughs are shown for deprecated procedures', async ({
200
+ page,
201
+ mount,
202
+ }) => {
189
203
  const editorPage = new CypherEditorPage(page);
190
204
  const query = `CALL apoc.create.uuids()`;
191
205
 
192
206
  await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
193
- await expect(editorPage.page.locator('.cm-deprecated-element').last()).toBeVisible(
194
- { timeout: 3000 },
207
+ await expect(
208
+ editorPage.page.locator('.cm-deprecated-element').last(),
209
+ ).toBeVisible({ timeout: 10000 });
210
+
211
+ await editorPage.checkWarningMessage(
212
+ 'apoc.create.uuids',
213
+ 'Procedure apoc.create.uuids is deprecated.',
214
+ );
215
+ });
216
+
217
+ test('Syntax validation depends on the Cypher version', async ({
218
+ page,
219
+ mount,
220
+ }) => {
221
+ await mount(
222
+ <CypherEditor
223
+ schema={testData.mockSchema}
224
+ featureFlags={{ cypher25: true }}
225
+ />,
195
226
  );
196
227
 
228
+ const editorPage = new CypherEditorPage(page);
229
+ const textField = page.getByRole('textbox');
230
+ await textField.fill('CYPHER 5 CALL apoc.create.uuids(5)');
231
+
197
232
  await editorPage.checkWarningMessage(
198
233
  'apoc.create.uuids',
199
- "Procedure apoc.create.uuids is deprecated.",
234
+ 'Procedure apoc.create.uuids is deprecated.',
200
235
  );
201
236
 
237
+ await textField.fill('CYPHER 25 CALL apoc.create.uuids(5)');
238
+
239
+ await editorPage.checkErrorMessage(
240
+ 'apoc.create.uuids',
241
+ 'Procedure apoc.create.uuids is not present in the database.',
242
+ );
202
243
  });
@@ -3,7 +3,10 @@ import {
3
3
  CompletionSource,
4
4
  snippet,
5
5
  } from '@codemirror/autocomplete';
6
- import { autocomplete } from '@neo4j-cypher/language-support';
6
+ import {
7
+ autocomplete,
8
+ shouldAutoCompleteYield,
9
+ } from '@neo4j-cypher/language-support';
7
10
  import {
8
11
  CompletionItemKind,
9
12
  CompletionItemTag,
@@ -58,15 +61,18 @@ export const completionStyles: (
58
61
  export const cypherAutocomplete: (config: CypherConfig) => CompletionSource =
59
62
  (config) => (context) => {
60
63
  const documentText = context.state.doc.toString();
61
-
64
+ const offset = context.pos;
62
65
  const triggerCharacters = ['.', ':', '{', '$', ')'];
63
- const lastCharacter = documentText.at(context.pos - 1);
64
-
66
+ const lastCharacter = documentText.at(offset - 1);
67
+ const yieldTriggered = shouldAutoCompleteYield(documentText, offset);
65
68
  const lastWord = context.matchBefore(/\w*/);
66
69
  const inWord = lastWord.from !== lastWord.to;
67
70
 
68
71
  const shouldTriggerCompletion =
69
- inWord || context.explicit || triggerCharacters.includes(lastCharacter);
72
+ inWord ||
73
+ context.explicit ||
74
+ triggerCharacters.includes(lastCharacter) ||
75
+ yieldTriggered;
70
76
 
71
77
  if (config.useLightVersion && !context.explicit) {
72
78
  return null;
@@ -78,9 +84,9 @@ export const cypherAutocomplete: (config: CypherConfig) => CompletionSource =
78
84
 
79
85
  const options = autocomplete(
80
86
  // TODO This is a temporary hack because completions are not working well
81
- documentText.slice(0, context.pos),
87
+ documentText.slice(0, offset),
82
88
  config.schema ?? {},
83
- context.pos,
89
+ offset,
84
90
  context.explicit,
85
91
  );
86
92
 
@@ -4,14 +4,11 @@ import {
4
4
  Language,
5
5
  LanguageSupport,
6
6
  } from '@codemirror/language';
7
- import {
8
- _internalFeatureFlags,
9
- type DbSchema,
10
- } from '@neo4j-cypher/language-support';
7
+ import { type DbSchema } from '@neo4j-cypher/language-support';
11
8
  import { completionStyles, cypherAutocomplete } from './autocomplete';
12
9
  import { ParserAdapter } from './parser-adapter';
13
10
  import { signatureHelpTooltip } from './signatureHelp';
14
- import { cypherLinter, semanticAnalysisLinter } from './syntaxValidation';
11
+ import { cypherLinter } from './syntaxValidation';
15
12
 
16
13
  const facet = defineLanguageFacet({
17
14
  commentTokens: { block: { open: '/*', close: '*/' }, line: '//' },
@@ -23,6 +20,7 @@ export type CypherConfig = {
23
20
  showSignatureTooltipBelow?: boolean;
24
21
  featureFlags?: {
25
22
  consoleCommands?: boolean;
23
+ cypher25?: boolean;
26
24
  };
27
25
  schema?: DbSchema;
28
26
  useLightVersion: boolean;
@@ -30,12 +28,6 @@ export type CypherConfig = {
30
28
  };
31
29
 
32
30
  export function cypher(config: CypherConfig) {
33
- const featureFlags = config.featureFlags;
34
- // We allow to override the consoleCommands feature flag
35
- if (featureFlags.consoleCommands !== undefined) {
36
- _internalFeatureFlags.consoleCommands = featureFlags.consoleCommands;
37
- }
38
-
39
31
  const parserAdapter = new ParserAdapter(facet, config);
40
32
 
41
33
  const cypherLanguage = new Language(facet, parserAdapter, [], 'cypher');
@@ -46,7 +38,6 @@ export function cypher(config: CypherConfig) {
46
38
  optionClass: completionStyles,
47
39
  }),
48
40
  cypherLinter(config),
49
- semanticAnalysisLinter(config),
50
41
  signatureHelpTooltip(config),
51
42
  ]);
52
43
  }