@neo4j-cypher/react-codemirror 2.0.0-next.0 → 2.0.0-next.10

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 (156) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +4 -1
  3. package/dist/{types/CypherEditor.d.ts → CypherEditor.d.ts} +69 -8
  4. package/dist/CypherEditor.js +300 -0
  5. package/dist/CypherEditor.js.map +1 -0
  6. package/dist/CypherEditor.test.js +151 -0
  7. package/dist/CypherEditor.test.js.map +1 -0
  8. package/dist/constants.d.ts +1 -0
  9. package/dist/constants.js +2 -0
  10. package/dist/constants.js.map +1 -0
  11. package/dist/e2e_tests/autoCompletion.spec.js +267 -0
  12. package/dist/e2e_tests/autoCompletion.spec.js.map +1 -0
  13. package/dist/e2e_tests/configuration.spec.js +83 -0
  14. package/dist/e2e_tests/configuration.spec.js.map +1 -0
  15. package/dist/e2e_tests/debounce.spec.js +65 -0
  16. package/dist/e2e_tests/debounce.spec.js.map +1 -0
  17. package/dist/e2e_tests/e2eUtils.js +60 -0
  18. package/dist/e2e_tests/e2eUtils.js.map +1 -0
  19. package/dist/e2e_tests/extraKeybindings.spec.js +43 -0
  20. package/dist/e2e_tests/extraKeybindings.spec.js.map +1 -0
  21. package/dist/e2e_tests/historyNavigation.spec.js +227 -0
  22. package/dist/e2e_tests/historyNavigation.spec.js.map +1 -0
  23. package/dist/e2e_tests/performanceTest.spec.d.ts +6 -0
  24. package/dist/e2e_tests/performanceTest.spec.js +96 -0
  25. package/dist/e2e_tests/performanceTest.spec.js.map +1 -0
  26. package/dist/e2e_tests/sanityChecks.spec.js +53 -0
  27. package/dist/e2e_tests/sanityChecks.spec.js.map +1 -0
  28. package/dist/e2e_tests/signatureHelp.spec.js +179 -0
  29. package/dist/e2e_tests/signatureHelp.spec.js.map +1 -0
  30. package/dist/e2e_tests/snippets.spec.js +62 -0
  31. package/dist/e2e_tests/snippets.spec.js.map +1 -0
  32. package/dist/e2e_tests/syntaxHighlighting.spec.d.ts +1 -0
  33. package/dist/e2e_tests/syntaxHighlighting.spec.js +90 -0
  34. package/dist/e2e_tests/syntaxHighlighting.spec.js.map +1 -0
  35. package/dist/e2e_tests/syntaxValidation.spec.d.ts +1 -0
  36. package/dist/e2e_tests/syntaxValidation.spec.js +79 -0
  37. package/dist/e2e_tests/syntaxValidation.spec.js.map +1 -0
  38. package/dist/historyNavigation.d.ts +7 -0
  39. package/dist/historyNavigation.js +163 -0
  40. package/dist/historyNavigation.js.map +1 -0
  41. package/dist/{types/icons.d.ts → icons.d.ts} +1 -1
  42. package/dist/icons.js +62 -0
  43. package/dist/icons.js.map +1 -0
  44. package/dist/index.d.ts +4 -0
  45. package/dist/index.js +5 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/lang-cypher/autocomplete.d.ts +6 -0
  48. package/dist/lang-cypher/autocomplete.js +124 -0
  49. package/dist/lang-cypher/autocomplete.js.map +1 -0
  50. package/dist/{types/lang-cypher → lang-cypher}/constants.d.ts +9 -0
  51. package/dist/lang-cypher/constants.js +65 -0
  52. package/dist/lang-cypher/constants.js.map +1 -0
  53. package/dist/lang-cypher/contants.test.d.ts +1 -0
  54. package/dist/lang-cypher/contants.test.js +103 -0
  55. package/dist/lang-cypher/contants.test.js.map +1 -0
  56. package/dist/lang-cypher/createCypherTheme.js +179 -0
  57. package/dist/lang-cypher/createCypherTheme.js.map +1 -0
  58. package/dist/lang-cypher/langCypher.d.ts +14 -0
  59. package/dist/lang-cypher/langCypher.js +30 -0
  60. package/dist/lang-cypher/langCypher.js.map +1 -0
  61. package/dist/lang-cypher/lintWorker.d.ts +8 -0
  62. package/dist/lang-cypher/lintWorker.js +4 -0
  63. package/dist/lang-cypher/lintWorker.js.map +1 -0
  64. package/dist/lang-cypher/parser-adapter.d.ts +19 -0
  65. package/dist/lang-cypher/parser-adapter.js +113 -0
  66. package/dist/lang-cypher/parser-adapter.js.map +1 -0
  67. package/dist/lang-cypher/signatureHelp.d.ts +4 -0
  68. package/dist/lang-cypher/signatureHelp.js +94 -0
  69. package/dist/lang-cypher/signatureHelp.js.map +1 -0
  70. package/dist/lang-cypher/syntaxValidation.d.ts +5 -0
  71. package/dist/lang-cypher/syntaxValidation.js +69 -0
  72. package/dist/lang-cypher/syntaxValidation.js.map +1 -0
  73. package/dist/lang-cypher/themeIcons.js +22 -0
  74. package/dist/lang-cypher/themeIcons.js.map +1 -0
  75. package/dist/lang-cypher/utils.d.ts +2 -0
  76. package/dist/lang-cypher/utils.js +10 -0
  77. package/dist/lang-cypher/utils.js.map +1 -0
  78. package/dist/ndlTokensCopy.js +380 -0
  79. package/dist/ndlTokensCopy.js.map +1 -0
  80. package/dist/ndlTokensCopy.test.d.ts +1 -0
  81. package/dist/ndlTokensCopy.test.js +12 -0
  82. package/dist/ndlTokensCopy.test.js.map +1 -0
  83. package/dist/neo4jSetup.js +120 -0
  84. package/dist/neo4jSetup.js.map +1 -0
  85. package/dist/{types/themes.d.ts → themes.d.ts} +1 -1
  86. package/dist/themes.js +114 -0
  87. package/dist/themes.js.map +1 -0
  88. package/dist/tsconfig.tsbuildinfo +1 -0
  89. package/package.json +30 -31
  90. package/src/CypherEditor.test.tsx +200 -0
  91. package/src/CypherEditor.tsx +292 -41
  92. package/src/constants.ts +1 -0
  93. package/src/e2e_tests/autoCompletion.spec.tsx +472 -0
  94. package/src/e2e_tests/configuration.spec.tsx +111 -0
  95. package/src/e2e_tests/debounce.spec.tsx +101 -0
  96. package/src/e2e_tests/{e2e-utils.ts → e2eUtils.ts} +11 -1
  97. package/src/e2e_tests/{extra-keybindings.spec.tsx → extraKeybindings.spec.tsx} +1 -3
  98. package/src/e2e_tests/historyNavigation.spec.tsx +315 -0
  99. package/src/e2e_tests/performanceTest.spec.tsx +158 -0
  100. package/src/e2e_tests/{sanity-checks.spec.tsx → sanityChecks.spec.tsx} +7 -22
  101. package/src/e2e_tests/signatureHelp.spec.tsx +380 -0
  102. package/src/e2e_tests/snippets.spec.tsx +92 -0
  103. package/src/e2e_tests/{syntax-highlighting.spec.tsx → syntaxHighlighting.spec.tsx} +1 -3
  104. package/src/e2e_tests/{syntax-validation.spec.tsx → syntaxValidation.spec.tsx} +6 -7
  105. package/src/{repl-mode.ts → historyNavigation.ts} +7 -30
  106. package/src/icons.ts +3 -0
  107. package/src/index.ts +5 -3
  108. package/src/lang-cypher/autocomplete.ts +107 -15
  109. package/src/lang-cypher/constants.ts +23 -0
  110. package/src/lang-cypher/contants.test.ts +6 -2
  111. package/src/lang-cypher/{create-cypher-theme.ts → createCypherTheme.ts} +41 -2
  112. package/src/lang-cypher/langCypher.ts +53 -0
  113. package/src/lang-cypher/lintWorker.ts +14 -0
  114. package/src/lang-cypher/parser-adapter.ts +145 -0
  115. package/src/lang-cypher/signatureHelp.ts +133 -0
  116. package/src/lang-cypher/syntaxValidation.ts +96 -0
  117. package/src/lang-cypher/utils.ts +9 -0
  118. package/src/{ndl-tokens-copy.test.ts → ndlTokensCopy.test.ts} +2 -1
  119. package/src/{neo4j-setup.tsx → neo4jSetup.tsx} +51 -1
  120. package/src/themes.ts +4 -2
  121. package/src/viteEnv.d.ts +1 -0
  122. package/dist/cjs/index.cjs +0 -1455
  123. package/dist/cjs/index.cjs.map +0 -7
  124. package/dist/esm/index.mjs +0 -1468
  125. package/dist/esm/index.mjs.map +0 -7
  126. package/dist/types/e2e_tests/mock-data.d.ts +0 -3779
  127. package/dist/types/index.d.ts +0 -5
  128. package/dist/types/lang-cypher/ParserAdapter.d.ts +0 -14
  129. package/dist/types/lang-cypher/autocomplete.d.ts +0 -3
  130. package/dist/types/lang-cypher/lang-cypher.d.ts +0 -7
  131. package/dist/types/lang-cypher/syntax-validation.d.ts +0 -3
  132. package/dist/types/repl-mode.d.ts +0 -8
  133. package/dist/types/tsconfig.tsbuildinfo +0 -1
  134. package/src/e2e_tests/auto-completion.spec.tsx +0 -232
  135. package/src/e2e_tests/history-navigation.spec.tsx +0 -144
  136. package/src/e2e_tests/mock-data.ts +0 -4310
  137. package/src/e2e_tests/performance-test.spec.tsx +0 -71
  138. package/src/lang-cypher/ParserAdapter.ts +0 -92
  139. package/src/lang-cypher/lang-cypher.ts +0 -32
  140. package/src/lang-cypher/syntax-validation.ts +0 -24
  141. /package/dist/{types/e2e_tests/auto-completion.spec.d.ts → CypherEditor.test.d.ts} +0 -0
  142. /package/dist/{types/e2e_tests/extra-keybindings.spec.d.ts → e2e_tests/autoCompletion.spec.d.ts} +0 -0
  143. /package/dist/{types/e2e_tests/history-navigation.spec.d.ts → e2e_tests/configuration.spec.d.ts} +0 -0
  144. /package/dist/{types/e2e_tests/performance-test.spec.d.ts → e2e_tests/debounce.spec.d.ts} +0 -0
  145. /package/dist/{types/e2e_tests/e2e-utils.d.ts → e2e_tests/e2eUtils.d.ts} +0 -0
  146. /package/dist/{types/e2e_tests/sanity-checks.spec.d.ts → e2e_tests/extraKeybindings.spec.d.ts} +0 -0
  147. /package/dist/{types/e2e_tests/syntax-highlighting.spec.d.ts → e2e_tests/historyNavigation.spec.d.ts} +0 -0
  148. /package/dist/{types/e2e_tests/syntax-validation.spec.d.ts → e2e_tests/sanityChecks.spec.d.ts} +0 -0
  149. /package/dist/{types/lang-cypher/contants.test.d.ts → e2e_tests/signatureHelp.spec.d.ts} +0 -0
  150. /package/dist/{types/ndl-tokens-copy.test.d.ts → e2e_tests/snippets.spec.d.ts} +0 -0
  151. /package/dist/{types/lang-cypher/create-cypher-theme.d.ts → lang-cypher/createCypherTheme.d.ts} +0 -0
  152. /package/dist/{types/lang-cypher/theme-icons.d.ts → lang-cypher/themeIcons.d.ts} +0 -0
  153. /package/dist/{types/ndl-tokens-copy.d.ts → ndlTokensCopy.d.ts} +0 -0
  154. /package/dist/{types/neo4j-setup.d.ts → neo4jSetup.d.ts} +0 -0
  155. /package/src/lang-cypher/{theme-icons.ts → themeIcons.ts} +0 -0
  156. /package/src/{ndl-tokens-copy.ts → ndlTokensCopy.ts} +0 -0
@@ -1,8 +1,6 @@
1
1
  import { expect, test } from '@playwright/experimental-ct-react';
2
2
  import { CypherEditor } from '../CypherEditor';
3
- import { CypherEditorPage } from './e2e-utils';
4
-
5
- test.use({ viewport: { width: 500, height: 500 } });
3
+ import { CypherEditorPage } from './e2eUtils';
6
4
 
7
5
  test('can add extra keybinding statically', async ({ mount, page }) => {
8
6
  const editorPage = new CypherEditorPage(page);
@@ -0,0 +1,315 @@
1
+ import { expect, test } from '@playwright/experimental-ct-react';
2
+ import { CypherEditor } from '../CypherEditor';
3
+ import { CypherEditorPage } from './e2eUtils';
4
+
5
+ test('respects preloaded history', async ({ page, mount }) => {
6
+ const editorPage = new CypherEditorPage(page);
7
+
8
+ const initialValue = 'MATCH (n) RETURN n;';
9
+
10
+ await mount(
11
+ <CypherEditor
12
+ value={initialValue}
13
+ history={['first', 'second']}
14
+ onExecute={() => {
15
+ /* needed to turn on history movements */
16
+ }}
17
+ />,
18
+ );
19
+
20
+ await editorPage.getEditor().press('ArrowUp');
21
+ await expect(page.getByText('first')).toBeVisible();
22
+
23
+ // First arrow up is to get to start of line
24
+ await editorPage.getEditor().press('ArrowUp');
25
+ await editorPage.getEditor().press('ArrowUp');
26
+ await expect(page.getByText('second')).toBeVisible();
27
+
28
+ // First arrow down is to get to end of line
29
+ await editorPage.getEditor().press('ArrowDown');
30
+ await editorPage.getEditor().press('ArrowDown');
31
+ await expect(page.getByText('first')).toBeVisible();
32
+ await editorPage.getEditor().press('ArrowDown');
33
+ await expect(page.getByText(initialValue)).toBeVisible();
34
+ });
35
+
36
+ test('can add new lines without onExecute', async ({ page, mount }) => {
37
+ const editorPage = new CypherEditorPage(page);
38
+
39
+ const editorComponent = await mount(<CypherEditor />);
40
+
41
+ // Ctrl-Enter does nothing when onExecute is false
42
+ await editorPage.getEditor().press('Control+Enter');
43
+ await expect(editorComponent).toHaveText('1\n', {
44
+ useInnerText: true,
45
+ });
46
+
47
+ // Enter adds new lines
48
+ await editorPage.getEditor().fill('Brock');
49
+ await editorPage.getEditor().press('Enter');
50
+ await editorPage.getEditor().press('Enter');
51
+ await expect(editorComponent).toHaveText('1\n2\n3\nBrock', {
52
+ useInnerText: true,
53
+ });
54
+
55
+ // Shift-Enter adds new lines
56
+ await editorPage.getEditor().press('Shift+Enter');
57
+ await editorPage.getEditor().press('Shift+Enter');
58
+ await expect(editorComponent).toHaveText('1\n2\n3\n4\n5\nBrock', {
59
+ useInnerText: true,
60
+ });
61
+ });
62
+
63
+ test('can add new lines with newLineOnEnter and without onExecute', async ({
64
+ page,
65
+ mount,
66
+ }) => {
67
+ const editorPage = new CypherEditorPage(page);
68
+
69
+ const editorComponent = await mount(<CypherEditor newLineOnEnter />);
70
+
71
+ // Ctrl-Enter does nothing when onExecute is false
72
+ await editorPage.getEditor().press('Control+Enter');
73
+ await expect(editorComponent).toHaveText('1\n', {
74
+ useInnerText: true,
75
+ });
76
+
77
+ // Enter adds new lines
78
+ await editorPage.getEditor().fill('Brock');
79
+ await editorPage.getEditor().press('Enter');
80
+ await editorPage.getEditor().press('Enter');
81
+ await expect(editorComponent).toHaveText('1\n2\n3\nBrock', {
82
+ useInnerText: true,
83
+ });
84
+
85
+ // Shift-Enter adds new lines
86
+ await editorPage.getEditor().press('Shift+Enter');
87
+ await editorPage.getEditor().press('Shift+Enter');
88
+ await expect(editorComponent).toHaveText('1\n2\n3\n4\n5\nBrock', {
89
+ useInnerText: true,
90
+ });
91
+ });
92
+
93
+ test('can execute queries and see them in history with newLineOnEnter', async ({
94
+ page,
95
+ mount,
96
+ }) => {
97
+ const editorPage = new CypherEditorPage(page);
98
+
99
+ const initialValue = `MATCH (n)
100
+ RETURN n;`;
101
+
102
+ const history: string[] = [];
103
+ const onExecute = (cmd: string) => {
104
+ history.unshift(cmd);
105
+ };
106
+
107
+ const editor = await mount(
108
+ <CypherEditor
109
+ value={initialValue}
110
+ history={history}
111
+ onExecute={onExecute}
112
+ newLineOnEnter
113
+ />,
114
+ );
115
+
116
+ // Execute initial query
117
+ await editorPage.getEditor().press('Control+Enter');
118
+ await editorPage.getEditor().press('Meta+Enter');
119
+ expect(history.length).toBe(1);
120
+
121
+ // Ensure query execution doesn't fire if the query is only whitespace
122
+ await editorPage.getEditor().fill(' ');
123
+ await editorPage.getEditor().press('Control+Enter');
124
+ await editorPage.getEditor().press('Meta+Enter');
125
+ expect(history.length).toBe(1);
126
+
127
+ // Ensure cmd+enter is required in multiline
128
+ await editorPage.getEditor().fill('multiline');
129
+ await editorPage.getEditor().press('Enter');
130
+ await editorPage.getEditor().press('Enter');
131
+ await editorPage.getEditor().press('Enter');
132
+ await editorPage.getEditor().press('Shift+Enter');
133
+ await page.keyboard.type('entry');
134
+ expect(history.length).toBe(1);
135
+
136
+ await editorPage.getEditor().press('Control+Enter');
137
+ await editorPage.getEditor().press('Meta+Enter');
138
+ expect(history.length).toBe(2);
139
+
140
+ // rerender with new history
141
+ await editor.update(
142
+ <CypherEditor
143
+ value={initialValue}
144
+ history={history}
145
+ onExecute={onExecute}
146
+ />,
147
+ );
148
+
149
+ // type a new query and make sure it's not lost when navigating history
150
+ await editorPage.getEditor().fill('draft');
151
+ await expect(page.getByText('draft')).toBeVisible();
152
+ expect(history.length).toBe(2);
153
+
154
+ // Navigate to the top of the editor before navigating history
155
+ await editorPage.getEditor().press('ArrowLeft');
156
+ await editorPage.getEditor().press('ArrowLeft');
157
+ await editorPage.getEditor().press('ArrowLeft');
158
+ await editorPage.getEditor().press('ArrowLeft');
159
+ await editorPage.getEditor().press('ArrowLeft');
160
+ await editorPage.getEditor().press('ArrowUp');
161
+
162
+ // Ensure moving down in the editor doesn't navigate history
163
+ await expect(page.getByText('multiline')).toBeVisible();
164
+
165
+ // arrow movements don't matter until bottom is hit
166
+ await editorPage.getEditor().press('ArrowDown');
167
+ await editorPage.getEditor().press('ArrowDown');
168
+ await editorPage.getEditor().press('ArrowUp');
169
+ await editorPage.getEditor().press('ArrowUp');
170
+ await editorPage.getEditor().press('ArrowDown');
171
+ await editorPage.getEditor().press('ArrowDown');
172
+ await editorPage.getEditor().press('ArrowDown');
173
+ await editorPage.getEditor().press('ArrowDown');
174
+ await editorPage.getEditor().press('ArrowDown');
175
+
176
+ // editor still multiline
177
+ await expect(page.getByText('multiline')).toBeVisible();
178
+
179
+ // until you hit the end where have the draft we created earlier
180
+ await editorPage.getEditor().press('ArrowDown');
181
+ await expect(page.getByText('draft')).toBeVisible();
182
+ });
183
+
184
+ test('can execute queries without newLineOnEnter', async ({ page, mount }) => {
185
+ const editorPage = new CypherEditorPage(page);
186
+
187
+ const initialValue = 'Brock';
188
+
189
+ const history: string[] = [];
190
+ const onExecute = (cmd: string) => {
191
+ history.unshift(cmd);
192
+ };
193
+
194
+ const editorComponent = await mount(
195
+ <CypherEditor value={initialValue} onExecute={onExecute} />,
196
+ );
197
+
198
+ // Cmd/Control still executes initial query
199
+ await editorPage.getEditor().press('Control+Enter');
200
+ await editorPage.getEditor().press('Meta+Enter');
201
+ expect(history.length).toBe(1);
202
+
203
+ // Ensure query execution doesn't fire if the query is only whitespace
204
+ await editorPage.getEditor().fill(' ');
205
+ await editorPage.getEditor().press('Control+Enter');
206
+ await editorPage.getEditor().press('Meta+Enter');
207
+ await editorPage.getEditor().press('Enter');
208
+ expect(history.length).toBe(1);
209
+
210
+ // Ensure enter executes query when in single line
211
+ await editorPage.getEditor().fill('Misty');
212
+ await editorPage.getEditor().press('Enter');
213
+ expect(history.length).toBe(2);
214
+ expect(history).toEqual(['Misty', 'Brock']);
215
+
216
+ // Ensure cmd+enter is required in multiline
217
+ await editorPage.getEditor().fill('multiline');
218
+ await editorPage.getEditor().press('Shift+Enter');
219
+ await editorPage.getEditor().press('Enter');
220
+ await editorPage.getEditor().press('A');
221
+
222
+ // line numbers and the text
223
+ await expect(editorComponent).toHaveText('1\n2\n3\nmultiline\nA', {
224
+ useInnerText: true,
225
+ });
226
+ await editorPage.getEditor().press('Enter');
227
+ await editorPage.getEditor().press('Enter');
228
+ await editorPage.getEditor().press('Enter');
229
+ await editorPage.getEditor().press('Enter');
230
+ expect(history.length).toBe(2);
231
+
232
+ await editorPage.getEditor().press('Control+Enter');
233
+ await editorPage.getEditor().press('Meta+Enter');
234
+ expect(history.length).toBe(3);
235
+ });
236
+
237
+ test('can navigate with cmd+up as well', async ({ page, mount }) => {
238
+ const editorPage = new CypherEditorPage(page);
239
+ const isMac = process.platform === 'darwin';
240
+ const metaUp = isMac ? 'Meta+ArrowUp' : 'Control+ArrowUp';
241
+ const metaDown = isMac ? 'Meta+ArrowDown' : 'Control+ArrowDown';
242
+
243
+ const initialValue = 'MATCH (n) RETURN n;';
244
+
245
+ await mount(
246
+ <CypherEditor
247
+ value={initialValue}
248
+ history={[
249
+ 'first',
250
+ `one
251
+ multiline
252
+ entry
253
+ .`,
254
+ ]}
255
+ onExecute={() => {
256
+ /* needed to turn on history movements */
257
+ }}
258
+ />,
259
+ );
260
+
261
+ await editorPage.getEditor().press(metaUp);
262
+ await expect(page.getByText('first')).toBeVisible();
263
+
264
+ // move in history
265
+ await editorPage.getEditor().press(metaUp);
266
+ await expect(page.getByText('multiline')).toBeVisible();
267
+
268
+ // Single meta down moves all the way to top of editor on mac
269
+ if (isMac) {
270
+ await editorPage.getEditor().press(metaDown);
271
+ } else {
272
+ await editorPage.getEditor().press('ArrowDown');
273
+ await editorPage.getEditor().press('ArrowDown');
274
+ await editorPage.getEditor().press('ArrowDown');
275
+ await editorPage.getEditor().press('ArrowDown');
276
+ }
277
+ await editorPage.getEditor().press(metaDown);
278
+ await expect(page.getByText('first')).toBeVisible();
279
+ });
280
+
281
+ test('test onExecute', async ({ page, mount }) => {
282
+ const editorPage = new CypherEditorPage(page);
283
+ const isMac = process.platform === 'darwin';
284
+ const execButton = isMac ? 'Meta+Enter' : 'Control+Enter';
285
+
286
+ const initialValue = 'MATCH (n) RETURN n;';
287
+ const history = [];
288
+
289
+ const onExecute = (cmd: string) => {
290
+ history.unshift(cmd);
291
+ };
292
+
293
+ const cypherEditor = await mount(
294
+ <CypherEditor value={initialValue} onExecute={onExecute} />,
295
+ );
296
+ await editorPage.getEditor().press(execButton);
297
+ expect(history).toEqual([initialValue]);
298
+
299
+ await editorPage.getEditor().press(execButton);
300
+ expect(history).toEqual([initialValue, initialValue]);
301
+
302
+ // can update onExecute
303
+ let newExecRan = false;
304
+ const newOnExecute = () => {
305
+ newExecRan = true;
306
+ };
307
+ await cypherEditor.update(
308
+ <CypherEditor value={initialValue} onExecute={newOnExecute} />,
309
+ );
310
+
311
+ await editorPage.getEditor().press(execButton);
312
+ expect(newExecRan).toEqual(true);
313
+ // old value should still be only 2
314
+ expect(history).toEqual([initialValue, initialValue]);
315
+ });
@@ -0,0 +1,158 @@
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 ({ mount, page }) => {
14
+ const client = await page.context().newCDPSession(page);
15
+ if (process.env.BENCHMARKING === 'true') {
16
+ test.setTimeout(1000000);
17
+ await client.send('Performance.enable');
18
+ await client.send('Emulation.setCPUThrottlingRate', { rate: 4 });
19
+ await client.send('Overlay.setShowFPSCounter', { show: true });
20
+
21
+ await page.evaluate(() => {
22
+ window.longtasks = [];
23
+ const observer = new PerformanceObserver((list) => {
24
+ window.longtasks.push(...list.getEntries().map((e) => e.duration));
25
+ });
26
+
27
+ observer.observe({ entryTypes: ['longtask'] });
28
+ });
29
+ } else {
30
+ test.setTimeout(30 * 1000);
31
+ }
32
+ const editorPage = new CypherEditorPage(page);
33
+ const component = await mount(
34
+ <CypherEditor
35
+ prompt="neo4j>"
36
+ theme="dark"
37
+ lint
38
+ schema={testData.mockSchema}
39
+ />,
40
+ );
41
+
42
+ // pressSequentially is less efficient -> we want to test the performance of the editor
43
+ await editorPage.getEditor().pressSequentially(`
44
+ MATCH (n:Person) RETURN m;`);
45
+
46
+ await editorPage.checkErrorMessage('m', 'Variable `m` not defined');
47
+
48
+ // set and unset large query a few times
49
+ await component.update(
50
+ <CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
51
+ );
52
+ await component.update(
53
+ <CypherEditor value="" schema={testData.mockSchema} />,
54
+ );
55
+
56
+ await component.update(
57
+ <CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
58
+ );
59
+ await component.update(<CypherEditor value="" />);
60
+
61
+ await component.update(
62
+ <CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
63
+ );
64
+ await component.update(
65
+ <CypherEditor value="" schema={testData.mockSchema} />,
66
+ );
67
+
68
+ await component.update(
69
+ <CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
70
+ );
71
+ await component.update(
72
+ <CypherEditor value="" schema={testData.mockSchema} />,
73
+ );
74
+
75
+ await component.update(
76
+ <CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
77
+ );
78
+
79
+ await editorPage.getEditor().pressSequentially(`
80
+ MATCH (n:P`);
81
+
82
+ await expect(
83
+ page.locator('.cm-tooltip-autocomplete').getByText('Person'),
84
+ ).toBeVisible();
85
+
86
+ await page.locator('.cm-tooltip-autocomplete').getByText('Person').click();
87
+
88
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
89
+
90
+ await expect(component).toContainText('MATCH (n:Person');
91
+
92
+ await editorPage.getEditor().pressSequentially(') RETRN my');
93
+
94
+ await expect(component).toContainText('MATCH (n:Person) RETRN m');
95
+
96
+ await editorPage.checkErrorMessage(
97
+ 'RETRN',
98
+ 'Unexpected token. Did you mean RETURN?',
99
+ );
100
+
101
+ await editorPage
102
+ .getEditor()
103
+ .pressSequentially('veryveryveryverylongvariable');
104
+
105
+ if (process.env.BENCHMARKING === 'true') {
106
+ const longtasks = await page.evaluate(() => window.longtasks);
107
+ const sortedLongTasks = longtasks.sort((a, b) => a - b);
108
+ const medianLongTask =
109
+ sortedLongTasks[Math.floor(sortedLongTasks.length / 2)];
110
+ const averageLongTask =
111
+ sortedLongTasks.reduce((a, b) => a + b, 0) / sortedLongTasks.length;
112
+ const over500 = sortedLongTasks.filter((t) => t > 500).length;
113
+ const nintyninethPercentile =
114
+ sortedLongTasks[Math.floor(sortedLongTasks.length * 0.99)];
115
+ const longTaskCount = longtasks.length;
116
+ const totalLongTaskTime = longtasks.reduce((a, b) => a + b, 0);
117
+
118
+ const USER_ID = 1226722;
119
+ const API_KEY = process.env.GRAFANA_API_KEY;
120
+ if (!API_KEY) {
121
+ throw new Error('Missing grafana api key');
122
+ }
123
+
124
+ const metrics = {
125
+ medianLongTask,
126
+ averageLongTask,
127
+ over500,
128
+ nintyninethPercentile,
129
+ longTaskCount,
130
+ totalLongTaskTime,
131
+ };
132
+ const body = Object.entries(metrics)
133
+ .map(
134
+ ([key, value]) =>
135
+ `benchmark,bar_label=${key},source=playwright metric=${value}`,
136
+ )
137
+ .join('\n');
138
+
139
+ await fetch(
140
+ 'https://influx-prod-39-prod-eu-north-0.grafana.net/api/v1/push/influx/write',
141
+ {
142
+ method: 'post',
143
+ body,
144
+ headers: {
145
+ Authorization: `Bearer ${USER_ID}:${API_KEY}`,
146
+ 'Content-Type': 'text/plain',
147
+ },
148
+ },
149
+ ).then((res) => {
150
+ if (res.ok) {
151
+ // eslint-disable-next-line no-console
152
+ console.log('Metrics pushed to grafana successfully');
153
+ } else {
154
+ throw new Error(`Failed to push metrics to grafana: ${res.statusText}`);
155
+ }
156
+ });
157
+ }
158
+ });
@@ -1,8 +1,6 @@
1
1
  import { expect, test } from '@playwright/experimental-ct-react';
2
2
  import { CypherEditor } from '../CypherEditor';
3
3
 
4
- test.use({ viewport: { width: 500, height: 500 } });
5
-
6
4
  test('can mount the editor with text', async ({ mount }) => {
7
5
  const component = await mount(<CypherEditor value="MATCH (n) RETURN n;" />);
8
6
 
@@ -22,7 +20,7 @@ test('the editors text can be externally controlled ', async ({ mount }) => {
22
20
  await expect(component).toContainText(newValue);
23
21
  });
24
22
 
25
- test('the editors can report changes to the text ', async ({ mount, page }) => {
23
+ test('the editor can report changes to the text ', async ({ mount, page }) => {
26
24
  const intitialValue = 'MATCH (n) ';
27
25
 
28
26
  let editorValueCopy = intitialValue;
@@ -34,13 +32,14 @@ test('the editors can report changes to the text ', async ({ mount, page }) => {
34
32
 
35
33
  const textField = page.getByRole('textbox');
36
34
 
35
+ await textField.fill('');
37
36
  await textField.fill('RETURN 12');
37
+ await expect(textField).toHaveText('RETURN 12');
38
38
 
39
- expect(editorValueCopy).toBe('RETURN 12');
40
-
41
- await page.keyboard.type('34');
42
-
43
- expect(editorValueCopy).toBe('RETURN 1234');
39
+ // editor update is debounced, retry wait for debounced
40
+ await expect(() => {
41
+ expect(editorValueCopy).toBe('RETURN 12');
42
+ }).toPass({ intervals: [300, 300, 1000] });
44
43
  });
45
44
 
46
45
  test('can complete RETURN', async ({ page, mount }) => {
@@ -71,17 +70,3 @@ test('can complete CALL/CREATE', async ({ page, mount }) => {
71
70
 
72
71
  await expect(textField).toHaveText('CALL');
73
72
  });
74
-
75
- test('prompt shows up', async ({ mount, page }) => {
76
- const component = await mount(<CypherEditor prompt="neo4j>" />);
77
-
78
- await expect(component).toContainText('neo4j>');
79
-
80
- await component.update(<CypherEditor prompt="test>" />);
81
- await expect(component).toContainText('test>');
82
-
83
- const textField = page.getByRole('textbox');
84
- await textField.press('a');
85
-
86
- await expect(textField).toHaveText('a');
87
- });