fix(editor): UI enhancements and fixes for expression inputs (#8996)

This commit is contained in:
Elias Meire 2024-03-29 16:01:32 +01:00 committed by GitHub
parent 1cbd044e41
commit 8788e2a35b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 41 additions and 86 deletions

View file

@ -12,3 +12,10 @@ window.ResizeObserver =
}));
Element.prototype.scrollIntoView = vi.fn();
Range.prototype.getBoundingClientRect = vi.fn();
Range.prototype.getClientRects = vi.fn(() => ({
item: vi.fn(),
length: 0,
[Symbol.iterator]: vi.fn(),
}));

View file

@ -169,7 +169,6 @@ const onBlur = (): void => {
display-options
hide-label
hide-hint
:rows="3"
:is-read-only="isReadOnly"
:parameter="nameParameter"
:value="assignment.name"
@ -196,7 +195,6 @@ const onBlur = (): void => {
hide-label
hide-issues
hide-hint
:rows="3"
is-assignment
:is-read-only="isReadOnly"
:options-position="breakpoint === 'default' ? 'top' : 'bottom'"

View file

@ -156,7 +156,6 @@ const onBlur = (): void => {
hide-label
hide-hint
hide-issues
:rows="3"
:is-read-only="readOnly"
:parameter="leftParameter"
:value="condition.leftValue"
@ -181,7 +180,6 @@ const onBlur = (): void => {
hide-label
hide-hint
hide-issues
:rows="3"
:is-read-only="readOnly"
:options-position="breakpoint === 'default' ? 'top' : 'bottom'"
:parameter="rightParameter"

View file

@ -45,21 +45,24 @@ const resolvedExpression = computed(() => {
});
const plaintextSegments = computed<Plaintext[]>(() => {
if (props.segments.length === 0) {
return [
{
from: 0,
to: resolvedExpression.value.length - 1,
plaintext: resolvedExpression.value,
kind: 'plaintext',
},
];
}
return props.segments.filter((s): s is Plaintext => s.kind === 'plaintext');
});
const resolvedSegments = computed<Resolved[]>(() => {
if (props.segments.length === 0) {
const emptyExpression = resolvedExpression.value;
const emptySegment: Resolved = {
from: 0,
to: emptyExpression.length,
kind: 'resolvable',
error: null,
resolvable: '',
resolved: emptyExpression,
state: 'pending',
};
return [emptySegment];
}
let cursor = 0;
return props.segments

View file

@ -1,6 +1,6 @@
<template>
<n8n-input-label
:class="$style.wrapper"
:class="[$style.wrapper, { [$style.tipVisible]: showDragnDropTip }]"
:label="hideLabel ? '' : i18n.nodeText().inputLabelDisplayName(parameter, path)"
:tooltip-text="hideLabel ? '' : i18n.nodeText().inputLabelDescription(parameter, path)"
:show-tooltip="focused"
@ -357,6 +357,11 @@ export default defineComponent({
}
}
.tipVisible {
--input-border-bottom-left-radius: 0;
--input-border-bottom-right-radius: 0;
}
.tip {
position: absolute;
z-index: 2;

View file

@ -6,8 +6,6 @@ import { setActivePinia } from 'pinia';
import { waitFor } from '@testing-library/vue';
describe('ExpressionParameterInput', () => {
const originalRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect;
const originalRangeGetClientRects = Range.prototype.getClientRects;
const renderComponent = createComponentRenderer(ExpressionEditorModalInput);
let pinia: TestingPinia;
@ -16,19 +14,6 @@ describe('ExpressionParameterInput', () => {
setActivePinia(pinia);
});
beforeAll(() => {
Range.prototype.getBoundingClientRect = vi.fn();
Range.prototype.getClientRects = () => ({
item: vi.fn(),
length: 0,
[Symbol.iterator]: vi.fn(),
});
});
afterAll(() => {
Range.prototype.getBoundingClientRect = originalRangeGetBoundingClientRect;
Range.prototype.getClientRects = originalRangeGetClientRects;
});
test.each([
['not be editable', 'readonly', true, ''],
['be editable', 'not readonly', false, 'test'],

View file

@ -17,9 +17,6 @@ const DEFAULT_SETUP = {
};
describe('HtmlEditor.vue', () => {
const originalRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect;
const originalRangeGetClientRects = Range.prototype.getClientRects;
const pinia = createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
@ -29,20 +26,6 @@ describe('HtmlEditor.vue', () => {
});
setActivePinia(pinia);
beforeAll(() => {
Range.prototype.getBoundingClientRect = vi.fn();
Range.prototype.getClientRects = () => ({
item: vi.fn(),
length: 0,
[Symbol.iterator]: vi.fn(),
});
});
afterAll(() => {
Range.prototype.getBoundingClientRect = originalRangeGetBoundingClientRect;
Range.prototype.getClientRects = originalRangeGetClientRects;
});
afterAll(() => {
vi.clearAllMocks();
});

View file

@ -28,9 +28,6 @@ vi.mock('@/stores/n8nRoot.store', () => ({
}));
describe('useAutocompleteTelemetry', () => {
const originalRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect;
const originalRangeGetClientRects = Range.prototype.getClientRects;
beforeEach(() => {
setActivePinia(createTestingPinia());
});
@ -39,20 +36,6 @@ describe('useAutocompleteTelemetry', () => {
vi.clearAllMocks();
});
beforeAll(() => {
Range.prototype.getBoundingClientRect = vi.fn();
Range.prototype.getClientRects = () => ({
item: vi.fn(),
length: 0,
[Symbol.iterator]: vi.fn(),
});
});
afterAll(() => {
Range.prototype.getBoundingClientRect = originalRangeGetBoundingClientRect;
Range.prototype.getClientRects = originalRangeGetClientRects;
});
const getEditor = (defaultDoc = '') => {
const extensionCompartment = new Compartment();
const state = EditorState.create({

View file

@ -21,9 +21,6 @@ vi.mock('@/stores/ndv.store', () => ({
}));
describe('useExpressionEditor', () => {
const originalRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect;
const originalRangeGetClientRects = Range.prototype.getClientRects;
const mockResolveExpression = () => {
const mock = vi.fn();
vi.spyOn(workflowHelpers, 'useWorkflowHelpers').mockReturnValueOnce({
@ -42,20 +39,6 @@ describe('useExpressionEditor', () => {
vi.clearAllMocks();
});
beforeAll(() => {
Range.prototype.getBoundingClientRect = vi.fn();
Range.prototype.getClientRects = () => ({
item: vi.fn(),
length: 0,
[Symbol.iterator]: vi.fn(),
});
});
afterAll(() => {
Range.prototype.getBoundingClientRect = originalRangeGetBoundingClientRect;
Range.prototype.getClientRects = originalRangeGetClientRects;
});
test('should create an editor', async () => {
const root = ref<HTMLElement>();
const { editor } = useExpressionEditor({

View file

@ -185,7 +185,7 @@ export const useExpressionEditor = ({
if (editor.value) {
editor.value.destroy();
}
editor.value = new EditorView({ parent, state });
editor.value = new EditorView({ parent, state, scrollTo: EditorView.scrollIntoView(0) });
debouncedUpdateSegments();
});
@ -385,7 +385,8 @@ export const useExpressionEditor = ({
function setCursorPosition(pos: number | 'lastExpression' | 'end'): void {
if (pos === 'lastExpression') {
const END_OF_EXPRESSION = ' }}';
pos = Math.max(readEditorValue().lastIndexOf(END_OF_EXPRESSION), 0);
const endOfLastExpression = readEditorValue().lastIndexOf(END_OF_EXPRESSION);
pos = endOfLastExpression !== -1 ? endOfLastExpression : editor.value?.state.doc.length ?? 0;
} else if (pos === 'end') {
pos = editor.value?.state.doc.length ?? 0;
}

View file

@ -382,7 +382,10 @@ function parseJson(value: string): unknown {
try {
return JSON.parse(value);
} catch (error) {
return undefined;
if (value.includes("'")) {
throw new ExpressionExtensionError("Parsing failed. Check you're using double quotes");
}
throw new ExpressionExtensionError('Parsing failed');
}
}

View file

@ -270,7 +270,13 @@ describe('Data Transformation Functions', () => {
test1: 1,
test2: '2',
});
expect(evaluate('={{ "hi".parseJson() }}')).toBeUndefined();
});
test('.parseJson should throw on invalid JSON', () => {
expect(() => evaluate("={{ \"{'test1':1,'test2':'2'}\".parseJson() }}")).toThrowError(
"Parsing failed. Check you're using double quotes",
);
expect(() => evaluate('={{ "No JSON here".parseJson() }}')).toThrowError('Parsing failed');
});
test('.toBoolean should work on a string', () => {