Files
Agent-n8n/n8n-n8n-1.109.2/packages/frontend/editor-ui/src/components/WhatsNewModal.test.ts

230 lines
8.5 KiB
TypeScript
Raw Normal View History

2025-09-08 04:48:28 +08:00
import userEvent from '@testing-library/user-event';
import { createTestingPinia } from '@pinia/testing';
import { waitFor, screen } from '@testing-library/vue';
import { createComponentRenderer } from '@/__tests__/render';
import {
cleanupAppModals,
createAppModals,
mockedStore,
type MockedStore,
} from '@/__tests__/utils';
import { useUIStore } from '@/stores/ui.store';
import { WHATS_NEW_MODAL_KEY, VERSIONS_MODAL_KEY } from '@/constants';
import { useVersionsStore } from '@/stores/versions.store';
import type { Version } from '@n8n/rest-api-client/api/versions';
import WhatsNewModal from './WhatsNewModal.vue';
import { useTelemetry } from '@/composables/useTelemetry';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
vi.mock('@/composables/usePageRedirectionHelper', () => {
const goToVersions = vi.fn();
return {
usePageRedirectionHelper: vi.fn().mockReturnValue({
goToVersions,
}),
};
});
vi.mock('@/composables/useTelemetry', () => {
const track = vi.fn();
return {
useTelemetry: () => {
return {
track,
};
},
};
});
const renderComponent = createComponentRenderer(WhatsNewModal, {
props: {
modalName: WHATS_NEW_MODAL_KEY,
},
});
let uiStore: MockedStore<typeof useUIStore>;
let versionsStore: MockedStore<typeof useVersionsStore>;
const telemetry = useTelemetry();
const pageRedirectionHelper = usePageRedirectionHelper();
const currentVersion: Version = {
name: '1.100.0',
nodes: [],
createdAt: '2025-06-24T00:00:00Z',
description: 'Latest version description',
documentationUrl: 'https://docs.n8n.io',
hasBreakingChange: false,
hasSecurityFix: false,
hasSecurityIssue: false,
securityIssueFixVersion: '',
};
describe('WhatsNewModal', () => {
beforeEach(() => {
createAppModals();
createTestingPinia();
uiStore = mockedStore(useUIStore);
uiStore.modalsById = {
[WHATS_NEW_MODAL_KEY]: {
open: true,
},
};
versionsStore = mockedStore(useVersionsStore);
versionsStore.hasVersionUpdates = false;
versionsStore.currentVersion = currentVersion;
versionsStore.latestVersion = currentVersion;
versionsStore.nextVersions = [];
versionsStore.whatsNew = {
createdAt: '2025-06-19T12:35:14.454Z',
updatedAt: null,
title: "What's New in n8n 1.100.0",
calloutText:
'Convert large workflows into sub-workflows for better modularity and performance.',
footer: 'This release contains performance improvements and bug fixes.',
items: [
{
id: 1,
title: 'Convert to sub-workflow',
content:
'Large, monolithic workflows can slow things down. Theyre harder to maintain, ' +
'tougher to debug, and more difficult to scale. With sub-workflows, you can take a ' +
'more modular approach, breaking up big workflows into smaller, manageable parts that ' +
'are easier to reuse, test, understand, and explain.\n\nUntil now, creating sub-workflows ' +
'required copying and pasting nodes manually, setting up a new workflow from scratch, and ' +
'reconnecting everything by hand. **Convert to sub-workflow** allows you to simplify this ' +
'process into a single action, so you can spend more time building and less time restructuring.\n\n' +
'### How it works\n\n1. Highlight the nodes you want to convert to a sub-workflow. These must:\n' +
' - Be fully connected, meaning no missing steps in between them\n' +
' - Start from a single starting node\n' +
' - End with a single node\n' +
'2. Right-click to open the context menu and select ' +
'**Convert to sub-workflow**\n' +
' - Or use the shortcut: `Alt + X`\n' +
'3. n8n will:\n' +
' - Open a new tab containing the selected nodes\n' +
' - Preserve all node parameters as-is\n' +
' - Replace the selected nodes in the original workflow with a **Call My Sub-workflow** node\n\n' +
'_Note:_ You will need to manually adjust the field types in the Start and Return nodes in the new sub-workflow.\n\n' +
'This makes it easier to keep workflows modular, performant, and easier to maintain.\n\n' +
'Learn more about [sub-workflows](https://docs.n8n.io/flow-logic/subworkflows/).\n\n' +
'This release contains performance improvements and bug fixes.\n\n' +
'@[youtube](ZCuL2e4zC_4)\n\n' +
'Fusce malesuada diam eget tincidunt ultrices. Mauris quis mauris mollis, venenatis risus ut.\n\n' +
'## Second level title\n\n### Third level title\n\nThis **is bold**, this _in italics_.\n' +
"~~Strikethrough is also something we support~~.\n\nHere's a peace of code:\n\n" +
'```typescript\nconst props = defineProps<{\n\tmodalName: string;\n\tdata: {\n\t\tarticleId: number;\n\t};\n}>();\n```\n\n' +
'Inline `code also works` withing text.\n\nThis is a list:\n- first\n- second\n- third\n\nAnd this list is ordered\n' +
'1. foo\n2. bar\n3. qux\n\nDividers:\n\nThree or more...\n\n---\n\nHyphens\n\n***\n\nAsterisks\n\n___\n\nUnderscores\n\n---\n\n' +
'<details>\n<summary>Fixes (4)</summary>\n\n' +
'- **Credential Storage Issue** Resolved an issue where credentials would occasionally become inaccessible after server restarts\n' +
'- **Webhook Timeout Handling** Fixed timeout issues with long-running webhook requests\n' +
'- **Node Connection Validation** Improved validation for node connections to prevent invalid workflow configurations\n' +
'- **Memory Leak in Execution Engine** Fixed memory leak that could occur during long-running workflow executions\n\n</details>\n\n',
createdAt: '2025-06-19T12:35:14.454Z',
updatedAt: '2025-06-19T12:41:53.220Z',
publishedAt: '2025-06-19T12:41:53.216Z',
},
],
};
});
afterEach(() => {
cleanupAppModals();
vi.clearAllMocks();
});
it('should render with update button disabled', async () => {
const { getByTestId, queryByTestId } = renderComponent({
props: {
data: {
articleId: 1,
},
},
});
await waitFor(() => expect(getByTestId('whatsNew-modal')).toBeInTheDocument());
await waitFor(() => expect(getByTestId('whats-new-item-1')).toBeInTheDocument());
expect(screen.getByText("What's New in n8n 1.100.0")).toBeInTheDocument();
expect(getByTestId('whats-new-item-1')).toMatchSnapshot();
expect(getByTestId('whats-new-modal-update-button')).toBeDisabled();
expect(queryByTestId('whats-new-modal-next-versions-link')).not.toBeInTheDocument();
});
it('should render with update button enabled', async () => {
versionsStore.hasVersionUpdates = true;
versionsStore.nextVersions = [
{
name: '1.100.1',
nodes: [],
createdAt: '2025-06-24T00:00:00Z',
description: 'Next version description',
documentationUrl: 'https://docs.n8n.io',
hasBreakingChange: false,
hasSecurityFix: false,
hasSecurityIssue: false,
securityIssueFixVersion: '',
},
];
const { getByTestId } = renderComponent({
props: {
data: {
articleId: 1,
},
},
});
await waitFor(() => expect(getByTestId('whatsNew-modal')).toBeInTheDocument());
await waitFor(() => expect(getByTestId('whats-new-item-1')).toBeInTheDocument());
expect(getByTestId('whats-new-modal-update-button')).toBeEnabled();
expect(getByTestId('whats-new-modal-next-versions-link')).toBeInTheDocument();
expect(getByTestId('whats-new-modal-next-versions-link')).toHaveTextContent('1 version behind');
});
it('should take user to update page when Update is clicked', async () => {
versionsStore.hasVersionUpdates = true;
const { getByTestId } = renderComponent({
props: {
data: {
articleId: 1,
},
},
});
await waitFor(() => expect(getByTestId('whatsNew-modal')).toBeInTheDocument());
await waitFor(() => expect(getByTestId('whats-new-item-1')).toBeInTheDocument());
await userEvent.click(getByTestId('whats-new-modal-update-button'));
expect(telemetry.track).toHaveBeenCalledWith('User clicked on update button', {
source: 'whats-new-modal',
});
expect(pageRedirectionHelper.goToVersions).toHaveBeenCalledWith();
});
it('should open the next versions drawer when clicking on the next versions link', async () => {
versionsStore.hasVersionUpdates = true;
const { getByTestId } = renderComponent({
props: {
data: {
articleId: 1,
},
},
});
await waitFor(() => expect(getByTestId('whatsNew-modal')).toBeInTheDocument());
await waitFor(() => expect(getByTestId('whats-new-item-1')).toBeInTheDocument());
await userEvent.click(getByTestId('whats-new-modal-next-versions-link'));
expect(uiStore.openModal).toHaveBeenCalledWith(VERSIONS_MODAL_KEY);
});
});