mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix(editor): Fix an issue with overlapping elements in the Assignment component (#18041)
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { fireEvent } from '@testing-library/vue';
|
||||
import Assignment from './Assignment.vue';
|
||||
import { defaultSettings } from '@/__tests__/defaults';
|
||||
import { STORES } from '@n8n/stores';
|
||||
import merge from 'lodash/merge';
|
||||
import { cleanupAppModals, createAppModals } from '@/__tests__/utils';
|
||||
import * as useResolvedExpression from '@/composables/useResolvedExpression';
|
||||
|
||||
const DEFAULT_SETUP = {
|
||||
pinia: createTestingPinia({
|
||||
@@ -69,4 +72,22 @@ describe('Assignment.vue', () => {
|
||||
// Check if the parameter input hint is not displayed
|
||||
expect(() => getByTestId('parameter-input-hint')).toThrow();
|
||||
});
|
||||
|
||||
it('should shorten the expression preview hint if options are on the bottom', async () => {
|
||||
vi.spyOn(useResolvedExpression, 'useResolvedExpression').mockReturnValueOnce({
|
||||
resolvedExpressionString: ref('foo'),
|
||||
resolvedExpression: ref(null),
|
||||
isExpression: computed(() => true),
|
||||
});
|
||||
const { getByTestId } = renderComponent();
|
||||
|
||||
const previewValue = getByTestId('parameter-expression-preview-value');
|
||||
|
||||
expect(previewValue).not.toHaveClass('optionsPadding');
|
||||
|
||||
await fireEvent.mouseEnter(getByTestId('assignment-value'));
|
||||
await nextTick();
|
||||
|
||||
expect(previewValue).toHaveClass('optionsPadding');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,6 +25,7 @@ interface Props {
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const assignment = ref<AssignmentValue>(props.modelValue);
|
||||
const valueInputHovered = ref(false);
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:model-value': [value: AssignmentValue];
|
||||
@@ -113,6 +114,10 @@ const onRemove = (): void => {
|
||||
const onBlur = (): void => {
|
||||
emit('update:model-value', assignment.value);
|
||||
};
|
||||
|
||||
const onValueInputHoverChange = (hovered: boolean): void => {
|
||||
valueInputHovered.value = hovered;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -186,11 +191,16 @@ const onBlur = (): void => {
|
||||
data-test-id="assignment-value"
|
||||
@update="onAssignmentValueChange"
|
||||
@blur="onBlur"
|
||||
@hover="onValueInputHoverChange"
|
||||
/>
|
||||
<ParameterInputHint
|
||||
v-if="resolvedExpressionString"
|
||||
data-test-id="parameter-expression-preview-value"
|
||||
:class="$style.hint"
|
||||
:class="{
|
||||
[$style.hint]: true,
|
||||
[$style.optionsPadding]:
|
||||
breakpoint !== 'default' && !isReadOnly && valueInputHovered,
|
||||
}"
|
||||
:highlight="highlightHint"
|
||||
:hint="hint"
|
||||
single-line
|
||||
@@ -248,6 +258,10 @@ const onBlur = (): void => {
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.optionsPadding {
|
||||
width: calc(100% - 140px);
|
||||
}
|
||||
}
|
||||
|
||||
.iconButton {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { renderComponent } from '@/__tests__/render';
|
||||
import { nextTick } from 'vue';
|
||||
import type { useNDVStore } from '@/stores/ndv.store';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import type { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
@@ -6,6 +6,8 @@ import type { useSettingsStore } from '@/stores/settings.store';
|
||||
import { cleanupAppModals, createAppModals } from '@/__tests__/utils';
|
||||
import ParameterInputFull from './ParameterInputFull.vue';
|
||||
import { FROM_AI_AUTO_GENERATED_MARKER } from 'n8n-workflow';
|
||||
import { fireEvent } from '@testing-library/vue';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
|
||||
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
|
||||
|
||||
@@ -34,6 +36,7 @@ beforeEach(() => {
|
||||
};
|
||||
mockNodeTypesState = {
|
||||
allNodeTypes: [],
|
||||
getNodeType: vi.fn().mockReturnValue({}),
|
||||
};
|
||||
mockSettingsState = {
|
||||
settings: {
|
||||
@@ -62,6 +65,19 @@ vi.mock('@/stores/settings.store', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const renderComponent = createComponentRenderer(ParameterInputFull, {
|
||||
pinia: createTestingPinia(),
|
||||
props: {
|
||||
path: 'myParam',
|
||||
value: '',
|
||||
parameter: {
|
||||
displayName: 'My Param',
|
||||
name: 'myParam',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('ParameterInputFull.vue', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -73,18 +89,7 @@ describe('ParameterInputFull.vue', () => {
|
||||
});
|
||||
|
||||
it('should render basic parameter', async () => {
|
||||
mockNodeTypesState.getNodeType = vi.fn().mockReturnValue({});
|
||||
const { getByTestId } = renderComponent(ParameterInputFull, {
|
||||
pinia: createTestingPinia(),
|
||||
props: {
|
||||
path: 'myParam',
|
||||
parameter: {
|
||||
displayName: 'My Param',
|
||||
name: 'myParam',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
});
|
||||
const { getByTestId } = renderComponent();
|
||||
expect(getByTestId('parameter-input')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -95,18 +100,7 @@ describe('ParameterInputFull.vue', () => {
|
||||
subcategories: { AI: ['Tools'] },
|
||||
},
|
||||
});
|
||||
const { getByTestId } = renderComponent(ParameterInputFull, {
|
||||
pinia: createTestingPinia(),
|
||||
props: {
|
||||
path: 'myParam',
|
||||
parameter: {
|
||||
displayName: 'My Param',
|
||||
name: 'myParam',
|
||||
type: 'string',
|
||||
},
|
||||
value: '',
|
||||
},
|
||||
});
|
||||
const { getByTestId } = renderComponent();
|
||||
expect(getByTestId('parameter-input')).toBeInTheDocument();
|
||||
expect(getByTestId('from-ai-override-button')).toBeInTheDocument();
|
||||
});
|
||||
@@ -118,15 +112,8 @@ describe('ParameterInputFull.vue', () => {
|
||||
subcategories: { AI: ['Tools'] },
|
||||
},
|
||||
});
|
||||
const { getByTestId } = renderComponent(ParameterInputFull, {
|
||||
pinia: createTestingPinia(),
|
||||
const { getByTestId } = renderComponent({
|
||||
props: {
|
||||
path: 'myParam',
|
||||
parameter: {
|
||||
displayName: 'My Param',
|
||||
name: 'myParam',
|
||||
type: 'string',
|
||||
},
|
||||
value: `={{
|
||||
'and the air is free'
|
||||
|
||||
@@ -145,19 +132,27 @@ describe('ParameterInputFull.vue', () => {
|
||||
subcategories: { AI: ['Tools'] },
|
||||
},
|
||||
});
|
||||
const { queryByTestId, getByTestId } = renderComponent(ParameterInputFull, {
|
||||
pinia: createTestingPinia(),
|
||||
const { queryByTestId, getByTestId } = renderComponent({
|
||||
props: {
|
||||
path: 'myParam',
|
||||
value: `={{ ${FROM_AI_AUTO_GENERATED_MARKER} $fromAI('myParam') }}`,
|
||||
parameter: {
|
||||
displayName: 'My Param',
|
||||
name: 'myParam',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(getByTestId('fromAI-override-field')).toBeInTheDocument();
|
||||
expect(queryByTestId('override-button')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should emit on wrapper hover', async () => {
|
||||
const { getByTestId, emitted } = renderComponent();
|
||||
const wrapper = getByTestId('input-label');
|
||||
|
||||
await fireEvent.mouseEnter(wrapper);
|
||||
await nextTick();
|
||||
|
||||
expect(emitted().hover).toEqual([[true]]);
|
||||
|
||||
await fireEvent.mouseLeave(wrapper);
|
||||
await nextTick();
|
||||
|
||||
expect(emitted().hover).toEqual([[true], [false]]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -57,6 +57,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
const emit = defineEmits<{
|
||||
blur: [];
|
||||
update: [value: IUpdateInformation];
|
||||
hover: [hovered: boolean];
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
@@ -66,6 +67,7 @@ const eventBus = ref(createEventBus());
|
||||
const focused = ref(false);
|
||||
const menuExpanded = ref(false);
|
||||
const forceShowExpression = ref(false);
|
||||
const wrapperHovered = ref(false);
|
||||
|
||||
const ndvStore = useNDVStore();
|
||||
const telemetry = useTelemetry();
|
||||
@@ -139,6 +141,14 @@ function onMenuExpanded(expanded: boolean) {
|
||||
menuExpanded.value = expanded;
|
||||
}
|
||||
|
||||
function onWrapperMouseEnter() {
|
||||
wrapperHovered.value = true;
|
||||
}
|
||||
|
||||
function onWrapperMouseLeave() {
|
||||
wrapperHovered.value = false;
|
||||
}
|
||||
|
||||
function optionSelected(command: string) {
|
||||
if (isContentOverride.value && command === 'resetValue') {
|
||||
removeOverride(true);
|
||||
@@ -249,6 +259,10 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch(wrapperHovered, (hovered) => {
|
||||
emit('hover', hovered);
|
||||
});
|
||||
|
||||
const parameterInputWrapper = useTemplateRef('parameterInputWrapper');
|
||||
const isSingleLineInput: ComputedRef<boolean> = computed(
|
||||
() => parameterInputWrapper.value?.isSingleLineInput ?? false,
|
||||
@@ -305,6 +319,8 @@ function removeOverride(clearField = false) {
|
||||
:bold="false"
|
||||
:size="label.size"
|
||||
color="text-dark"
|
||||
@mouseenter="onWrapperMouseEnter"
|
||||
@mouseleave="onWrapperMouseLeave"
|
||||
>
|
||||
<template
|
||||
v-if="showOverrideButton && !isSingleLineInput && optionsPosition === 'top'"
|
||||
|
||||
Reference in New Issue
Block a user