mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
fix(editor): Code node overwrites code when switching nodes after edits (#13078)
This commit is contained in:
parent
16d59e98ed
commit
00e3ebc9e2
|
@ -7,6 +7,9 @@ import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
|||
const WorkflowPage = new WorkflowPageClass();
|
||||
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 editor', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -40,10 +43,26 @@ describe('Code node', () => {
|
|||
successToast().contains('Node executed successfully');
|
||||
});
|
||||
|
||||
it('should show lint errors in `runOnceForAllItems` mode', () => {
|
||||
const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible');
|
||||
const getEditor = () => getParameter().find('.cm-content').should('exist');
|
||||
it('should allow switching between sibling code nodes', () => {
|
||||
// Setup
|
||||
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()
|
||||
.type('{selectall}')
|
||||
.paste(`$input.itemMatching()
|
||||
|
@ -66,9 +85,6 @@ return
|
|||
});
|
||||
|
||||
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.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item');
|
||||
getEditor()
|
||||
|
|
|
@ -151,6 +151,9 @@ export class NDV extends BasePage {
|
|||
schemaViewNodeName: () => cy.getByTestId('run-data-schema-node-name'),
|
||||
expressionExpanders: () => cy.getByTestId('expander'),
|
||||
expressionModalOutput: () => cy.getByTestId('expression-modal-output'),
|
||||
floatingNodes: () => cy.getByTestId('floating-node'),
|
||||
floatingNodeByName: (name: string) =>
|
||||
cy.getByTestId('floating-node').filter(`[data-node-name="${name}"]`),
|
||||
};
|
||||
|
||||
actions = {
|
||||
|
@ -339,6 +342,9 @@ export class NDV extends BasePage {
|
|||
dragMainPanelToRight: () => {
|
||||
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 i18n = useI18n();
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
const { callDebounced } = useDebounce();
|
||||
const { debounce } = useDebounce();
|
||||
const router = useRouter();
|
||||
const workflowHelpers = useWorkflowHelpers({ router });
|
||||
const telemetry = useTelemetry();
|
||||
|
@ -801,9 +801,9 @@ function onTextInputChange(value: string) {
|
|||
|
||||
emit('textInput', parameterData);
|
||||
}
|
||||
function valueChangedDebounced(value: NodeParameterValueType | {} | Date) {
|
||||
void callDebounced(valueChanged, { debounceTime: 100 }, value);
|
||||
}
|
||||
|
||||
const valueChangedDebounced = debounce(valueChanged, { debounceTime: 100 });
|
||||
|
||||
function onUpdateTextInput(value: string) {
|
||||
valueChanged(value);
|
||||
onTextInputChange(value);
|
||||
|
@ -1032,6 +1032,7 @@ defineExpose({
|
|||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
valueChangedDebounced.cancel();
|
||||
props.eventBus.off('optionSelected', optionSelected);
|
||||
});
|
||||
|
||||
|
|
|
@ -154,10 +154,7 @@ export const useCodeEditor = <L extends CodeEditorLanguage>({
|
|||
}
|
||||
}
|
||||
|
||||
const emitChanges = debounce((update: ViewUpdate) => {
|
||||
onChange(update);
|
||||
}, 300);
|
||||
const lastChange = ref<ViewUpdate>();
|
||||
const emitChanges = debounce(onChange, 300);
|
||||
|
||||
function onEditorUpdate(update: ViewUpdate) {
|
||||
autocompleteStatus.value = completionStatus(update.view.state);
|
||||
|
@ -168,7 +165,6 @@ export const useCodeEditor = <L extends CodeEditorLanguage>({
|
|||
);
|
||||
|
||||
if (update.docChanged && !shouldIgnoreUpdate) {
|
||||
lastChange.value = update;
|
||||
hasChanges.value = true;
|
||||
emitChanges(update);
|
||||
}
|
||||
|
@ -375,9 +371,7 @@ export const useCodeEditor = <L extends CodeEditorLanguage>({
|
|||
localStorage.removeItem(storedStateId.value);
|
||||
}
|
||||
|
||||
if (lastChange.value) {
|
||||
onChange(lastChange.value);
|
||||
}
|
||||
emitChanges.flush();
|
||||
editor.value.destroy();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
import { ref } from 'vue';
|
||||
import { debounce as _debounce } from 'lodash-es';
|
||||
import { debounce as _debounce, type DebouncedFunc } from 'lodash-es';
|
||||
|
||||
export interface DebounceOptions {
|
||||
debounceTime: number;
|
||||
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() {
|
||||
// Create a ref for the WeakMap to store debounced functions.
|
||||
const debounceCache = ref(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
new WeakMap<DebouncedFunction<any, any>, DebouncedFunction<any, any>>(),
|
||||
);
|
||||
const debounceCache = ref(new WeakMap<AnyFunction, DebouncedFunc<AnyFunction>>());
|
||||
|
||||
const debounce = <T extends DebouncedFunction<Parameters<T>, ReturnType<T>>>(
|
||||
fn: T,
|
||||
options: DebounceOptions,
|
||||
): T => {
|
||||
const debounce = <T extends AnyFunction>(fn: T, options: DebounceOptions): DebouncedFunc<T> => {
|
||||
const { trailing, debounceTime } = options;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
return debouncedFn as T;
|
||||
return debouncedFn;
|
||||
};
|
||||
|
||||
const callDebounced = <T extends DebouncedFunction<Parameters<T>, ReturnType<T>>>(
|
||||
const callDebounced = <T extends AnyFunction>(
|
||||
fn: T,
|
||||
options: DebounceOptions,
|
||||
...inputParameters: Parameters<T>
|
||||
): ReturnType<T> => {
|
||||
): ReturnType<T> | undefined => {
|
||||
const debouncedFn = debounce(fn, options);
|
||||
|
||||
return debouncedFn(...inputParameters);
|
||||
|
|
Loading…
Reference in a new issue