@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,92 @@
1
+ import { expect, test } from '@playwright/experimental-ct-react';
2
+ import { CypherEditor } from '../CypherEditor';
3
+
4
+ test('can complete pattern snippet', async ({ page, mount }) => {
5
+ await mount(<CypherEditor />);
6
+ const textField = page.getByRole('textbox');
7
+
8
+ await textField.fill('MATCH ()-[]->()');
9
+
10
+ await page.locator('.cm-tooltip-autocomplete').getByText('-[]->()').click();
11
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
12
+
13
+ await textField.press('Tab');
14
+ await textField.press('Tab');
15
+
16
+ await expect(textField).toHaveText('MATCH ()-[]->()-[ ]->( )');
17
+ });
18
+
19
+ test('can navigate snippet', async ({ page, mount }) => {
20
+ await mount(<CypherEditor />);
21
+ const textField = page.getByRole('textbox');
22
+
23
+ await textField.fill('CREATE INDEX abc FOR ()');
24
+
25
+ await page
26
+ .locator('.cm-tooltip-autocomplete')
27
+ .getByText('-[]-()', { exact: true })
28
+ .click();
29
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
30
+ await expect(page.locator('.cm-snippetField')).toHaveCount(2);
31
+
32
+ await textField.press('Tab');
33
+ await textField.press('Shift+Tab');
34
+
35
+ await expect(textField).toHaveText('CREATE INDEX abc FOR ()-[ ]-( )');
36
+
37
+ await textField.press('a');
38
+ await expect(textField).toHaveText('CREATE INDEX abc FOR ()-[a]-( )');
39
+
40
+ await textField.press('Escape');
41
+ await textField.press('Escape');
42
+ await expect(page.locator('.cm-snippetField')).toHaveCount(0);
43
+ await textField.press('Tab');
44
+ await expect(textField).toHaveText('CREATE INDEX abc FOR ()-[a ]-( )');
45
+ });
46
+
47
+ test('can accept completion inside pattern snippet', async ({
48
+ page,
49
+ mount,
50
+ }) => {
51
+ await mount(<CypherEditor schema={{ labels: ['City'] }} />);
52
+ const textField = page.getByRole('textbox');
53
+
54
+ await textField.fill('MATCH ()-[]->()');
55
+
56
+ await page.locator('.cm-tooltip-autocomplete').getByText('-[]->()').click();
57
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
58
+
59
+ // move to node
60
+ await textField.press('Tab');
61
+
62
+ // get & accept completion
63
+ await textField.press(':');
64
+ await expect(
65
+ page.locator('.cm-tooltip-autocomplete').getByText('City'),
66
+ ).toBeVisible();
67
+
68
+ await textField.press('Tab', { delay: 300 });
69
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
70
+
71
+ // tab out of the snippet
72
+ await textField.press('Tab', { delay: 300 });
73
+
74
+ await expect(textField).toHaveText('MATCH ()-[]->()-[ ]->(:City)');
75
+ });
76
+
77
+ test('does not automatically open completion panel for expressions after snippet trigger char', async ({
78
+ page,
79
+ mount,
80
+ }) => {
81
+ await mount(<CypherEditor />);
82
+ const textField = page.getByRole('textbox');
83
+
84
+ await textField.fill('RETURN (1)');
85
+
86
+ // expect the panel to not show up
87
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
88
+
89
+ // unless manually triggered
90
+ await textField.press('Control+ ');
91
+ await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible();
92
+ });
@@ -0,0 +1,200 @@
1
+ import { expect, test } from '@playwright/experimental-ct-react';
2
+ import { CypherEditor } from '../CypherEditor';
3
+ import { darkThemeConstants, lightThemeConstants } from '../themes';
4
+ import { CypherEditorPage } from './e2eUtils';
5
+
6
+ test('light theme highlighting', async ({ page, mount }) => {
7
+ const editorPage = new CypherEditorPage(page);
8
+ const query = `
9
+ MATCH (variable :Label)-[:REL_TYPE]->()
10
+ WHERE variable.property = "String"
11
+ OR namespaced.function() = false
12
+ // comment
13
+ OR $parameter > 1234
14
+ RETURN variable;`;
15
+
16
+ await mount(<CypherEditor value={query} theme="light" />);
17
+
18
+ const keywordcolors = await Promise.all(
19
+ ['MATCH', 'WHERE', 'RETURN'].map((kw) =>
20
+ editorPage.getHexColorOfLocator(page.getByText(kw)),
21
+ ),
22
+ );
23
+ keywordcolors.every((kw) =>
24
+ expect(kw).toEqual(
25
+ lightThemeConstants.highlightStyles.keyword.toLowerCase(),
26
+ ),
27
+ );
28
+
29
+ const labelReltype = await Promise.all(
30
+ ['Label', 'REL_TYPE'].map((kw) =>
31
+ editorPage.getHexColorOfLocator(page.getByText(kw)),
32
+ ),
33
+ );
34
+ labelReltype.every((kw) =>
35
+ expect(kw).toEqual(lightThemeConstants.highlightStyles.label.toLowerCase()),
36
+ );
37
+
38
+ expect(
39
+ await editorPage.getHexColorOfLocator(page.getByText('parameter')),
40
+ ).toEqual(lightThemeConstants.highlightStyles.paramValue.toLowerCase());
41
+
42
+ expect(
43
+ await editorPage.getHexColorOfLocator(page.getByText('property')),
44
+ ).toEqual(lightThemeConstants.highlightStyles.property.toLowerCase());
45
+
46
+ expect(
47
+ await editorPage.getHexColorOfLocator(page.getByText('false')),
48
+ ).toEqual(lightThemeConstants.highlightStyles.booleanLiteral.toLowerCase());
49
+
50
+ expect(
51
+ await editorPage.getHexColorOfLocator(page.getByText('String')),
52
+ ).toEqual(lightThemeConstants.highlightStyles.stringLiteral.toLowerCase());
53
+
54
+ expect(
55
+ await editorPage.getHexColorOfLocator(page.getByText('comment')),
56
+ ).toEqual(lightThemeConstants.highlightStyles.comment.toLowerCase());
57
+
58
+ expect(
59
+ await editorPage.getHexColorOfLocator(
60
+ page.getByText('1234', { exact: true }),
61
+ ),
62
+ ).toEqual(lightThemeConstants.highlightStyles.numberLiteral.toLowerCase());
63
+
64
+ expect(await editorPage.editorBackgroundIsUnset()).toEqual(false);
65
+ });
66
+
67
+ test('dark theme highlighting', async ({ page, mount }) => {
68
+ const editorPage = new CypherEditorPage(page);
69
+ const query = `
70
+ MATCH (variable :Label)-[:REL_TYPE]->()
71
+ WHERE variable.property = "String"
72
+ OR namespaced.function() = false
73
+ // comment
74
+ OR $parameter > 1234
75
+ RETURN variable;`;
76
+
77
+ await mount(<CypherEditor value={query} theme="dark" />);
78
+
79
+ const keywordcolors = await Promise.all(
80
+ ['MATCH', 'WHERE', 'RETURN'].map((kw) =>
81
+ editorPage.getHexColorOfLocator(page.getByText(kw)),
82
+ ),
83
+ );
84
+ keywordcolors.every((kw) =>
85
+ expect(kw).toEqual(
86
+ darkThemeConstants.highlightStyles.keyword.toLowerCase(),
87
+ ),
88
+ );
89
+
90
+ const labelReltype = await Promise.all(
91
+ ['Label', 'REL_TYPE'].map((kw) =>
92
+ editorPage.getHexColorOfLocator(page.getByText(kw)),
93
+ ),
94
+ );
95
+ labelReltype.every((kw) =>
96
+ expect(kw).toEqual(darkThemeConstants.highlightStyles.label.toLowerCase()),
97
+ );
98
+
99
+ expect(
100
+ await editorPage.getHexColorOfLocator(page.getByText('parameter')),
101
+ ).toEqual(darkThemeConstants.highlightStyles.paramValue.toLowerCase());
102
+
103
+ expect(
104
+ await editorPage.getHexColorOfLocator(page.getByText('property')),
105
+ ).toEqual(darkThemeConstants.highlightStyles.property.toLowerCase());
106
+
107
+ expect(
108
+ await editorPage.getHexColorOfLocator(page.getByText('false')),
109
+ ).toEqual(darkThemeConstants.highlightStyles.booleanLiteral.toLowerCase());
110
+
111
+ expect(
112
+ await editorPage.getHexColorOfLocator(page.getByText('String')),
113
+ ).toEqual(darkThemeConstants.highlightStyles.stringLiteral.toLowerCase());
114
+
115
+ expect(
116
+ await editorPage.getHexColorOfLocator(page.getByText('comment')),
117
+ ).toEqual(darkThemeConstants.highlightStyles.comment.toLowerCase());
118
+
119
+ expect(
120
+ await editorPage.getHexColorOfLocator(
121
+ page.getByText('1234', { exact: true }),
122
+ ),
123
+ ).toEqual(darkThemeConstants.highlightStyles.numberLiteral.toLowerCase());
124
+
125
+ expect(await editorPage.editorBackgroundIsUnset()).toEqual(false);
126
+ });
127
+
128
+ test('can live switch theme ', async ({ page, mount }) => {
129
+ const editorPage = new CypherEditorPage(page);
130
+ const component = await mount(<CypherEditor theme="light" value="RETURN" />);
131
+
132
+ expect(
133
+ await editorPage.getHexColorOfLocator(
134
+ page.getByText('RETURN', { exact: true }),
135
+ ),
136
+ ).toEqual(lightThemeConstants.highlightStyles.keyword.toLowerCase());
137
+
138
+ await component.update(<CypherEditor theme="dark" value="RETURN" />);
139
+
140
+ expect(
141
+ await editorPage.getHexColorOfLocator(
142
+ page.getByText('RETURN', { exact: true }),
143
+ ),
144
+ ).toEqual(darkThemeConstants.highlightStyles.keyword.toLowerCase());
145
+ });
146
+
147
+ test('respects prop to allow overriding bkg color', async ({ page, mount }) => {
148
+ const editorPage = new CypherEditorPage(page);
149
+ await mount(
150
+ <CypherEditor theme="light" value="text" overrideThemeBackgroundColor />,
151
+ );
152
+
153
+ expect(await editorPage.editorBackgroundIsUnset()).toEqual(true);
154
+ });
155
+
156
+ test('highlights multiline string literal correctly', async ({
157
+ page,
158
+ mount,
159
+ }) => {
160
+ const editorPage = new CypherEditorPage(page);
161
+ const query = `
162
+ RETURN "
163
+ multilinestring";`;
164
+
165
+ await mount(<CypherEditor theme="light" value={query} />);
166
+
167
+ expect(
168
+ await editorPage.getHexColorOfLocator(page.getByText('multilinestring')),
169
+ ).toEqual(lightThemeConstants.highlightStyles.stringLiteral.toLowerCase());
170
+ });
171
+
172
+ test('highlights multiline label correctly', async ({ page, mount }) => {
173
+ const editorPage = new CypherEditorPage(page);
174
+ const query = `
175
+ MATCH (v:\`
176
+
177
+ Label\`)
178
+ `;
179
+
180
+ await mount(<CypherEditor theme="light" value={query} />);
181
+
182
+ expect(
183
+ await editorPage.getHexColorOfLocator(page.getByText('Label')),
184
+ ).toEqual(lightThemeConstants.highlightStyles.label.toLowerCase());
185
+ });
186
+
187
+ test('highlights multiline comment correctly', async ({ page, mount }) => {
188
+ const editorPage = new CypherEditorPage(page);
189
+ const query = `
190
+ /*
191
+
192
+ comment
193
+ */";`;
194
+
195
+ await mount(<CypherEditor theme="light" value={query} />);
196
+
197
+ expect(
198
+ await editorPage.getHexColorOfLocator(page.getByText('comment')),
199
+ ).toEqual(lightThemeConstants.highlightStyles.comment.toLowerCase());
200
+ });
@@ -0,0 +1,243 @@
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
+ test('Prop lint set to false disables syntax validation', async ({
8
+ page,
9
+ mount,
10
+ }) => {
11
+ const query = 'METCH (n) RETURN n';
12
+
13
+ await mount(<CypherEditor value={query} lint={false} />);
14
+
15
+ await expect(page.locator('.cm-lintRange-error').last()).not.toBeVisible({
16
+ timeout: 10000,
17
+ });
18
+ });
19
+
20
+ test('Can turn linting back on', async ({ page, mount }) => {
21
+ const editorPage = new CypherEditorPage(page);
22
+ const query = 'METCH (n) RETURN n';
23
+
24
+ const component = await mount(<CypherEditor value={query} lint={false} />);
25
+
26
+ await expect(page.locator('.cm-lintRange-error').last()).not.toBeVisible({
27
+ timeout: 10000,
28
+ });
29
+
30
+ await component.update(<CypherEditor value={query} lint={true} />);
31
+
32
+ await editorPage.getEditor().fill('METCH (n) RETURN n');
33
+
34
+ await editorPage.checkErrorMessage(
35
+ 'METCH',
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
+ );
38
+ });
39
+
40
+ test('Syntactic errors are surfaced', async ({ page, mount }) => {
41
+ const editorPage = new CypherEditorPage(page);
42
+ const query = 'METCH (n) RETURN n';
43
+
44
+ await mount(<CypherEditor value={query} />);
45
+
46
+ await editorPage.checkErrorMessage(
47
+ 'METCH',
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
+ );
50
+ });
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
+
64
+ test('Errors for undefined labels are surfaced', async ({ page, mount }) => {
65
+ const editorPage = new CypherEditorPage(page);
66
+ const query = 'MATCH (n: Person) RETURN n';
67
+
68
+ await mount(
69
+ <CypherEditor
70
+ value={query}
71
+ schema={{ labels: ['Movie'], relationshipTypes: [] }}
72
+ />,
73
+ );
74
+
75
+ await editorPage.checkWarningMessage(
76
+ 'Person',
77
+ "Label Person is not present in the database. Make sure you didn't misspell it or that it is available when you run this statement in your application",
78
+ );
79
+ });
80
+
81
+ test('Errors for multiline undefined labels are highlighted correctly', async ({
82
+ page,
83
+ mount,
84
+ }) => {
85
+ const editorPage = new CypherEditorPage(page);
86
+ const query = `MATCH (n:\`Foo
87
+ Bar\`) RETURN n`;
88
+ const expectedMsg = `Label \`Foo
89
+ Bar\` is not present in the database. Make sure you didn't misspell it or that it is available when you run this statement in your application`;
90
+
91
+ await mount(
92
+ <CypherEditor
93
+ value={query}
94
+ schema={{ labels: ['Movie'], relationshipTypes: [] }}
95
+ />,
96
+ );
97
+
98
+ await editorPage.checkWarningMessage('`Foo', expectedMsg);
99
+ await editorPage.checkWarningMessage('Bar`', expectedMsg);
100
+ });
101
+
102
+ test('Semantic errors work in firefox', async ({
103
+ browserName,
104
+ page,
105
+ mount,
106
+ }) => {
107
+ test.skip(browserName !== 'firefox');
108
+ const editorPage = new CypherEditorPage(page);
109
+ const query = 'MATCH (n:OperationalPoint)--(m:OperationalPoint) RETURN s,m,n';
110
+
111
+ await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
112
+
113
+ await editorPage.checkErrorMessage('s,m,n', 'Variable `s` not defined');
114
+ });
115
+
116
+ test('Semantic errors are surfaced when there are no syntactic errors', async ({
117
+ page,
118
+ mount,
119
+ }) => {
120
+ const editorPage = new CypherEditorPage(page);
121
+ const query = 'MATCH (n) RETURN m';
122
+
123
+ await mount(<CypherEditor value={query} />);
124
+
125
+ await editorPage.checkErrorMessage('m', 'Variable `m` not defined');
126
+ });
127
+
128
+ test('Semantic errors are correctly accumulated', async ({ page, mount }) => {
129
+ const editorPage = new CypherEditorPage(page);
130
+ const query = 'CALL { MATCH (n) } IN TRANSACTIONS OF -1 ROWS';
131
+
132
+ await mount(<CypherEditor value={query} />);
133
+
134
+ await editorPage.checkErrorMessage(
135
+ 'MATCH (n)',
136
+ 'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
137
+ );
138
+
139
+ await editorPage.checkErrorMessage(
140
+ '-1',
141
+ "Invalid input. '-1' is not a valid value. Must be a positive integer",
142
+ );
143
+ });
144
+
145
+ test('Multiline errors are correctly placed', async ({ page, mount }) => {
146
+ const editorPage = new CypherEditorPage(page);
147
+ const query = `CALL {
148
+ MATCH (n)
149
+ } IN TRANSACTIONS
150
+ OF -1 ROWS`;
151
+
152
+ await mount(<CypherEditor value={query} />);
153
+
154
+ await editorPage.checkErrorMessage(
155
+ 'MATCH (n)',
156
+ 'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD)',
157
+ );
158
+
159
+ await editorPage.checkErrorMessage(
160
+ '-1',
161
+ "Invalid input. '-1' is not a valid value. Must be a positive integer",
162
+ );
163
+ });
164
+
165
+ test('Validation errors are correctly overlapped', async ({ page, mount }) => {
166
+ const editorPage = new CypherEditorPage(page);
167
+ const query = `CALL { MATCH (n)
168
+ RETURN n
169
+ } IN TRANSACTIONS
170
+ OF -1 ROWS`;
171
+
172
+ await mount(<CypherEditor value={query} />);
173
+
174
+ await editorPage.checkErrorMessage(
175
+ '-1',
176
+ 'Query cannot conclude with CALL (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
177
+ );
178
+
179
+ await editorPage.checkErrorMessage(
180
+ '-1',
181
+ "Invalid input. '-1' is not a valid value. Must be a positive integer",
182
+ );
183
+ });
184
+
185
+ test('Strikethroughs are shown for deprecated functions', async ({
186
+ page,
187
+ mount,
188
+ }) => {
189
+ const editorPage = new CypherEditorPage(page);
190
+ const query = `RETURN id()`;
191
+
192
+ await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
193
+ await expect(
194
+ editorPage.page.locator('.cm-deprecated-element').last(),
195
+ ).toBeVisible({ timeout: 10000 });
196
+ await editorPage.checkWarningMessage('id', 'Function id is deprecated.');
197
+ });
198
+
199
+ test('Strikethroughs are shown for deprecated procedures', async ({
200
+ page,
201
+ mount,
202
+ }) => {
203
+ const editorPage = new CypherEditorPage(page);
204
+ const query = `CALL apoc.create.uuids()`;
205
+
206
+ await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
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
+ />,
226
+ );
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
+
232
+ await editorPage.checkWarningMessage(
233
+ 'apoc.create.uuids',
234
+ 'Procedure apoc.create.uuids is deprecated.',
235
+ );
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
+ );
243
+ });