@neo4j-cypher/react-codemirror 2.0.0-next.3 → 2.0.0-next.31
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 +265 -0
- package/README.md +3 -2
- package/dist/{types → src}/CypherEditor.d.ts +81 -3
- package/dist/src/CypherEditor.js +336 -0
- package/dist/src/CypherEditor.js.map +1 -0
- package/dist/src/CypherEditor.test.js +154 -0
- package/dist/src/CypherEditor.test.js.map +1 -0
- package/dist/src/constants.d.ts +1 -0
- package/dist/src/constants.js +2 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/e2e_tests/autoCompletion.spec.js +332 -0
- package/dist/src/e2e_tests/autoCompletion.spec.js.map +1 -0
- package/dist/src/e2e_tests/configuration.spec.js +83 -0
- package/dist/src/e2e_tests/configuration.spec.js.map +1 -0
- package/dist/src/e2e_tests/debounce.spec.js +66 -0
- package/dist/src/e2e_tests/debounce.spec.js.map +1 -0
- package/dist/{types/e2e_tests/e2e-utils.d.ts → src/e2e_tests/e2eUtils.d.ts} +2 -0
- package/dist/src/e2e_tests/e2eUtils.js +79 -0
- package/dist/src/e2e_tests/e2eUtils.js.map +1 -0
- package/dist/src/e2e_tests/extraKeybindings.spec.js +43 -0
- package/dist/src/e2e_tests/extraKeybindings.spec.js.map +1 -0
- package/dist/src/e2e_tests/historyNavigation.spec.js +227 -0
- package/dist/src/e2e_tests/historyNavigation.spec.js.map +1 -0
- package/dist/src/e2e_tests/performanceTest.spec.d.ts +6 -0
- package/dist/src/e2e_tests/performanceTest.spec.js +97 -0
- package/dist/src/e2e_tests/performanceTest.spec.js.map +1 -0
- package/dist/src/e2e_tests/sanityChecks.spec.js +53 -0
- package/dist/src/e2e_tests/sanityChecks.spec.js.map +1 -0
- package/dist/src/e2e_tests/signatureHelp.spec.js +228 -0
- package/dist/src/e2e_tests/signatureHelp.spec.js.map +1 -0
- package/dist/src/e2e_tests/snippets.spec.js +62 -0
- package/dist/src/e2e_tests/snippets.spec.js.map +1 -0
- package/dist/src/e2e_tests/syntaxHighlighting.spec.d.ts +1 -0
- package/dist/src/e2e_tests/syntaxHighlighting.spec.js +90 -0
- package/dist/src/e2e_tests/syntaxHighlighting.spec.js.map +1 -0
- package/dist/src/e2e_tests/syntaxValidation.spec.d.ts +1 -0
- package/dist/src/e2e_tests/syntaxValidation.spec.js +126 -0
- package/dist/src/e2e_tests/syntaxValidation.spec.js.map +1 -0
- package/dist/src/historyNavigation.js +163 -0
- package/dist/src/historyNavigation.js.map +1 -0
- package/dist/{types → src}/icons.d.ts +1 -1
- package/dist/src/icons.js +62 -0
- package/dist/src/icons.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lang-cypher/autocomplete.d.ts +6 -0
- package/dist/src/lang-cypher/autocomplete.js +113 -0
- package/dist/src/lang-cypher/autocomplete.js.map +1 -0
- package/dist/{types → src}/lang-cypher/constants.d.ts +11 -0
- package/dist/src/lang-cypher/constants.js +69 -0
- package/dist/src/lang-cypher/constants.js.map +1 -0
- package/dist/src/lang-cypher/contants.test.d.ts +1 -0
- package/dist/src/lang-cypher/contants.test.js +103 -0
- package/dist/src/lang-cypher/contants.test.js.map +1 -0
- package/dist/src/lang-cypher/createCypherTheme.js +183 -0
- package/dist/src/lang-cypher/createCypherTheme.js.map +1 -0
- package/dist/src/lang-cypher/langCypher.d.ts +13 -0
- package/dist/src/lang-cypher/langCypher.js +23 -0
- package/dist/src/lang-cypher/langCypher.js.map +1 -0
- package/dist/src/lang-cypher/lintWorker.mjs +2022 -0
- package/dist/src/lang-cypher/parser-adapter.d.ts +19 -0
- package/dist/src/lang-cypher/parser-adapter.js +113 -0
- package/dist/src/lang-cypher/parser-adapter.js.map +1 -0
- package/dist/src/lang-cypher/signatureHelp.d.ts +4 -0
- package/dist/src/lang-cypher/signatureHelp.js +109 -0
- package/dist/src/lang-cypher/signatureHelp.js.map +1 -0
- package/dist/{types/lang-cypher/syntax-validation.d.ts → src/lang-cypher/syntaxValidation.d.ts} +2 -1
- package/dist/src/lang-cypher/syntaxValidation.js +57 -0
- package/dist/src/lang-cypher/syntaxValidation.js.map +1 -0
- package/dist/src/lang-cypher/themeIcons.js +22 -0
- package/dist/src/lang-cypher/themeIcons.js.map +1 -0
- package/dist/src/lang-cypher/utils.d.ts +2 -0
- package/dist/src/lang-cypher/utils.js +10 -0
- package/dist/src/lang-cypher/utils.js.map +1 -0
- package/dist/src/ndlTokensCopy.d.ts +570 -0
- package/dist/src/ndlTokensCopy.js +571 -0
- package/dist/src/ndlTokensCopy.js.map +1 -0
- package/dist/src/ndlTokensCopy.test.d.ts +1 -0
- package/dist/src/ndlTokensCopy.test.js +12 -0
- package/dist/src/ndlTokensCopy.test.js.map +1 -0
- package/dist/src/neo4jSetup.d.ts +6 -0
- package/dist/src/neo4jSetup.js +120 -0
- package/dist/src/neo4jSetup.js.map +1 -0
- package/dist/src/richClipboardCopier.d.ts +4 -0
- package/dist/src/richClipboardCopier.js +78 -0
- package/dist/src/richClipboardCopier.js.map +1 -0
- package/dist/src/richClipboardCopier.test.d.ts +1 -0
- package/dist/src/richClipboardCopier.test.js +53 -0
- package/dist/src/richClipboardCopier.test.js.map +1 -0
- package/dist/{types → src}/themes.d.ts +1 -1
- package/dist/src/themes.js +93 -0
- package/dist/src/themes.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +43 -41
- package/src/CypherEditor.test.tsx +204 -0
- package/src/CypherEditor.tsx +316 -42
- package/src/constants.ts +1 -0
- package/src/e2e_tests/autoCompletion.spec.tsx +571 -0
- package/src/e2e_tests/configuration.spec.tsx +111 -0
- package/src/e2e_tests/debounce.spec.tsx +106 -0
- package/src/e2e_tests/{e2e-utils.ts → e2eUtils.ts} +41 -3
- package/src/e2e_tests/{extra-keybindings.spec.tsx → extraKeybindings.spec.tsx} +1 -3
- package/src/e2e_tests/{history-navigation.spec.tsx → historyNavigation.spec.tsx} +137 -18
- package/src/e2e_tests/performanceTest.spec.tsx +163 -0
- package/src/e2e_tests/{sanity-checks.spec.tsx → sanityChecks.spec.tsx} +7 -22
- package/src/e2e_tests/signatureHelp.spec.tsx +444 -0
- package/src/e2e_tests/snippets.spec.tsx +92 -0
- package/src/e2e_tests/{syntax-highlighting.spec.tsx → syntaxHighlighting.spec.tsx} +26 -24
- package/src/e2e_tests/syntaxValidation.spec.tsx +259 -0
- package/src/{history-navigation.ts → historyNavigation.ts} +1 -1
- package/src/icons.ts +3 -0
- package/src/index.ts +2 -2
- package/src/lang-cypher/autocomplete.ts +99 -18
- package/src/lang-cypher/constants.ts +27 -0
- package/src/lang-cypher/contants.test.ts +6 -2
- package/src/lang-cypher/{create-cypher-theme.ts → createCypherTheme.ts} +45 -2
- package/src/lang-cypher/langCypher.ts +42 -0
- package/src/lang-cypher/lintWorker.mjs +2022 -0
- package/src/lang-cypher/parser-adapter.ts +145 -0
- package/src/lang-cypher/signatureHelp.ts +151 -0
- package/src/lang-cypher/syntaxValidation.ts +72 -0
- package/src/lang-cypher/utils.ts +9 -0
- package/src/{ndl-tokens-copy.test.ts → ndlTokensCopy.test.ts} +2 -1
- package/src/ndlTokensCopy.ts +570 -0
- package/src/{neo4j-setup.tsx → neo4jSetup.tsx} +78 -17
- package/src/richClipboardCopier.test.ts +65 -0
- package/src/richClipboardCopier.ts +99 -0
- package/src/themes.ts +45 -70
- package/src/viteEnv.d.ts +1 -0
- package/dist/cjs/index.cjs +0 -1440
- package/dist/cjs/index.cjs.map +0 -7
- package/dist/esm/index.mjs +0 -1463
- package/dist/esm/index.mjs.map +0 -7
- package/dist/types/e2e_tests/mock-data.d.ts +0 -3779
- package/dist/types/index.d.ts +0 -4
- package/dist/types/lang-cypher/ParserAdapter.d.ts +0 -14
- package/dist/types/lang-cypher/autocomplete.d.ts +0 -3
- package/dist/types/lang-cypher/lang-cypher.d.ts +0 -7
- package/dist/types/ndl-tokens-copy.d.ts +0 -379
- package/dist/types/neo4j-setup.d.ts +0 -2
- package/dist/types/tsconfig.tsbuildinfo +0 -1
- package/src/e2e_tests/auto-completion.spec.tsx +0 -232
- package/src/e2e_tests/mock-data.ts +0 -4310
- package/src/e2e_tests/performance-test.spec.tsx +0 -71
- package/src/e2e_tests/syntax-validation.spec.tsx +0 -156
- package/src/lang-cypher/ParserAdapter.ts +0 -92
- package/src/lang-cypher/lang-cypher.ts +0 -32
- package/src/lang-cypher/syntax-validation.ts +0 -24
- package/src/ndl-tokens-copy.ts +0 -379
- /package/dist/{types/e2e_tests/auto-completion.spec.d.ts → src/CypherEditor.test.d.ts} +0 -0
- /package/dist/{types/e2e_tests/extra-keybindings.spec.d.ts → src/e2e_tests/autoCompletion.spec.d.ts} +0 -0
- /package/dist/{types/e2e_tests/history-navigation.spec.d.ts → src/e2e_tests/configuration.spec.d.ts} +0 -0
- /package/dist/{types/e2e_tests/performance-test.spec.d.ts → src/e2e_tests/debounce.spec.d.ts} +0 -0
- /package/dist/{types/e2e_tests/sanity-checks.spec.d.ts → src/e2e_tests/extraKeybindings.spec.d.ts} +0 -0
- /package/dist/{types/e2e_tests/syntax-highlighting.spec.d.ts → src/e2e_tests/historyNavigation.spec.d.ts} +0 -0
- /package/dist/{types/e2e_tests/syntax-validation.spec.d.ts → src/e2e_tests/sanityChecks.spec.d.ts} +0 -0
- /package/dist/{types/lang-cypher/contants.test.d.ts → src/e2e_tests/signatureHelp.spec.d.ts} +0 -0
- /package/dist/{types/ndl-tokens-copy.test.d.ts → src/e2e_tests/snippets.spec.d.ts} +0 -0
- /package/dist/{types/history-navigation.d.ts → src/historyNavigation.d.ts} +0 -0
- /package/dist/{types/lang-cypher/create-cypher-theme.d.ts → src/lang-cypher/createCypherTheme.d.ts} +0 -0
- /package/dist/{types/lang-cypher/theme-icons.d.ts → src/lang-cypher/themeIcons.d.ts} +0 -0
- /package/src/lang-cypher/{theme-icons.ts → themeIcons.ts} +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/experimental-ct-react';
|
|
2
|
+
import { DEBOUNCE_TIME } from '../constants';
|
|
3
|
+
import { CypherEditor } from '../CypherEditor';
|
|
4
|
+
import { CypherEditorPage } from './e2eUtils';
|
|
5
|
+
|
|
6
|
+
const DEBOUNCE_TIME_WITH_MARGIN = DEBOUNCE_TIME + 100;
|
|
7
|
+
// value updates from outside onExecute are overwritten by pending updates
|
|
8
|
+
test.fail(
|
|
9
|
+
'external updates should override debounced updates',
|
|
10
|
+
async ({ mount, page }) => {
|
|
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_TIME_WITH_MARGIN);
|
|
26
|
+
await expect(component).toContainText('foo');
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// TODO Fix this test
|
|
31
|
+
test.fixme(
|
|
32
|
+
'onExecute updates should override debounce updates',
|
|
33
|
+
async ({ mount, page }) => {
|
|
34
|
+
const editorPage = new CypherEditorPage(page);
|
|
35
|
+
let value = '';
|
|
36
|
+
|
|
37
|
+
const onExecute = () => {
|
|
38
|
+
value = '';
|
|
39
|
+
void component.update(
|
|
40
|
+
<CypherEditor
|
|
41
|
+
value={value}
|
|
42
|
+
onChange={onChange}
|
|
43
|
+
onExecute={onExecute}
|
|
44
|
+
/>,
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const onChange = (val: string) => {
|
|
49
|
+
value = val;
|
|
50
|
+
void component.update(
|
|
51
|
+
<CypherEditor value={val} onChange={onChange} onExecute={onExecute} />,
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const component = await mount(
|
|
56
|
+
<CypherEditor value={value} onChange={onChange} onExecute={onExecute} />,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
await editorPage.getEditor().pressSequentially('RETURN 1');
|
|
60
|
+
await editorPage.getEditor().press('Enter');
|
|
61
|
+
await page.waitForTimeout(DEBOUNCE_TIME_WITH_MARGIN);
|
|
62
|
+
await expect(component).not.toContainText('RETURN 1');
|
|
63
|
+
|
|
64
|
+
await editorPage.getEditor().pressSequentially('RETURN 1');
|
|
65
|
+
await editorPage.getEditor().pressSequentially('');
|
|
66
|
+
await editorPage.getEditor().pressSequentially('RETURN 1');
|
|
67
|
+
await editorPage.getEditor().press('Enter');
|
|
68
|
+
await page.waitForTimeout(DEBOUNCE_TIME_WITH_MARGIN);
|
|
69
|
+
await expect(component).not.toContainText('RETURN 1');
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
test('onExecute should fire after debounced updates', async ({
|
|
74
|
+
mount,
|
|
75
|
+
page,
|
|
76
|
+
}) => {
|
|
77
|
+
const editorPage = new CypherEditorPage(page);
|
|
78
|
+
let value = '';
|
|
79
|
+
let executedCommand = '';
|
|
80
|
+
|
|
81
|
+
const onExecute = (cmd: string) => {
|
|
82
|
+
executedCommand = cmd;
|
|
83
|
+
void component.update(
|
|
84
|
+
<CypherEditor value={value} onChange={onChange} onExecute={onExecute} />,
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const onChange = (val: string) => {
|
|
89
|
+
value = val;
|
|
90
|
+
void component.update(
|
|
91
|
+
<CypherEditor value={val} onChange={onChange} onExecute={onExecute} />,
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const component = await mount(
|
|
96
|
+
<CypherEditor value={value} onChange={onChange} onExecute={onExecute} />,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
await editorPage.getEditor().fill('RETURN 1');
|
|
100
|
+
await editorPage.getEditor().press('Enter');
|
|
101
|
+
await editorPage.getEditor().fill('RETURN 2');
|
|
102
|
+
await editorPage.getEditor().press('Enter');
|
|
103
|
+
await page.waitForTimeout(DEBOUNCE_TIME_WITH_MARGIN);
|
|
104
|
+
await expect(component).toContainText('RETURN 2');
|
|
105
|
+
expect(executedCommand).toBe('RETURN 2');
|
|
106
|
+
});
|
|
@@ -59,17 +59,55 @@ export class CypherEditorPage {
|
|
|
59
59
|
return this.checkNotificationMessage('warning', queryChunk, expectedMsg);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
async checkNoNotificationMessage(type: 'error' | 'warning') {
|
|
63
|
+
await this.page.waitForTimeout(10000);
|
|
64
|
+
await expect(this.page.locator('.cm-lintRange-' + type)).toHaveCount(0, {
|
|
65
|
+
timeout: 10000,
|
|
66
|
+
});
|
|
67
|
+
await expect(this.page.locator('.cm-lintPoint-' + type)).toHaveCount(0, {
|
|
68
|
+
timeout: 10000,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
62
72
|
private async checkNotificationMessage(
|
|
63
73
|
type: 'error' | 'warning',
|
|
64
74
|
queryChunk: string,
|
|
65
75
|
expectedMsg: string,
|
|
66
76
|
) {
|
|
67
77
|
await expect(this.page.locator('.cm-lintRange-' + type).last()).toBeVisible(
|
|
68
|
-
{ timeout:
|
|
78
|
+
{ timeout: 10000 },
|
|
69
79
|
);
|
|
70
80
|
|
|
71
81
|
await this.page.getByText(queryChunk, { exact: true }).hover();
|
|
72
|
-
await expect(this.page.locator('.cm-tooltip-hover').last()).toBeVisible(
|
|
73
|
-
|
|
82
|
+
await expect(this.page.locator('.cm-tooltip-hover').last()).toBeVisible({
|
|
83
|
+
timeout: 10000,
|
|
84
|
+
});
|
|
85
|
+
await this.checkHoverMessage(expectedMsg, type);
|
|
86
|
+
/* Return the mouse to the beginning of the query and
|
|
87
|
+
This is because if for example we have an overlay with a
|
|
88
|
+
first interaction that covers the element we want to perform
|
|
89
|
+
the second interaction on, we won't be able to see that second element
|
|
90
|
+
*/
|
|
91
|
+
await this.page.mouse.move(0, 0);
|
|
92
|
+
// Make sure the tooltip closed
|
|
93
|
+
await expect(
|
|
94
|
+
this.page.locator('.cm-tooltip-hover').last(),
|
|
95
|
+
).not.toBeVisible();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private async checkHoverMessage(
|
|
99
|
+
expectedMsg: string,
|
|
100
|
+
type: 'error' | 'warning',
|
|
101
|
+
) {
|
|
102
|
+
const locator =
|
|
103
|
+
type === 'error'
|
|
104
|
+
? 'li.cm-diagnostic.cm-diagnostic-error'
|
|
105
|
+
: 'li.cm-diagnostic.cm-diagnostic-warning';
|
|
106
|
+
const tooltips = await this.page.locator(locator).all();
|
|
107
|
+
|
|
108
|
+
const tooltipTexts = await Promise.all(
|
|
109
|
+
tooltips.map((t) => t.textContent()),
|
|
110
|
+
);
|
|
111
|
+
expect(tooltipTexts).toContain(expectedMsg);
|
|
74
112
|
}
|
|
75
113
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { expect, test } from '@playwright/experimental-ct-react';
|
|
2
2
|
import { CypherEditor } from '../CypherEditor';
|
|
3
|
-
import { CypherEditorPage } from './
|
|
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);
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { expect, test } from '@playwright/experimental-ct-react';
|
|
2
2
|
import { CypherEditor } from '../CypherEditor';
|
|
3
|
-
import { CypherEditorPage } from './
|
|
4
|
-
|
|
5
|
-
test.use({ viewport: { width: 500, height: 500 } });
|
|
3
|
+
import { CypherEditorPage } from './e2eUtils';
|
|
6
4
|
|
|
7
5
|
test('respects preloaded history', async ({ page, mount }) => {
|
|
8
6
|
const editorPage = new CypherEditorPage(page);
|
|
@@ -27,13 +25,75 @@ test('respects preloaded history', async ({ page, mount }) => {
|
|
|
27
25
|
await editorPage.getEditor().press('ArrowUp');
|
|
28
26
|
await expect(page.getByText('second')).toBeVisible();
|
|
29
27
|
|
|
28
|
+
// First arrow down is to get to end of line
|
|
29
|
+
await editorPage.getEditor().press('ArrowDown');
|
|
30
30
|
await editorPage.getEditor().press('ArrowDown');
|
|
31
31
|
await expect(page.getByText('first')).toBeVisible();
|
|
32
32
|
await editorPage.getEditor().press('ArrowDown');
|
|
33
33
|
await expect(page.getByText(initialValue)).toBeVisible();
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
test('can
|
|
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
|
+
}) => {
|
|
37
97
|
const editorPage = new CypherEditorPage(page);
|
|
38
98
|
|
|
39
99
|
const initialValue = `MATCH (n)
|
|
@@ -49,6 +109,7 @@ RETURN n;`;
|
|
|
49
109
|
value={initialValue}
|
|
50
110
|
history={history}
|
|
51
111
|
onExecute={onExecute}
|
|
112
|
+
newLineOnEnter
|
|
52
113
|
/>,
|
|
53
114
|
);
|
|
54
115
|
|
|
@@ -63,12 +124,12 @@ RETURN n;`;
|
|
|
63
124
|
await editorPage.getEditor().press('Meta+Enter');
|
|
64
125
|
expect(history.length).toBe(1);
|
|
65
126
|
|
|
66
|
-
// Ensure
|
|
127
|
+
// Ensure cmd+enter is required in multiline
|
|
67
128
|
await editorPage.getEditor().fill('multiline');
|
|
68
129
|
await editorPage.getEditor().press('Enter');
|
|
69
130
|
await editorPage.getEditor().press('Enter');
|
|
70
131
|
await editorPage.getEditor().press('Enter');
|
|
71
|
-
await editorPage.getEditor().press('Enter');
|
|
132
|
+
await editorPage.getEditor().press('Shift+Enter');
|
|
72
133
|
await page.keyboard.type('entry');
|
|
73
134
|
expect(history.length).toBe(1);
|
|
74
135
|
|
|
@@ -102,10 +163,15 @@ RETURN n;`;
|
|
|
102
163
|
await expect(page.getByText('multiline')).toBeVisible();
|
|
103
164
|
|
|
104
165
|
// arrow movements don't matter until bottom is hit
|
|
166
|
+
await editorPage.getEditor().press('ArrowDown');
|
|
167
|
+
await editorPage.getEditor().press('ArrowDown');
|
|
105
168
|
await editorPage.getEditor().press('ArrowUp');
|
|
106
169
|
await editorPage.getEditor().press('ArrowUp');
|
|
107
170
|
await editorPage.getEditor().press('ArrowDown');
|
|
108
171
|
await editorPage.getEditor().press('ArrowDown');
|
|
172
|
+
await editorPage.getEditor().press('ArrowDown');
|
|
173
|
+
await editorPage.getEditor().press('ArrowDown');
|
|
174
|
+
await editorPage.getEditor().press('ArrowDown');
|
|
109
175
|
|
|
110
176
|
// editor still multiline
|
|
111
177
|
await expect(page.getByText('multiline')).toBeVisible();
|
|
@@ -115,6 +181,59 @@ RETURN n;`;
|
|
|
115
181
|
await expect(page.getByText('draft')).toBeVisible();
|
|
116
182
|
});
|
|
117
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
|
+
|
|
118
237
|
test('can navigate with cmd+up as well', async ({ page, mount }) => {
|
|
119
238
|
const editorPage = new CypherEditorPage(page);
|
|
120
239
|
const isMac = process.platform === 'darwin';
|
|
@@ -127,11 +246,11 @@ test('can navigate with cmd+up as well', async ({ page, mount }) => {
|
|
|
127
246
|
<CypherEditor
|
|
128
247
|
value={initialValue}
|
|
129
248
|
history={[
|
|
249
|
+
'first',
|
|
130
250
|
`one
|
|
131
251
|
multiline
|
|
132
252
|
entry
|
|
133
253
|
.`,
|
|
134
|
-
'second',
|
|
135
254
|
]}
|
|
136
255
|
onExecute={() => {
|
|
137
256
|
/* needed to turn on history movements */
|
|
@@ -139,24 +258,24 @@ entry
|
|
|
139
258
|
/>,
|
|
140
259
|
);
|
|
141
260
|
|
|
261
|
+
await editorPage.getEditor().press(metaUp);
|
|
262
|
+
await expect(page.getByText('first')).toBeVisible();
|
|
263
|
+
|
|
264
|
+
// move in history
|
|
142
265
|
await editorPage.getEditor().press(metaUp);
|
|
143
266
|
await expect(page.getByText('multiline')).toBeVisible();
|
|
144
267
|
|
|
145
|
-
// Single meta
|
|
268
|
+
// Single meta down moves all the way to top of editor on mac
|
|
146
269
|
if (isMac) {
|
|
147
|
-
await editorPage.getEditor().press(
|
|
270
|
+
await editorPage.getEditor().press(metaDown);
|
|
148
271
|
} else {
|
|
149
|
-
await editorPage.getEditor().press('
|
|
150
|
-
await editorPage.getEditor().press('
|
|
151
|
-
await editorPage.getEditor().press('
|
|
152
|
-
await editorPage.getEditor().press('
|
|
272
|
+
await editorPage.getEditor().press('ArrowDown');
|
|
273
|
+
await editorPage.getEditor().press('ArrowDown');
|
|
274
|
+
await editorPage.getEditor().press('ArrowDown');
|
|
275
|
+
await editorPage.getEditor().press('ArrowDown');
|
|
153
276
|
}
|
|
154
|
-
// move in history
|
|
155
|
-
await editorPage.getEditor().press(metaUp);
|
|
156
|
-
await expect(page.getByText('second')).toBeVisible();
|
|
157
|
-
|
|
158
277
|
await editorPage.getEditor().press(metaDown);
|
|
159
|
-
await expect(page.getByText('
|
|
278
|
+
await expect(page.getByText('first')).toBeVisible();
|
|
160
279
|
});
|
|
161
280
|
|
|
162
281
|
test('test onExecute', async ({ page, mount }) => {
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { testData } from '@neo4j-cypher/language-support';
|
|
2
|
+
import { expect, test } from '@playwright/experimental-ct-react';
|
|
3
|
+
import { CypherEditor } from '../CypherEditor';
|
|
4
|
+
import { CypherEditorPage } from './e2eUtils';
|
|
5
|
+
|
|
6
|
+
test.use({ viewport: { width: 1000, height: 500 } });
|
|
7
|
+
declare global {
|
|
8
|
+
interface Window {
|
|
9
|
+
longtasks: number[];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
test('benchmarking & performance test session', async ({
|
|
14
|
+
browserName,
|
|
15
|
+
mount,
|
|
16
|
+
page,
|
|
17
|
+
}) => {
|
|
18
|
+
test.skip(browserName !== 'chromium');
|
|
19
|
+
const client = await page.context().newCDPSession(page);
|
|
20
|
+
if (process.env.BENCHMARKING === 'true') {
|
|
21
|
+
test.setTimeout(1000000);
|
|
22
|
+
await client.send('Performance.enable');
|
|
23
|
+
await client.send('Emulation.setCPUThrottlingRate', { rate: 4 });
|
|
24
|
+
await client.send('Overlay.setShowFPSCounter', { show: true });
|
|
25
|
+
|
|
26
|
+
await page.evaluate(() => {
|
|
27
|
+
window.longtasks = [];
|
|
28
|
+
const observer = new PerformanceObserver((list) => {
|
|
29
|
+
window.longtasks.push(...list.getEntries().map((e) => e.duration));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
observer.observe({ entryTypes: ['longtask'] });
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
test.setTimeout(30 * 1000);
|
|
36
|
+
}
|
|
37
|
+
const editorPage = new CypherEditorPage(page);
|
|
38
|
+
const component = await mount(
|
|
39
|
+
<CypherEditor
|
|
40
|
+
prompt="neo4j>"
|
|
41
|
+
theme="dark"
|
|
42
|
+
lint
|
|
43
|
+
schema={testData.mockSchema}
|
|
44
|
+
/>,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// pressSequentially is less efficient -> we want to test the performance of the editor
|
|
48
|
+
await editorPage.getEditor().pressSequentially(`
|
|
49
|
+
MATCH (n:Person) RETURN m;`);
|
|
50
|
+
|
|
51
|
+
await editorPage.checkErrorMessage('m', 'Variable `m` not defined');
|
|
52
|
+
|
|
53
|
+
// set and unset large query a few times
|
|
54
|
+
await component.update(
|
|
55
|
+
<CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
|
|
56
|
+
);
|
|
57
|
+
await component.update(
|
|
58
|
+
<CypherEditor value="" schema={testData.mockSchema} />,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
await component.update(
|
|
62
|
+
<CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
|
|
63
|
+
);
|
|
64
|
+
await component.update(<CypherEditor value="" />);
|
|
65
|
+
|
|
66
|
+
await component.update(
|
|
67
|
+
<CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
|
|
68
|
+
);
|
|
69
|
+
await component.update(
|
|
70
|
+
<CypherEditor value="" schema={testData.mockSchema} />,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
await component.update(
|
|
74
|
+
<CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
|
|
75
|
+
);
|
|
76
|
+
await component.update(
|
|
77
|
+
<CypherEditor value="" schema={testData.mockSchema} />,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
await component.update(
|
|
81
|
+
<CypherEditor value={testData.largeQuery} schema={testData.mockSchema} />,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
await editorPage.getEditor().pressSequentially(`
|
|
85
|
+
MATCH (n:P`);
|
|
86
|
+
|
|
87
|
+
await expect(
|
|
88
|
+
page.locator('.cm-tooltip-autocomplete').getByText('Person'),
|
|
89
|
+
).toBeVisible();
|
|
90
|
+
|
|
91
|
+
await page.locator('.cm-tooltip-autocomplete').getByText('Person').click();
|
|
92
|
+
|
|
93
|
+
await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
|
|
94
|
+
|
|
95
|
+
await expect(component).toContainText('MATCH (n:Person');
|
|
96
|
+
|
|
97
|
+
await editorPage.getEditor().pressSequentially(') RETRN my');
|
|
98
|
+
|
|
99
|
+
await expect(component).toContainText('MATCH (n:Person) RETRN m');
|
|
100
|
+
|
|
101
|
+
await editorPage.checkErrorMessage(
|
|
102
|
+
'RETRN',
|
|
103
|
+
`Invalid input 'RETRN': expected a graph pattern, ',', 'ORDER BY', 'CALL', 'CREATE', 'LOAD CSV', 'DELETE', 'DETACH', 'FINISH', 'FOREACH', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REMOVE', 'RETURN', 'SET', 'SKIP', 'UNION', 'UNWIND', 'USE', 'USING', 'WHERE', 'WITH' or <EOF>`,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
await editorPage
|
|
107
|
+
.getEditor()
|
|
108
|
+
.pressSequentially('veryveryveryverylongvariable');
|
|
109
|
+
|
|
110
|
+
if (process.env.BENCHMARKING === 'true') {
|
|
111
|
+
const longtasks = await page.evaluate(() => window.longtasks);
|
|
112
|
+
const sortedLongTasks = longtasks.sort((a, b) => a - b);
|
|
113
|
+
const medianLongTask =
|
|
114
|
+
sortedLongTasks[Math.floor(sortedLongTasks.length / 2)];
|
|
115
|
+
const averageLongTask =
|
|
116
|
+
sortedLongTasks.reduce((a, b) => a + b, 0) / sortedLongTasks.length;
|
|
117
|
+
const over500 = sortedLongTasks.filter((t) => t > 500).length;
|
|
118
|
+
const nintyninethPercentile =
|
|
119
|
+
sortedLongTasks[Math.floor(sortedLongTasks.length * 0.99)];
|
|
120
|
+
const longTaskCount = longtasks.length;
|
|
121
|
+
const totalLongTaskTime = longtasks.reduce((a, b) => a + b, 0);
|
|
122
|
+
|
|
123
|
+
const USER_ID = 1226722;
|
|
124
|
+
const API_KEY = process.env.GRAFANA_API_KEY;
|
|
125
|
+
if (!API_KEY) {
|
|
126
|
+
throw new Error('Missing grafana api key');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const metrics = {
|
|
130
|
+
medianLongTask,
|
|
131
|
+
averageLongTask,
|
|
132
|
+
over500,
|
|
133
|
+
nintyninethPercentile,
|
|
134
|
+
longTaskCount,
|
|
135
|
+
totalLongTaskTime,
|
|
136
|
+
};
|
|
137
|
+
const body = Object.entries(metrics)
|
|
138
|
+
.map(
|
|
139
|
+
([key, value]) =>
|
|
140
|
+
`benchmark,bar_label=${key},source=playwright metric=${value}`,
|
|
141
|
+
)
|
|
142
|
+
.join('\n');
|
|
143
|
+
|
|
144
|
+
await fetch(
|
|
145
|
+
'https://influx-prod-39-prod-eu-north-0.grafana.net/api/v1/push/influx/write',
|
|
146
|
+
{
|
|
147
|
+
method: 'post',
|
|
148
|
+
body,
|
|
149
|
+
headers: {
|
|
150
|
+
Authorization: `Bearer ${USER_ID}:${API_KEY}`,
|
|
151
|
+
'Content-Type': 'text/plain',
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
).then((res) => {
|
|
155
|
+
if (res.ok) {
|
|
156
|
+
// eslint-disable-next-line no-console
|
|
157
|
+
console.log('Metrics pushed to grafana successfully');
|
|
158
|
+
} else {
|
|
159
|
+
throw new Error(`Failed to push metrics to grafana: ${res.statusText}`);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
});
|
|
@@ -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
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
});
|