@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.
- package/CHANGELOG.md +18 -0
- package/dist/CypherEditor.d.ts +28 -1
- package/dist/CypherEditor.js +70 -11
- package/dist/CypherEditor.js.map +1 -1
- package/dist/e2e_tests/autoCompletion.spec.js +117 -1
- package/dist/e2e_tests/autoCompletion.spec.js.map +1 -1
- package/dist/e2e_tests/configuration.spec.js +11 -1
- package/dist/e2e_tests/configuration.spec.js.map +1 -1
- package/dist/e2e_tests/debounce.spec.d.ts +1 -0
- package/dist/e2e_tests/debounce.spec.js +63 -0
- package/dist/e2e_tests/debounce.spec.js.map +1 -0
- package/dist/e2e_tests/extraKeybindings.spec.js +0 -1
- package/dist/e2e_tests/extraKeybindings.spec.js.map +1 -1
- package/dist/e2e_tests/historyNavigation.spec.js +107 -16
- package/dist/e2e_tests/historyNavigation.spec.js.map +1 -1
- package/dist/e2e_tests/sanityChecks.spec.js +0 -1
- package/dist/e2e_tests/sanityChecks.spec.js.map +1 -1
- package/dist/e2e_tests/signatureHelp.spec.js +27 -0
- package/dist/e2e_tests/signatureHelp.spec.js.map +1 -1
- package/dist/e2e_tests/snippets.spec.js +0 -1
- package/dist/e2e_tests/snippets.spec.js.map +1 -1
- package/dist/e2e_tests/syntaxHighlighting.spec.js +0 -1
- package/dist/e2e_tests/syntaxHighlighting.spec.js.map +1 -1
- package/dist/historyNavigation.js +1 -1
- package/dist/historyNavigation.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lang-cypher/autocomplete.d.ts +4 -1
- package/dist/lang-cypher/autocomplete.js +79 -17
- package/dist/lang-cypher/autocomplete.js.map +1 -1
- package/dist/lang-cypher/contants.test.js +2 -2
- package/dist/lang-cypher/contants.test.js.map +1 -1
- package/dist/lang-cypher/createCypherTheme.js +9 -2
- package/dist/lang-cypher/createCypherTheme.js.map +1 -1
- package/dist/lang-cypher/langCypher.d.ts +5 -0
- package/dist/lang-cypher/langCypher.js +11 -5
- package/dist/lang-cypher/langCypher.js.map +1 -1
- package/dist/lang-cypher/signatureHelp.js +4 -3
- package/dist/lang-cypher/signatureHelp.js.map +1 -1
- package/dist/lang-cypher/utils.d.ts +2 -0
- package/dist/lang-cypher/utils.js +10 -0
- package/dist/lang-cypher/utils.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/CypherEditor.tsx +131 -21
- package/src/e2e_tests/autoCompletion.spec.tsx +206 -2
- package/src/e2e_tests/configuration.spec.tsx +16 -2
- package/src/e2e_tests/debounce.spec.tsx +100 -0
- package/src/e2e_tests/extraKeybindings.spec.tsx +0 -2
- package/src/e2e_tests/historyNavigation.spec.tsx +136 -17
- package/src/e2e_tests/sanityChecks.spec.tsx +0 -2
- package/src/e2e_tests/signatureHelp.spec.tsx +71 -0
- package/src/e2e_tests/snippets.spec.tsx +0 -2
- package/src/e2e_tests/syntaxHighlighting.spec.tsx +0 -2
- package/src/historyNavigation.ts +1 -1
- package/src/index.ts +4 -1
- package/src/lang-cypher/autocomplete.ts +95 -19
- package/src/lang-cypher/contants.test.ts +5 -2
- package/src/lang-cypher/createCypherTheme.ts +9 -3
- package/src/lang-cypher/langCypher.ts +17 -5
- package/src/lang-cypher/signatureHelp.ts +5 -3
- package/src/lang-cypher/utils.ts +9 -0
package/src/CypherEditor.tsx
CHANGED
|
@@ -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
|
|
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 = (
|
|
141
|
-
onExecute
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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(
|
|
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([
|
|
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 (
|
|
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(
|
|
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;
|