@neo4j-cypher/react-codemirror 0.0.0-canary-20250423075344

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 (138) hide show
  1. package/CHANGELOG.md +279 -0
  2. package/LICENSE.md +201 -0
  3. package/README.md +35 -0
  4. package/dist/CypherEditor.d.ts +192 -0
  5. package/dist/CypherEditor.js +326 -0
  6. package/dist/CypherEditor.js.map +1 -0
  7. package/dist/CypherEditor.test.d.ts +1 -0
  8. package/dist/CypherEditor.test.js +151 -0
  9. package/dist/CypherEditor.test.js.map +1 -0
  10. package/dist/constants.d.ts +1 -0
  11. package/dist/constants.js +2 -0
  12. package/dist/constants.js.map +1 -0
  13. package/dist/e2e_tests/autoCompletion.spec.d.ts +1 -0
  14. package/dist/e2e_tests/autoCompletion.spec.js +318 -0
  15. package/dist/e2e_tests/autoCompletion.spec.js.map +1 -0
  16. package/dist/e2e_tests/configuration.spec.d.ts +1 -0
  17. package/dist/e2e_tests/configuration.spec.js +83 -0
  18. package/dist/e2e_tests/configuration.spec.js.map +1 -0
  19. package/dist/e2e_tests/debounce.spec.d.ts +1 -0
  20. package/dist/e2e_tests/debounce.spec.js +66 -0
  21. package/dist/e2e_tests/debounce.spec.js.map +1 -0
  22. package/dist/e2e_tests/e2eUtils.d.ts +13 -0
  23. package/dist/e2e_tests/e2eUtils.js +71 -0
  24. package/dist/e2e_tests/e2eUtils.js.map +1 -0
  25. package/dist/e2e_tests/extraKeybindings.spec.d.ts +1 -0
  26. package/dist/e2e_tests/extraKeybindings.spec.js +43 -0
  27. package/dist/e2e_tests/extraKeybindings.spec.js.map +1 -0
  28. package/dist/e2e_tests/historyNavigation.spec.d.ts +1 -0
  29. package/dist/e2e_tests/historyNavigation.spec.js +227 -0
  30. package/dist/e2e_tests/historyNavigation.spec.js.map +1 -0
  31. package/dist/e2e_tests/performanceTest.spec.d.ts +6 -0
  32. package/dist/e2e_tests/performanceTest.spec.js +97 -0
  33. package/dist/e2e_tests/performanceTest.spec.js.map +1 -0
  34. package/dist/e2e_tests/sanityChecks.spec.d.ts +1 -0
  35. package/dist/e2e_tests/sanityChecks.spec.js +53 -0
  36. package/dist/e2e_tests/sanityChecks.spec.js.map +1 -0
  37. package/dist/e2e_tests/signatureHelp.spec.d.ts +1 -0
  38. package/dist/e2e_tests/signatureHelp.spec.js +228 -0
  39. package/dist/e2e_tests/signatureHelp.spec.js.map +1 -0
  40. package/dist/e2e_tests/snippets.spec.d.ts +1 -0
  41. package/dist/e2e_tests/snippets.spec.js +62 -0
  42. package/dist/e2e_tests/snippets.spec.js.map +1 -0
  43. package/dist/e2e_tests/syntaxHighlighting.spec.d.ts +1 -0
  44. package/dist/e2e_tests/syntaxHighlighting.spec.js +90 -0
  45. package/dist/e2e_tests/syntaxHighlighting.spec.js.map +1 -0
  46. package/dist/e2e_tests/syntaxValidation.spec.d.ts +1 -0
  47. package/dist/e2e_tests/syntaxValidation.spec.js +116 -0
  48. package/dist/e2e_tests/syntaxValidation.spec.js.map +1 -0
  49. package/dist/historyNavigation.d.ts +7 -0
  50. package/dist/historyNavigation.js +163 -0
  51. package/dist/historyNavigation.js.map +1 -0
  52. package/dist/icons.d.ts +2 -0
  53. package/dist/icons.js +62 -0
  54. package/dist/icons.js.map +1 -0
  55. package/dist/index.d.ts +4 -0
  56. package/dist/index.js +5 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/lang-cypher/autocomplete.d.ts +6 -0
  59. package/dist/lang-cypher/autocomplete.js +115 -0
  60. package/dist/lang-cypher/autocomplete.js.map +1 -0
  61. package/dist/lang-cypher/constants.d.ts +42 -0
  62. package/dist/lang-cypher/constants.js +69 -0
  63. package/dist/lang-cypher/constants.js.map +1 -0
  64. package/dist/lang-cypher/contants.test.d.ts +1 -0
  65. package/dist/lang-cypher/contants.test.js +103 -0
  66. package/dist/lang-cypher/contants.test.js.map +1 -0
  67. package/dist/lang-cypher/createCypherTheme.d.ts +26 -0
  68. package/dist/lang-cypher/createCypherTheme.js +182 -0
  69. package/dist/lang-cypher/createCypherTheme.js.map +1 -0
  70. package/dist/lang-cypher/langCypher.d.ts +14 -0
  71. package/dist/lang-cypher/langCypher.js +23 -0
  72. package/dist/lang-cypher/langCypher.js.map +1 -0
  73. package/dist/lang-cypher/lintWorker.d.ts +12 -0
  74. package/dist/lang-cypher/lintWorker.js +14 -0
  75. package/dist/lang-cypher/lintWorker.js.map +1 -0
  76. package/dist/lang-cypher/parser-adapter.d.ts +19 -0
  77. package/dist/lang-cypher/parser-adapter.js +113 -0
  78. package/dist/lang-cypher/parser-adapter.js.map +1 -0
  79. package/dist/lang-cypher/signatureHelp.d.ts +4 -0
  80. package/dist/lang-cypher/signatureHelp.js +109 -0
  81. package/dist/lang-cypher/signatureHelp.js.map +1 -0
  82. package/dist/lang-cypher/syntaxValidation.d.ts +4 -0
  83. package/dist/lang-cypher/syntaxValidation.js +52 -0
  84. package/dist/lang-cypher/syntaxValidation.js.map +1 -0
  85. package/dist/lang-cypher/themeIcons.d.ts +7 -0
  86. package/dist/lang-cypher/themeIcons.js +22 -0
  87. package/dist/lang-cypher/themeIcons.js.map +1 -0
  88. package/dist/lang-cypher/utils.d.ts +2 -0
  89. package/dist/lang-cypher/utils.js +10 -0
  90. package/dist/lang-cypher/utils.js.map +1 -0
  91. package/dist/ndlTokensCopy.d.ts +570 -0
  92. package/dist/ndlTokensCopy.js +571 -0
  93. package/dist/ndlTokensCopy.js.map +1 -0
  94. package/dist/ndlTokensCopy.test.d.ts +1 -0
  95. package/dist/ndlTokensCopy.test.js +12 -0
  96. package/dist/ndlTokensCopy.test.js.map +1 -0
  97. package/dist/neo4jSetup.d.ts +6 -0
  98. package/dist/neo4jSetup.js +120 -0
  99. package/dist/neo4jSetup.js.map +1 -0
  100. package/dist/themes.d.ts +11 -0
  101. package/dist/themes.js +93 -0
  102. package/dist/themes.js.map +1 -0
  103. package/dist/tsconfig.tsbuildinfo +1 -0
  104. package/package.json +82 -0
  105. package/src/CypherEditor.test.tsx +200 -0
  106. package/src/CypherEditor.tsx +607 -0
  107. package/src/constants.ts +1 -0
  108. package/src/e2e_tests/autoCompletion.spec.tsx +546 -0
  109. package/src/e2e_tests/configuration.spec.tsx +111 -0
  110. package/src/e2e_tests/debounce.spec.tsx +106 -0
  111. package/src/e2e_tests/e2eUtils.ts +97 -0
  112. package/src/e2e_tests/extraKeybindings.spec.tsx +55 -0
  113. package/src/e2e_tests/historyNavigation.spec.tsx +315 -0
  114. package/src/e2e_tests/performanceTest.spec.tsx +163 -0
  115. package/src/e2e_tests/sanityChecks.spec.tsx +72 -0
  116. package/src/e2e_tests/signatureHelp.spec.tsx +445 -0
  117. package/src/e2e_tests/snippets.spec.tsx +92 -0
  118. package/src/e2e_tests/syntaxHighlighting.spec.tsx +200 -0
  119. package/src/e2e_tests/syntaxValidation.spec.tsx +243 -0
  120. package/src/historyNavigation.ts +191 -0
  121. package/src/icons.ts +90 -0
  122. package/src/index.ts +4 -0
  123. package/src/lang-cypher/autocomplete.ts +147 -0
  124. package/src/lang-cypher/constants.ts +88 -0
  125. package/src/lang-cypher/contants.test.ts +108 -0
  126. package/src/lang-cypher/createCypherTheme.ts +249 -0
  127. package/src/lang-cypher/langCypher.ts +43 -0
  128. package/src/lang-cypher/lintWorker.ts +31 -0
  129. package/src/lang-cypher/parser-adapter.ts +145 -0
  130. package/src/lang-cypher/signatureHelp.ts +151 -0
  131. package/src/lang-cypher/syntaxValidation.ts +66 -0
  132. package/src/lang-cypher/themeIcons.ts +27 -0
  133. package/src/lang-cypher/utils.ts +9 -0
  134. package/src/ndlTokensCopy.test.ts +12 -0
  135. package/src/ndlTokensCopy.ts +570 -0
  136. package/src/neo4jSetup.tsx +190 -0
  137. package/src/themes.ts +105 -0
  138. package/src/viteEnv.d.ts +1 -0
@@ -0,0 +1,163 @@
1
+ import { testData } from '@neo4j-cypher/language-support';
2
+ import { expect, test } from '@playwright/experimental-ct-react';
3
+ import { CypherEditor } from '../CypherEditor';
4
+ import { CypherEditorPage } from './e2eUtils';
5
+
6
+ test.use({ viewport: { width: 1000, height: 500 } });
7
+ declare global {
8
+ interface Window {
9
+ longtasks: number[];
10
+ }
11
+ }
12
+
13
+ test('benchmarking & performance test session', async ({
14
+ browserName,
15
+ mount,
16
+ page,
17
+ }) => {
18
+ test.skip(browserName !== 'chromium');
19
+ const client = await page.context().newCDPSession(page);
20
+ if (process.env.BENCHMARKING === 'true') {
21
+ test.setTimeout(1000000);
22
+ await client.send('Performance.enable');
23
+ await client.send('Emulation.setCPUThrottlingRate', { rate: 4 });
24
+ await client.send('Overlay.setShowFPSCounter', { show: true });
25
+
26
+ await page.evaluate(() => {
27
+ window.longtasks = [];
28
+ const observer = new PerformanceObserver((list) => {
29
+ window.longtasks.push(...list.getEntries().map((e) => e.duration));
30
+ });
31
+
32
+ observer.observe({ entryTypes: ['longtask'] });
33
+ });
34
+ } else {
35
+ test.setTimeout(30 * 1000);
36
+ }
37
+ const editorPage = new CypherEditorPage(page);
38
+ const component = await mount(
39
+ <CypherEditor
40
+ prompt="neo4j>"
41
+ theme="dark"
42
+ lint
43
+ schema={testData.mockSchema}
44
+ />,
45
+ );
46
+
47
+ // pressSequentially is less efficient -> we want to test the performance of the editor
48
+ await editorPage.getEditor().pressSequentially(`
49
+ MATCH (n:Person) RETURN m;`);
50
+
51
+ await editorPage.checkErrorMessage('m', 'Variable `m` not defined');
52
+
53
+ // set and unset large query a few times
54
+ await component.update(
55
+ <CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
56
+ );
57
+ await component.update(
58
+ <CypherEditor value="" schema={testData.mockSchema} />,
59
+ );
60
+
61
+ await component.update(
62
+ <CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
63
+ );
64
+ await component.update(<CypherEditor value="" />);
65
+
66
+ await component.update(
67
+ <CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
68
+ );
69
+ await component.update(
70
+ <CypherEditor value="" schema={testData.mockSchema} />,
71
+ );
72
+
73
+ await component.update(
74
+ <CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
75
+ );
76
+ await component.update(
77
+ <CypherEditor value="" schema={testData.mockSchema} />,
78
+ );
79
+
80
+ await component.update(
81
+ <CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
82
+ );
83
+
84
+ await editorPage.getEditor().pressSequentially(`
85
+ MATCH (n:P`);
86
+
87
+ await expect(
88
+ page.locator('.cm-tooltip-autocomplete').getByText('Person'),
89
+ ).toBeVisible();
90
+
91
+ await page.locator('.cm-tooltip-autocomplete').getByText('Person').click();
92
+
93
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
94
+
95
+ await expect(component).toContainText('MATCH (n:Person');
96
+
97
+ await editorPage.getEditor().pressSequentially(') RETRN my');
98
+
99
+ await expect(component).toContainText('MATCH (n:Person) RETRN m');
100
+
101
+ await editorPage.checkErrorMessage(
102
+ 'RETRN',
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
+ );
105
+
106
+ await editorPage
107
+ .getEditor()
108
+ .pressSequentially('veryveryveryverylongvariable');
109
+
110
+ if (process.env.BENCHMARKING === 'true') {
111
+ const longtasks = await page.evaluate(() => window.longtasks);
112
+ const sortedLongTasks = longtasks.sort((a, b) => a - b);
113
+ const medianLongTask =
114
+ sortedLongTasks[Math.floor(sortedLongTasks.length / 2)];
115
+ const averageLongTask =
116
+ sortedLongTasks.reduce((a, b) => a + b, 0) / sortedLongTasks.length;
117
+ const over500 = sortedLongTasks.filter((t) => t > 500).length;
118
+ const nintyninethPercentile =
119
+ sortedLongTasks[Math.floor(sortedLongTasks.length * 0.99)];
120
+ const longTaskCount = longtasks.length;
121
+ const totalLongTaskTime = longtasks.reduce((a, b) => a + b, 0);
122
+
123
+ const USER_ID = 1226722;
124
+ const API_KEY = process.env.GRAFANA_API_KEY;
125
+ if (!API_KEY) {
126
+ throw new Error('Missing grafana api key');
127
+ }
128
+
129
+ const metrics = {
130
+ medianLongTask,
131
+ averageLongTask,
132
+ over500,
133
+ nintyninethPercentile,
134
+ longTaskCount,
135
+ totalLongTaskTime,
136
+ };
137
+ const body = Object.entries(metrics)
138
+ .map(
139
+ ([key, value]) =>
140
+ `benchmark,bar_label=${key},source=playwright metric=${value}`,
141
+ )
142
+ .join('\n');
143
+
144
+ await fetch(
145
+ 'https://influx-prod-39-prod-eu-north-0.grafana.net/api/v1/push/influx/write',
146
+ {
147
+ method: 'post',
148
+ body,
149
+ headers: {
150
+ Authorization: `Bearer ${USER_ID}:${API_KEY}`,
151
+ 'Content-Type': 'text/plain',
152
+ },
153
+ },
154
+ ).then((res) => {
155
+ if (res.ok) {
156
+ // eslint-disable-next-line no-console
157
+ console.log('Metrics pushed to grafana successfully');
158
+ } else {
159
+ throw new Error(`Failed to push metrics to grafana: ${res.statusText}`);
160
+ }
161
+ });
162
+ }
163
+ });
@@ -0,0 +1,72 @@
1
+ import { expect, test } from '@playwright/experimental-ct-react';
2
+ import { CypherEditor } from '../CypherEditor';
3
+
4
+ test('can mount the editor with text', async ({ mount }) => {
5
+ const component = await mount(<CypherEditor value="MATCH (n) RETURN n;" />);
6
+
7
+ await expect(component).toContainText('MATCH (n) RETURN n;');
8
+ });
9
+
10
+ test('the editors text can be externally controlled ', async ({ mount }) => {
11
+ const intitialValue = 'MATCH (n) RETURN n;';
12
+
13
+ const component = await mount(<CypherEditor value={intitialValue} />);
14
+
15
+ await expect(component).toContainText(intitialValue);
16
+
17
+ const newValue = 'RETURN 123';
18
+ await component.update(<CypherEditor value={newValue} />);
19
+
20
+ await expect(component).toContainText(newValue);
21
+ });
22
+
23
+ test('the editor can report changes to the text ', async ({ mount, page }) => {
24
+ const intitialValue = 'MATCH (n) ';
25
+
26
+ let editorValueCopy = intitialValue;
27
+ const onChange = (val: string) => {
28
+ editorValueCopy = val;
29
+ };
30
+
31
+ await mount(<CypherEditor value={intitialValue} onChange={onChange} />);
32
+
33
+ const textField = page.getByRole('textbox');
34
+
35
+ await textField.fill('');
36
+ await textField.fill('RETURN 12');
37
+ await expect(textField).toHaveText('RETURN 12');
38
+
39
+ // editor update is debounced, retry wait for debounced
40
+ await expect(() => {
41
+ expect(editorValueCopy).toBe('RETURN 12');
42
+ }).toPass({ intervals: [300, 300, 1000] });
43
+ });
44
+
45
+ test('can complete RETURN', async ({ page, mount }) => {
46
+ await mount(<CypherEditor />);
47
+ const textField = page.getByRole('textbox');
48
+
49
+ await textField.fill('RETU');
50
+
51
+ await page.getByText('RETURN').click();
52
+ await expect(textField).toHaveText('RETURN');
53
+ });
54
+
55
+ test('can complete CALL/CREATE', async ({ page, mount }) => {
56
+ await mount(<CypherEditor />);
57
+ const textField = page.getByRole('textbox');
58
+
59
+ await textField.fill('C');
60
+ await expect(page.getByText('CALL')).toBeVisible();
61
+ await expect(page.getByText('CREATE')).toBeVisible();
62
+
63
+ await textField.fill('CA');
64
+ await expect(page.getByText('CALL')).toBeVisible();
65
+ await expect(page.getByText('CREATE')).not.toBeVisible();
66
+
67
+ // wait for the autocomplete interactivity
68
+ await page.waitForTimeout(500);
69
+ await textField.press('Enter');
70
+
71
+ await expect(textField).toHaveText('CALL');
72
+ });
@@ -0,0 +1,445 @@
1
+ /* eslint-disable @typescript-eslint/unbound-method */
2
+ import { testData } from '@neo4j-cypher/language-support';
3
+ import { expect, test } from '@playwright/experimental-ct-react';
4
+ import { Locator } from 'playwright/test';
5
+ import { CypherEditor } from '../CypherEditor';
6
+
7
+ test.use({ viewport: { width: 1000, height: 500 } });
8
+
9
+ type TooltipExpectations = {
10
+ includes?: string[];
11
+ excludes?: string[];
12
+ };
13
+
14
+ const importCsvProc =
15
+ testData.mockSchema.procedures['CYPHER 5']['apoc.import.csv'];
16
+
17
+ function testTooltip(tooltip: Locator, expectations: TooltipExpectations) {
18
+ const includes = expectations.includes ?? [];
19
+ const excludes = expectations.excludes ?? [];
20
+
21
+ const included = Promise.all(
22
+ includes.map((text) => {
23
+ return expect(tooltip).toContainText(text, {
24
+ timeout: 10000,
25
+ });
26
+ }),
27
+ );
28
+
29
+ const excluded = Promise.all(
30
+ excludes.map((text) => {
31
+ return expect(tooltip).not.toContainText(text, {
32
+ timeout: 10000,
33
+ });
34
+ }),
35
+ );
36
+
37
+ return Promise.all([included, excluded]);
38
+ }
39
+
40
+ test('Signature help works for functions', async ({ page, mount }) => {
41
+ const query = 'RETURN abs(';
42
+
43
+ await mount(
44
+ <CypherEditor
45
+ value={query}
46
+ schema={testData.mockSchema}
47
+ autofocus={true}
48
+ />,
49
+ );
50
+
51
+ await expect(page.locator('.cm-signature-help-panel')).toBeVisible({
52
+ timeout: 10000,
53
+ });
54
+ });
55
+
56
+ test('Signature help works for procedures', async ({ page, mount }) => {
57
+ const query = 'CALL apoc.import.csv(';
58
+
59
+ await mount(
60
+ <CypherEditor
61
+ value={query}
62
+ schema={testData.mockSchema}
63
+ autofocus={true}
64
+ />,
65
+ );
66
+
67
+ await expect(page.locator('.cm-signature-help-panel')).toBeVisible({
68
+ timeout: 10000,
69
+ });
70
+ });
71
+
72
+ test('Signature help shows the description for the first argument', async ({
73
+ page,
74
+ mount,
75
+ }) => {
76
+ const query = 'CALL apoc.import.csv(';
77
+
78
+ await mount(
79
+ <CypherEditor
80
+ value={query}
81
+ schema={testData.mockSchema}
82
+ autofocus={true}
83
+ />,
84
+ );
85
+
86
+ const tooltip = page.locator('.cm-signature-help-panel');
87
+
88
+ await testTooltip(tooltip, {
89
+ includes: [
90
+ testData.mockSchema.procedures['CYPHER 5']['apoc.import.csv']
91
+ .argumentDescription[0].description,
92
+ testData.mockSchema.procedures['CYPHER 5']['apoc.import.csv'].description,
93
+ ],
94
+ });
95
+ });
96
+
97
+ test('Signature help shows the description for the first argument when the cursor is at that position', async ({
98
+ page,
99
+ mount,
100
+ }) => {
101
+ const query = 'CALL apoc.import.csv()';
102
+
103
+ await mount(
104
+ <CypherEditor value={query} schema={testData.mockSchema} offset={21} />,
105
+ );
106
+
107
+ const tooltip = page.locator('.cm-signature-help-panel');
108
+
109
+ await testTooltip(tooltip, {
110
+ includes: [
111
+ importCsvProc.argumentDescription[0].description,
112
+ importCsvProc.description,
113
+ ],
114
+ });
115
+ });
116
+
117
+ test('Signature help shows the description for the second argument', async ({
118
+ page,
119
+ mount,
120
+ }) => {
121
+ const query = 'CALL apoc.import.csv(nodes,';
122
+
123
+ await mount(
124
+ <CypherEditor
125
+ value={query}
126
+ schema={testData.mockSchema}
127
+ autofocus={true}
128
+ />,
129
+ );
130
+
131
+ const tooltip = page.locator('.cm-signature-help-panel');
132
+
133
+ await testTooltip(tooltip, {
134
+ includes: [
135
+ importCsvProc.argumentDescription[1].description,
136
+ importCsvProc.description,
137
+ ],
138
+ });
139
+ });
140
+
141
+ test('Signature help shows the description for the second argument when the cursor is at that position', async ({
142
+ page,
143
+ mount,
144
+ }) => {
145
+ const query = 'CALL apoc.import.csv(nodes,)';
146
+
147
+ await mount(
148
+ <CypherEditor value={query} schema={testData.mockSchema} offset={27} />,
149
+ );
150
+
151
+ const tooltip = page.locator('.cm-signature-help-panel');
152
+
153
+ await testTooltip(tooltip, {
154
+ includes: [
155
+ importCsvProc.argumentDescription[1].description,
156
+ importCsvProc.description,
157
+ ],
158
+ });
159
+ });
160
+
161
+ test('Signature help shows the description for the second argument when the cursor is at that position, even after whitespaces', async ({
162
+ page,
163
+ mount,
164
+ }) => {
165
+ const query = 'CALL apoc.import.csv(nodes, )';
166
+
167
+ await mount(
168
+ <CypherEditor value={query} schema={testData.mockSchema} offset={28} />,
169
+ );
170
+
171
+ const tooltip = page.locator('.cm-signature-help-panel');
172
+
173
+ await testTooltip(tooltip, {
174
+ includes: [
175
+ 'rels :: LIST<MAP>',
176
+ 'Imports `NODE` and `RELATIONSHIP` values with the given labels and types from the provided CSV file',
177
+ ],
178
+ });
179
+ });
180
+
181
+ test('Signature help shows description for arguments with a space following a separator', async ({
182
+ page,
183
+ mount,
184
+ }) => {
185
+ const query = 'CALL apoc.import.csv(nodes, ';
186
+
187
+ await mount(
188
+ <CypherEditor
189
+ value={query}
190
+ schema={testData.mockSchema}
191
+ autofocus={true}
192
+ />,
193
+ );
194
+
195
+ const tooltip = page.locator('.cm-signature-help-panel');
196
+
197
+ await testTooltip(tooltip, {
198
+ includes: [
199
+ 'rels :: LIST<MAP>',
200
+ 'Imports `NODE` and `RELATIONSHIP` values with the given labels and types from the provided CSV file',
201
+ ],
202
+ });
203
+ });
204
+
205
+ test('Signature help shows the description for the third argument', async ({
206
+ page,
207
+ mount,
208
+ }) => {
209
+ const query = 'CALL apoc.import.csv(nodes, rels,';
210
+
211
+ await mount(
212
+ <CypherEditor
213
+ value={query}
214
+ schema={testData.mockSchema}
215
+ autofocus={true}
216
+ />,
217
+ );
218
+
219
+ const tooltip = page.locator('.cm-signature-help-panel');
220
+
221
+ await testTooltip(tooltip, {
222
+ includes: [
223
+ 'config :: MAP',
224
+ 'Imports `NODE` and `RELATIONSHIP` values with the given labels and types from the provided CSV file',
225
+ ],
226
+ });
227
+ });
228
+
229
+ test('Signature help works on multiline queries', async ({ page, mount }) => {
230
+ const query = `CALL apoc.import.csv(
231
+ nodes,
232
+ rels,
233
+ `;
234
+
235
+ await mount(
236
+ <CypherEditor
237
+ value={query}
238
+ schema={testData.mockSchema}
239
+ autofocus={true}
240
+ />,
241
+ );
242
+
243
+ const tooltip = page.locator('.cm-signature-help-panel');
244
+
245
+ await testTooltip(tooltip, {
246
+ includes: [
247
+ 'config :: MAP',
248
+ 'Imports `NODE` and `RELATIONSHIP` values with the given labels and types from the provided CSV file',
249
+ ],
250
+ });
251
+ });
252
+
253
+ test('Signature help only shows the description past the last argument', async ({
254
+ page,
255
+ mount,
256
+ }) => {
257
+ const query = 'CALL apoc.import.csv(nodes, rels, config,';
258
+
259
+ await mount(
260
+ <CypherEditor
261
+ value={query}
262
+ schema={testData.mockSchema}
263
+ autofocus={true}
264
+ />,
265
+ );
266
+ 1;
267
+
268
+ const tooltip = page.locator('.cm-signature-help-panel');
269
+
270
+ await testTooltip(tooltip, {
271
+ includes: [
272
+ 'apoc.import.csv(nodes :: LIST<MAP>, rels :: LIST<MAP>, config :: MAP)',
273
+ 'Imports `NODE` and `RELATIONSHIP` values with the given labels and types from the provided CSV file.',
274
+ ],
275
+ });
276
+ });
277
+
278
+ test('Signature help does not show any help when method finished', async ({
279
+ page,
280
+ mount,
281
+ }) => {
282
+ const query = 'CALL apoc.import.csv(nodes, rels, config)';
283
+
284
+ await mount(
285
+ <CypherEditor
286
+ value={query}
287
+ schema={testData.mockSchema}
288
+ autofocus={true}
289
+ />,
290
+ );
291
+
292
+ await expect(page.locator('.cm-signature-help-panel')).not.toBeVisible({
293
+ timeout: 10000,
294
+ });
295
+ });
296
+
297
+ test('Signature help does not blow up on empty query', async ({
298
+ page,
299
+ mount,
300
+ }) => {
301
+ const query = '';
302
+
303
+ await mount(
304
+ <CypherEditor
305
+ value={query}
306
+ schema={testData.mockSchema}
307
+ autofocus={true}
308
+ />,
309
+ );
310
+
311
+ await expect(page.locator('.cm-signature-help-panel')).not.toBeVisible({
312
+ timeout: 10000,
313
+ });
314
+ });
315
+
316
+ test('Signature help is shown below the text by default', async ({
317
+ page,
318
+ mount,
319
+ }) => {
320
+ // We need to introduce new lines to make sure there's
321
+ // enough space to show the tooltip above
322
+ const query = '\n\n\n\n\n\n\nRETURN abs(';
323
+
324
+ await mount(
325
+ <CypherEditor
326
+ value={query}
327
+ schema={testData.mockSchema}
328
+ autofocus={true}
329
+ />,
330
+ );
331
+
332
+ await expect(
333
+ page.locator('.cm-signature-help-panel.cm-tooltip-below'),
334
+ ).toBeVisible({
335
+ timeout: 10000,
336
+ });
337
+ });
338
+
339
+ test('Setting showSignatureTooltipBelow to true shows the signature help above the text', async ({
340
+ page,
341
+ mount,
342
+ }) => {
343
+ // We need to introduce new lines to make sure there's
344
+ // enough space to show the tooltip above
345
+ const query = '\n\n\n\n\n\n\nRETURN abs(';
346
+
347
+ await mount(
348
+ <CypherEditor
349
+ value={query}
350
+ schema={testData.mockSchema}
351
+ showSignatureTooltipBelow={true}
352
+ autofocus={true}
353
+ />,
354
+ );
355
+
356
+ await expect(
357
+ page.locator('.cm-signature-help-panel.cm-tooltip-below'),
358
+ ).toBeVisible({
359
+ timeout: 10000,
360
+ });
361
+ });
362
+
363
+ test('Setting showSignatureTooltipBelow to false shows the signature help above the text', async ({
364
+ page,
365
+ mount,
366
+ }) => {
367
+ // We need to introduce new lines to make sure there's
368
+ // enough space to show the tooltip above
369
+ const query = '\n\n\n\n\n\n\nRETURN abs(';
370
+
371
+ await mount(
372
+ <CypherEditor
373
+ value={query}
374
+ schema={testData.mockSchema}
375
+ showSignatureTooltipBelow={false}
376
+ autofocus={true}
377
+ />,
378
+ );
379
+
380
+ await expect(
381
+ page.locator('.cm-signature-help-panel.cm-tooltip-above'),
382
+ ).toBeVisible({
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],
444
+ });
445
+ });