mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(editor): Code node overwrites code when switching nodes after edits (#13078)
This commit is contained in:
@@ -7,6 +7,9 @@ import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
|||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible');
|
||||||
|
const getEditor = () => getParameter().find('.cm-content').should('exist');
|
||||||
|
|
||||||
describe('Code node', () => {
|
describe('Code node', () => {
|
||||||
describe('Code editor', () => {
|
describe('Code editor', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -40,10 +43,26 @@ describe('Code node', () => {
|
|||||||
successToast().contains('Node executed successfully');
|
successToast().contains('Node executed successfully');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show lint errors in `runOnceForAllItems` mode', () => {
|
it('should allow switching between sibling code nodes', () => {
|
||||||
const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible');
|
// Setup
|
||||||
const getEditor = () => getParameter().find('.cm-content').should('exist');
|
getEditor().type('{selectall}').paste("console.log('code node 1')");
|
||||||
|
ndv.actions.close();
|
||||||
|
WorkflowPage.actions.addNodeToCanvas('Code', true, true);
|
||||||
|
getEditor().type('{selectall}').paste("console.log('code node 2')");
|
||||||
|
ndv.actions.close();
|
||||||
|
|
||||||
|
WorkflowPage.actions.openNode('Code');
|
||||||
|
ndv.actions.clickFloatingNode('Code1');
|
||||||
|
getEditor().should('have.text', "console.log('code node 2')");
|
||||||
|
getEditor().type('{selectall}').type("console.log('code node 2 edited')");
|
||||||
|
// wait for debounce
|
||||||
|
cy.wait(200);
|
||||||
|
|
||||||
|
ndv.actions.clickFloatingNode('Code');
|
||||||
|
getEditor().should('have.text', "console.log('code node 1')");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show lint errors in `runOnceForAllItems` mode', () => {
|
||||||
getEditor()
|
getEditor()
|
||||||
.type('{selectall}')
|
.type('{selectall}')
|
||||||
.paste(`$input.itemMatching()
|
.paste(`$input.itemMatching()
|
||||||
@@ -66,9 +85,6 @@ return
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should show lint errors in `runOnceForEachItem` mode', () => {
|
it('should show lint errors in `runOnceForEachItem` mode', () => {
|
||||||
const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible');
|
|
||||||
const getEditor = () => getParameter().find('.cm-content').should('exist');
|
|
||||||
|
|
||||||
ndv.getters.parameterInput('mode').click();
|
ndv.getters.parameterInput('mode').click();
|
||||||
ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item');
|
ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item');
|
||||||
getEditor()
|
getEditor()
|
||||||
|
|||||||
@@ -151,6 +151,9 @@ export class NDV extends BasePage {
|
|||||||
schemaViewNodeName: () => cy.getByTestId('run-data-schema-node-name'),
|
schemaViewNodeName: () => cy.getByTestId('run-data-schema-node-name'),
|
||||||
expressionExpanders: () => cy.getByTestId('expander'),
|
expressionExpanders: () => cy.getByTestId('expander'),
|
||||||
expressionModalOutput: () => cy.getByTestId('expression-modal-output'),
|
expressionModalOutput: () => cy.getByTestId('expression-modal-output'),
|
||||||
|
floatingNodes: () => cy.getByTestId('floating-node'),
|
||||||
|
floatingNodeByName: (name: string) =>
|
||||||
|
cy.getByTestId('floating-node').filter(`[data-node-name="${name}"]`),
|
||||||
};
|
};
|
||||||
|
|
||||||
actions = {
|
actions = {
|
||||||
@@ -339,6 +342,9 @@ export class NDV extends BasePage {
|
|||||||
dragMainPanelToRight: () => {
|
dragMainPanelToRight: () => {
|
||||||
cy.drag('[data-test-id=panel-drag-button]', [1000, 0], { moveTwice: true });
|
cy.drag('[data-test-id=panel-drag-button]', [1000, 0], { moveTwice: true });
|
||||||
},
|
},
|
||||||
|
clickFloatingNode: (name: string) => {
|
||||||
|
this.getters.floatingNodeByName(name).realHover().click({ force: true });
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ const emit = defineEmits<{
|
|||||||
const externalHooks = useExternalHooks();
|
const externalHooks = useExternalHooks();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const nodeHelpers = useNodeHelpers();
|
const nodeHelpers = useNodeHelpers();
|
||||||
const { callDebounced } = useDebounce();
|
const { debounce } = useDebounce();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const workflowHelpers = useWorkflowHelpers({ router });
|
const workflowHelpers = useWorkflowHelpers({ router });
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
@@ -801,9 +801,9 @@ function onTextInputChange(value: string) {
|
|||||||
|
|
||||||
emit('textInput', parameterData);
|
emit('textInput', parameterData);
|
||||||
}
|
}
|
||||||
function valueChangedDebounced(value: NodeParameterValueType | {} | Date) {
|
|
||||||
void callDebounced(valueChanged, { debounceTime: 100 }, value);
|
const valueChangedDebounced = debounce(valueChanged, { debounceTime: 100 });
|
||||||
}
|
|
||||||
function onUpdateTextInput(value: string) {
|
function onUpdateTextInput(value: string) {
|
||||||
valueChanged(value);
|
valueChanged(value);
|
||||||
onTextInputChange(value);
|
onTextInputChange(value);
|
||||||
@@ -1032,6 +1032,7 @@ defineExpose({
|
|||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
valueChangedDebounced.cancel();
|
||||||
props.eventBus.off('optionSelected', optionSelected);
|
props.eventBus.off('optionSelected', optionSelected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -154,10 +154,7 @@ export const useCodeEditor = <L extends CodeEditorLanguage>({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const emitChanges = debounce((update: ViewUpdate) => {
|
const emitChanges = debounce(onChange, 300);
|
||||||
onChange(update);
|
|
||||||
}, 300);
|
|
||||||
const lastChange = ref<ViewUpdate>();
|
|
||||||
|
|
||||||
function onEditorUpdate(update: ViewUpdate) {
|
function onEditorUpdate(update: ViewUpdate) {
|
||||||
autocompleteStatus.value = completionStatus(update.view.state);
|
autocompleteStatus.value = completionStatus(update.view.state);
|
||||||
@@ -168,7 +165,6 @@ export const useCodeEditor = <L extends CodeEditorLanguage>({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (update.docChanged && !shouldIgnoreUpdate) {
|
if (update.docChanged && !shouldIgnoreUpdate) {
|
||||||
lastChange.value = update;
|
|
||||||
hasChanges.value = true;
|
hasChanges.value = true;
|
||||||
emitChanges(update);
|
emitChanges(update);
|
||||||
}
|
}
|
||||||
@@ -375,9 +371,7 @@ export const useCodeEditor = <L extends CodeEditorLanguage>({
|
|||||||
localStorage.removeItem(storedStateId.value);
|
localStorage.removeItem(storedStateId.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastChange.value) {
|
emitChanges.flush();
|
||||||
onChange(lastChange.value);
|
|
||||||
}
|
|
||||||
editor.value.destroy();
|
editor.value.destroy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,24 +1,19 @@
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { debounce as _debounce } from 'lodash-es';
|
import { debounce as _debounce, type DebouncedFunc } from 'lodash-es';
|
||||||
|
|
||||||
export interface DebounceOptions {
|
export interface DebounceOptions {
|
||||||
debounceTime: number;
|
debounceTime: number;
|
||||||
trailing?: boolean;
|
trailing?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DebouncedFunction<Args extends unknown[] = unknown[], R = void> = (...args: Args) => R;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
type AnyFunction = (...args: any) => any;
|
||||||
|
|
||||||
export function useDebounce() {
|
export function useDebounce() {
|
||||||
// Create a ref for the WeakMap to store debounced functions.
|
// Create a ref for the WeakMap to store debounced functions.
|
||||||
const debounceCache = ref(
|
const debounceCache = ref(new WeakMap<AnyFunction, DebouncedFunc<AnyFunction>>());
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
new WeakMap<DebouncedFunction<any, any>, DebouncedFunction<any, any>>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const debounce = <T extends DebouncedFunction<Parameters<T>, ReturnType<T>>>(
|
const debounce = <T extends AnyFunction>(fn: T, options: DebounceOptions): DebouncedFunc<T> => {
|
||||||
fn: T,
|
|
||||||
options: DebounceOptions,
|
|
||||||
): T => {
|
|
||||||
const { trailing, debounceTime } = options;
|
const { trailing, debounceTime } = options;
|
||||||
|
|
||||||
// Check if a debounced version of the function is already stored in the WeakMap.
|
// Check if a debounced version of the function is already stored in the WeakMap.
|
||||||
@@ -37,14 +32,14 @@ export function useDebounce() {
|
|||||||
debounceCache.value.set(fn, debouncedFn);
|
debounceCache.value.set(fn, debouncedFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
return debouncedFn as T;
|
return debouncedFn;
|
||||||
};
|
};
|
||||||
|
|
||||||
const callDebounced = <T extends DebouncedFunction<Parameters<T>, ReturnType<T>>>(
|
const callDebounced = <T extends AnyFunction>(
|
||||||
fn: T,
|
fn: T,
|
||||||
options: DebounceOptions,
|
options: DebounceOptions,
|
||||||
...inputParameters: Parameters<T>
|
...inputParameters: Parameters<T>
|
||||||
): ReturnType<T> => {
|
): ReturnType<T> | undefined => {
|
||||||
const debouncedFn = debounce(fn, options);
|
const debouncedFn = debounce(fn, options);
|
||||||
|
|
||||||
return debouncedFn(...inputParameters);
|
return debouncedFn(...inputParameters);
|
||||||
|
|||||||
Reference in New Issue
Block a user