@neo4j-cypher/react-codemirror 2.0.0-next.31 → 2.0.0-next.33

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 (35) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/src/CypherEditor.js +1 -1
  3. package/dist/src/CypherEditor.js.map +1 -1
  4. package/dist/src/CypherEditor.test.js.map +1 -1
  5. package/dist/src/e2e_tests/autoCompletion.spec.js +6 -3
  6. package/dist/src/e2e_tests/autoCompletion.spec.js.map +1 -1
  7. package/dist/src/e2e_tests/debounce.spec.js +1 -1
  8. package/dist/src/e2e_tests/debounce.spec.js.map +1 -1
  9. package/dist/src/e2e_tests/signatureHelp.spec.js +0 -1
  10. package/dist/src/e2e_tests/signatureHelp.spec.js.map +1 -1
  11. package/dist/src/e2e_tests/snippets.spec.js +0 -48
  12. package/dist/src/e2e_tests/snippets.spec.js.map +1 -1
  13. package/dist/src/e2e_tests/syntaxValidation.spec.js +10 -4
  14. package/dist/src/e2e_tests/syntaxValidation.spec.js.map +1 -1
  15. package/dist/src/lang-cypher/autocomplete.js +1 -1
  16. package/dist/src/lang-cypher/autocomplete.js.map +1 -1
  17. package/dist/src/lang-cypher/createCypherTheme.js.map +1 -1
  18. package/dist/src/lang-cypher/lintWorker.mjs +201 -202
  19. package/dist/src/lang-cypher/parser-adapter.js.map +1 -1
  20. package/dist/src/lang-cypher/utils.js +1 -1
  21. package/dist/src/lang-cypher/utils.js.map +1 -1
  22. package/dist/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +24 -24
  24. package/src/CypherEditor.test.tsx +18 -19
  25. package/src/CypherEditor.tsx +1 -1
  26. package/src/e2e_tests/autoCompletion.spec.tsx +13 -7
  27. package/src/e2e_tests/debounce.spec.tsx +32 -36
  28. package/src/e2e_tests/signatureHelp.spec.tsx +0 -1
  29. package/src/e2e_tests/snippets.spec.tsx +0 -73
  30. package/src/e2e_tests/syntaxValidation.spec.tsx +21 -13
  31. package/src/lang-cypher/autocomplete.ts +1 -1
  32. package/src/lang-cypher/createCypherTheme.ts +7 -21
  33. package/src/lang-cypher/lintWorker.mjs +201 -202
  34. package/src/lang-cypher/parser-adapter.ts +4 -1
  35. package/src/lang-cypher/utils.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,22 @@
1
1
  {
2
2
  "name": "@neo4j-cypher/react-codemirror",
3
+ "version": "2.0.0-next.33",
4
+ "keywords": [
5
+ "codemirror",
6
+ "codemirror 6",
7
+ "cypher",
8
+ "editor",
9
+ "neo4j",
10
+ "react"
11
+ ],
12
+ "bugs": {
13
+ "url": "https://github.com/neo4j/cypher-language-support/issues"
14
+ },
3
15
  "license": "Apache-2.0",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git://github.com/neo4j/cypher-language-support.git"
19
+ },
4
20
  "files": [
5
21
  "dist",
6
22
  "src",
@@ -9,30 +25,10 @@
9
25
  "LICENSE.md",
10
26
  "CHANGELOG.md"
11
27
  ],
12
- "keywords": [
13
- "neo4j",
14
- "cypher",
15
- "react",
16
- "editor",
17
- "codemirror",
18
- "codemirror 6"
19
- ],
20
- "version": "2.0.0-next.31",
21
- "main": "./dist/src/index.js",
22
- "types": "./dist/src/index.d.ts",
23
28
  "type": "module",
24
29
  "sideEffects": false,
25
- "repository": {
26
- "type": "git",
27
- "url": "git://github.com/neo4j/cypher-language-support.git"
28
- },
29
- "bugs": {
30
- "url": "https://github.com/neo4j/cypher-language-support/issues"
31
- },
32
- "engineStrict": true,
33
- "engines": {
34
- "node": ">=24.11.1"
35
- },
30
+ "main": "./dist/src/index.js",
31
+ "types": "./dist/src/index.d.ts",
36
32
  "dependencies": {
37
33
  "@codemirror/autocomplete": "^6.18.6",
38
34
  "@codemirror/commands": "^6.8.1",
@@ -51,8 +47,8 @@
51
47
  "style-mod": "^4.1.2",
52
48
  "vscode-languageserver-types": "^3.17.3",
53
49
  "workerpool": "^9.3.3",
54
- "@neo4j-cypher/language-support": "2.0.0-next.28",
55
- "@neo4j-cypher/lint-worker": "1.10.1-next.5"
50
+ "@neo4j-cypher/language-support": "2.0.0-next.30",
51
+ "@neo4j-cypher/lint-worker": "1.10.1-next.7"
56
52
  },
57
53
  "devDependencies": {
58
54
  "@neo4j-ndl/base": "^3.2.10",
@@ -73,6 +69,10 @@
73
69
  "peerDependencies": {
74
70
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
75
71
  },
72
+ "engines": {
73
+ "node": ">=24.11.1"
74
+ },
75
+ "engineStrict": true,
76
76
  "scripts": {
77
77
  "dev": "tsc --watch",
78
78
  "build": "pnpm copy-lint-worker && tsc --declaration --outDir dist/",
@@ -124,27 +124,24 @@ test.fails('new props.value should cancel onChange', async () => {
124
124
  });
125
125
 
126
126
  // value updates from outside onExecute are overwritten by pending updates
127
- test.fails(
128
- 'new props.value set to same value should cancel onChange',
129
- async () => {
130
- // 1. value is set initially
131
- value = 'same value';
132
- rerender();
127
+ test.fails('new props.value set to same value should cancel onChange', async () => {
128
+ // 1. value is set initially
129
+ value = 'same value';
130
+ rerender();
133
131
 
134
- // 2. value is updated internally
135
- ref.current.setValueAndFocus('update');
132
+ // 2. value is updated internally
133
+ ref.current.setValueAndFocus('update');
136
134
 
137
- // 3. editor is rerendered with a new value while a value update is still pending
138
- value = 'same value';
139
- rerender();
135
+ // 3. editor is rerendered with a new value while a value update is still pending
136
+ value = 'same value';
137
+ rerender();
140
138
 
141
- await debounce();
139
+ await debounce();
142
140
 
143
- // expect(onChange).not.toHaveBeenCalled();
144
- expect(getEditorValue()).toBe('same value');
145
- expect(value).toBe('same value');
146
- },
147
- );
141
+ // expect(onChange).not.toHaveBeenCalled();
142
+ expect(getEditorValue()).toBe('same value');
143
+ expect(value).toBe('same value');
144
+ });
148
145
 
149
146
  test('rerender should not cancel onChange', async () => {
150
147
  // 1. value is updated internally
@@ -200,5 +197,7 @@ test('rerender with prior external update should not cancel onChange', async ()
200
197
  });
201
198
 
202
199
  test('setValueAndFocus should handle CRLF newline characters', () => {
203
- expect(() => ref.current.setValueAndFocus('new value\r\nnew line')).not.toThrow();
204
- });
200
+ expect(() =>
201
+ ref.current.setValueAndFocus('new value\r\nnew line'),
202
+ ).not.toThrow();
203
+ });
@@ -190,7 +190,7 @@ const format = (view: EditorView): void => {
190
190
  },
191
191
  selection: { anchor: newCursorPos },
192
192
  });
193
- } catch (error) {
193
+ } catch {
194
194
  // Formatting failed, likely because of a syntax error
195
195
  }
196
196
  };
@@ -33,7 +33,10 @@ RETURN n;`}
33
33
  await textField.press('Control+ ');
34
34
 
35
35
  await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible();
36
- await page.locator('.cm-tooltip-autocomplete').getByText('WHERE', {exact: true}).click();
36
+ await page
37
+ .locator('.cm-tooltip-autocomplete')
38
+ .getByText('WHERE', { exact: true })
39
+ .click();
37
40
 
38
41
  await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
39
42
 
@@ -544,13 +547,16 @@ test('completions depend on the Cypher version', async ({ page, mount }) => {
544
547
  ).toBeVisible();
545
548
  });
546
549
 
547
- test('does not complete properties for non node / relationship variables', async ({ page, mount }) => {
550
+ test('does not complete properties for non node / relationship variables', async ({
551
+ page,
552
+ mount,
553
+ }) => {
548
554
  await mount(
549
555
  <CypherEditor
550
- schema={{
551
- propertyKeys: ["nodeProperty"]
556
+ schema={{
557
+ propertyKeys: ['nodeProperty'],
552
558
  }}
553
- />
559
+ />,
554
560
  );
555
561
 
556
562
  const textField = page.getByRole('textbox');
@@ -562,10 +568,10 @@ test('does not complete properties for non node / relationship variables', async
562
568
 
563
569
  await textField.fill('WITH 1 AS x RETURN x.');
564
570
  // This could be flaky if the semantic analysis takes too long
565
- await page.waitForTimeout(500)
571
+ await page.waitForTimeout(500);
566
572
  await textField.press('Escape');
567
573
  await textField.press('Control+ ');
568
574
  await expect(
569
- page.locator('.cm-tooltip-autocomplete').getByText('nodeProperty')
575
+ page.locator('.cm-tooltip-autocomplete').getByText('nodeProperty'),
570
576
  ).not.toBeVisible();
571
577
  });
@@ -28,47 +28,43 @@ test.fail(
28
28
  );
29
29
 
30
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
- };
31
+ test.fixme('onExecute updates should override debounce updates', async ({
32
+ mount,
33
+ page,
34
+ }) => {
35
+ const editorPage = new CypherEditorPage(page);
36
+ let value = '';
54
37
 
55
- const component = await mount(
38
+ const onExecute = () => {
39
+ value = '';
40
+ void component.update(
56
41
  <CypherEditor value={value} onChange={onChange} onExecute={onExecute} />,
57
42
  );
43
+ };
58
44
 
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');
45
+ const onChange = (val: string) => {
46
+ value = val;
47
+ void component.update(
48
+ <CypherEditor value={val} onChange={onChange} onExecute={onExecute} />,
49
+ );
50
+ };
63
51
 
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
- );
52
+ const component = await mount(
53
+ <CypherEditor value={value} onChange={onChange} onExecute={onExecute} />,
54
+ );
55
+
56
+ await editorPage.getEditor().pressSequentially('RETURN 1');
57
+ await editorPage.getEditor().press('Enter');
58
+ await page.waitForTimeout(DEBOUNCE_TIME_WITH_MARGIN);
59
+ await expect(component).not.toContainText('RETURN 1');
60
+
61
+ await editorPage.getEditor().pressSequentially('RETURN 1');
62
+ await editorPage.getEditor().pressSequentially('');
63
+ await editorPage.getEditor().pressSequentially('RETURN 1');
64
+ await editorPage.getEditor().press('Enter');
65
+ await page.waitForTimeout(DEBOUNCE_TIME_WITH_MARGIN);
66
+ await expect(component).not.toContainText('RETURN 1');
67
+ });
72
68
 
73
69
  test('onExecute should fire after debounced updates', async ({
74
70
  mount,
@@ -263,7 +263,6 @@ test('Signature help only shows the description past the last argument', async (
263
263
  autofocus={true}
264
264
  />,
265
265
  );
266
- 1;
267
266
 
268
267
  const tooltip = page.locator('.cm-signature-help-panel');
269
268
 
@@ -1,79 +1,6 @@
1
1
  import { expect, test } from '@playwright/experimental-ct-react';
2
2
  import { CypherEditor } from '../CypherEditor';
3
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
4
  test('does not automatically open completion panel for expressions after snippet trigger char', async ({
78
5
  page,
79
6
  mount,
@@ -182,9 +182,9 @@ test('Validation errors are correctly overlapped', async ({ page, mount }) => {
182
182
  );
183
183
  });
184
184
 
185
- test('Syntax highlighting works as expected with multiple separate linting messages', async ( {
185
+ test('Syntax highlighting works as expected with multiple separate linting messages', async ({
186
186
  page,
187
- mount
187
+ mount,
188
188
  }) => {
189
189
  const editorPage = new CypherEditorPage(page);
190
190
  const query = `MATCH (n)--(m) CALL (n) {RETURN id(n) AS b} RETURN apoc.create.uuid(), a`;
@@ -192,12 +192,20 @@ test('Syntax highlighting works as expected with multiple separate linting messa
192
192
  await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
193
193
  await expect(
194
194
  editorPage.page.locator('.cm-deprecated-element').last(),
195
- ).toBeVisible({ timeout: 10000 });
195
+ ).toBeVisible({
196
+ timeout: 10000,
197
+ });
196
198
  await editorPage.checkWarningMessage('id', 'Function id is deprecated.');
197
- await editorPage.checkWarningMessage('id', `The query used a deprecated function. ('id' has been replaced by 'elementId or consider using an application-generated id')`);
198
- await editorPage.checkWarningMessage('apoc.create.uuid', 'Function apoc.create.uuid is deprecated. Alternative: Neo4j randomUUID() function');
199
+ await editorPage.checkWarningMessage(
200
+ 'id',
201
+ `The query used a deprecated function. ('id' has been replaced by 'elementId or consider using an application-generated id')`,
202
+ );
203
+ await editorPage.checkWarningMessage(
204
+ 'apoc.create.uuid',
205
+ 'Function apoc.create.uuid is deprecated. Alternative: Neo4j randomUUID() function',
206
+ );
199
207
  await editorPage.checkErrorMessage('a', 'Variable `a` not defined');
200
- })
208
+ });
201
209
 
202
210
  test('Strikethroughs are shown for deprecated functions', async ({
203
211
  page,
@@ -209,7 +217,9 @@ test('Strikethroughs are shown for deprecated functions', async ({
209
217
  await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
210
218
  await expect(
211
219
  editorPage.page.locator('.cm-deprecated-element').last(),
212
- ).toBeVisible({ timeout: 10000 });
220
+ ).toBeVisible({
221
+ timeout: 10000,
222
+ });
213
223
  await editorPage.checkWarningMessage('id', 'Function id is deprecated.');
214
224
  });
215
225
 
@@ -223,7 +233,9 @@ test('Strikethroughs are shown for deprecated procedures', async ({
223
233
  await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
224
234
  await expect(
225
235
  editorPage.page.locator('.cm-deprecated-element').last(),
226
- ).toBeVisible({ timeout: 10000 });
236
+ ).toBeVisible({
237
+ timeout: 10000,
238
+ });
227
239
 
228
240
  await editorPage.checkWarningMessage(
229
241
  'apoc.create.uuids',
@@ -235,11 +247,7 @@ test('Syntax validation depends on the Cypher version', async ({
235
247
  page,
236
248
  mount,
237
249
  }) => {
238
- await mount(
239
- <CypherEditor
240
- schema={testData.mockSchema}
241
- />,
242
- );
250
+ await mount(<CypherEditor schema={testData.mockSchema} />);
243
251
 
244
252
  const editorPage = new CypherEditorPage(page);
245
253
  const textField = page.getByRole('textbox');
@@ -62,7 +62,7 @@ export const cypherAutocomplete: (config: CypherConfig) => CompletionSource =
62
62
  (config) => (context) => {
63
63
  const documentText = context.state.doc.toString();
64
64
  const offset = context.pos;
65
- const triggerCharacters = ['.', ':', '{', '$', ')'];
65
+ const triggerCharacters = ['.', ':', '{', '$', ')', ']', '-', '<'];
66
66
  const lastCharacter = documentText.at(offset - 1);
67
67
  const yieldTriggered = shouldAutoCompleteYield(documentText, offset);
68
68
  const lastWord = context.matchBefore(/\w*/);
@@ -160,24 +160,16 @@ export const createCypherTheme = ({
160
160
  border: 'none',
161
161
  verticalAlign: 'middle',
162
162
  '&[name=next]::before': {
163
- content: `url("data:image/svg+xml;base64,${window.btoa(
164
- downArrowSvg,
165
- )}")`,
163
+ content: `url("data:image/svg+xml;base64,${window.btoa(downArrowSvg)}")`,
166
164
  },
167
165
  '&[name=prev]::before': {
168
- content: `url("data:image/svg+xml;base64,${window.btoa(
169
- upArrowSvg,
170
- )}")`,
166
+ content: `url("data:image/svg+xml;base64,${window.btoa(upArrowSvg)}")`,
171
167
  },
172
168
  '&[name=replace]::before': {
173
- content: `url("data:image/svg+xml;base64,${window.btoa(
174
- replaceSvg,
175
- )}")`,
169
+ content: `url("data:image/svg+xml;base64,${window.btoa(replaceSvg)}")`,
176
170
  },
177
171
  '&[name=replaceAll]::before': {
178
- content: `url("data:image/svg+xml;base64,${window.btoa(
179
- replaceAllSvg,
180
- )}")`,
172
+ content: `url("data:image/svg+xml;base64,${window.btoa(replaceAllSvg)}")`,
181
173
  },
182
174
  width: '20px',
183
175
  height: '20px',
@@ -209,19 +201,13 @@ export const createCypherTheme = ({
209
201
  borderRadius: '4px',
210
202
 
211
203
  '&[name=case]::before': {
212
- content: `url("data:image/svg+xml;base64,${window.btoa(
213
- caseSensitiveSvg,
214
- )}")`,
204
+ content: `url("data:image/svg+xml;base64,${window.btoa(caseSensitiveSvg)}")`,
215
205
  },
216
206
  '&[name=re]::before': {
217
- content: `url("data:image/svg+xml;base64,${window.btoa(
218
- regexSvg,
219
- )}")`,
207
+ content: `url("data:image/svg+xml;base64,${window.btoa(regexSvg)}")`,
220
208
  },
221
209
  '&[name=word]::before': {
222
- content: `url("data:image/svg+xml;base64,${window.btoa(
223
- byWordSvg,
224
- )}")`,
210
+ content: `url("data:image/svg+xml;base64,${window.btoa(byWordSvg)}")`,
225
211
  },
226
212
  '&:hover': {
227
213
  backgroundColor: settings.searchPanel.buttonHoverBackground,