mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
fix(editor): Prevent expression editor focus being lost when user is selecting (#9525)
This commit is contained in:
parent
3be7bb898b
commit
6698179a69
|
@ -20,33 +20,33 @@ describe('Inline expression editor', () => {
|
|||
|
||||
it('should resolve primitive resolvables', () => {
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('1 + 2');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^3$/);
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('"ab"');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{rightArrow}+');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('"cd"');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^abcd$/);
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('true && false');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^false$/);
|
||||
});
|
||||
|
||||
it('should resolve object resolvables', () => {
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.type('{ a: 1 }', { parseSpecialCharSequences: false });
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^\[Object: \{"a": 1\}\]$/);
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.type('{ a: 1 }.a', { parseSpecialCharSequences: false });
|
||||
|
@ -55,13 +55,13 @@ describe('Inline expression editor', () => {
|
|||
|
||||
it('should resolve array resolvables', () => {
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('[1, 2, 3]');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^\[Array: \[1,2,3\]\]$/);
|
||||
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('[1, 2, 3]');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('[0]');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^1$/);
|
||||
|
@ -81,7 +81,7 @@ describe('Inline expression editor', () => {
|
|||
|
||||
it('should resolve $parameter[]', () => {
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
// Resolving $parameter is slow, especially on CI runner
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('$parameter["operation"]');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().should('have.text', 'getAll');
|
||||
|
@ -90,19 +90,19 @@ describe('Inline expression editor', () => {
|
|||
it('should resolve input: $json,$input,$(nodeName)', () => {
|
||||
// Previous nodes have not run, input is empty
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('$json.myStr');
|
||||
WorkflowPage.getters
|
||||
.inlineExpressionEditorOutput()
|
||||
.should('have.text', '[Execute previous nodes for preview]');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('$input.item.json.myStr');
|
||||
WorkflowPage.getters
|
||||
.inlineExpressionEditorOutput()
|
||||
.should('have.text', '[Execute previous nodes for preview]');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.type("$('Schedule Trigger').item.json.myStr");
|
||||
|
@ -118,15 +118,15 @@ describe('Inline expression editor', () => {
|
|||
|
||||
// Previous nodes have run, input can be resolved
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('$json.myStr');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().should('have.text', 'Monday');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('$input.item.json.myStr');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().should('have.text', 'Monday');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.type("$('Schedule Trigger').item.json.myStr");
|
||||
|
|
|
@ -20,16 +20,16 @@ describe('Expression editor modal', () => {
|
|||
|
||||
it('should resolve primitive resolvables', () => {
|
||||
WorkflowPage.getters.expressionModalInput().clear();
|
||||
WorkflowPage.getters.expressionModalInput().type('{{ 1 + 2');
|
||||
WorkflowPage.getters.expressionModalInput().click().type('{{ 1 + 2');
|
||||
WorkflowPage.getters.expressionModalOutput().contains(/^3$/);
|
||||
WorkflowPage.getters.expressionModalInput().clear();
|
||||
|
||||
WorkflowPage.getters.expressionModalInput().type('{{ "ab" + "cd"');
|
||||
WorkflowPage.getters.expressionModalInput().click().type('{{ "ab" + "cd"');
|
||||
WorkflowPage.getters.expressionModalOutput().contains(/^abcd$/);
|
||||
|
||||
WorkflowPage.getters.expressionModalInput().clear();
|
||||
|
||||
WorkflowPage.getters.expressionModalInput().type('{{ true && false');
|
||||
WorkflowPage.getters.expressionModalInput().click().type('{{ true && false');
|
||||
WorkflowPage.getters.expressionModalOutput().contains(/^false$/);
|
||||
});
|
||||
|
||||
|
@ -37,6 +37,7 @@ describe('Expression editor modal', () => {
|
|||
WorkflowPage.getters.expressionModalInput().clear();
|
||||
WorkflowPage.getters
|
||||
.expressionModalInput()
|
||||
.click()
|
||||
.type('{{ { a : 1 }', { parseSpecialCharSequences: false });
|
||||
WorkflowPage.getters.expressionModalOutput().contains(/^\[Object: \{"a": 1\}\]$/);
|
||||
|
||||
|
@ -44,18 +45,19 @@ describe('Expression editor modal', () => {
|
|||
|
||||
WorkflowPage.getters
|
||||
.expressionModalInput()
|
||||
.click()
|
||||
.type('{{ { a : 1 }.a', { parseSpecialCharSequences: false });
|
||||
WorkflowPage.getters.expressionModalOutput().contains(/^1$/);
|
||||
});
|
||||
|
||||
it('should resolve array resolvables', () => {
|
||||
WorkflowPage.getters.expressionModalInput().clear();
|
||||
WorkflowPage.getters.expressionModalInput().type('{{ [1, 2, 3]');
|
||||
WorkflowPage.getters.expressionModalInput().click().type('{{ [1, 2, 3]');
|
||||
WorkflowPage.getters.expressionModalOutput().contains(/^\[Array: \[1,2,3\]\]$/);
|
||||
|
||||
WorkflowPage.getters.expressionModalInput().clear();
|
||||
|
||||
WorkflowPage.getters.expressionModalInput().type('{{ [1, 2, 3][0]');
|
||||
WorkflowPage.getters.expressionModalInput().click().type('{{ [1, 2, 3][0]');
|
||||
WorkflowPage.getters.expressionModalOutput().contains(/^1$/);
|
||||
});
|
||||
});
|
||||
|
@ -73,24 +75,27 @@ describe('Expression editor modal', () => {
|
|||
|
||||
it('should resolve $parameter[]', () => {
|
||||
WorkflowPage.getters.expressionModalInput().clear();
|
||||
WorkflowPage.getters.expressionModalInput().type('{{ $parameter["operation"]');
|
||||
WorkflowPage.getters.expressionModalInput().click().type('{{ $parameter["operation"]');
|
||||
WorkflowPage.getters.expressionModalOutput().should('have.text', 'getAll');
|
||||
});
|
||||
|
||||
it('should resolve input: $json,$input,$(nodeName)', () => {
|
||||
// Previous nodes have not run, input is empty
|
||||
WorkflowPage.getters.expressionModalInput().clear();
|
||||
WorkflowPage.getters.expressionModalInput().type('{{ $json.myStr');
|
||||
WorkflowPage.getters.expressionModalInput().click().type('{{ $json.myStr');
|
||||
WorkflowPage.getters
|
||||
.expressionModalOutput()
|
||||
.should('have.text', '[Execute previous nodes for preview]');
|
||||
WorkflowPage.getters.expressionModalInput().clear();
|
||||
WorkflowPage.getters.expressionModalInput().type('{{ $input.item.json.myStr');
|
||||
WorkflowPage.getters.expressionModalInput().click().type('{{ $input.item.json.myStr');
|
||||
WorkflowPage.getters
|
||||
.expressionModalOutput()
|
||||
.should('have.text', '[Execute previous nodes for preview]');
|
||||
WorkflowPage.getters.expressionModalInput().clear();
|
||||
WorkflowPage.getters.expressionModalInput().type("{{ $('Schedule Trigger').item.json.myStr");
|
||||
WorkflowPage.getters
|
||||
.expressionModalInput()
|
||||
.click()
|
||||
.type("{{ $('Schedule Trigger').item.json.myStr");
|
||||
WorkflowPage.getters
|
||||
.expressionModalOutput()
|
||||
.should('have.text', '[Execute previous nodes for preview]');
|
||||
|
@ -104,13 +109,16 @@ describe('Expression editor modal', () => {
|
|||
|
||||
// Previous nodes have run, input can be resolved
|
||||
WorkflowPage.getters.expressionModalInput().clear();
|
||||
WorkflowPage.getters.expressionModalInput().type('{{ $json.myStr');
|
||||
WorkflowPage.getters.expressionModalInput().click().type('{{ $json.myStr');
|
||||
WorkflowPage.getters.expressionModalOutput().should('have.text', 'Monday');
|
||||
WorkflowPage.getters.expressionModalInput().clear();
|
||||
WorkflowPage.getters.expressionModalInput().type('{{ $input.item.json.myStr');
|
||||
WorkflowPage.getters.expressionModalInput().click().type('{{ $input.item.json.myStr');
|
||||
WorkflowPage.getters.expressionModalOutput().should('have.text', 'Monday');
|
||||
WorkflowPage.getters.expressionModalInput().clear();
|
||||
WorkflowPage.getters.expressionModalInput().type("{{ $('Schedule Trigger').item.json.myStr");
|
||||
WorkflowPage.getters
|
||||
.expressionModalInput()
|
||||
.click()
|
||||
.type("{{ $('Schedule Trigger').item.json.myStr");
|
||||
WorkflowPage.getters.expressionModalOutput().should('have.text', 'Monday');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,15 +26,15 @@
|
|||
"test:dev": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.11.1",
|
||||
"@codemirror/commands": "^6.3.2",
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@codemirror/autocomplete": "^6.16.0",
|
||||
"@codemirror/commands": "^6.5.0",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-python": "^6.1.3",
|
||||
"@codemirror/language": "^6.9.3",
|
||||
"@codemirror/lint": "^6.4.2",
|
||||
"@codemirror/state": "^6.3.3",
|
||||
"@codemirror/view": "^6.22.3",
|
||||
"@codemirror/lang-python": "^6.1.6",
|
||||
"@codemirror/language": "^6.10.1",
|
||||
"@codemirror/lint": "^6.8.0",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@codemirror/view": "^6.26.3",
|
||||
"@fontsource/open-sans": "^4.5.0",
|
||||
"@jsplumb/browser-ui": "^5.13.2",
|
||||
"@jsplumb/common": "^5.13.2",
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import * as workflowHelpers from '@/composables/useWorkflowHelpers';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { waitFor } from '@testing-library/vue';
|
||||
import { waitFor, fireEvent } from '@testing-library/vue';
|
||||
import { setActivePinia } from 'pinia';
|
||||
import { beforeEach, describe, vi } from 'vitest';
|
||||
import { ref, toValue } from 'vue';
|
||||
import { n8nLang } from '../../plugins/codemirror/n8nLang';
|
||||
import { defineComponent, h, ref, toValue } from 'vue';
|
||||
import { n8nLang } from '@/plugins/codemirror/n8nLang';
|
||||
import { useExpressionEditor } from '../useExpressionEditor';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { EditorSelection } from '@codemirror/state';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { renderComponent } from '@/__tests__/render';
|
||||
|
||||
vi.mock('@/composables/useAutocompleteTelemetry', () => ({
|
||||
useAutocompleteTelemetry: vi.fn(),
|
||||
|
@ -31,6 +33,26 @@ describe('useExpressionEditor', () => {
|
|||
return mock;
|
||||
};
|
||||
|
||||
const renderExpressionEditor = async (
|
||||
options: Omit<Parameters<typeof useExpressionEditor>[0], 'editorRef'> = {},
|
||||
) => {
|
||||
let expressionEditor!: ReturnType<typeof useExpressionEditor>;
|
||||
const renderResult = renderComponent(
|
||||
defineComponent({
|
||||
setup() {
|
||||
const root = ref<HTMLElement>();
|
||||
expressionEditor = useExpressionEditor({ ...options, editorRef: root });
|
||||
|
||||
return () => h('div', { ref: root, 'data-test-id': 'editor-root' });
|
||||
},
|
||||
}),
|
||||
{ props: { options } },
|
||||
);
|
||||
expect(renderResult.getByTestId('editor-root')).toBeInTheDocument();
|
||||
await waitFor(() => toValue(expressionEditor.editor));
|
||||
return { renderResult, expressionEditor };
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createTestingPinia());
|
||||
});
|
||||
|
@ -40,27 +62,21 @@ describe('useExpressionEditor', () => {
|
|||
});
|
||||
|
||||
test('should create an editor', async () => {
|
||||
const root = ref<HTMLElement>();
|
||||
const { editor } = useExpressionEditor({
|
||||
editorRef: root,
|
||||
});
|
||||
const { expressionEditor } = await renderExpressionEditor();
|
||||
|
||||
root.value = document.createElement('div');
|
||||
|
||||
await waitFor(() => expect(toValue(editor)).toBeInstanceOf(EditorView));
|
||||
await waitFor(() => expect(toValue(expressionEditor.editor)).toBeInstanceOf(EditorView));
|
||||
});
|
||||
|
||||
test('should calculate segments', async () => {
|
||||
mockResolveExpression().mockReturnValueOnce(15);
|
||||
const root = ref<HTMLElement>();
|
||||
const { segments } = useExpressionEditor({
|
||||
editorRef: root,
|
||||
|
||||
const {
|
||||
expressionEditor: { segments },
|
||||
} = await renderExpressionEditor({
|
||||
editorValue: 'before {{ $json.test.length }} after',
|
||||
extensions: [n8nLang()],
|
||||
});
|
||||
|
||||
root.value = document.createElement('div');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toValue(segments.all)).toEqual([
|
||||
{
|
||||
|
@ -118,134 +134,124 @@ describe('useExpressionEditor', () => {
|
|||
describe('readEditorValue()', () => {
|
||||
test('should return the full editor value (unresolved)', async () => {
|
||||
mockResolveExpression().mockReturnValueOnce(15);
|
||||
const root = ref<HTMLElement>();
|
||||
const { readEditorValue } = useExpressionEditor({
|
||||
editorRef: root,
|
||||
const {
|
||||
expressionEditor: { readEditorValue },
|
||||
} = await renderExpressionEditor({
|
||||
editorValue: 'before {{ $json.test.length }} after',
|
||||
extensions: [n8nLang()],
|
||||
});
|
||||
|
||||
root.value = document.createElement('div');
|
||||
|
||||
await waitFor(() =>
|
||||
expect(readEditorValue()).toEqual('before {{ $json.test.length }} after'),
|
||||
);
|
||||
expect(readEditorValue()).toEqual('before {{ $json.test.length }} after');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCursorPosition()', () => {
|
||||
test('should set cursor position to number correctly', async () => {
|
||||
const root = ref<HTMLElement>();
|
||||
const editorValue = 'text here';
|
||||
const { editor, setCursorPosition } = useExpressionEditor({
|
||||
editorRef: root,
|
||||
const {
|
||||
expressionEditor: { editor, setCursorPosition },
|
||||
} = await renderExpressionEditor({
|
||||
editorValue,
|
||||
extensions: [],
|
||||
});
|
||||
|
||||
root.value = document.createElement('div');
|
||||
await waitFor(() => toValue(editor));
|
||||
setCursorPosition(4);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(toValue(editor)?.state.selection).toEqual(EditorSelection.single(4)),
|
||||
);
|
||||
expect(toValue(editor)?.state.selection).toEqual(EditorSelection.single(4));
|
||||
});
|
||||
|
||||
test('should set cursor position to end correctly', async () => {
|
||||
const root = ref<HTMLElement>();
|
||||
const editorValue = 'text here';
|
||||
const correctPosition = editorValue.length;
|
||||
const { editor, setCursorPosition } = useExpressionEditor({
|
||||
editorRef: root,
|
||||
const {
|
||||
expressionEditor: { editor, setCursorPosition },
|
||||
} = await renderExpressionEditor({
|
||||
editorValue,
|
||||
extensions: [],
|
||||
});
|
||||
|
||||
root.value = document.createElement('div');
|
||||
await waitFor(() => toValue(editor));
|
||||
setCursorPosition('end');
|
||||
|
||||
await waitFor(() =>
|
||||
expect(toValue(editor)?.state.selection).toEqual(EditorSelection.single(correctPosition)),
|
||||
);
|
||||
expect(toValue(editor)?.state.selection).toEqual(EditorSelection.single(correctPosition));
|
||||
});
|
||||
|
||||
test('should set cursor position to last expression correctly', async () => {
|
||||
const root = ref<HTMLElement>();
|
||||
const editorValue = 'text {{ $json.foo }} {{ $json.bar }} here';
|
||||
const correctPosition = editorValue.indexOf('bar') + 'bar'.length;
|
||||
const { editor, setCursorPosition } = useExpressionEditor({
|
||||
editorRef: root,
|
||||
const {
|
||||
expressionEditor: { editor, setCursorPosition },
|
||||
} = await renderExpressionEditor({
|
||||
editorValue,
|
||||
extensions: [n8nLang()],
|
||||
});
|
||||
|
||||
root.value = document.createElement('div');
|
||||
await waitFor(() => toValue(editor));
|
||||
setCursorPosition('lastExpression');
|
||||
|
||||
await waitFor(() =>
|
||||
expect(toValue(editor)?.state.selection).toEqual(EditorSelection.single(correctPosition)),
|
||||
);
|
||||
expect(toValue(editor)?.state.selection).toEqual(EditorSelection.single(correctPosition));
|
||||
});
|
||||
});
|
||||
|
||||
describe('select()', () => {
|
||||
test('should select number range', async () => {
|
||||
const root = ref<HTMLElement>();
|
||||
const editorValue = 'text here';
|
||||
const { editor, select } = useExpressionEditor({
|
||||
editorRef: root,
|
||||
const {
|
||||
expressionEditor: { editor, select },
|
||||
} = await renderExpressionEditor({
|
||||
editorValue,
|
||||
extensions: [],
|
||||
});
|
||||
|
||||
root.value = document.createElement('div');
|
||||
await waitFor(() => toValue(editor));
|
||||
select(4, 7);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(toValue(editor)?.state.selection).toEqual(EditorSelection.single(4, 7)),
|
||||
);
|
||||
expect(toValue(editor)?.state.selection).toEqual(EditorSelection.single(4, 7));
|
||||
});
|
||||
|
||||
test('should select until end', async () => {
|
||||
const root = ref<HTMLElement>();
|
||||
const editorValue = 'text here';
|
||||
const { editor, select } = useExpressionEditor({
|
||||
editorRef: root,
|
||||
const {
|
||||
expressionEditor: { editor, select },
|
||||
} = await renderExpressionEditor({
|
||||
editorValue,
|
||||
extensions: [],
|
||||
});
|
||||
|
||||
root.value = document.createElement('div');
|
||||
await waitFor(() => toValue(editor));
|
||||
select(4, 'end');
|
||||
|
||||
await waitFor(() =>
|
||||
expect(toValue(editor)?.state.selection).toEqual(EditorSelection.single(4, 9)),
|
||||
);
|
||||
expect(toValue(editor)?.state.selection).toEqual(EditorSelection.single(4, 9));
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectAll()', () => {
|
||||
test('should select all', async () => {
|
||||
const root = ref<HTMLElement>();
|
||||
const editorValue = 'text here';
|
||||
const { editor, selectAll } = useExpressionEditor({
|
||||
editorRef: root,
|
||||
const {
|
||||
expressionEditor: { editor, selectAll },
|
||||
} = await renderExpressionEditor({
|
||||
editorValue,
|
||||
extensions: [],
|
||||
});
|
||||
|
||||
root.value = document.createElement('div');
|
||||
await waitFor(() => toValue(editor));
|
||||
selectAll();
|
||||
expect(toValue(editor)?.state.selection).toEqual(EditorSelection.single(0, 9));
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(toValue(editor)?.state.selection).toEqual(EditorSelection.single(0, 9)),
|
||||
);
|
||||
describe('blur on click outside', () => {
|
||||
test('should blur when another element is clicked', async () => {
|
||||
const { renderResult, expressionEditor } = await renderExpressionEditor();
|
||||
|
||||
const root = renderResult.getByTestId('editor-root');
|
||||
const input = root.querySelector('.cm-line') as HTMLDivElement;
|
||||
|
||||
await userEvent.click(input);
|
||||
expect(expressionEditor.editor.value?.hasFocus).toBe(true);
|
||||
|
||||
await fireEvent(document, new MouseEvent('click'));
|
||||
expect(expressionEditor.editor.value?.hasFocus).toBe(false);
|
||||
});
|
||||
|
||||
test('should NOT blur when another element is clicked while selecting', async () => {
|
||||
const { renderResult, expressionEditor } = await renderExpressionEditor();
|
||||
|
||||
const root = renderResult.getByTestId('editor-root');
|
||||
const input = root.querySelector('.cm-line') as HTMLDivElement;
|
||||
|
||||
await userEvent.click(input);
|
||||
expect(expressionEditor.editor.value?.hasFocus).toBe(true);
|
||||
await fireEvent(input, new MouseEvent('mousedown', { bubbles: true }));
|
||||
|
||||
await fireEvent(document, new MouseEvent('click'));
|
||||
expect(expressionEditor.editor.value?.hasFocus).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -72,6 +72,7 @@ export const useExpressionEditor = ({
|
|||
const readOnlyExtensions = ref<Compartment>(new Compartment());
|
||||
const telemetryExtensions = ref<Compartment>(new Compartment());
|
||||
const autocompleteStatus = ref<'pending' | 'active' | null>(null);
|
||||
const dragging = ref(false);
|
||||
|
||||
const updateSegments = (): void => {
|
||||
const state = editor.value?.state;
|
||||
|
@ -169,9 +170,10 @@ export const useExpressionEditor = ({
|
|||
}
|
||||
|
||||
function blurOnClickOutside(event: MouseEvent) {
|
||||
if (event.target && !editor.value?.dom.contains(event.target as Node)) {
|
||||
if (event.target && !dragging.value && !editor.value?.dom.contains(event.target as Node)) {
|
||||
blur();
|
||||
}
|
||||
dragging.value = false;
|
||||
}
|
||||
|
||||
watch(editorRef, () => {
|
||||
|
@ -199,6 +201,11 @@ export const useExpressionEditor = ({
|
|||
return null;
|
||||
}),
|
||||
EditorView.contentAttributes.of({ 'data-gramm': 'false' }), // disable grammarly
|
||||
EditorView.domEventHandlers({
|
||||
mousedown: () => {
|
||||
dragging.value = true;
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
|
|
161
pnpm-lock.yaml
161
pnpm-lock.yaml
|
@ -1069,32 +1069,32 @@ importers:
|
|||
packages/editor-ui:
|
||||
dependencies:
|
||||
'@codemirror/autocomplete':
|
||||
specifier: ^6.11.1
|
||||
version: 6.11.1(@codemirror/language@6.9.3)(@codemirror/state@6.3.3)(@codemirror/view@6.22.3)(@lezer/common@1.1.0)
|
||||
specifier: ^6.16.0
|
||||
version: 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.1.0)
|
||||
'@codemirror/commands':
|
||||
specifier: ^6.3.2
|
||||
version: 6.3.2
|
||||
specifier: ^6.5.0
|
||||
version: 6.5.0
|
||||
'@codemirror/lang-javascript':
|
||||
specifier: ^6.2.1
|
||||
version: 6.2.1
|
||||
specifier: ^6.2.2
|
||||
version: 6.2.2
|
||||
'@codemirror/lang-json':
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1
|
||||
'@codemirror/lang-python':
|
||||
specifier: ^6.1.3
|
||||
version: 6.1.3(@codemirror/state@6.3.3)(@codemirror/view@6.22.3)(@lezer/common@1.1.0)
|
||||
specifier: ^6.1.6
|
||||
version: 6.1.6(@codemirror/view@6.26.3)
|
||||
'@codemirror/language':
|
||||
specifier: ^6.9.3
|
||||
version: 6.9.3
|
||||
specifier: ^6.10.1
|
||||
version: 6.10.1
|
||||
'@codemirror/lint':
|
||||
specifier: ^6.4.2
|
||||
version: 6.4.2
|
||||
specifier: ^6.8.0
|
||||
version: 6.8.0
|
||||
'@codemirror/state':
|
||||
specifier: ^6.3.3
|
||||
version: 6.3.3
|
||||
specifier: ^6.4.1
|
||||
version: 6.4.1
|
||||
'@codemirror/view':
|
||||
specifier: ^6.22.3
|
||||
version: 6.22.3
|
||||
specifier: ^6.26.3
|
||||
version: 6.26.3
|
||||
'@fontsource/open-sans':
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.12
|
||||
|
@ -1136,7 +1136,7 @@ importers:
|
|||
version: link:../@n8n/codemirror-lang
|
||||
'@n8n/codemirror-lang-sql':
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2(@codemirror/view@6.22.3)(@lezer/common@1.1.0)
|
||||
version: 1.0.2(@codemirror/view@6.26.3)(@lezer/common@1.1.0)
|
||||
'@n8n/permissions':
|
||||
specifier: workspace:*
|
||||
version: link:../@n8n/permissions
|
||||
|
@ -4670,49 +4670,63 @@ packages:
|
|||
- react
|
||||
dev: true
|
||||
|
||||
/@codemirror/autocomplete@6.11.1(@codemirror/language@6.9.3)(@codemirror/state@6.3.3)(@codemirror/view@6.22.3)(@lezer/common@1.1.0):
|
||||
resolution: {integrity: sha512-L5UInv8Ffd6BPw0P3EF7JLYAMeEbclY7+6Q11REt8vhih8RuLreKtPy/xk8wPxs4EQgYqzI7cdgpiYwWlbS/ow==}
|
||||
/@codemirror/autocomplete@6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.1.0):
|
||||
resolution: {integrity: sha512-P/LeCTtZHRTCU4xQsa89vSKWecYv1ZqwzOd5topheGRf+qtacFgBeIMQi3eL8Kt/BUNvxUWkx+5qP2jlGoARrg==}
|
||||
peerDependencies:
|
||||
'@codemirror/language': ^6.0.0
|
||||
'@codemirror/state': ^6.0.0
|
||||
'@codemirror/view': ^6.0.0
|
||||
'@lezer/common': ^1.0.0
|
||||
dependencies:
|
||||
'@codemirror/language': 6.9.3
|
||||
'@codemirror/state': 6.3.3
|
||||
'@codemirror/view': 6.22.3
|
||||
'@codemirror/language': 6.10.1
|
||||
'@codemirror/state': 6.4.1
|
||||
'@codemirror/view': 6.26.3
|
||||
'@lezer/common': 1.1.0
|
||||
dev: false
|
||||
|
||||
/@codemirror/commands@6.3.2:
|
||||
resolution: {integrity: sha512-tjoi4MCWDNxgIpoLZ7+tezdS9OEB6pkiDKhfKx9ReJ/XBcs2G2RXIu+/FxXBlWsPTsz6C9q/r4gjzrsxpcnqCQ==}
|
||||
/@codemirror/autocomplete@6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1):
|
||||
resolution: {integrity: sha512-P/LeCTtZHRTCU4xQsa89vSKWecYv1ZqwzOd5topheGRf+qtacFgBeIMQi3eL8Kt/BUNvxUWkx+5qP2jlGoARrg==}
|
||||
peerDependencies:
|
||||
'@codemirror/language': ^6.0.0
|
||||
'@codemirror/state': ^6.0.0
|
||||
'@codemirror/view': ^6.0.0
|
||||
'@lezer/common': ^1.0.0
|
||||
dependencies:
|
||||
'@codemirror/language': 6.9.3
|
||||
'@codemirror/state': 6.3.3
|
||||
'@codemirror/view': 6.22.3
|
||||
'@codemirror/language': 6.10.1
|
||||
'@codemirror/state': 6.4.1
|
||||
'@codemirror/view': 6.26.3
|
||||
'@lezer/common': 1.2.1
|
||||
dev: false
|
||||
|
||||
/@codemirror/commands@6.5.0:
|
||||
resolution: {integrity: sha512-rK+sj4fCAN/QfcY9BEzYMgp4wwL/q5aj/VfNSoH1RWPF9XS/dUwBkvlL3hpWgEjOqlpdN1uLC9UkjJ4tmyjJYg==}
|
||||
dependencies:
|
||||
'@codemirror/language': 6.10.1
|
||||
'@codemirror/state': 6.4.1
|
||||
'@codemirror/view': 6.26.3
|
||||
'@lezer/common': 1.1.0
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-css@6.0.1(@codemirror/view@6.22.3)(@lezer/common@1.1.0):
|
||||
/@codemirror/lang-css@6.0.1(@codemirror/view@6.26.3)(@lezer/common@1.1.0):
|
||||
resolution: {integrity: sha512-rlLq1Dt0WJl+2epLQeAsfqIsx3lGu4HStHCJu95nGGuz2P2fNugbU3dQYafr2VRjM4eMC9HviI6jvS98CNtG5w==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.11.1(@codemirror/language@6.9.3)(@codemirror/state@6.3.3)(@codemirror/view@6.22.3)(@lezer/common@1.1.0)
|
||||
'@codemirror/language': 6.9.3
|
||||
'@codemirror/state': 6.3.3
|
||||
'@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.1.0)
|
||||
'@codemirror/language': 6.10.1
|
||||
'@codemirror/state': 6.4.1
|
||||
'@lezer/css': 1.1.1
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/view'
|
||||
- '@lezer/common'
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-javascript@6.2.1:
|
||||
resolution: {integrity: sha512-jlFOXTejVyiQCW3EQwvKH0m99bUYIw40oPmFjSX2VS78yzfe0HELZ+NEo9Yfo1MkGRpGlj3Gnu4rdxV1EnAs5A==}
|
||||
/@codemirror/lang-javascript@6.2.2:
|
||||
resolution: {integrity: sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.11.1(@codemirror/language@6.9.3)(@codemirror/state@6.3.3)(@codemirror/view@6.22.3)(@lezer/common@1.1.0)
|
||||
'@codemirror/language': 6.9.3
|
||||
'@codemirror/lint': 6.4.2
|
||||
'@codemirror/state': 6.3.3
|
||||
'@codemirror/view': 6.22.3
|
||||
'@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.1.0)
|
||||
'@codemirror/language': 6.10.1
|
||||
'@codemirror/lint': 6.8.0
|
||||
'@codemirror/state': 6.4.1
|
||||
'@codemirror/view': 6.26.3
|
||||
'@lezer/common': 1.1.0
|
||||
'@lezer/javascript': 1.0.2
|
||||
dev: false
|
||||
|
@ -4720,20 +4734,31 @@ packages:
|
|||
/@codemirror/lang-json@6.0.1:
|
||||
resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==}
|
||||
dependencies:
|
||||
'@codemirror/language': 6.9.3
|
||||
'@codemirror/language': 6.10.1
|
||||
'@lezer/json': 1.0.0
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-python@6.1.3(@codemirror/state@6.3.3)(@codemirror/view@6.22.3)(@lezer/common@1.1.0):
|
||||
resolution: {integrity: sha512-S9w2Jl74hFlD5nqtUMIaXAq9t5WlM0acCkyuQWUUSvZclk1sV+UfnpFiZzuZSG+hfEaOmxKR5UxY/Uxswn7EhQ==}
|
||||
/@codemirror/lang-python@6.1.6(@codemirror/view@6.26.3):
|
||||
resolution: {integrity: sha512-ai+01WfZhWqM92UqjnvorkxosZ2aq2u28kHvr+N3gu012XqY2CThD67JPMHnGceRfXPDBmn1HnyqowdpF57bNg==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.11.1(@codemirror/language@6.9.3)(@codemirror/state@6.3.3)(@codemirror/view@6.22.3)(@lezer/common@1.1.0)
|
||||
'@codemirror/language': 6.9.3
|
||||
'@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
|
||||
'@codemirror/language': 6.10.1
|
||||
'@codemirror/state': 6.4.1
|
||||
'@lezer/common': 1.2.1
|
||||
'@lezer/python': 1.1.5
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/state'
|
||||
- '@codemirror/view'
|
||||
- '@lezer/common'
|
||||
dev: false
|
||||
|
||||
/@codemirror/language@6.10.1:
|
||||
resolution: {integrity: sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==}
|
||||
dependencies:
|
||||
'@codemirror/state': 6.4.1
|
||||
'@codemirror/view': 6.26.3
|
||||
'@lezer/common': 1.1.0
|
||||
'@lezer/highlight': 1.1.1
|
||||
'@lezer/lr': 1.4.0
|
||||
style-mod: 4.1.0
|
||||
dev: false
|
||||
|
||||
/@codemirror/language@6.9.3:
|
||||
|
@ -4747,11 +4772,11 @@ packages:
|
|||
style-mod: 4.1.0
|
||||
dev: false
|
||||
|
||||
/@codemirror/lint@6.4.2:
|
||||
resolution: {integrity: sha512-wzRkluWb1ptPKdzlsrbwwjYCPLgzU6N88YBAmlZi8WFyuiEduSd05MnJYNogzyc8rPK7pj6m95ptUApc8sHKVA==}
|
||||
/@codemirror/lint@6.8.0:
|
||||
resolution: {integrity: sha512-lsFofvaw0lnPRJlQylNsC4IRt/1lI4OD/yYslrSGVndOJfStc58v+8p9dgGiD90ktOfL7OhBWns1ZETYgz0EJA==}
|
||||
dependencies:
|
||||
'@codemirror/state': 6.3.3
|
||||
'@codemirror/view': 6.22.3
|
||||
'@codemirror/state': 6.4.1
|
||||
'@codemirror/view': 6.26.3
|
||||
crelt: 1.0.5
|
||||
dev: false
|
||||
|
||||
|
@ -4759,10 +4784,22 @@ packages:
|
|||
resolution: {integrity: sha512-0wufKcTw2dEwEaADajjHf6hBy1sh3M6V0e+q4JKIhLuiMSe5td5HOWpUdvKth1fT1M9VYOboajoBHpkCd7PG7A==}
|
||||
dev: false
|
||||
|
||||
/@codemirror/state@6.4.1:
|
||||
resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==}
|
||||
dev: false
|
||||
|
||||
/@codemirror/view@6.22.3:
|
||||
resolution: {integrity: sha512-rqnq+Zospwoi3x1vZ8BGV1MlRsaGljX+6qiGYmIpJ++M+LCC+wjfDaPklhwpWSgv7pr/qx29KiAKQBH5+DOn4w==}
|
||||
dependencies:
|
||||
'@codemirror/state': 6.3.3
|
||||
'@codemirror/state': 6.4.1
|
||||
style-mod: 4.1.0
|
||||
w3c-keyname: 2.2.6
|
||||
dev: false
|
||||
|
||||
/@codemirror/view@6.26.3:
|
||||
resolution: {integrity: sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==}
|
||||
dependencies:
|
||||
'@codemirror/state': 6.4.1
|
||||
style-mod: 4.1.0
|
||||
w3c-keyname: 2.2.6
|
||||
dev: false
|
||||
|
@ -6666,6 +6703,10 @@ packages:
|
|||
/@lezer/common@1.1.0:
|
||||
resolution: {integrity: sha512-XPIN3cYDXsoJI/oDWoR2tD++juVrhgIago9xyKhZ7IhGlzdDM9QgC8D8saKNCz5pindGcznFr2HBSsEQSWnSjw==}
|
||||
|
||||
/@lezer/common@1.2.1:
|
||||
resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==}
|
||||
dev: false
|
||||
|
||||
/@lezer/css@1.1.1:
|
||||
resolution: {integrity: sha512-mSjx+unLLapEqdOYDejnGBokB5+AiJKZVclmud0MKQOKx3DLJ5b5VTCstgDDknR6iIV4gVrN6euzsCnj0A2gQA==}
|
||||
dependencies:
|
||||
|
@ -6835,12 +6876,12 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@n8n/codemirror-lang-sql@1.0.2(@codemirror/view@6.22.3)(@lezer/common@1.1.0):
|
||||
/@n8n/codemirror-lang-sql@1.0.2(@codemirror/view@6.26.3)(@lezer/common@1.1.0):
|
||||
resolution: {integrity: sha512-sOf/KyewSu3Ikij0CkRtzJJDhRDZcwNCEYl8UdH4U/riL0/XZGcBD7MYofCCcKszanJZiEWRZ2KU1sRp234iMg==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.11.1(@codemirror/language@6.9.3)(@codemirror/state@6.3.3)(@codemirror/view@6.22.3)(@lezer/common@1.1.0)
|
||||
'@codemirror/language': 6.9.3
|
||||
'@codemirror/state': 6.3.3
|
||||
'@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.1.0)
|
||||
'@codemirror/language': 6.10.1
|
||||
'@codemirror/state': 6.4.1
|
||||
'@lezer/highlight': 1.1.1
|
||||
'@lezer/lr': 1.4.0
|
||||
transitivePeerDependencies:
|
||||
|
@ -12662,12 +12703,12 @@ packages:
|
|||
/codemirror-lang-html-n8n@1.0.0:
|
||||
resolution: {integrity: sha512-ofNP6VTDGJ5rue+kTCZlDZdF1PnE0sl2cAkfrsCAd5MlBgDmqTwuFJIkTI6KXOJXs0ucdTYH6QLhy9BSW7EaOQ==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.11.1(@codemirror/language@6.9.3)(@codemirror/state@6.3.3)(@codemirror/view@6.22.3)(@lezer/common@1.1.0)
|
||||
'@codemirror/lang-css': 6.0.1(@codemirror/view@6.22.3)(@lezer/common@1.1.0)
|
||||
'@codemirror/lang-javascript': 6.2.1
|
||||
'@codemirror/language': 6.9.3
|
||||
'@codemirror/state': 6.3.3
|
||||
'@codemirror/view': 6.22.3
|
||||
'@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.1.0)
|
||||
'@codemirror/lang-css': 6.0.1(@codemirror/view@6.26.3)(@lezer/common@1.1.0)
|
||||
'@codemirror/lang-javascript': 6.2.2
|
||||
'@codemirror/language': 6.10.1
|
||||
'@codemirror/state': 6.4.1
|
||||
'@codemirror/view': 6.26.3
|
||||
'@lezer/common': 1.1.0
|
||||
'@lezer/css': 1.1.1
|
||||
'@lezer/highlight': 1.1.1
|
||||
|
|
Loading…
Reference in a new issue