@neo4j-cypher/react-codemirror 2.0.0-next.8 → 2.0.0-next.9

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 (63) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/CypherEditor.d.ts +28 -1
  3. package/dist/CypherEditor.js +70 -11
  4. package/dist/CypherEditor.js.map +1 -1
  5. package/dist/e2e_tests/autoCompletion.spec.js +117 -1
  6. package/dist/e2e_tests/autoCompletion.spec.js.map +1 -1
  7. package/dist/e2e_tests/configuration.spec.js +11 -1
  8. package/dist/e2e_tests/configuration.spec.js.map +1 -1
  9. package/dist/e2e_tests/debounce.spec.d.ts +1 -0
  10. package/dist/e2e_tests/debounce.spec.js +63 -0
  11. package/dist/e2e_tests/debounce.spec.js.map +1 -0
  12. package/dist/e2e_tests/extraKeybindings.spec.js +0 -1
  13. package/dist/e2e_tests/extraKeybindings.spec.js.map +1 -1
  14. package/dist/e2e_tests/historyNavigation.spec.js +107 -16
  15. package/dist/e2e_tests/historyNavigation.spec.js.map +1 -1
  16. package/dist/e2e_tests/sanityChecks.spec.js +0 -1
  17. package/dist/e2e_tests/sanityChecks.spec.js.map +1 -1
  18. package/dist/e2e_tests/signatureHelp.spec.js +27 -0
  19. package/dist/e2e_tests/signatureHelp.spec.js.map +1 -1
  20. package/dist/e2e_tests/snippets.spec.js +0 -1
  21. package/dist/e2e_tests/snippets.spec.js.map +1 -1
  22. package/dist/e2e_tests/syntaxHighlighting.spec.js +0 -1
  23. package/dist/e2e_tests/syntaxHighlighting.spec.js.map +1 -1
  24. package/dist/historyNavigation.js +1 -1
  25. package/dist/historyNavigation.js.map +1 -1
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/lang-cypher/autocomplete.d.ts +4 -1
  30. package/dist/lang-cypher/autocomplete.js +79 -17
  31. package/dist/lang-cypher/autocomplete.js.map +1 -1
  32. package/dist/lang-cypher/contants.test.js +2 -2
  33. package/dist/lang-cypher/contants.test.js.map +1 -1
  34. package/dist/lang-cypher/createCypherTheme.js +9 -2
  35. package/dist/lang-cypher/createCypherTheme.js.map +1 -1
  36. package/dist/lang-cypher/langCypher.d.ts +5 -0
  37. package/dist/lang-cypher/langCypher.js +11 -5
  38. package/dist/lang-cypher/langCypher.js.map +1 -1
  39. package/dist/lang-cypher/signatureHelp.js +4 -3
  40. package/dist/lang-cypher/signatureHelp.js.map +1 -1
  41. package/dist/lang-cypher/utils.d.ts +2 -0
  42. package/dist/lang-cypher/utils.js +10 -0
  43. package/dist/lang-cypher/utils.js.map +1 -0
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +9 -9
  46. package/src/CypherEditor.tsx +131 -21
  47. package/src/e2e_tests/autoCompletion.spec.tsx +206 -2
  48. package/src/e2e_tests/configuration.spec.tsx +16 -2
  49. package/src/e2e_tests/debounce.spec.tsx +100 -0
  50. package/src/e2e_tests/extraKeybindings.spec.tsx +0 -2
  51. package/src/e2e_tests/historyNavigation.spec.tsx +136 -17
  52. package/src/e2e_tests/sanityChecks.spec.tsx +0 -2
  53. package/src/e2e_tests/signatureHelp.spec.tsx +71 -0
  54. package/src/e2e_tests/snippets.spec.tsx +0 -2
  55. package/src/e2e_tests/syntaxHighlighting.spec.tsx +0 -2
  56. package/src/historyNavigation.ts +1 -1
  57. package/src/index.ts +4 -1
  58. package/src/lang-cypher/autocomplete.ts +95 -19
  59. package/src/lang-cypher/contants.test.ts +5 -2
  60. package/src/lang-cypher/createCypherTheme.ts +9 -3
  61. package/src/lang-cypher/langCypher.ts +17 -5
  62. package/src/lang-cypher/signatureHelp.ts +5 -3
  63. package/src/lang-cypher/utils.ts +9 -0
@@ -1,3 +1,4 @@
1
+ import { insertNewline } from '@codemirror/commands';
1
2
  import {
2
3
  Annotation,
3
4
  Compartment,
@@ -12,7 +13,7 @@ import {
12
13
  placeholder,
13
14
  ViewUpdate,
14
15
  } from '@codemirror/view';
15
- import type { DbSchema } from '@neo4j-cypher/language-support';
16
+ import { type DbSchema } from '@neo4j-cypher/language-support';
16
17
  import debounce from 'lodash.debounce';
17
18
  import { Component, createRef } from 'react';
18
19
  import {
@@ -43,6 +44,13 @@ export interface CypherEditorProps {
43
44
  * @returns void
44
45
  */
45
46
  onExecute?: (cmd: string) => void;
47
+ /**
48
+ * If true, pressing enter will add a new line to the editor and cmd/ctrl + enter will execute the query.
49
+ * Otherwise pressing enter on a single line will execute the query.
50
+ *
51
+ * @default false
52
+ */
53
+ newLineOnEnter?: boolean;
46
54
  /**
47
55
  * The editor history navigable via up/down arrow keys. Order newest to oldest.
48
56
  * Add to this list with the `onExecute` callback for REPL style history.
@@ -77,6 +85,21 @@ export interface CypherEditorProps {
77
85
  * @default true
78
86
  */
79
87
  lint?: boolean;
88
+ /**
89
+ * Whether the signature help tooltip should be shown below the text.
90
+ * If false, it will be shown above.
91
+ *
92
+ * @default true
93
+ */
94
+ showSignatureTooltipBelow?: boolean;
95
+ /**
96
+ * Internal feature flags for the editor. Don't use in production
97
+ *
98
+ */
99
+ featureFlags?: {
100
+ consoleCommands?: boolean;
101
+ signatureInfoOnAutoCompletions?: boolean;
102
+ };
80
103
  /**
81
104
  * The schema to use for autocompletion and linting.
82
105
  *
@@ -135,26 +158,74 @@ export interface CypherEditorProps {
135
158
  * @default false
136
159
  */
137
160
  readonly?: boolean;
161
+
162
+ /**
163
+ * String value to assign to the aria-label attribute of the editor
164
+ */
165
+ ariaLabel?: string;
138
166
  }
139
167
 
140
- const executeKeybinding = (onExecute?: (cmd: string) => void) =>
141
- onExecute
142
- ? [
143
- {
144
- key: 'Ctrl-Enter',
145
- mac: 'Mod-Enter',
146
- preventDefault: true,
147
- run: (view: EditorView) => {
148
- const doc = view.state.doc.toString();
149
- if (doc.trim() !== '') {
150
- onExecute(doc);
151
- }
168
+ const executeKeybinding = (
169
+ onExecute?: (cmd: string) => void,
170
+ newLineOnEnter?: boolean,
171
+ flush?: () => void,
172
+ ) => {
173
+ const keybindings: Record<string, KeyBinding> = {
174
+ 'Shift-Enter': {
175
+ key: 'Shift-Enter',
176
+ run: insertNewline,
177
+ },
178
+ 'Ctrl-Enter': {
179
+ key: 'Ctrl-Enter',
180
+ run: () => true,
181
+ },
182
+ Enter: {
183
+ key: 'Enter',
184
+ run: insertNewline,
185
+ },
186
+ };
187
+
188
+ if (onExecute) {
189
+ keybindings['Ctrl-Enter'] = {
190
+ key: 'Ctrl-Enter',
191
+ mac: 'Mod-Enter',
192
+ preventDefault: true,
193
+ run: (view: EditorView) => {
194
+ const doc = view.state.doc.toString();
195
+ if (doc.trim() !== '') {
196
+ flush?.();
197
+ onExecute(doc);
198
+ }
199
+
200
+ return true;
201
+ },
202
+ };
152
203
 
153
- return true;
154
- },
204
+ if (!newLineOnEnter) {
205
+ keybindings['Enter'] = {
206
+ key: 'Enter',
207
+ preventDefault: true,
208
+ run: (view: EditorView) => {
209
+ const doc = view.state.doc.toString();
210
+ if (doc.includes('\n')) {
211
+ // Returning false means the event will mark the event
212
+ // as not handled and the default behavior will be executed
213
+ return false;
214
+ }
215
+
216
+ if (doc.trim() !== '') {
217
+ flush?.();
218
+ onExecute(doc);
219
+ }
220
+
221
+ return true;
155
222
  },
156
- ]
157
- : [];
223
+ };
224
+ }
225
+ }
226
+
227
+ return Object.values(keybindings);
228
+ };
158
229
 
159
230
  const themeCompartment = new Compartment();
160
231
  const keyBindingCompartment = new Compartment();
@@ -194,6 +265,8 @@ export class CypherEditor extends Component<
194
265
  editorView: React.MutableRefObject<EditorView> = createRef();
195
266
  private schemaRef: React.MutableRefObject<CypherConfig> = createRef();
196
267
 
268
+ private latestDispatchedValue: string | undefined;
269
+
197
270
  /**
198
271
  * Focus the editor
199
272
  */
@@ -232,14 +305,22 @@ export class CypherEditor extends Component<
232
305
  schema: {},
233
306
  overrideThemeBackgroundColor: false,
234
307
  lineWrap: false,
308
+ showSignatureTooltipBelow: true,
235
309
  extraKeybindings: [],
236
310
  history: [],
237
311
  theme: 'light',
238
312
  lineNumbers: true,
313
+ newLineOnEnter: false,
239
314
  };
240
315
 
241
316
  private debouncedOnChange = this.props.onChange
242
- ? debounce(this.props.onChange, 200)
317
+ ? debounce(
318
+ ((value, viewUpdate) => {
319
+ this.latestDispatchedValue = value;
320
+ this.props.onChange(value, viewUpdate);
321
+ }) satisfies CypherEditorProps['onChange'],
322
+ 200,
323
+ )
243
324
  : undefined;
244
325
 
245
326
  componentDidMount(): void {
@@ -250,12 +331,20 @@ export class CypherEditor extends Component<
250
331
  overrideThemeBackgroundColor,
251
332
  schema,
252
333
  lint,
334
+ showSignatureTooltipBelow,
335
+ featureFlags,
253
336
  onExecute,
337
+ newLineOnEnter,
254
338
  } = this.props;
255
339
 
256
340
  this.schemaRef.current = {
257
341
  schema,
258
342
  lint,
343
+ showSignatureTooltipBelow,
344
+ featureFlags: {
345
+ consoleCommands: true,
346
+ ...featureFlags,
347
+ },
259
348
  useLightVersion: false,
260
349
  setUseLightVersion: (newVal) => {
261
350
  if (this.schemaRef.current !== undefined) {
@@ -285,10 +374,17 @@ export class CypherEditor extends Component<
285
374
  ]
286
375
  : [];
287
376
 
377
+ this.latestDispatchedValue = this.props.value;
378
+
288
379
  this.editorState.current = EditorState.create({
289
380
  extensions: [
290
381
  keyBindingCompartment.of(
291
- keymap.of([...executeKeybinding(onExecute), ...extraKeybindings]),
382
+ keymap.of([
383
+ ...executeKeybinding(onExecute, newLineOnEnter, () =>
384
+ this.debouncedOnChange.flush(),
385
+ ),
386
+ ...extraKeybindings,
387
+ ]),
292
388
  ),
293
389
  historyNavigation(this.props),
294
390
  basicNeo4jSetup(),
@@ -311,6 +407,11 @@ export class CypherEditor extends Component<
311
407
  ? EditorView.domEventHandlers(this.props.domEventHandlers)
312
408
  : [],
313
409
  ),
410
+ this.props.ariaLabel
411
+ ? EditorView.contentAttributes.of({
412
+ 'aria-label': this.props.ariaLabel,
413
+ })
414
+ : [],
314
415
  ],
315
416
  doc: this.props.value,
316
417
  });
@@ -338,7 +439,11 @@ export class CypherEditor extends Component<
338
439
  // Handle externally set value
339
440
  const currentCmValue = this.editorView.current.state?.doc.toString() ?? '';
340
441
 
341
- if (this.props.value !== undefined && currentCmValue !== this.props.value) {
442
+ if (
443
+ this.props.value !== undefined &&
444
+ this.props.value !== this.latestDispatchedValue
445
+ ) {
446
+ this.debouncedOnChange?.cancel();
342
447
  this.editorView.current.dispatch({
343
448
  changes: {
344
449
  from: 0,
@@ -402,7 +507,11 @@ export class CypherEditor extends Component<
402
507
  this.editorView.current.dispatch({
403
508
  effects: keyBindingCompartment.reconfigure(
404
509
  keymap.of([
405
- ...executeKeybinding(this.props.onExecute),
510
+ ...executeKeybinding(
511
+ this.props.onExecute,
512
+ this.props.newLineOnEnter,
513
+ () => this.debouncedOnChange.flush(),
514
+ ),
406
515
  ...this.props.extraKeybindings,
407
516
  ]),
408
517
  ),
@@ -438,6 +547,7 @@ export class CypherEditor extends Component<
438
547
  */
439
548
  this.schemaRef.current.schema = this.props.schema;
440
549
  this.schemaRef.current.lint = this.props.lint;
550
+ this.schemaRef.current.featureFlags = this.props.featureFlags;
441
551
  }
442
552
 
443
553
  componentWillUnmount(): void {
@@ -1,9 +1,8 @@
1
1
  import { testData } from '@neo4j-cypher/language-support';
2
2
  import { expect, test } from '@playwright/experimental-ct-react';
3
+ import type { Page } from '@playwright/test';
3
4
  import { CypherEditor } from '../CypherEditor';
4
5
 
5
- test.use({ viewport: { width: 500, height: 500 } });
6
-
7
6
  test('hello world end 2 end test', async ({ mount }) => {
8
7
  const component = await mount(<CypherEditor value="hello world" />);
9
8
  await expect(component).toContainText('hello world');
@@ -234,3 +233,208 @@ test('completes allShortestPaths correctly', async ({ page, mount }) => {
234
233
  'MATCH (n) REURN n; MATCH allShortestPaths',
235
234
  );
236
235
  });
236
+
237
+ async function getInfoTooltip(page: Page, methodName: string) {
238
+ const infoTooltip = page.locator('.cm-completionInfo');
239
+ const firstOption = page.locator('li[aria-selected="true"]');
240
+ let selectedOption = firstOption;
241
+
242
+ while (!(await infoTooltip.textContent()).includes(methodName)) {
243
+ await page.keyboard.press('ArrowDown');
244
+ const currentSelected = page.locator('li[aria-selected="true"]');
245
+ expect(currentSelected).not.toBe(selectedOption);
246
+ expect(currentSelected).not.toBe(firstOption);
247
+ selectedOption = currentSelected;
248
+ }
249
+
250
+ return infoTooltip;
251
+ }
252
+
253
+ test('shows signature help information on auto-completion for procedures', async ({
254
+ page,
255
+ mount,
256
+ }) => {
257
+ await mount(
258
+ <CypherEditor
259
+ schema={testData.mockSchema}
260
+ featureFlags={{
261
+ signatureInfoOnAutoCompletions: true,
262
+ }}
263
+ />,
264
+ );
265
+ const procName = 'apoc.periodic.iterate';
266
+ const procedure = testData.mockSchema.procedures[procName];
267
+
268
+ const textField = page.getByRole('textbox');
269
+ await textField.fill('CALL apoc.periodic.');
270
+
271
+ await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible();
272
+
273
+ const infoTooltip = await getInfoTooltip(page, procName);
274
+ await expect(infoTooltip).toContainText(procedure.signature);
275
+ await expect(infoTooltip).toContainText(procedure.description);
276
+ });
277
+
278
+ test('shows signature help information on auto-completion for functions', async ({
279
+ page,
280
+ mount,
281
+ }) => {
282
+ await mount(
283
+ <CypherEditor
284
+ schema={testData.mockSchema}
285
+ featureFlags={{
286
+ signatureInfoOnAutoCompletions: true,
287
+ }}
288
+ />,
289
+ );
290
+ const fnName = 'apoc.coll.combinations';
291
+ const fn = testData.mockSchema.functions[fnName];
292
+
293
+ const textField = page.getByRole('textbox');
294
+ await textField.fill('RETURN apoc.coll.');
295
+
296
+ await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible();
297
+
298
+ const infoTooltip = await getInfoTooltip(page, fnName);
299
+ await expect(infoTooltip).toContainText(fn.signature);
300
+ await expect(infoTooltip).toContainText(fn.description);
301
+ });
302
+
303
+ test('shows deprecated procedures as strikethrough on auto-completion', async ({
304
+ page,
305
+ mount,
306
+ }) => {
307
+ const procName = 'apoc.trigger.resume';
308
+
309
+ await mount(
310
+ <CypherEditor
311
+ schema={{
312
+ procedures: { [procName]: testData.mockSchema.procedures[procName] },
313
+ }}
314
+ featureFlags={{
315
+ signatureInfoOnAutoCompletions: true,
316
+ }}
317
+ />,
318
+ );
319
+ const textField = page.getByRole('textbox');
320
+ await textField.fill('CALL apoc.trigger.');
321
+
322
+ // We need to assert on the element having the right class
323
+ // and trusting the CSS is making this truly strikethrough
324
+ await expect(page.locator('.cm-deprecated-completion')).toBeVisible();
325
+ });
326
+
327
+ test('shows deprecated function as strikethrough on auto-completion', async ({
328
+ page,
329
+ mount,
330
+ }) => {
331
+ const fnName = 'apoc.create.uuid';
332
+
333
+ await mount(
334
+ <CypherEditor
335
+ schema={{
336
+ functions: { [fnName]: testData.mockSchema.functions[fnName] },
337
+ }}
338
+ featureFlags={{
339
+ signatureInfoOnAutoCompletions: true,
340
+ }}
341
+ />,
342
+ );
343
+ const textField = page.getByRole('textbox');
344
+ await textField.fill('RETURN apoc.create.');
345
+
346
+ // We need to assert on the element having the right class
347
+ // and trusting the CSS is making this truly strikethrough
348
+ await expect(page.locator('.cm-deprecated-completion')).toBeVisible();
349
+ });
350
+
351
+ test('does not signature help information on auto-completion if flag not enabled explicitly', async ({
352
+ page,
353
+ mount,
354
+ }) => {
355
+ await mount(<CypherEditor schema={testData.mockSchema} />);
356
+
357
+ const textField = page.getByRole('textbox');
358
+ await textField.fill('CALL apoc.periodic.');
359
+
360
+ await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible();
361
+ await expect(page.locator('.cm-completionInfo')).not.toBeVisible();
362
+ });
363
+
364
+ test('does not signature help information on auto-completion if docs and signature are empty', async ({
365
+ page,
366
+ mount,
367
+ }) => {
368
+ await mount(
369
+ <CypherEditor
370
+ schema={testData.mockSchema}
371
+ featureFlags={{
372
+ signatureInfoOnAutoCompletions: true,
373
+ }}
374
+ />,
375
+ );
376
+
377
+ const textField = page.getByRole('textbox');
378
+ await textField.fill('C');
379
+
380
+ await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible();
381
+ await expect(page.locator('.cm-completionInfo')).not.toBeVisible();
382
+ });
383
+
384
+ test('shows signature help information on auto-completion if description is not empty, signature is', async ({
385
+ page,
386
+ mount,
387
+ }) => {
388
+ await mount(
389
+ <CypherEditor
390
+ schema={{
391
+ procedures: {
392
+ 'db.ping': {
393
+ ...testData.emptyProcedure,
394
+ description: 'foo',
395
+ signature: '',
396
+ name: 'db.ping',
397
+ },
398
+ },
399
+ }}
400
+ featureFlags={{
401
+ signatureInfoOnAutoCompletions: true,
402
+ }}
403
+ />,
404
+ );
405
+
406
+ const textField = page.getByRole('textbox');
407
+ await textField.fill('CALL db.');
408
+
409
+ await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible();
410
+ await expect(page.locator('.cm-completionInfo')).toBeVisible();
411
+ });
412
+
413
+ test('shows signature help information on auto-completion if signature is not empty, description is', async ({
414
+ page,
415
+ mount,
416
+ }) => {
417
+ await mount(
418
+ <CypherEditor
419
+ schema={{
420
+ procedures: {
421
+ 'db.ping': {
422
+ ...testData.emptyProcedure,
423
+ description: '',
424
+ signature: 'foo',
425
+ name: 'db.ping',
426
+ },
427
+ },
428
+ }}
429
+ featureFlags={{
430
+ signatureInfoOnAutoCompletions: true,
431
+ }}
432
+ />,
433
+ );
434
+
435
+ const textField = page.getByRole('textbox');
436
+ await textField.fill('CALL db.');
437
+
438
+ await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible();
439
+ await expect(page.locator('.cm-completionInfo')).toBeVisible();
440
+ });
@@ -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('prompt shows up', async ({ mount, page }) => {
7
5
  const component = await mount(<CypherEditor prompt="neo4j>" />);
8
6
 
@@ -95,3 +93,19 @@ test('can set/unset onFocus/onBlur', async ({ mount, page }) => {
95
93
  expect(blurFireCount).toBe(1);
96
94
  }).toPass();
97
95
  });
96
+
97
+ test('aria-label is not set by default', async ({ mount, page }) => {
98
+ await mount(<CypherEditor />);
99
+
100
+ const textField = page.getByRole('textbox');
101
+ expect(await textField.getAttribute('aria-label')).toBeNull();
102
+ });
103
+
104
+ test('can set aria-label', async ({ mount, page }) => {
105
+ const ariaLabel = 'Cypher Editor';
106
+
107
+ await mount(<CypherEditor ariaLabel={ariaLabel} />);
108
+
109
+ const textField = page.getByRole('textbox');
110
+ expect(await textField.getAttribute('aria-label')).toEqual(ariaLabel);
111
+ });
@@ -0,0 +1,100 @@
1
+ import { expect, test } from '@playwright/experimental-ct-react';
2
+ import { CypherEditor } from '../CypherEditor';
3
+ import { CypherEditorPage } from './e2eUtils';
4
+
5
+ const DEBOUNCE_TIMER = 200;
6
+
7
+ test('external updates should override debounced updates', async ({
8
+ mount,
9
+ page,
10
+ }) => {
11
+ const editorPage = new CypherEditorPage(page);
12
+ let value = '';
13
+
14
+ const onChange = (val: string) => {
15
+ value = val;
16
+ void component.update(<CypherEditor value={val} onChange={onChange} />);
17
+ };
18
+
19
+ const component = await mount(
20
+ <CypherEditor value={value} onChange={onChange} />,
21
+ );
22
+
23
+ await editorPage.getEditor().pressSequentially('RETURN 1');
24
+ onChange('foo');
25
+ await page.waitForTimeout(DEBOUNCE_TIMER);
26
+ await expect(component).toContainText('foo');
27
+ });
28
+
29
+ test('onExecute updates should override debounce updates', async ({
30
+ mount,
31
+ page,
32
+ }) => {
33
+ const editorPage = new CypherEditorPage(page);
34
+ let value = '';
35
+
36
+ const onExecute = () => {
37
+ value = '';
38
+ void component.update(
39
+ <CypherEditor value={value} onChange={onChange} onExecute={onExecute} />,
40
+ );
41
+ };
42
+
43
+ const onChange = (val: string) => {
44
+ value = val;
45
+ void component.update(
46
+ <CypherEditor value={val} onChange={onChange} onExecute={onExecute} />,
47
+ );
48
+ };
49
+
50
+ const component = await mount(
51
+ <CypherEditor value={value} onChange={onChange} onExecute={onExecute} />,
52
+ );
53
+
54
+ await editorPage.getEditor().pressSequentially('RETURN 1');
55
+ await editorPage.getEditor().press('Control+Enter');
56
+ await page.waitForTimeout(DEBOUNCE_TIMER);
57
+ await expect(component).not.toContainText('RETURN 1');
58
+
59
+ await editorPage.getEditor().pressSequentially('RETURN 1');
60
+ await editorPage.getEditor().pressSequentially('');
61
+ await editorPage.getEditor().pressSequentially('RETURN 1');
62
+ await editorPage.getEditor().press('Control+Enter');
63
+ await page.waitForTimeout(DEBOUNCE_TIMER);
64
+ await expect(component).not.toContainText('RETURN 1');
65
+ });
66
+
67
+ test('onExecute should fire after debounced updates', async ({
68
+ mount,
69
+ page,
70
+ }) => {
71
+ const editorPage = new CypherEditorPage(page);
72
+ let value = '';
73
+ let executedCommand = '';
74
+
75
+ const onExecute = (cmd: string) => {
76
+ executedCommand = cmd;
77
+ void component.update(
78
+ <CypherEditor value={value} onChange={onChange} onExecute={onExecute} />,
79
+ );
80
+ };
81
+
82
+ const onChange = (val: string) => {
83
+ value = val;
84
+ void component.update(
85
+ <CypherEditor value={val} onChange={onChange} onExecute={onExecute} />,
86
+ );
87
+ };
88
+
89
+ const component = await mount(
90
+ <CypherEditor value={value} onChange={onChange} onExecute={onExecute} />,
91
+ );
92
+
93
+ await editorPage.getEditor().fill('RETURN 1');
94
+ await editorPage.getEditor().press('Control+Enter');
95
+ await editorPage.getEditor().fill('RETURN 2');
96
+ await editorPage.getEditor().press('Control+Enter');
97
+ await page.waitForTimeout(DEBOUNCE_TIMER);
98
+ await expect(component).toContainText('RETURN 2');
99
+ expect(executedCommand).toBe('RETURN 2');
100
+ });
@@ -2,8 +2,6 @@ import { expect, test } from '@playwright/experimental-ct-react';
2
2
  import { CypherEditor } from '../CypherEditor';
3
3
  import { CypherEditorPage } from './e2eUtils';
4
4
 
5
- test.use({ viewport: { width: 500, height: 500 } });
6
-
7
5
  test('can add extra keybinding statically', async ({ mount, page }) => {
8
6
  const editorPage = new CypherEditorPage(page);
9
7
  let hasRun = false;