Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(editor): Improve formatting of expired trial error message #11708

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"type": "node",
"env": {
// "N8N_PORT": "5679",
},
"envFile": "${workspaceFolder}/.env",
"outputCapture": "std",
"killBehavior": "polite"
},
Expand Down
111 changes: 111 additions & 0 deletions packages/editor-ui/src/composables/usePushConnection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import type { PushMessage, PushPayload } from '@n8n/api-types';
import { usePushConnection } from '@/composables/usePushConnection';
import { usePushConnectionStore } from '@/stores/pushConnection.store';
import { useOrchestrationStore } from '@/stores/orchestration.store';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useToast } from '@/composables/useToast';
import type { WorkflowOperationError } from 'n8n-workflow';

vi.mock('vue-router', () => {
return {
Expand All @@ -16,21 +20,44 @@ vi.mock('vue-router', () => {
};
});

vi.mock('@/composables/useToast', () => {
const showMessage = vi.fn();
const showError = vi.fn();
return {
useToast: () => {
return {
showMessage,
showError,
};
},
};
});

vi.useFakeTimers();

describe('usePushConnection()', () => {
let router: ReturnType<typeof useRouter>;
let pushStore: ReturnType<typeof usePushConnectionStore>;
let orchestrationStore: ReturnType<typeof useOrchestrationStore>;
let pushConnection: ReturnType<typeof usePushConnection>;
let uiStore: ReturnType<typeof useUIStore>;
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
let toast: ReturnType<typeof useToast>;

beforeEach(() => {
setActivePinia(createPinia());

router = vi.mocked(useRouter)();
pushStore = usePushConnectionStore();
orchestrationStore = useOrchestrationStore();
uiStore = useUIStore();
workflowsStore = useWorkflowsStore();
pushConnection = usePushConnection({ router });
toast = useToast();
});

afterEach(() => {
vi.restoreAllMocks();
});

describe('initialize()', () => {
Expand Down Expand Up @@ -106,5 +133,89 @@ describe('usePushConnection()', () => {
expect(result).toBeTruthy();
});
});

describe('executionFinished', () => {
it('should handle executionFinished event correctly', async () => {
const event: PushMessage = {
type: 'executionFinished',
data: {
executionId: '1',
data: {
data: {
resultData: {
runData: {},
},
},
finished: true,
mode: 'manual',
startedAt: new Date(),
stoppedAt: new Date(),
status: 'success',
},
},
};

workflowsStore.activeExecutionId = '1';
uiStore.isActionActive.workflowRunning = true;

const result = await pushConnection.pushMessageReceived(event);

expect(result).toBeTruthy();
expect(workflowsStore.workflowExecutionData).toBeDefined();
expect(uiStore.isActionActive['workflowRunning']).toBeTruthy();

expect(toast.showMessage).toHaveBeenCalledWith({
title: 'Workflow executed successfully',
type: 'success',
});
});

it('should handle isManualExecutionCancelled correctly', async () => {
const event: PushMessage = {
type: 'executionFinished',
data: {
executionId: '1',
data: {
data: {
startData: {},
resultData: {
runData: {
'Last Node': [],
},
lastNodeExecuted: 'Last Node',
error: {
message:
'Your trial has ended. <a href="https://app.n8n.cloud/account/change-plan">Upgrade now</a> to keep automating',
name: 'NodeApiError',
node: 'Last Node',
} as unknown as WorkflowOperationError,
},
},
startedAt: new Date(),
mode: 'manual',
status: 'running',
},
},
};

workflowsStore.activeExecutionId = '1';
uiStore.isActionActive['workflowRunning'] = true;

const result = await pushConnection.pushMessageReceived(event);

expect(useToast().showMessage).toHaveBeenCalledWith({
message:
'Your trial has ended. <a href="https://app.n8n.cloud/account/change-plan">Upgrade now</a> to keep automating',
title: 'Problem in node ‘Last Node‘',
type: 'error',
duration: 0,
dangerouslyUseHTMLString: true,
});

expect(result).toBeTruthy();
expect(workflowsStore.workflowExecutionData).toBeDefined();
expect(uiStore.isActionActive.workflowRunning).toBeTruthy();
});
});
});
});
1 change: 1 addition & 0 deletions packages/editor-ui/src/composables/usePushConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
message: runDataExecutedErrorMessage,
type: 'error',
duration: 0,
dangerouslyUseHTMLString: true,
igatanasov marked this conversation as resolved.
Show resolved Hide resolved
});
}
}
Expand Down
55 changes: 55 additions & 0 deletions packages/editor-ui/src/composables/useToast.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useToast } from './useToast';
import { createPinia, setActivePinia } from 'pinia';
import { ElNotification as Notification } from 'element-plus';

vi.mock('element-plus', async () => {
const original = await vi.importActual('element-plus');
return {
...original,
ElNotification: vi.fn(),
ElTooltip: vi.fn(),
};
});

describe('useToast', () => {
let toast: ReturnType<typeof useToast>;

beforeEach(() => {
setActivePinia(createPinia());

toast = useToast();
});

afterEach(() => {
vi.restoreAllMocks();
});

it('should show a message', () => {
const messageData = { message: 'Test message', title: 'Test title' };
toast.showMessage(messageData);

expect(Notification).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Test message',
title: 'Test title',
}),
);
});

it('should sanitize message and title', () => {
const messageData = {
message: '<script>alert("xss")</script>',
title: '<script>alert("xss")</script>',
};

toast.showMessage(messageData);

expect(Notification).toHaveBeenCalledWith(
expect.objectContaining({
message: 'alert("xss")',
title: 'alert("xss")',
}),
);
});
});
Loading