@neo4j-cypher/react-codemirror 0.0.0-canary-20250423075344
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +279 -0
- package/LICENSE.md +201 -0
- package/README.md +35 -0
- package/dist/CypherEditor.d.ts +192 -0
- package/dist/CypherEditor.js +326 -0
- package/dist/CypherEditor.js.map +1 -0
- package/dist/CypherEditor.test.d.ts +1 -0
- package/dist/CypherEditor.test.js +151 -0
- package/dist/CypherEditor.test.js.map +1 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -0
- package/dist/e2e_tests/autoCompletion.spec.d.ts +1 -0
- package/dist/e2e_tests/autoCompletion.spec.js +318 -0
- package/dist/e2e_tests/autoCompletion.spec.js.map +1 -0
- package/dist/e2e_tests/configuration.spec.d.ts +1 -0
- package/dist/e2e_tests/configuration.spec.js +83 -0
- package/dist/e2e_tests/configuration.spec.js.map +1 -0
- package/dist/e2e_tests/debounce.spec.d.ts +1 -0
- package/dist/e2e_tests/debounce.spec.js +66 -0
- package/dist/e2e_tests/debounce.spec.js.map +1 -0
- package/dist/e2e_tests/e2eUtils.d.ts +13 -0
- package/dist/e2e_tests/e2eUtils.js +71 -0
- package/dist/e2e_tests/e2eUtils.js.map +1 -0
- package/dist/e2e_tests/extraKeybindings.spec.d.ts +1 -0
- package/dist/e2e_tests/extraKeybindings.spec.js +43 -0
- package/dist/e2e_tests/extraKeybindings.spec.js.map +1 -0
- package/dist/e2e_tests/historyNavigation.spec.d.ts +1 -0
- package/dist/e2e_tests/historyNavigation.spec.js +227 -0
- package/dist/e2e_tests/historyNavigation.spec.js.map +1 -0
- package/dist/e2e_tests/performanceTest.spec.d.ts +6 -0
- package/dist/e2e_tests/performanceTest.spec.js +97 -0
- package/dist/e2e_tests/performanceTest.spec.js.map +1 -0
- package/dist/e2e_tests/sanityChecks.spec.d.ts +1 -0
- package/dist/e2e_tests/sanityChecks.spec.js +53 -0
- package/dist/e2e_tests/sanityChecks.spec.js.map +1 -0
- package/dist/e2e_tests/signatureHelp.spec.d.ts +1 -0
- package/dist/e2e_tests/signatureHelp.spec.js +228 -0
- package/dist/e2e_tests/signatureHelp.spec.js.map +1 -0
- package/dist/e2e_tests/snippets.spec.d.ts +1 -0
- package/dist/e2e_tests/snippets.spec.js +62 -0
- package/dist/e2e_tests/snippets.spec.js.map +1 -0
- package/dist/e2e_tests/syntaxHighlighting.spec.d.ts +1 -0
- package/dist/e2e_tests/syntaxHighlighting.spec.js +90 -0
- package/dist/e2e_tests/syntaxHighlighting.spec.js.map +1 -0
- package/dist/e2e_tests/syntaxValidation.spec.d.ts +1 -0
- package/dist/e2e_tests/syntaxValidation.spec.js +116 -0
- package/dist/e2e_tests/syntaxValidation.spec.js.map +1 -0
- package/dist/historyNavigation.d.ts +7 -0
- package/dist/historyNavigation.js +163 -0
- package/dist/historyNavigation.js.map +1 -0
- package/dist/icons.d.ts +2 -0
- package/dist/icons.js +62 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/lang-cypher/autocomplete.d.ts +6 -0
- package/dist/lang-cypher/autocomplete.js +115 -0
- package/dist/lang-cypher/autocomplete.js.map +1 -0
- package/dist/lang-cypher/constants.d.ts +42 -0
- package/dist/lang-cypher/constants.js +69 -0
- package/dist/lang-cypher/constants.js.map +1 -0
- package/dist/lang-cypher/contants.test.d.ts +1 -0
- package/dist/lang-cypher/contants.test.js +103 -0
- package/dist/lang-cypher/contants.test.js.map +1 -0
- package/dist/lang-cypher/createCypherTheme.d.ts +26 -0
- package/dist/lang-cypher/createCypherTheme.js +182 -0
- package/dist/lang-cypher/createCypherTheme.js.map +1 -0
- package/dist/lang-cypher/langCypher.d.ts +14 -0
- package/dist/lang-cypher/langCypher.js +23 -0
- package/dist/lang-cypher/langCypher.js.map +1 -0
- package/dist/lang-cypher/lintWorker.d.ts +12 -0
- package/dist/lang-cypher/lintWorker.js +14 -0
- package/dist/lang-cypher/lintWorker.js.map +1 -0
- package/dist/lang-cypher/parser-adapter.d.ts +19 -0
- package/dist/lang-cypher/parser-adapter.js +113 -0
- package/dist/lang-cypher/parser-adapter.js.map +1 -0
- package/dist/lang-cypher/signatureHelp.d.ts +4 -0
- package/dist/lang-cypher/signatureHelp.js +109 -0
- package/dist/lang-cypher/signatureHelp.js.map +1 -0
- package/dist/lang-cypher/syntaxValidation.d.ts +4 -0
- package/dist/lang-cypher/syntaxValidation.js +52 -0
- package/dist/lang-cypher/syntaxValidation.js.map +1 -0
- package/dist/lang-cypher/themeIcons.d.ts +7 -0
- package/dist/lang-cypher/themeIcons.js +22 -0
- package/dist/lang-cypher/themeIcons.js.map +1 -0
- 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/ndlTokensCopy.d.ts +570 -0
- package/dist/ndlTokensCopy.js +571 -0
- package/dist/ndlTokensCopy.js.map +1 -0
- package/dist/ndlTokensCopy.test.d.ts +1 -0
- package/dist/ndlTokensCopy.test.js +12 -0
- package/dist/ndlTokensCopy.test.js.map +1 -0
- package/dist/neo4jSetup.d.ts +6 -0
- package/dist/neo4jSetup.js +120 -0
- package/dist/neo4jSetup.js.map +1 -0
- package/dist/themes.d.ts +11 -0
- package/dist/themes.js +93 -0
- package/dist/themes.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +82 -0
- package/src/CypherEditor.test.tsx +200 -0
- package/src/CypherEditor.tsx +607 -0
- package/src/constants.ts +1 -0
- package/src/e2e_tests/autoCompletion.spec.tsx +546 -0
- package/src/e2e_tests/configuration.spec.tsx +111 -0
- package/src/e2e_tests/debounce.spec.tsx +106 -0
- package/src/e2e_tests/e2eUtils.ts +97 -0
- package/src/e2e_tests/extraKeybindings.spec.tsx +55 -0
- package/src/e2e_tests/historyNavigation.spec.tsx +315 -0
- package/src/e2e_tests/performanceTest.spec.tsx +163 -0
- package/src/e2e_tests/sanityChecks.spec.tsx +72 -0
- package/src/e2e_tests/signatureHelp.spec.tsx +445 -0
- package/src/e2e_tests/snippets.spec.tsx +92 -0
- package/src/e2e_tests/syntaxHighlighting.spec.tsx +200 -0
- package/src/e2e_tests/syntaxValidation.spec.tsx +243 -0
- package/src/historyNavigation.ts +191 -0
- package/src/icons.ts +90 -0
- package/src/index.ts +4 -0
- package/src/lang-cypher/autocomplete.ts +147 -0
- package/src/lang-cypher/constants.ts +88 -0
- package/src/lang-cypher/contants.test.ts +108 -0
- package/src/lang-cypher/createCypherTheme.ts +249 -0
- package/src/lang-cypher/langCypher.ts +43 -0
- package/src/lang-cypher/lintWorker.ts +31 -0
- package/src/lang-cypher/parser-adapter.ts +145 -0
- package/src/lang-cypher/signatureHelp.ts +151 -0
- package/src/lang-cypher/syntaxValidation.ts +66 -0
- package/src/lang-cypher/themeIcons.ts +27 -0
- package/src/lang-cypher/utils.ts +9 -0
- package/src/ndlTokensCopy.test.ts +12 -0
- package/src/ndlTokensCopy.ts +570 -0
- package/src/neo4jSetup.tsx +190 -0
- package/src/themes.ts +105 -0
- package/src/viteEnv.d.ts +1 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/experimental-ct-react';
|
|
2
|
+
import { CypherEditor } from '../CypherEditor';
|
|
3
|
+
|
|
4
|
+
test('can complete pattern snippet', async ({ page, mount }) => {
|
|
5
|
+
await mount(<CypherEditor />);
|
|
6
|
+
const textField = page.getByRole('textbox');
|
|
7
|
+
|
|
8
|
+
await textField.fill('MATCH ()-[]->()');
|
|
9
|
+
|
|
10
|
+
await page.locator('.cm-tooltip-autocomplete').getByText('-[]->()').click();
|
|
11
|
+
await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
|
|
12
|
+
|
|
13
|
+
await textField.press('Tab');
|
|
14
|
+
await textField.press('Tab');
|
|
15
|
+
|
|
16
|
+
await expect(textField).toHaveText('MATCH ()-[]->()-[ ]->( )');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('can navigate snippet', async ({ page, mount }) => {
|
|
20
|
+
await mount(<CypherEditor />);
|
|
21
|
+
const textField = page.getByRole('textbox');
|
|
22
|
+
|
|
23
|
+
await textField.fill('CREATE INDEX abc FOR ()');
|
|
24
|
+
|
|
25
|
+
await page
|
|
26
|
+
.locator('.cm-tooltip-autocomplete')
|
|
27
|
+
.getByText('-[]-()', { exact: true })
|
|
28
|
+
.click();
|
|
29
|
+
await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
|
|
30
|
+
await expect(page.locator('.cm-snippetField')).toHaveCount(2);
|
|
31
|
+
|
|
32
|
+
await textField.press('Tab');
|
|
33
|
+
await textField.press('Shift+Tab');
|
|
34
|
+
|
|
35
|
+
await expect(textField).toHaveText('CREATE INDEX abc FOR ()-[ ]-( )');
|
|
36
|
+
|
|
37
|
+
await textField.press('a');
|
|
38
|
+
await expect(textField).toHaveText('CREATE INDEX abc FOR ()-[a]-( )');
|
|
39
|
+
|
|
40
|
+
await textField.press('Escape');
|
|
41
|
+
await textField.press('Escape');
|
|
42
|
+
await expect(page.locator('.cm-snippetField')).toHaveCount(0);
|
|
43
|
+
await textField.press('Tab');
|
|
44
|
+
await expect(textField).toHaveText('CREATE INDEX abc FOR ()-[a ]-( )');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('can accept completion inside pattern snippet', async ({
|
|
48
|
+
page,
|
|
49
|
+
mount,
|
|
50
|
+
}) => {
|
|
51
|
+
await mount(<CypherEditor schema={{ labels: ['City'] }} />);
|
|
52
|
+
const textField = page.getByRole('textbox');
|
|
53
|
+
|
|
54
|
+
await textField.fill('MATCH ()-[]->()');
|
|
55
|
+
|
|
56
|
+
await page.locator('.cm-tooltip-autocomplete').getByText('-[]->()').click();
|
|
57
|
+
await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
|
|
58
|
+
|
|
59
|
+
// move to node
|
|
60
|
+
await textField.press('Tab');
|
|
61
|
+
|
|
62
|
+
// get & accept completion
|
|
63
|
+
await textField.press(':');
|
|
64
|
+
await expect(
|
|
65
|
+
page.locator('.cm-tooltip-autocomplete').getByText('City'),
|
|
66
|
+
).toBeVisible();
|
|
67
|
+
|
|
68
|
+
await textField.press('Tab', { delay: 300 });
|
|
69
|
+
await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
|
|
70
|
+
|
|
71
|
+
// tab out of the snippet
|
|
72
|
+
await textField.press('Tab', { delay: 300 });
|
|
73
|
+
|
|
74
|
+
await expect(textField).toHaveText('MATCH ()-[]->()-[ ]->(:City)');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('does not automatically open completion panel for expressions after snippet trigger char', async ({
|
|
78
|
+
page,
|
|
79
|
+
mount,
|
|
80
|
+
}) => {
|
|
81
|
+
await mount(<CypherEditor />);
|
|
82
|
+
const textField = page.getByRole('textbox');
|
|
83
|
+
|
|
84
|
+
await textField.fill('RETURN (1)');
|
|
85
|
+
|
|
86
|
+
// expect the panel to not show up
|
|
87
|
+
await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
|
|
88
|
+
|
|
89
|
+
// unless manually triggered
|
|
90
|
+
await textField.press('Control+ ');
|
|
91
|
+
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible();
|
|
92
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/experimental-ct-react';
|
|
2
|
+
import { CypherEditor } from '../CypherEditor';
|
|
3
|
+
import { darkThemeConstants, lightThemeConstants } from '../themes';
|
|
4
|
+
import { CypherEditorPage } from './e2eUtils';
|
|
5
|
+
|
|
6
|
+
test('light theme highlighting', async ({ page, mount }) => {
|
|
7
|
+
const editorPage = new CypherEditorPage(page);
|
|
8
|
+
const query = `
|
|
9
|
+
MATCH (variable :Label)-[:REL_TYPE]->()
|
|
10
|
+
WHERE variable.property = "String"
|
|
11
|
+
OR namespaced.function() = false
|
|
12
|
+
// comment
|
|
13
|
+
OR $parameter > 1234
|
|
14
|
+
RETURN variable;`;
|
|
15
|
+
|
|
16
|
+
await mount(<CypherEditor value={query} theme="light" />);
|
|
17
|
+
|
|
18
|
+
const keywordcolors = await Promise.all(
|
|
19
|
+
['MATCH', 'WHERE', 'RETURN'].map((kw) =>
|
|
20
|
+
editorPage.getHexColorOfLocator(page.getByText(kw)),
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
keywordcolors.every((kw) =>
|
|
24
|
+
expect(kw).toEqual(
|
|
25
|
+
lightThemeConstants.highlightStyles.keyword.toLowerCase(),
|
|
26
|
+
),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const labelReltype = await Promise.all(
|
|
30
|
+
['Label', 'REL_TYPE'].map((kw) =>
|
|
31
|
+
editorPage.getHexColorOfLocator(page.getByText(kw)),
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
labelReltype.every((kw) =>
|
|
35
|
+
expect(kw).toEqual(lightThemeConstants.highlightStyles.label.toLowerCase()),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
expect(
|
|
39
|
+
await editorPage.getHexColorOfLocator(page.getByText('parameter')),
|
|
40
|
+
).toEqual(lightThemeConstants.highlightStyles.paramValue.toLowerCase());
|
|
41
|
+
|
|
42
|
+
expect(
|
|
43
|
+
await editorPage.getHexColorOfLocator(page.getByText('property')),
|
|
44
|
+
).toEqual(lightThemeConstants.highlightStyles.property.toLowerCase());
|
|
45
|
+
|
|
46
|
+
expect(
|
|
47
|
+
await editorPage.getHexColorOfLocator(page.getByText('false')),
|
|
48
|
+
).toEqual(lightThemeConstants.highlightStyles.booleanLiteral.toLowerCase());
|
|
49
|
+
|
|
50
|
+
expect(
|
|
51
|
+
await editorPage.getHexColorOfLocator(page.getByText('String')),
|
|
52
|
+
).toEqual(lightThemeConstants.highlightStyles.stringLiteral.toLowerCase());
|
|
53
|
+
|
|
54
|
+
expect(
|
|
55
|
+
await editorPage.getHexColorOfLocator(page.getByText('comment')),
|
|
56
|
+
).toEqual(lightThemeConstants.highlightStyles.comment.toLowerCase());
|
|
57
|
+
|
|
58
|
+
expect(
|
|
59
|
+
await editorPage.getHexColorOfLocator(
|
|
60
|
+
page.getByText('1234', { exact: true }),
|
|
61
|
+
),
|
|
62
|
+
).toEqual(lightThemeConstants.highlightStyles.numberLiteral.toLowerCase());
|
|
63
|
+
|
|
64
|
+
expect(await editorPage.editorBackgroundIsUnset()).toEqual(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('dark theme highlighting', async ({ page, mount }) => {
|
|
68
|
+
const editorPage = new CypherEditorPage(page);
|
|
69
|
+
const query = `
|
|
70
|
+
MATCH (variable :Label)-[:REL_TYPE]->()
|
|
71
|
+
WHERE variable.property = "String"
|
|
72
|
+
OR namespaced.function() = false
|
|
73
|
+
// comment
|
|
74
|
+
OR $parameter > 1234
|
|
75
|
+
RETURN variable;`;
|
|
76
|
+
|
|
77
|
+
await mount(<CypherEditor value={query} theme="dark" />);
|
|
78
|
+
|
|
79
|
+
const keywordcolors = await Promise.all(
|
|
80
|
+
['MATCH', 'WHERE', 'RETURN'].map((kw) =>
|
|
81
|
+
editorPage.getHexColorOfLocator(page.getByText(kw)),
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
keywordcolors.every((kw) =>
|
|
85
|
+
expect(kw).toEqual(
|
|
86
|
+
darkThemeConstants.highlightStyles.keyword.toLowerCase(),
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const labelReltype = await Promise.all(
|
|
91
|
+
['Label', 'REL_TYPE'].map((kw) =>
|
|
92
|
+
editorPage.getHexColorOfLocator(page.getByText(kw)),
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
labelReltype.every((kw) =>
|
|
96
|
+
expect(kw).toEqual(darkThemeConstants.highlightStyles.label.toLowerCase()),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(
|
|
100
|
+
await editorPage.getHexColorOfLocator(page.getByText('parameter')),
|
|
101
|
+
).toEqual(darkThemeConstants.highlightStyles.paramValue.toLowerCase());
|
|
102
|
+
|
|
103
|
+
expect(
|
|
104
|
+
await editorPage.getHexColorOfLocator(page.getByText('property')),
|
|
105
|
+
).toEqual(darkThemeConstants.highlightStyles.property.toLowerCase());
|
|
106
|
+
|
|
107
|
+
expect(
|
|
108
|
+
await editorPage.getHexColorOfLocator(page.getByText('false')),
|
|
109
|
+
).toEqual(darkThemeConstants.highlightStyles.booleanLiteral.toLowerCase());
|
|
110
|
+
|
|
111
|
+
expect(
|
|
112
|
+
await editorPage.getHexColorOfLocator(page.getByText('String')),
|
|
113
|
+
).toEqual(darkThemeConstants.highlightStyles.stringLiteral.toLowerCase());
|
|
114
|
+
|
|
115
|
+
expect(
|
|
116
|
+
await editorPage.getHexColorOfLocator(page.getByText('comment')),
|
|
117
|
+
).toEqual(darkThemeConstants.highlightStyles.comment.toLowerCase());
|
|
118
|
+
|
|
119
|
+
expect(
|
|
120
|
+
await editorPage.getHexColorOfLocator(
|
|
121
|
+
page.getByText('1234', { exact: true }),
|
|
122
|
+
),
|
|
123
|
+
).toEqual(darkThemeConstants.highlightStyles.numberLiteral.toLowerCase());
|
|
124
|
+
|
|
125
|
+
expect(await editorPage.editorBackgroundIsUnset()).toEqual(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('can live switch theme ', async ({ page, mount }) => {
|
|
129
|
+
const editorPage = new CypherEditorPage(page);
|
|
130
|
+
const component = await mount(<CypherEditor theme="light" value="RETURN" />);
|
|
131
|
+
|
|
132
|
+
expect(
|
|
133
|
+
await editorPage.getHexColorOfLocator(
|
|
134
|
+
page.getByText('RETURN', { exact: true }),
|
|
135
|
+
),
|
|
136
|
+
).toEqual(lightThemeConstants.highlightStyles.keyword.toLowerCase());
|
|
137
|
+
|
|
138
|
+
await component.update(<CypherEditor theme="dark" value="RETURN" />);
|
|
139
|
+
|
|
140
|
+
expect(
|
|
141
|
+
await editorPage.getHexColorOfLocator(
|
|
142
|
+
page.getByText('RETURN', { exact: true }),
|
|
143
|
+
),
|
|
144
|
+
).toEqual(darkThemeConstants.highlightStyles.keyword.toLowerCase());
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('respects prop to allow overriding bkg color', async ({ page, mount }) => {
|
|
148
|
+
const editorPage = new CypherEditorPage(page);
|
|
149
|
+
await mount(
|
|
150
|
+
<CypherEditor theme="light" value="text" overrideThemeBackgroundColor />,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(await editorPage.editorBackgroundIsUnset()).toEqual(true);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('highlights multiline string literal correctly', async ({
|
|
157
|
+
page,
|
|
158
|
+
mount,
|
|
159
|
+
}) => {
|
|
160
|
+
const editorPage = new CypherEditorPage(page);
|
|
161
|
+
const query = `
|
|
162
|
+
RETURN "
|
|
163
|
+
multilinestring";`;
|
|
164
|
+
|
|
165
|
+
await mount(<CypherEditor theme="light" value={query} />);
|
|
166
|
+
|
|
167
|
+
expect(
|
|
168
|
+
await editorPage.getHexColorOfLocator(page.getByText('multilinestring')),
|
|
169
|
+
).toEqual(lightThemeConstants.highlightStyles.stringLiteral.toLowerCase());
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('highlights multiline label correctly', async ({ page, mount }) => {
|
|
173
|
+
const editorPage = new CypherEditorPage(page);
|
|
174
|
+
const query = `
|
|
175
|
+
MATCH (v:\`
|
|
176
|
+
|
|
177
|
+
Label\`)
|
|
178
|
+
`;
|
|
179
|
+
|
|
180
|
+
await mount(<CypherEditor theme="light" value={query} />);
|
|
181
|
+
|
|
182
|
+
expect(
|
|
183
|
+
await editorPage.getHexColorOfLocator(page.getByText('Label')),
|
|
184
|
+
).toEqual(lightThemeConstants.highlightStyles.label.toLowerCase());
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('highlights multiline comment correctly', async ({ page, mount }) => {
|
|
188
|
+
const editorPage = new CypherEditorPage(page);
|
|
189
|
+
const query = `
|
|
190
|
+
/*
|
|
191
|
+
|
|
192
|
+
comment
|
|
193
|
+
*/";`;
|
|
194
|
+
|
|
195
|
+
await mount(<CypherEditor theme="light" value={query} />);
|
|
196
|
+
|
|
197
|
+
expect(
|
|
198
|
+
await editorPage.getHexColorOfLocator(page.getByText('comment')),
|
|
199
|
+
).toEqual(lightThemeConstants.highlightStyles.comment.toLowerCase());
|
|
200
|
+
});
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { testData } from '@neo4j-cypher/language-support';
|
|
2
|
+
import { expect, test } from '@playwright/experimental-ct-react';
|
|
3
|
+
import { CypherEditor } from '../CypherEditor';
|
|
4
|
+
import { CypherEditorPage } from './e2eUtils';
|
|
5
|
+
|
|
6
|
+
test.use({ viewport: { width: 1000, height: 500 } });
|
|
7
|
+
test('Prop lint set to false disables syntax validation', async ({
|
|
8
|
+
page,
|
|
9
|
+
mount,
|
|
10
|
+
}) => {
|
|
11
|
+
const query = 'METCH (n) RETURN n';
|
|
12
|
+
|
|
13
|
+
await mount(<CypherEditor value={query} lint={false} />);
|
|
14
|
+
|
|
15
|
+
await expect(page.locator('.cm-lintRange-error').last()).not.toBeVisible({
|
|
16
|
+
timeout: 10000,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('Can turn linting back on', async ({ page, mount }) => {
|
|
21
|
+
const editorPage = new CypherEditorPage(page);
|
|
22
|
+
const query = 'METCH (n) RETURN n';
|
|
23
|
+
|
|
24
|
+
const component = await mount(<CypherEditor value={query} lint={false} />);
|
|
25
|
+
|
|
26
|
+
await expect(page.locator('.cm-lintRange-error').last()).not.toBeVisible({
|
|
27
|
+
timeout: 10000,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
await component.update(<CypherEditor value={query} lint={true} />);
|
|
31
|
+
|
|
32
|
+
await editorPage.getEditor().fill('METCH (n) RETURN n');
|
|
33
|
+
|
|
34
|
+
await editorPage.checkErrorMessage(
|
|
35
|
+
'METCH',
|
|
36
|
+
`Invalid input 'METCH': expected 'FOREACH', 'ALTER', 'ORDER BY', 'CALL', 'USING PERIODIC COMMIT', 'CREATE', 'LOAD CSV', 'START DATABASE', 'STOP DATABASE', 'DEALLOCATE', 'DELETE', 'DENY', 'DETACH', 'DROP', 'DRYRUN', 'FINISH', 'GRANT', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REALLOCATE', 'REMOVE', 'RENAME', 'RETURN', 'REVOKE', 'ENABLE SERVER', 'SET', 'SHOW', 'SKIP', 'TERMINATE', 'UNWIND', 'USE' or 'WITH'`,
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('Syntactic errors are surfaced', async ({ page, mount }) => {
|
|
41
|
+
const editorPage = new CypherEditorPage(page);
|
|
42
|
+
const query = 'METCH (n) RETURN n';
|
|
43
|
+
|
|
44
|
+
await mount(<CypherEditor value={query} />);
|
|
45
|
+
|
|
46
|
+
await editorPage.checkErrorMessage(
|
|
47
|
+
'METCH',
|
|
48
|
+
`Invalid input 'METCH': expected 'FOREACH', 'ALTER', 'ORDER BY', 'CALL', 'USING PERIODIC COMMIT', 'CREATE', 'LOAD CSV', 'START DATABASE', 'STOP DATABASE', 'DEALLOCATE', 'DELETE', 'DENY', 'DETACH', 'DROP', 'DRYRUN', 'FINISH', 'GRANT', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REALLOCATE', 'REMOVE', 'RENAME', 'RETURN', 'REVOKE', 'ENABLE SERVER', 'SET', 'SHOW', 'SKIP', 'TERMINATE', 'UNWIND', 'USE' or 'WITH'`,
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('Does not trigger syntax errors for backticked parameters in parameter creation', async ({
|
|
53
|
+
page,
|
|
54
|
+
mount,
|
|
55
|
+
}) => {
|
|
56
|
+
const editorPage = new CypherEditorPage(page);
|
|
57
|
+
|
|
58
|
+
const query = ':param x => "abc"';
|
|
59
|
+
await mount(<CypherEditor value={query} />);
|
|
60
|
+
|
|
61
|
+
await editorPage.checkNoNotificationMessage('error');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('Errors for undefined labels are surfaced', async ({ page, mount }) => {
|
|
65
|
+
const editorPage = new CypherEditorPage(page);
|
|
66
|
+
const query = 'MATCH (n: Person) RETURN n';
|
|
67
|
+
|
|
68
|
+
await mount(
|
|
69
|
+
<CypherEditor
|
|
70
|
+
value={query}
|
|
71
|
+
schema={{ labels: ['Movie'], relationshipTypes: [] }}
|
|
72
|
+
/>,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
await editorPage.checkWarningMessage(
|
|
76
|
+
'Person',
|
|
77
|
+
"Label Person is not present in the database. Make sure you didn't misspell it or that it is available when you run this statement in your application",
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('Errors for multiline undefined labels are highlighted correctly', async ({
|
|
82
|
+
page,
|
|
83
|
+
mount,
|
|
84
|
+
}) => {
|
|
85
|
+
const editorPage = new CypherEditorPage(page);
|
|
86
|
+
const query = `MATCH (n:\`Foo
|
|
87
|
+
Bar\`) RETURN n`;
|
|
88
|
+
const expectedMsg = `Label \`Foo
|
|
89
|
+
Bar\` is not present in the database. Make sure you didn't misspell it or that it is available when you run this statement in your application`;
|
|
90
|
+
|
|
91
|
+
await mount(
|
|
92
|
+
<CypherEditor
|
|
93
|
+
value={query}
|
|
94
|
+
schema={{ labels: ['Movie'], relationshipTypes: [] }}
|
|
95
|
+
/>,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
await editorPage.checkWarningMessage('`Foo', expectedMsg);
|
|
99
|
+
await editorPage.checkWarningMessage('Bar`', expectedMsg);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('Semantic errors work in firefox', async ({
|
|
103
|
+
browserName,
|
|
104
|
+
page,
|
|
105
|
+
mount,
|
|
106
|
+
}) => {
|
|
107
|
+
test.skip(browserName !== 'firefox');
|
|
108
|
+
const editorPage = new CypherEditorPage(page);
|
|
109
|
+
const query = 'MATCH (n:OperationalPoint)--(m:OperationalPoint) RETURN s,m,n';
|
|
110
|
+
|
|
111
|
+
await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
|
|
112
|
+
|
|
113
|
+
await editorPage.checkErrorMessage('s,m,n', 'Variable `s` not defined');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('Semantic errors are surfaced when there are no syntactic errors', async ({
|
|
117
|
+
page,
|
|
118
|
+
mount,
|
|
119
|
+
}) => {
|
|
120
|
+
const editorPage = new CypherEditorPage(page);
|
|
121
|
+
const query = 'MATCH (n) RETURN m';
|
|
122
|
+
|
|
123
|
+
await mount(<CypherEditor value={query} />);
|
|
124
|
+
|
|
125
|
+
await editorPage.checkErrorMessage('m', 'Variable `m` not defined');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('Semantic errors are correctly accumulated', async ({ page, mount }) => {
|
|
129
|
+
const editorPage = new CypherEditorPage(page);
|
|
130
|
+
const query = 'CALL { MATCH (n) } IN TRANSACTIONS OF -1 ROWS';
|
|
131
|
+
|
|
132
|
+
await mount(<CypherEditor value={query} />);
|
|
133
|
+
|
|
134
|
+
await editorPage.checkErrorMessage(
|
|
135
|
+
'MATCH (n)',
|
|
136
|
+
'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
await editorPage.checkErrorMessage(
|
|
140
|
+
'-1',
|
|
141
|
+
"Invalid input. '-1' is not a valid value. Must be a positive integer",
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('Multiline errors are correctly placed', async ({ page, mount }) => {
|
|
146
|
+
const editorPage = new CypherEditorPage(page);
|
|
147
|
+
const query = `CALL {
|
|
148
|
+
MATCH (n)
|
|
149
|
+
} IN TRANSACTIONS
|
|
150
|
+
OF -1 ROWS`;
|
|
151
|
+
|
|
152
|
+
await mount(<CypherEditor value={query} />);
|
|
153
|
+
|
|
154
|
+
await editorPage.checkErrorMessage(
|
|
155
|
+
'MATCH (n)',
|
|
156
|
+
'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD)',
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
await editorPage.checkErrorMessage(
|
|
160
|
+
'-1',
|
|
161
|
+
"Invalid input. '-1' is not a valid value. Must be a positive integer",
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('Validation errors are correctly overlapped', async ({ page, mount }) => {
|
|
166
|
+
const editorPage = new CypherEditorPage(page);
|
|
167
|
+
const query = `CALL { MATCH (n)
|
|
168
|
+
RETURN n
|
|
169
|
+
} IN TRANSACTIONS
|
|
170
|
+
OF -1 ROWS`;
|
|
171
|
+
|
|
172
|
+
await mount(<CypherEditor value={query} />);
|
|
173
|
+
|
|
174
|
+
await editorPage.checkErrorMessage(
|
|
175
|
+
'-1',
|
|
176
|
+
'Query cannot conclude with CALL (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
await editorPage.checkErrorMessage(
|
|
180
|
+
'-1',
|
|
181
|
+
"Invalid input. '-1' is not a valid value. Must be a positive integer",
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('Strikethroughs are shown for deprecated functions', async ({
|
|
186
|
+
page,
|
|
187
|
+
mount,
|
|
188
|
+
}) => {
|
|
189
|
+
const editorPage = new CypherEditorPage(page);
|
|
190
|
+
const query = `RETURN id()`;
|
|
191
|
+
|
|
192
|
+
await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
|
|
193
|
+
await expect(
|
|
194
|
+
editorPage.page.locator('.cm-deprecated-element').last(),
|
|
195
|
+
).toBeVisible({ timeout: 10000 });
|
|
196
|
+
await editorPage.checkWarningMessage('id', 'Function id is deprecated.');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('Strikethroughs are shown for deprecated procedures', async ({
|
|
200
|
+
page,
|
|
201
|
+
mount,
|
|
202
|
+
}) => {
|
|
203
|
+
const editorPage = new CypherEditorPage(page);
|
|
204
|
+
const query = `CALL apoc.create.uuids()`;
|
|
205
|
+
|
|
206
|
+
await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
|
|
207
|
+
await expect(
|
|
208
|
+
editorPage.page.locator('.cm-deprecated-element').last(),
|
|
209
|
+
).toBeVisible({ timeout: 10000 });
|
|
210
|
+
|
|
211
|
+
await editorPage.checkWarningMessage(
|
|
212
|
+
'apoc.create.uuids',
|
|
213
|
+
'Procedure apoc.create.uuids is deprecated.',
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('Syntax validation depends on the Cypher version', async ({
|
|
218
|
+
page,
|
|
219
|
+
mount,
|
|
220
|
+
}) => {
|
|
221
|
+
await mount(
|
|
222
|
+
<CypherEditor
|
|
223
|
+
schema={testData.mockSchema}
|
|
224
|
+
featureFlags={{ cypher25: true }}
|
|
225
|
+
/>,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const editorPage = new CypherEditorPage(page);
|
|
229
|
+
const textField = page.getByRole('textbox');
|
|
230
|
+
await textField.fill('CYPHER 5 CALL apoc.create.uuids(5)');
|
|
231
|
+
|
|
232
|
+
await editorPage.checkWarningMessage(
|
|
233
|
+
'apoc.create.uuids',
|
|
234
|
+
'Procedure apoc.create.uuids is deprecated.',
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
await textField.fill('CYPHER 25 CALL apoc.create.uuids(5)');
|
|
238
|
+
|
|
239
|
+
await editorPage.checkErrorMessage(
|
|
240
|
+
'apoc.create.uuids',
|
|
241
|
+
'Procedure apoc.create.uuids is not present in the database.',
|
|
242
|
+
);
|
|
243
|
+
});
|