mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 16:10:30 +02:00
fix(editor): Show error toasts in Instance AI executable canvas (#29328)
This commit is contained in:
parent
0a89814220
commit
dc33223d3b
|
|
@ -1,7 +1,7 @@
|
|||
import type { Mock, MockInstance } from 'vitest';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import { waitFor } from '@testing-library/vue';
|
||||
import type { ExecutionSummary } from 'n8n-workflow';
|
||||
import { jsonParse, type ExecutionSummary } from 'n8n-workflow';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import type { INodeUi, IWorkflowDb } from '@/Interface';
|
||||
import WorkflowPreview from '@/app/components/WorkflowPreview.vue';
|
||||
|
|
@ -21,6 +21,14 @@ const sendPostMessageCommand = (command: string) => {
|
|||
window.postMessage(`{"command":"${command}"}`, '*');
|
||||
};
|
||||
|
||||
const expectIframePostMessage = (expectedPayload: Record<string, unknown>) => {
|
||||
const payloads = postMessageSpy.mock.calls
|
||||
.filter(([payload, targetOrigin]) => typeof payload === 'string' && targetOrigin === '*')
|
||||
.map(([payload]) => jsonParse(payload as string));
|
||||
|
||||
expect(payloads).toEqual(expect.arrayContaining([expect.objectContaining(expectedPayload)]));
|
||||
};
|
||||
|
||||
describe('WorkflowPreview', () => {
|
||||
beforeEach(() => {
|
||||
pinia = createPinia();
|
||||
|
|
@ -105,21 +113,45 @@ describe('WorkflowPreview', () => {
|
|||
sendPostMessageCommand('n8nReady');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(postMessageSpy).toHaveBeenCalledWith(
|
||||
JSON.stringify({
|
||||
command: 'openWorkflow',
|
||||
workflow,
|
||||
canOpenNDV: true,
|
||||
hideNodeIssues: false,
|
||||
suppressNotifications: false,
|
||||
projectId: 'test-project-id',
|
||||
}),
|
||||
'*',
|
||||
);
|
||||
expectIframePostMessage({
|
||||
command: 'openWorkflow',
|
||||
workflow,
|
||||
canOpenNDV: true,
|
||||
hideNodeIssues: false,
|
||||
suppressNotifications: false,
|
||||
allowErrorNotifications: false,
|
||||
projectId: 'test-project-id',
|
||||
});
|
||||
expect(focusSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass allowErrorNotifications using PostMessage when enabled', async () => {
|
||||
const nodes = [{ name: 'Start' }] as INodeUi[];
|
||||
const workflow = { nodes } as IWorkflowDb;
|
||||
renderComponent({
|
||||
pinia,
|
||||
props: {
|
||||
workflow,
|
||||
allowErrorNotifications: true,
|
||||
},
|
||||
});
|
||||
|
||||
sendPostMessageCommand('n8nReady');
|
||||
|
||||
await waitFor(() => {
|
||||
expectIframePostMessage({
|
||||
command: 'openWorkflow',
|
||||
workflow,
|
||||
canOpenNDV: true,
|
||||
hideNodeIssues: false,
|
||||
suppressNotifications: false,
|
||||
allowErrorNotifications: true,
|
||||
projectId: 'test-project-id',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not call iframe postMessage with "openExecution" when executionId is passed but mode not set to "execution"', async () => {
|
||||
const executionId = '123';
|
||||
renderComponent({
|
||||
|
|
@ -149,16 +181,13 @@ describe('WorkflowPreview', () => {
|
|||
sendPostMessageCommand('n8nReady');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(postMessageSpy).toHaveBeenCalledWith(
|
||||
JSON.stringify({
|
||||
command: 'openExecution',
|
||||
executionId,
|
||||
executionMode: '',
|
||||
canOpenNDV: true,
|
||||
projectId: 'test-project-id',
|
||||
}),
|
||||
'*',
|
||||
);
|
||||
expectIframePostMessage({
|
||||
command: 'openExecution',
|
||||
executionId,
|
||||
executionMode: '',
|
||||
canOpenNDV: true,
|
||||
projectId: 'test-project-id',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -179,24 +208,18 @@ describe('WorkflowPreview', () => {
|
|||
sendPostMessageCommand('n8nReady');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(postMessageSpy).toHaveBeenCalledWith(
|
||||
JSON.stringify({
|
||||
command: 'openExecution',
|
||||
executionId,
|
||||
executionMode: '',
|
||||
canOpenNDV: true,
|
||||
projectId: 'test-project-id',
|
||||
}),
|
||||
'*',
|
||||
);
|
||||
expectIframePostMessage({
|
||||
command: 'openExecution',
|
||||
executionId,
|
||||
executionMode: '',
|
||||
canOpenNDV: true,
|
||||
projectId: 'test-project-id',
|
||||
});
|
||||
|
||||
expect(postMessageSpy).toHaveBeenCalledWith(
|
||||
JSON.stringify({
|
||||
command: 'setActiveExecution',
|
||||
executionId: 'abc',
|
||||
}),
|
||||
'*',
|
||||
);
|
||||
expectIframePostMessage({
|
||||
command: 'setActiveExecution',
|
||||
executionId: 'abc',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -217,17 +240,15 @@ describe('WorkflowPreview', () => {
|
|||
sendPostMessageCommand('n8nReady');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(postMessageSpy).toHaveBeenCalledWith(
|
||||
JSON.stringify({
|
||||
command: 'openWorkflow',
|
||||
workflow,
|
||||
canOpenNDV: true,
|
||||
hideNodeIssues: false,
|
||||
suppressNotifications: false,
|
||||
projectId: 'test-project-id',
|
||||
}),
|
||||
'*',
|
||||
);
|
||||
expectIframePostMessage({
|
||||
command: 'openWorkflow',
|
||||
workflow,
|
||||
canOpenNDV: true,
|
||||
hideNodeIssues: false,
|
||||
suppressNotifications: false,
|
||||
allowErrorNotifications: false,
|
||||
projectId: 'test-project-id',
|
||||
});
|
||||
});
|
||||
|
||||
sendPostMessageCommand('openNDV');
|
||||
|
|
@ -255,17 +276,15 @@ describe('WorkflowPreview', () => {
|
|||
});
|
||||
sendPostMessageCommand('n8nReady');
|
||||
await waitFor(() => {
|
||||
expect(postMessageSpy).toHaveBeenCalledWith(
|
||||
JSON.stringify({
|
||||
command: 'openWorkflow',
|
||||
workflow,
|
||||
canOpenNDV: false,
|
||||
hideNodeIssues: false,
|
||||
suppressNotifications: false,
|
||||
projectId: 'test-project-id',
|
||||
}),
|
||||
'*',
|
||||
);
|
||||
expectIframePostMessage({
|
||||
command: 'openWorkflow',
|
||||
workflow,
|
||||
canOpenNDV: false,
|
||||
hideNodeIssues: false,
|
||||
suppressNotifications: false,
|
||||
allowErrorNotifications: false,
|
||||
projectId: 'test-project-id',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const props = withDefaults(
|
|||
focusOnLoad?: boolean;
|
||||
hideControls?: boolean;
|
||||
suppressNotifications?: boolean;
|
||||
allowErrorNotifications?: boolean;
|
||||
canExecute?: boolean;
|
||||
}>(),
|
||||
{
|
||||
|
|
@ -37,6 +38,7 @@ const props = withDefaults(
|
|||
focusOnLoad: true,
|
||||
hideControls: false,
|
||||
suppressNotifications: false,
|
||||
allowErrorNotifications: false,
|
||||
canExecute: false,
|
||||
},
|
||||
);
|
||||
|
|
@ -95,6 +97,7 @@ const loadWorkflow = () => {
|
|||
canOpenNDV: props.canOpenNDV,
|
||||
hideNodeIssues: props.hideNodeIssues,
|
||||
suppressNotifications: props.suppressNotifications,
|
||||
allowErrorNotifications: props.allowErrorNotifications,
|
||||
projectId: projectsStore.currentProjectId,
|
||||
}),
|
||||
'*',
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { createTestingPinia } from '@pinia/testing';
|
|||
import { jsonParse } from 'n8n-workflow';
|
||||
import { usePostMessageHandler } from './usePostMessageHandler';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
import type { WorkflowState } from '@/app/composables/useWorkflowState';
|
||||
import type { IExecutionResponse } from '@/features/execution/executions/executions.types';
|
||||
|
||||
|
|
@ -47,10 +48,12 @@ vi.mock('@/app/composables/useTelemetry', () => ({
|
|||
})),
|
||||
}));
|
||||
|
||||
const mockToastShowError = vi.hoisted(() => vi.fn());
|
||||
const mockToastShowMessage = vi.hoisted(() => vi.fn());
|
||||
vi.mock('@/app/composables/useToast', () => ({
|
||||
useToast: vi.fn(() => ({
|
||||
showError: vi.fn(),
|
||||
showMessage: vi.fn(),
|
||||
showError: mockToastShowError,
|
||||
showMessage: mockToastShowMessage,
|
||||
})),
|
||||
}));
|
||||
|
||||
|
|
@ -97,6 +100,14 @@ function createMockWorkflowState(): WorkflowState {
|
|||
} as unknown as WorkflowState;
|
||||
}
|
||||
|
||||
function dispatchPostMessage(payload: Record<string, unknown>) {
|
||||
window.dispatchEvent(
|
||||
new MessageEvent('message', {
|
||||
data: JSON.stringify(payload),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
describe('usePostMessageHandler', () => {
|
||||
let workflowState: WorkflowState;
|
||||
|
||||
|
|
@ -198,6 +209,96 @@ describe('usePostMessageHandler', () => {
|
|||
cleanup();
|
||||
});
|
||||
|
||||
it('should set notification suppression and error allowance from openWorkflow message', async () => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }));
|
||||
const uiStore = useUIStore();
|
||||
const { setup, cleanup } = usePostMessageHandler({
|
||||
workflowState,
|
||||
currentWorkflowDocumentStore: shallowRef(null),
|
||||
});
|
||||
setup();
|
||||
|
||||
window.dispatchEvent(
|
||||
new MessageEvent('message', {
|
||||
data: JSON.stringify({
|
||||
command: 'openWorkflow',
|
||||
workflow: { nodes: [], connections: {} },
|
||||
suppressNotifications: true,
|
||||
allowErrorNotifications: true,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(mockImportWorkflowExact).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(uiStore.areNotificationsSuppressed).toBe(true);
|
||||
expect(uiStore.allowErrorNotificationsWhenSuppressed).toBe(true);
|
||||
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('should clear notification suppression and error allowance when suppression is false', async () => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }));
|
||||
const uiStore = useUIStore();
|
||||
uiStore.setNotificationsSuppressed(true, { allowErrors: true });
|
||||
const { setup, cleanup } = usePostMessageHandler({
|
||||
workflowState,
|
||||
currentWorkflowDocumentStore: shallowRef(null),
|
||||
});
|
||||
setup();
|
||||
|
||||
window.dispatchEvent(
|
||||
new MessageEvent('message', {
|
||||
data: JSON.stringify({
|
||||
command: 'openWorkflow',
|
||||
workflow: { nodes: [], connections: {} },
|
||||
suppressNotifications: false,
|
||||
allowErrorNotifications: true,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(mockImportWorkflowExact).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(uiStore.areNotificationsSuppressed).toBe(false);
|
||||
expect(uiStore.allowErrorNotificationsWhenSuppressed).toBe(false);
|
||||
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('should clear notification suppression and error allowance when suppression is absent', async () => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }));
|
||||
const uiStore = useUIStore();
|
||||
uiStore.setNotificationsSuppressed(true, { allowErrors: true });
|
||||
const { setup, cleanup } = usePostMessageHandler({
|
||||
workflowState,
|
||||
currentWorkflowDocumentStore: shallowRef(null),
|
||||
});
|
||||
setup();
|
||||
|
||||
window.dispatchEvent(
|
||||
new MessageEvent('message', {
|
||||
data: JSON.stringify({
|
||||
command: 'openWorkflow',
|
||||
workflow: { nodes: [], connections: {} },
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(mockImportWorkflowExact).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(uiStore.areNotificationsSuppressed).toBe(false);
|
||||
expect(uiStore.allowErrorNotificationsWhenSuppressed).toBe(false);
|
||||
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('should override workflow id to "demo" on demo route when canExecute is not set', async () => {
|
||||
mockRoute.name = 'WorkflowDemo';
|
||||
|
||||
|
|
@ -401,6 +502,47 @@ describe('usePostMessageHandler', () => {
|
|||
cleanup();
|
||||
});
|
||||
|
||||
it('should show an error toast when opening execution fails with error allowance enabled', async () => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }));
|
||||
const uiStore = useUIStore();
|
||||
const { setup, cleanup } = usePostMessageHandler({
|
||||
workflowState,
|
||||
currentWorkflowDocumentStore: shallowRef(null),
|
||||
});
|
||||
setup();
|
||||
|
||||
dispatchPostMessage({
|
||||
command: 'openWorkflow',
|
||||
workflow: { nodes: [], connections: {} },
|
||||
suppressNotifications: true,
|
||||
allowErrorNotifications: true,
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(mockImportWorkflowExact).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(uiStore.areNotificationsSuppressed).toBe(true);
|
||||
expect(uiStore.allowErrorNotificationsWhenSuppressed).toBe(true);
|
||||
|
||||
mockOpenExecution.mockRejectedValueOnce(new Error('Execution could not be opened'));
|
||||
dispatchPostMessage({
|
||||
command: 'openExecution',
|
||||
executionId: 'exec-1',
|
||||
executionMode: 'trigger',
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(mockToastShowMessage).toHaveBeenCalledWith({
|
||||
title: expect.any(String),
|
||||
message: 'Execution could not be opened',
|
||||
type: 'error',
|
||||
});
|
||||
});
|
||||
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('should not set isProductionExecutionPreview for manual executions', async () => {
|
||||
mockOpenExecution.mockResolvedValue({
|
||||
workflowData: { id: 'w1', name: 'Test' },
|
||||
|
|
@ -540,6 +682,47 @@ describe('usePostMessageHandler', () => {
|
|||
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('should show an error toast when opening execution preview fails with error allowance enabled', async () => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }));
|
||||
const uiStore = useUIStore();
|
||||
const { setup, cleanup } = usePostMessageHandler({
|
||||
workflowState,
|
||||
currentWorkflowDocumentStore: shallowRef(null),
|
||||
});
|
||||
setup();
|
||||
|
||||
dispatchPostMessage({
|
||||
command: 'openWorkflow',
|
||||
workflow: { nodes: [], connections: {} },
|
||||
suppressNotifications: true,
|
||||
allowErrorNotifications: true,
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(mockImportWorkflowExact).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(uiStore.areNotificationsSuppressed).toBe(true);
|
||||
expect(uiStore.allowErrorNotificationsWhenSuppressed).toBe(true);
|
||||
|
||||
dispatchPostMessage({
|
||||
command: 'openExecutionPreview',
|
||||
workflow: { connections: {} },
|
||||
nodeExecutionSchema: {},
|
||||
executionStatus: 'success',
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(mockToastShowMessage).toHaveBeenCalledWith({
|
||||
title: expect.any(String),
|
||||
message: 'Invalid workflow object',
|
||||
type: 'error',
|
||||
});
|
||||
});
|
||||
|
||||
cleanup();
|
||||
});
|
||||
});
|
||||
|
||||
describe('message filtering', () => {
|
||||
|
|
|
|||
|
|
@ -78,10 +78,11 @@ export function usePostMessageHandler({
|
|||
projectId?: string;
|
||||
tidyUp?: boolean;
|
||||
suppressNotifications?: boolean;
|
||||
allowErrorNotifications?: boolean;
|
||||
}) {
|
||||
if (json.suppressNotifications) {
|
||||
uiStore.setNotificationsSuppressed(true);
|
||||
}
|
||||
uiStore.setNotificationsSuppressed(json.suppressNotifications === true, {
|
||||
allowErrors: json.allowErrorNotifications === true,
|
||||
});
|
||||
|
||||
if (json.projectId) {
|
||||
await projectsStore.fetchAndSetProject(json.projectId);
|
||||
|
|
|
|||
|
|
@ -215,9 +215,10 @@ describe('useToast', () => {
|
|||
});
|
||||
|
||||
describe('notification suppression', () => {
|
||||
it('should not render notification when notifications are suppressed', async () => {
|
||||
it('should not render non-error notification when notifications are suppressed', async () => {
|
||||
const uiStore = useUIStore();
|
||||
uiStore.areNotificationsSuppressed = true;
|
||||
uiStore.allowErrorNotificationsWhenSuppressed = true;
|
||||
|
||||
toast.showMessage({ message: 'Should not appear', title: 'Suppressed' });
|
||||
|
||||
|
|
@ -232,6 +233,69 @@ describe('useToast', () => {
|
|||
),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should not render error notification when notifications are suppressed and errors are not allowed', async () => {
|
||||
const uiStore = useUIStore();
|
||||
uiStore.areNotificationsSuppressed = true;
|
||||
uiStore.allowErrorNotificationsWhenSuppressed = false;
|
||||
|
||||
toast.showMessage({
|
||||
message: 'Error should not appear',
|
||||
title: 'Suppressed error',
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
await expect(
|
||||
waitFor(
|
||||
() => {
|
||||
expect(screen.getByRole('alert')).toBeVisible();
|
||||
},
|
||||
{ timeout: 200 },
|
||||
),
|
||||
).rejects.toThrow();
|
||||
expect(telemetryTrackSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render error notification when notifications are suppressed and errors are allowed', async () => {
|
||||
const uiStore = useUIStore();
|
||||
uiStore.areNotificationsSuppressed = true;
|
||||
uiStore.allowErrorNotificationsWhenSuppressed = true;
|
||||
|
||||
toast.showMessage({
|
||||
message: 'Error should appear',
|
||||
title: 'Allowed error',
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('alert')).toBeVisible();
|
||||
expect(
|
||||
within(screen.getByRole('alert')).getByRole('heading', { level: 2 }),
|
||||
).toHaveTextContent('Allowed error');
|
||||
expect(screen.getByRole('alert')).toContainHTML('<p>Error should appear</p>');
|
||||
});
|
||||
});
|
||||
|
||||
it('should track telemetry for allowed suppressed error notification', async () => {
|
||||
const uiStore = useUIStore();
|
||||
uiStore.areNotificationsSuppressed = true;
|
||||
uiStore.allowErrorNotificationsWhenSuppressed = true;
|
||||
|
||||
toast.showMessage({
|
||||
message: 'Allowed error tracked',
|
||||
title: 'Allowed error',
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(telemetryTrackSpy).toHaveBeenCalledWith('Instance FE emitted error', {
|
||||
error_title: 'Allowed error',
|
||||
error_message: 'Allowed error tracked',
|
||||
caused_by_credential: false,
|
||||
workflow_id: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearAllStickyNotifications', () => {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ export function useToast() {
|
|||
const { APP_Z_INDEXES } = useStyles();
|
||||
|
||||
function showMessage(messageData: Partial<NotificationOptions>, track = true) {
|
||||
if (uiStore.areNotificationsSuppressed) {
|
||||
const suppressed = uiStore.areNotificationsSuppressed;
|
||||
const allowErrors = uiStore.allowErrorNotificationsWhenSuppressed;
|
||||
if (suppressed && !(allowErrors && messageData.type === 'error')) {
|
||||
return { close: () => {} } as NotificationHandle;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -299,6 +299,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
|||
const addFirstStepOnLoad = ref<boolean>(false);
|
||||
const pendingNotificationsForViews = ref<{ [key in VIEWS]?: NotificationOptions[] }>({});
|
||||
const areNotificationsSuppressed = ref(false);
|
||||
const allowErrorNotificationsWhenSuppressed = ref(false);
|
||||
const processingExecutionResults = ref<boolean>(false);
|
||||
const isBlankRedirect = ref<boolean>(false);
|
||||
|
||||
|
|
@ -629,8 +630,9 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
|||
pendingNotificationsForViews.value[view] = notifications;
|
||||
};
|
||||
|
||||
const setNotificationsSuppressed = (suppressed: boolean) => {
|
||||
const setNotificationsSuppressed = (suppressed: boolean, options?: { allowErrors?: boolean }) => {
|
||||
areNotificationsSuppressed.value = suppressed;
|
||||
allowErrorNotificationsWhenSuppressed.value = suppressed && options?.allowErrors === true;
|
||||
};
|
||||
|
||||
function resetLastInteractedWith() {
|
||||
|
|
@ -756,6 +758,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
|||
isAnyModalOpen,
|
||||
pendingNotificationsForViews,
|
||||
areNotificationsSuppressed,
|
||||
allowErrorNotificationsWhenSuppressed,
|
||||
activeModals,
|
||||
isProcessingExecutionResults,
|
||||
setTheme,
|
||||
|
|
|
|||
|
|
@ -16,8 +16,19 @@ const renderComponent = createComponentRenderer(InstanceAiWorkflowPreview, {
|
|||
global: {
|
||||
stubs: {
|
||||
WorkflowPreview: {
|
||||
template: '<div data-test-id="workflow-preview" />',
|
||||
props: ['mode', 'workflow', 'executionId', 'canOpenNdv', 'hideControls', 'loaderType'],
|
||||
template:
|
||||
'<div data-test-id="workflow-preview" :data-can-execute="canExecute" :data-suppress-notifications="suppressNotifications" :data-allow-error-notifications="allowErrorNotifications" />',
|
||||
props: [
|
||||
'mode',
|
||||
'workflow',
|
||||
'executionId',
|
||||
'canOpenNdv',
|
||||
'canExecute',
|
||||
'hideControls',
|
||||
'suppressNotifications',
|
||||
'allowErrorNotifications',
|
||||
'loaderType',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -73,6 +84,21 @@ describe('InstanceAiWorkflowPreview', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should allow error notifications for executable preview', async () => {
|
||||
mockFetchWorkflow.mockResolvedValue(fakeWorkflow);
|
||||
|
||||
const { getByTestId } = renderComponent({
|
||||
props: { workflowId: 'wf-123', executionId: null },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const preview = getByTestId('workflow-preview');
|
||||
expect(preview).toHaveAttribute('data-can-execute', 'true');
|
||||
expect(preview).toHaveAttribute('data-suppress-notifications', 'true');
|
||||
expect(preview).toHaveAttribute('data-allow-error-notifications', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit iframe-ready on n8nReady postMessage', async () => {
|
||||
const { emitted } = renderComponent({
|
||||
props: { workflowId: null, executionId: null },
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@ defineExpose({ relayPushEvent });
|
|||
:can-execute="true"
|
||||
:hide-controls="false"
|
||||
:suppress-notifications="true"
|
||||
:allow-error-notifications="true"
|
||||
loader-type="spinner"
|
||||
/>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user