feat(editor): Migrate Design System and Editor UI to Vue 3 (#6476)

* feat: remove vue-fragment (no-changelog)

* feat: partial design-system migration

* feat: migrate info-accordion and info-tip components

* feat: migrate several components to vue 3

* feat: migrated several components

* feat: migrate several components

* feat: migrate several components

* feat: migrate several components

* feat: re-exported all design system components

* fix: fix design for popper components

* fix: editor kind of working, lots of issues to fix

* fix: fix several vue 3 migration issues

* fix: replace @change with @update:modelValue in several places

* fix: fix translation linking

* fix: fix inline-edit input

* fix: fix ndv and dialog design

* fix: update parameter input event bindings

* fix: rename deprecated lifecycle methods

* fix: fix json view mapping

* build: update lock file

* fix(editor): revisit last conflict with master and fix issues

* fix(editor): revisit last conflict with master and fix issues

* fix: fix expression editor bug causing code mirror to no longer be reactive

* fix: fix resource locator bug

* fix: fix vue-agile integration

* fix: remove global import for vue-agile

* fix: replace element-plus buttons with n8n-buttons everywhere

* fix(editor): Fix various element-plus styles (#6571)

* fix(editor): Fix various element-plus styles

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

* Remove debugging code

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

* Address PR comments

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

---------

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

* fix(editor): Fix loading in production mode [Vue 3] (#6578)

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

* fix(editor): First round of e2e tests fixes with Vue 3 (#6579)

* fix(editor): Fix broken smoke and workflow list e2e tests
* ✔️ Fix failing canvas action tests. Updating some selectors used in credentials and workflow tests

* feat: add vue 3 eslint rules and fix issues

* fix: fix tags-dropdown

* fix: fix white-space issues caused by i18n-t

* fix: rename non-generic click events

* fix: fix search in resources list layout

* fix: fix datatable paginator

* fix: fix popper select caret and dropdown size

* fix: add width to action-dropdown

* fix: fix workflow settings icon not being hidden

* fix: refactor newly added code

* fix: fix merge issue

* fix: fix ndv credentials watcher

* fix: fix workflow saving and grabber notch

* fix: fix nodes list panel transition

* fix: fix node title visibility

* fix: fix data unpinning

* fix: fix value access

* fix: show  input panel only if trigger panel enabled or not trigger node

* fix: fix tags dropdown and executions status spcing

* fix(editor): Prevent execution list to load back when leaving the route (#6697)

fix(editor): prevent execution list to load back when leaving the route

* fix: fix drawer visibility

* fix: fix expression toggle padding

* fix: fix expressions editor styling

* chore: prepare for testing

* fix: fix styling for el-button without patching

* test: fix unit tests in design-system

* test: fix most unit tests

* fix: remove import cycle.

* fix: fix personalization modal tests

* fix further resource mapper test adjustments

* fix: fix multiple tests and n8n-route attr duplication

* fix: fix source control tets

* fix: fixed remaining unit tests

* fix: fix workflows and credentials e2e tests

* fix: fix localizeNodeNames

* fix: update ndv e2e tests

* fix: fix popper left placement arrow

* fix: fix 5-ndv e2e tests

* fix: fix 6-code-node e2e tests

* fix(editor): Drop click outside directive from NodeCreator (#6716)

* fix(editor): Drop click outside directive from NodeCreator

* fix(editor): make sure mouseup outside is unbound at least before the component is unmounted

* fix: fix 10-settings-log-streaming e2e tests

* fix: fix node redrawing

* fix: fix tooltip buttons styling

* fix: fix varous e2e suites

* fix: fix 15-scheduler-node e2e suite

* fix: fix route watcher

* fix: fixed param name update and credential edit

* feat: update event names

* refactor: Remove deprecated `$data` (#6576)

Co-authored-by: Alex Grozav <alex@grozav.com>

* fix: fix 17-sharing e2e suite

* fix: fix tags dropdown

* fix: fix tags manager

* fix(editor): move :deep selectors to a separate scoped style block

* fix: fix sticky component and inline text edit

* fix: update e2e tests

* fix: remove button override references

* fix(editor): Adjust spacing in templates for Vue 3 (#6744)

* fix(editor): Adjust spacing in templates

* fix: Undo unneeded change

* fix: Undo unneeded change

* fix(editor): Adjust NDV height for Vue 3 (#6742)

fix(editor): Adjust NDV height

* fix(editor): Restore collapsed sidebar items for Vue 3 (#6743)

fix(editor): Restore collapsed sidebar items

* fix: fix linting issues

* fix: fix design-system deps

* fix: post-merge fixes

* fix: update tests

* fix: increase timeout for executionslist tets

* chore: fix linting issue

* fix: fix 14-mapping e2e tests in ci

* fix: re-enable tests

* fix: fix workflow duplication e2e tests after tags update

* fix(editor): Change component prop to be typed

* fix: fix tags dropdown in duplicate wf modal

* fix: fix focus behaviour in tags selector

* fix: fix tag creation

* fix: fix log streaming e2e race condition

* fix(editor): Fix Vue 3 linting issues (#6748)

* fix(editor): Fix Vue 3 linting issues

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

* fix MainSidebar linter issues

* revert pnpm lock

* update pnpm lock file

---------

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
Co-authored-by: Alex Grozav <alex@grozav.com>

* fix(editor): Some css fixes for vue3 branch (#6749)

*  Fixing filter button height

*  Update input modal button position

*  Updating tags styling

*  Fix event logging settings spacing

* 👕 Fixing lint errors

* fix: fix linting issues

* Revert to `// eslint-disable-next-line @typescript-eslint/no-misused-promises` disabling of mixins init

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

* fix: fix css issue

* fix(editor): Lint fix

* fix(editor): Fix settings initialisation (#6750)

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

* fix: fix initial settings loading

* fix: replace realClick with click force

* fix: fix randomly failing mapping e2e tests

* fix(editor): Fix menu item event handling

* fix: fix resource filters dropdown events (#6752)

* fix: fix resource filters dropdown events

* fix: remove teleported:false

* fix: fix event selection event naming (#6753)

* fix: removed console.log (#6754)

* fix: rever await nextTick changes

* fix: redo linting changes

* fix(editor): Redraw node connections if adding more than one node to canvas (#6755)

* fix(editor): Redraw node connections if adding more than one node to canvas

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

* Update position before connection two nodes

* Lint fix

---------

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
Co-authored-by: Alex Grozav <alex@grozav.com>

* fix(editor): Fix `ResourceMapper` unit tests (#6758)

* ✔️ Fix matching columns test

* ✔️ Fix multiple matching columns test

* ✔️ Removing `skip` from the last test

* fix: Allow pasting a big workflow (#6760)

* fix: pasting a big workflow

* chore: update comment

* refactor: move try/catch to function

* refactor: move try/catch to function

* fix(editor): Fix modal layer width

* fix: fix position changes

* fix: undo it.only

* fix: make undo/redo multiple steps more verbose

* fix: Fix value survey styles (#6764)

* fix: fix value survey styles

* fix: lint

* Revert "fix: lint"

72869c431f1448861df021be041b61c62f1e3118

* fix: lint

* fix(editor): Fix collapsed sub menu

* fix: Fix drawer animation (#6767)

fix: drawer animation

* fix(editor): Fix source control buttons (#6769)

* fix(editor): Fix App loading & auth  (#6768)

* fix(editor): Fix App loading & auth

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

* Await promises

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

* Fix eslint error

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

---------

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

---------

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
Co-authored-by: OlegIvaniv <me@olegivaniv.com>
Co-authored-by: Milorad FIlipović <milorad@n8n.io>
Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com>
This commit is contained in:
Alex Grozav
2023-07-28 10:51:07 +03:00
committed by GitHub
parent d050b99fb2
commit dd6a4c956a
459 changed files with 8815 additions and 9913 deletions

View File

@@ -1,5 +1,4 @@
import { PiniaVuePlugin } from 'pinia';
import { render, within } from '@testing-library/vue';
import { within } from '@testing-library/vue';
import { merge } from 'lodash-es';
import userEvent from '@testing-library/user-event';
@@ -10,53 +9,52 @@ import { createTestingPinia } from '@pinia/testing';
import BannerStack from '@/components/banners/BannerStack.vue';
import { useUIStore } from '@/stores/ui.store';
import { useUsersStore } from '@/stores/users.store';
import type { RenderOptions } from '@/__tests__/render';
import { createComponentRenderer } from '@/__tests__/render';
let uiStore: ReturnType<typeof useUIStore>;
let usersStore: ReturnType<typeof useUsersStore>;
const DEFAULT_SETUP = {
pinia: createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
settings: merge({}, SETTINGS_STORE_DEFAULT_STATE.settings),
},
[STORES.UI]: {
banners: {
V1: { dismissed: false },
TRIAL: { dismissed: false },
TRIAL_OVER: { dismissed: false },
const initialState = {
[STORES.SETTINGS]: {
settings: merge({}, SETTINGS_STORE_DEFAULT_STATE.settings),
},
[STORES.UI]: {
banners: {
V1: { dismissed: false },
TRIAL: { dismissed: false },
TRIAL_OVER: { dismissed: false },
},
},
[STORES.USERS]: {
currentUserId: 'aaa-bbb',
users: {
'aaa-bbb': {
id: 'aaa-bbb',
globalRole: {
id: '1',
name: 'owner',
scope: 'global',
},
},
[STORES.USERS]: {
currentUserId: 'aaa-bbb',
users: {
'aaa-bbb': {
id: 'aaa-bbb',
globalRole: {
id: '1',
name: 'owner',
scope: 'global',
},
},
'bbb-bbb': {
id: 'bbb-bbb',
globalRoleId: 2,
globalRole: {
id: '2',
name: 'member',
scope: 'global',
},
},
'bbb-bbb': {
id: 'bbb-bbb',
globalRoleId: 2,
globalRole: {
id: '2',
name: 'member',
scope: 'global',
},
},
},
}),
},
};
const renderComponent = (renderOptions: Parameters<typeof render>[1] = {}) =>
render(BannerStack, merge(DEFAULT_SETUP, renderOptions), (vue) => {
vue.use(PiniaVuePlugin);
});
const defaultRenderOptions: RenderOptions = {
pinia: createTestingPinia({ initialState }),
};
const renderComponent = createComponentRenderer(BannerStack, defaultRenderOptions);
describe('BannerStack', () => {
beforeEach(() => {
@@ -82,19 +80,17 @@ describe('BannerStack', () => {
it('should not render dismissed banners', async () => {
const { getByTestId } = renderComponent({
pinia: createTestingPinia({
initialState: merge(
{
[STORES.UI]: {
banners: {
V1: { dismissed: true },
TRIAL: { dismissed: true },
},
initialState: merge(initialState, {
[STORES.UI]: {
banners: {
V1: { dismissed: true },
TRIAL: { dismissed: true },
},
},
DEFAULT_SETUP.pinia,
),
}),
}),
});
const bannerStack = getByTestId('banner-stack');
expect(bannerStack).toBeInTheDocument();
@@ -117,7 +113,7 @@ describe('BannerStack', () => {
it('should permanently dismiss banner on click', async () => {
const { getByTestId } = renderComponent({
pinia: createTestingPinia({
initialState: merge(DEFAULT_SETUP.pinia, {
initialState: merge(initialState, {
[STORES.UI]: {
banners: {
V1: { dismissed: false },
@@ -139,7 +135,7 @@ describe('BannerStack', () => {
it('should not render permanent dismiss link if user is not owner', async () => {
const { queryByTestId } = renderComponent({
pinia: createTestingPinia({
initialState: merge(DEFAULT_SETUP.pinia, {
initialState: merge(initialState, {
[STORES.USERS]: {
currentUserId: 'bbb-bbb',
},

View File

@@ -1,5 +1,3 @@
import { PiniaVuePlugin } from 'pinia';
import { render } from '@testing-library/vue';
import { merge } from 'lodash-es';
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
@@ -7,6 +5,7 @@ import { STORES } from '@/constants';
import { createTestingPinia } from '@pinia/testing';
import CopyInput from '@/components/CopyInput.vue';
import { createComponentRenderer } from '@/__tests__/render';
const DEFAULT_SETUP = {
pinia: createTestingPinia({
@@ -22,10 +21,7 @@ const DEFAULT_SETUP = {
},
};
const renderComponent = (renderOptions: Parameters<typeof render>[1] = {}) =>
render(CopyInput, merge(DEFAULT_SETUP, renderOptions), (vue) => {
vue.use(PiniaVuePlugin);
});
const renderComponent = createComponentRenderer(CopyInput, DEFAULT_SETUP);
describe('BannerStack', () => {
afterEach(() => {

View File

@@ -1,29 +1,14 @@
import { describe, test, expect } from 'vitest';
import Vue from 'vue';
import { PiniaVuePlugin } from 'pinia';
import { createTestingPinia } from '@pinia/testing';
import type { RenderOptions } from '@testing-library/vue';
import { render } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { faker } from '@faker-js/faker';
import ExecutionFilter from '@/components/ExecutionFilter.vue';
import { STORES } from '@/constants';
import { i18nInstance } from '@/plugins/i18n';
import type { IWorkflowShortResponse, ExecutionFilterType } from '@/Interface';
import { useTelemetry } from '@/composables';
vi.mock('@/composables', () => {
const track = vi.fn();
return {
useTelemetry: () => ({
track,
}),
};
});
let telemetry: ReturnType<typeof useTelemetry>;
Vue.use(PiniaVuePlugin);
import { createComponentRenderer } from '@/__tests__/render';
import * as telemetryModule from '@/composables/useTelemetry';
import type { Telemetry } from '@/plugins/telemetry';
import { merge } from 'lodash-es';
const defaultFilterState: ExecutionFilterType = {
status: 'all',
@@ -65,13 +50,36 @@ const initialState = {
},
};
const renderOptions: RenderOptions<ExecutionFilter> = {
i18n: i18nInstance,
};
const renderComponent = createComponentRenderer(ExecutionFilter, {
props: {
teleported: false,
},
});
describe('ExecutionFilter', () => {
beforeEach(() => {
telemetry = useTelemetry();
afterAll(() => {
vi.clearAllMocks();
});
test('telemetry sent only once after component is mounted', async () => {
const track = vi.fn();
const spy = vi.spyOn(telemetryModule, 'useTelemetry');
spy.mockImplementation(
() =>
({
track,
} as unknown as Telemetry),
);
const { getByTestId } = renderComponent({
pinia: createTestingPinia({ initialState }),
});
const customDataKeyInput = getByTestId('execution-filter-saved-data-key-input');
await userEvent.type(customDataKeyInput, 'test');
await userEvent.type(customDataKeyInput, 'key');
expect(track).toHaveBeenCalledTimes(1);
});
test.each([
@@ -86,30 +94,49 @@ describe('ExecutionFilter', () => {
])(
'renders in %s environment on %s deployment with advancedExecutionFilters %s and workflows %s',
async (environment, deployment, advancedExecutionFilters, workflows) => {
initialState[STORES.SETTINGS].settings.license.environment = environment;
initialState[STORES.SETTINGS].settings.deployment.type = deployment;
initialState[STORES.SETTINGS].settings.enterprise.advancedExecutionFilters =
advancedExecutionFilters;
renderOptions.pinia = createTestingPinia({ initialState });
renderOptions.props = { workflows };
const { getByTestId, queryByTestId } = render(ExecutionFilter, renderOptions);
const { html, getByTestId, queryByTestId, queryAllByTestId } = renderComponent({
props: { workflows },
pinia: createTestingPinia({
initialState: merge(initialState, {
[STORES.SETTINGS]: {
settings: {
license: {
environment,
},
deployment: {
type: deployment,
},
enterprise: {
advancedExecutionFilters,
},
},
},
}),
}),
});
await userEvent.click(getByTestId('executions-filter-button'));
await userEvent.hover(getByTestId('execution-filter-saved-data-key-input'));
if (advancedExecutionFilters) {
expect(queryByTestId('executions-filter-view-plans-link')).not.toBeInTheDocument();
expect(queryByTestId('executions-filter-view-plans-link')).not.toBeVisible();
}
expect(queryByTestId('executions-filter-reset-button')).not.toBeInTheDocument();
expect(!!queryByTestId('executions-filter-workflows-select')).toBe(!!workflows?.length);
const select = queryByTestId('executions-filter-workflows-select');
if (workflows && workflows.length > 0) {
expect(select).toBeVisible();
} else {
expect(select).not.toBeInTheDocument();
}
},
);
test('state change', async () => {
const { getByTestId, queryByTestId, emitted } = render(ExecutionFilter, renderOptions);
const { html, getByTestId, queryByTestId, emitted } = renderComponent({
pinia: createTestingPinia({ initialState }),
});
const filterChangedEvent = emitted().filterChanged;
expect(filterChangedEvent).toHaveLength(1);
@@ -123,6 +150,7 @@ describe('ExecutionFilter', () => {
expect(getByTestId('execution-filter-form')).toBeVisible();
await userEvent.click(getByTestId('executions-filter-status-select'));
await userEvent.click(getByTestId('executions-filter-status-select').querySelectorAll('li')[1]);
expect(emitted().filterChanged).toHaveLength(2);
@@ -136,14 +164,4 @@ describe('ExecutionFilter', () => {
expect(queryByTestId('executions-filter-reset-button')).not.toBeInTheDocument();
expect(queryByTestId('execution-filter-badge')).not.toBeInTheDocument();
});
test('telemetry sent only once after component is mounted', async () => {
const { getByTestId } = render(ExecutionFilter, renderOptions);
const customDataKeyInput = getByTestId('execution-filter-saved-data-key-input');
await userEvent.type(customDataKeyInput, 'test');
await userEvent.type(customDataKeyInput, 'key');
expect(telemetry.track).toHaveBeenCalledTimes(1);
});
});

View File

@@ -1,17 +1,16 @@
import { vi, describe, it, expect } from 'vitest';
import { merge } from 'lodash-es';
import { PiniaVuePlugin } from 'pinia';
import { createTestingPinia } from '@pinia/testing';
import { render } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { faker } from '@faker-js/faker';
import { STORES } from '@/constants';
import ExecutionsList from '@/components/ExecutionsList.vue';
import { i18nInstance } from '@/plugins/i18n';
import type { IWorkflowDb } from '@/Interface';
import type { IExecutionsSummary } from 'n8n-workflow';
import { retry, SETTINGS_STORE_DEFAULT_STATE, waitAllPromises } from '@/__tests__/utils';
import { useWorkflowsStore } from '@/stores';
import { useWorkflowsStore } from '@/stores/workflows.store';
import type { RenderOptions } from '@/__tests__/render';
import { createComponentRenderer } from '@/__tests__/render';
let pinia: ReturnType<typeof createTestingPinia>;
@@ -65,28 +64,19 @@ const generateExecutionsData = () =>
estimated: false,
}));
const renderComponent = async () => {
const renderResult = render(
ExecutionsList,
{
pinia,
propsData: {
autoRefreshEnabled: false,
},
i18n: i18nInstance,
const defaultRenderOptions: RenderOptions = {
props: {
autoRefreshEnabled: false,
},
global: {
stubs: {
stubs: ['font-awesome-icon'],
},
(vue) => {
vue.use(PiniaVuePlugin);
vue.prototype.$telemetry = {
track: () => {},
};
},
);
await waitAllPromises();
return renderResult;
},
};
const renderComponent = createComponentRenderer(ExecutionsList, defaultRenderOptions);
describe('ExecutionsList.vue', () => {
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
let workflowsData: IWorkflowDb[];
@@ -123,7 +113,13 @@ describe('ExecutionsList.vue', () => {
results: [],
estimated: false,
});
const { queryAllByTestId, queryByTestId, getByTestId } = await renderComponent();
const { queryAllByTestId, queryByTestId, getByTestId } = renderComponent({
global: {
plugins: [pinia],
},
});
await waitAllPromises();
await userEvent.click(getByTestId('execution-auto-refresh-checkbox'));
expect(queryAllByTestId('select-execution-checkbox').length).toBe(0);
@@ -132,58 +128,67 @@ describe('ExecutionsList.vue', () => {
expect(getByTestId('execution-list-empty')).toBeInTheDocument();
});
it('should handle selection flow when loading more items', async () => {
const storeSpy = vi
.spyOn(workflowsStore, 'getPastExecutions')
.mockResolvedValueOnce(executionsData[0])
.mockResolvedValueOnce(executionsData[1]);
it(
'should handle selection flow when loading more items',
async () => {
const storeSpy = vi
.spyOn(workflowsStore, 'getPastExecutions')
.mockResolvedValueOnce(executionsData[0])
.mockResolvedValueOnce(executionsData[1]);
const { getByTestId, getAllByTestId, queryByTestId } = await renderComponent();
const { getByTestId, getAllByTestId, queryByTestId } = renderComponent({
global: {
plugins: [pinia],
},
});
await waitAllPromises();
expect(storeSpy).toHaveBeenCalledTimes(1);
expect(storeSpy).toHaveBeenCalledTimes(1);
await userEvent.click(getByTestId('select-visible-executions-checkbox'));
await userEvent.click(getByTestId('select-visible-executions-checkbox'));
await retry(() =>
await retry(() =>
expect(
getAllByTestId('select-execution-checkbox').filter((el) =>
el.contains(el.querySelector(':checked')),
).length,
).toBe(10),
);
expect(getByTestId('select-all-executions-checkbox')).toBeInTheDocument();
expect(getByTestId('selected-executions-info').textContent).toContain(10);
await userEvent.click(getByTestId('load-more-button'));
expect(storeSpy).toHaveBeenCalledTimes(2);
expect(getAllByTestId('select-execution-checkbox').length).toBe(20);
expect(
getAllByTestId('select-execution-checkbox').filter((el) =>
el.contains(el.querySelector(':checked')),
).length,
).toBe(10),
);
expect(getByTestId('select-all-executions-checkbox')).toBeInTheDocument();
expect(getByTestId('selected-executions-info').textContent).toContain(10);
).toBe(10);
await userEvent.click(getByTestId('load-more-button'));
await userEvent.click(getByTestId('select-all-executions-checkbox'));
expect(getAllByTestId('select-execution-checkbox').length).toBe(20);
expect(
getAllByTestId('select-execution-checkbox').filter((el) =>
el.contains(el.querySelector(':checked')),
).length,
).toBe(20);
expect(getByTestId('selected-executions-info').textContent).toContain(20);
expect(storeSpy).toHaveBeenCalledTimes(2);
expect(getAllByTestId('select-execution-checkbox').length).toBe(20);
expect(
getAllByTestId('select-execution-checkbox').filter((el) =>
el.contains(el.querySelector(':checked')),
).length,
).toBe(10);
await userEvent.click(getByTestId('select-all-executions-checkbox'));
expect(getAllByTestId('select-execution-checkbox').length).toBe(20);
expect(
getAllByTestId('select-execution-checkbox').filter((el) =>
el.contains(el.querySelector(':checked')),
).length,
).toBe(20);
expect(getByTestId('selected-executions-info').textContent).toContain(20);
await userEvent.click(getAllByTestId('select-execution-checkbox')[2]);
expect(getAllByTestId('select-execution-checkbox').length).toBe(20);
expect(
getAllByTestId('select-execution-checkbox').filter((el) =>
el.contains(el.querySelector(':checked')),
).length,
).toBe(19);
expect(getByTestId('selected-executions-info').textContent).toContain(19);
expect(getByTestId('select-visible-executions-checkbox')).toBeInTheDocument();
expect(queryByTestId('select-all-executions-checkbox')).not.toBeInTheDocument();
});
await userEvent.click(getAllByTestId('select-execution-checkbox')[2]);
expect(getAllByTestId('select-execution-checkbox').length).toBe(20);
expect(
getAllByTestId('select-execution-checkbox').filter((el) =>
el.contains(el.querySelector(':checked')),
).length,
).toBe(19);
expect(getByTestId('selected-executions-info').textContent).toContain(19);
expect(getByTestId('select-visible-executions-checkbox')).toBeInTheDocument();
expect(queryByTestId('select-all-executions-checkbox')).not.toBeInTheDocument();
},
{ timeout: 10000 },
);
it('should show "retry" data when appropriate', async () => {
vi.spyOn(workflowsStore, 'getPastExecutions').mockResolvedValue(executionsData[0]);
@@ -192,7 +197,12 @@ describe('ExecutionsList.vue', () => {
(execution) => !execution.retryOf && execution.retrySuccessId,
);
const { queryAllByText } = await renderComponent();
const { queryAllByText } = renderComponent({
global: {
plugins: [pinia],
},
});
await waitAllPromises();
expect(queryAllByText(/Retry of/).length).toBe(retryOf.length);
expect(queryAllByText(/Success retry/).length).toBe(retrySuccessId.length);

View File

@@ -1,34 +1,22 @@
import { describe, it, expect, vi } from 'vitest';
import { render, waitFor } from '@testing-library/vue';
import { waitFor } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { PiniaVuePlugin } from 'pinia';
import { createTestingPinia } from '@pinia/testing';
import { merge } from 'lodash-es';
import { SOURCE_CONTROL_PULL_MODAL_KEY, STORES } from '@/constants';
import { i18nInstance } from '@/plugins/i18n';
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
import MainSidebarSourceControl from '@/components/MainSidebarSourceControl.vue';
import { useSourceControlStore, useUIStore } from '@/stores';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useUIStore } from '@/stores/ui.store';
import { useUsersStore } from '@/stores/users.store';
import { createComponentRenderer } from '@/__tests__/render';
let pinia: ReturnType<typeof createTestingPinia>;
let sourceControlStore: ReturnType<typeof useSourceControlStore>;
let uiStore: ReturnType<typeof useUIStore>;
let usersStore: ReturnType<typeof useUsersStore>;
const renderComponent = (renderOptions: Parameters<typeof render>[1] = {}) => {
return render(
MainSidebarSourceControl,
{
pinia,
i18n: i18nInstance,
...renderOptions,
},
(vue) => {
vue.use(PiniaVuePlugin);
},
);
};
const renderComponent = createComponentRenderer(MainSidebarSourceControl);
describe('MainSidebarSourceControl', () => {
beforeEach(() => {
@@ -51,12 +39,12 @@ describe('MainSidebarSourceControl', () => {
it('should render nothing when not instance owner', async () => {
vi.spyOn(usersStore, 'isInstanceOwner', 'get').mockReturnValue(false);
const { container } = renderComponent({ props: { isCollapsed: false } });
const { container } = renderComponent({ pinia, props: { isCollapsed: false } });
expect(container).toBeEmptyDOMElement();
});
it('should render empty content when instance owner but not connected', async () => {
const { getByTestId } = renderComponent({ props: { isCollapsed: false } });
const { getByTestId } = renderComponent({ pinia, props: { isCollapsed: false } });
expect(getByTestId('main-sidebar-source-control')).toBeInTheDocument();
expect(getByTestId('main-sidebar-source-control')).toBeEmptyDOMElement();
});
@@ -75,7 +63,10 @@ describe('MainSidebarSourceControl', () => {
});
it('should render the appropriate content', async () => {
const { getByTestId, queryByTestId } = renderComponent({ props: { isCollapsed: false } });
const { getByTestId, queryByTestId } = renderComponent({
pinia,
props: { isCollapsed: false },
});
expect(getByTestId('main-sidebar-source-control-connected')).toBeInTheDocument();
expect(queryByTestId('main-sidebar-source-control-setup')).not.toBeInTheDocument();
});
@@ -84,7 +75,10 @@ describe('MainSidebarSourceControl', () => {
vi.spyOn(sourceControlStore, 'pullWorkfolder').mockRejectedValueOnce({
response: { status: 400 },
});
const { getAllByRole, getByRole } = renderComponent({ props: { isCollapsed: false } });
const { getAllByRole, getByRole } = renderComponent({
pinia,
props: { isCollapsed: false },
});
await userEvent.click(getAllByRole('button')[0]);
await waitFor(() => expect(getByRole('alert')).toBeInTheDocument());
@@ -97,7 +91,10 @@ describe('MainSidebarSourceControl', () => {
});
const openModalSpy = vi.spyOn(uiStore, 'openModalWithData');
const { getAllByRole, getByRole } = renderComponent({ props: { isCollapsed: false } });
const { getAllByRole, getByRole } = renderComponent({
pinia,
props: { isCollapsed: false },
});
await userEvent.click(getAllByRole('button')[0]);
await waitFor(() =>

View File

@@ -1,19 +1,23 @@
import { PiniaVuePlugin } from 'pinia';
import { createLocalVue, mount } from '@vue/test-utils';
import PersonalizationModal from '@/components/PersonalizationModal.vue';
import { createTestingPinia } from '@pinia/testing';
import { PERSONALIZATION_MODAL_KEY } from '@/constants';
import { PERSONALIZATION_MODAL_KEY, STORES } from '@/constants';
import { retry } from '@/__tests__/utils';
import { createComponentRenderer } from '@/__tests__/render';
import { fireEvent } from '@testing-library/vue';
describe('PersonalizationModal.vue', () => {
const pinia = createTestingPinia({
const renderComponent = createComponentRenderer(PersonalizationModal, {
props: {
teleported: false,
appendToBody: false,
},
pinia: createTestingPinia({
initialState: {
ui: {
[STORES.UI]: {
modals: {
[PERSONALIZATION_MODAL_KEY]: { open: true },
},
},
settings: {
[STORES.SETTINGS]: {
settings: {
templates: {
host: '',
@@ -21,41 +25,39 @@ describe('PersonalizationModal.vue', () => {
},
},
},
});
const localVue = createLocalVue();
localVue.use(PiniaVuePlugin);
}),
});
describe('PersonalizationModal.vue', () => {
it('should render correctly', async () => {
const wrapper = mount(PersonalizationModal, {
localVue,
pinia,
});
const wrapper = renderComponent();
await retry(() => expect(wrapper.find('.modal-content').exists()).toBe(true));
await retry(() =>
expect(wrapper.container.querySelector('.modal-content')).toBeInTheDocument(),
);
expect(wrapper.findAll('.n8n-select').length).toEqual(5);
expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.container.querySelectorAll('.n8n-select').length).toEqual(5);
});
it('should display new option when role is "Devops", "Engineering", "IT", or "Sales and marketing"', async () => {
const wrapper = mount(PersonalizationModal, {
localVue,
pinia,
});
const wrapper = renderComponent();
await retry(() => expect(wrapper.find('.modal-content').exists()).toBe(true));
await retry(() =>
expect(wrapper.container.querySelector('.modal-content')).toBeInTheDocument(),
);
for (const index of [3, 4, 5, 6]) {
await wrapper.find('.n8n-select[name="role"]').trigger('click');
await wrapper
.find('.n8n-select[name="role"]')
.findAll('.el-select-dropdown__item')
.at(index)
.trigger('click');
const select = wrapper.container.querySelectorAll('.n8n-select')[1]!;
await fireEvent.click(select);
const item = select.querySelectorAll('.el-select-dropdown__item')[index];
await fireEvent.click(item);
await retry(() => {
expect(wrapper.findAll('.n8n-select').length).toEqual(6);
expect(wrapper.find('.n8n-select[name^="automationGoal"]').exists()).toBe(true);
expect(wrapper.container.querySelectorAll('.n8n-select').length).toEqual(6);
expect(wrapper.container.querySelector('[name^="automationGoal"]')).toBeInTheDocument();
});
}
});

View File

@@ -1,6 +1,3 @@
import { PiniaVuePlugin } from 'pinia';
import { render, within } from '@testing-library/vue';
import { merge } from 'lodash-es';
import {
DEFAULT_SETUP,
MAPPING_COLUMNS_RESPONSE,
@@ -12,15 +9,13 @@ import { waitAllPromises } from '@/__tests__/utils';
import * as workflowHelpers from '@/mixins/workflowHelpers';
import ResourceMapper from '@/components/ResourceMapper/ResourceMapper.vue';
import userEvent from '@testing-library/user-event';
import { createComponentRenderer } from '@/__tests__/render';
import type { SpyInstance } from 'vitest';
let nodeTypeStore: ReturnType<typeof useNodeTypesStore>;
let fetchFieldsSpy: SpyInstance, resolveParameterSpy: SpyInstance;
const renderComponent = (renderOptions: Parameters<typeof render>[1] = {}) =>
render(ResourceMapper, merge(DEFAULT_SETUP, renderOptions), (vue) => {
vue.use(PiniaVuePlugin);
});
const renderComponent = createComponentRenderer(ResourceMapper, DEFAULT_SETUP);
describe('ResourceMapper.vue', () => {
beforeAll(() => {
@@ -50,18 +45,21 @@ describe('ResourceMapper.vue', () => {
).toBe(MAPPING_COLUMNS_RESPONSE.fields.length);
});
it('renders add mode properly', async () => {
const { getByTestId, queryByTestId } = renderComponent({
props: {
parameter: {
typeOptions: {
resourceMapper: {
mode: 'add',
it('renders add mode properly', async () => {
const { getByTestId, queryByTestId } = renderComponent(
{
props: {
parameter: {
typeOptions: {
resourceMapper: {
mode: 'add',
},
},
},
},
},
});
{ merge: true },
);
await waitAllPromises();
expect(getByTestId('resource-mapper-container')).toBeInTheDocument();
// This mode doesn't render matching column selector
@@ -69,113 +67,124 @@ describe('ResourceMapper.vue', () => {
});
it('renders multi-key match selector properly', async () => {
const { container, getByTestId } = renderComponent({
props: {
parameter: {
typeOptions: {
resourceMapper: {
mode: 'upsert',
multiKeyMatch: true,
const { container, getByTestId } = renderComponent(
{
props: {
parameter: {
typeOptions: {
resourceMapper: {
mode: 'upsert',
multiKeyMatch: true,
},
},
},
},
},
});
{ merge: true },
);
await waitAllPromises();
expect(getByTestId('resource-mapper-container')).toBeInTheDocument();
expect(container.querySelector('.el-select__tags')).toBeInTheDocument();
});
it('does not render mapping mode selector if it is disabled', async () => {
const { getByTestId, queryByTestId } = renderComponent({
props: {
parameter: {
typeOptions: {
resourceMapper: {
supportAutoMap: false,
const { getByTestId, queryByTestId } = renderComponent(
{
props: {
parameter: {
typeOptions: {
resourceMapper: {
supportAutoMap: false,
},
},
},
},
},
});
{ merge: true },
);
await waitAllPromises();
expect(getByTestId('resource-mapper-container')).toBeInTheDocument();
expect(queryByTestId('mapping-mode-select')).not.toBeInTheDocument();
});
it('renders field on top of the list when they are selected for matching', async () => {
const { container, getByTestId } = renderComponent({
props: {
parameter: {
typeOptions: {
resourceMapper: {
supportAutoMap: true,
mode: 'upsert',
multiKeyMatch: false,
},
},
},
},
});
await waitAllPromises();
expect(getByTestId('resource-mapper-container')).toBeInTheDocument();
// Id should be the first field in the list
expect(container.querySelector('.parameter-item')).toContainHTML('id (using to match)');
// // Select Last Name as matching column
await userEvent.click(getByTestId('matching-column-select'));
const matchingColumnDropdown = getByTestId('matching-column-select');
await userEvent.click(within(matchingColumnDropdown).getByText('Last Name'));
// // Now, last name should be the first field in the list
expect(container.querySelector('.parameter-item')).toContainHTML('Last Name (using to match)');
});
it('renders selected matching columns properly when multiple key matching is enabled', async () => {
const { getByTestId, getByText, queryByText } = renderComponent({
props: {
parameter: {
typeOptions: {
resourceMapper: {
supportAutoMap: true,
mode: 'upsert',
multiKeyMatch: true,
},
},
},
},
});
await waitAllPromises();
expect(getByTestId('resource-mapper-container')).toBeInTheDocument();
const matchingColumnDropdown = getByTestId('matching-column-select');
await userEvent.click(matchingColumnDropdown);
await userEvent.click(within(matchingColumnDropdown).getByText('Username'));
// Both matching columns should be rendered in the dropdown
expect(getByTestId('matching-column-select')).toContainHTML(
'<span class="el-select__tags-text">id</span>',
);
expect(getByTestId('matching-column-select')).toContainHTML(
'<span class="el-select__tags-text">Username</span>',
);
// All selected columns should have correct labels
expect(getByText('id (using to match)')).toBeInTheDocument();
expect(getByText('Username (using to match)')).toBeInTheDocument();
expect(queryByText('First Name (using to match)')).not.toBeInTheDocument();
});
it('uses field words defined in node definition', async () => {
const { getByText } = renderComponent({
props: {
parameter: {
typeOptions: {
resourceMapper: {
fieldWords: {
singular: 'foo',
plural: 'foos',
const { container, getByTestId } = renderComponent(
{
props: {
parameter: {
typeOptions: {
resourceMapper: {
supportAutoMap: true,
mode: 'upsert',
multiKeyMatch: false,
},
},
},
},
},
});
{ merge: true },
);
await waitAllPromises();
expect(getByTestId('resource-mapper-container')).toBeInTheDocument();
// Id should be the first field in the list
expect(container.querySelector('.parameter-item')).toContainHTML('id (using to match)');
// Select Last Name as matching column
await userEvent.click(getByTestId('matching-column-option-Last name'));
// Now, last name should be the first field in the list
expect(container.querySelector('.parameter-item div.title')).toHaveTextContent(
'Last name (using to match)',
);
});
it('renders selected matching columns properly when multiple key matching is enabled', async () => {
const { getByTestId, getAllByText, queryByText } = renderComponent(
{
props: {
parameter: {
typeOptions: {
resourceMapper: {
supportAutoMap: true,
mode: 'upsert',
multiKeyMatch: true,
},
},
},
},
},
{ merge: true },
);
await waitAllPromises();
expect(getByTestId('resource-mapper-container')).toBeInTheDocument();
await userEvent.click(getByTestId('matching-column-option-Username'));
// Both matching columns (id and Username) should be rendered in the dropdown
expect(
getByTestId('matching-column-select').querySelector('.el-select > div'),
).toHaveTextContent('idUsername');
// All selected columns should have correct labels
expect(getAllByText('id (using to match)')[0]).toBeInTheDocument();
expect(getAllByText('Username (using to match)')[0]).toBeInTheDocument();
expect(queryByText('First Name (using to match)')).not.toBeInTheDocument();
});
it('uses field words defined in node definition', async () => {
const { getByText } = renderComponent(
{
props: {
parameter: {
typeOptions: {
resourceMapper: {
fieldWords: {
singular: 'foo',
plural: 'foos',
},
},
},
},
},
},
{ merge: true },
);
await waitAllPromises();
expect(getByText('Set the value for each foo')).toBeInTheDocument();
expect(
@@ -186,24 +195,27 @@ describe('ResourceMapper.vue', () => {
});
it('should render correct fields based on saved schema', async () => {
const { getByTestId, queryAllByTestId } = renderComponent({
props: {
node: {
parameters: {
columns: {
schema: UPDATED_SCHEMA,
const { getByTestId, queryAllByTestId } = renderComponent(
{
props: {
node: {
parameters: {
columns: {
schema: UPDATED_SCHEMA,
},
},
},
},
parameter: {
typeOptions: {
resourceMapper: {
mode: 'add',
parameter: {
typeOptions: {
resourceMapper: {
mode: 'add',
},
},
},
},
},
});
{ merge: true },
);
await waitAllPromises();
// There should be 4 fields rendered and only 1 of them should have remove button
expect(
@@ -213,24 +225,27 @@ describe('ResourceMapper.vue', () => {
});
it('should render correct options based on saved schema', async () => {
const { getByTestId } = renderComponent({
props: {
node: {
parameters: {
columns: {
schema: UPDATED_SCHEMA,
const { getByTestId } = renderComponent(
{
props: {
node: {
parameters: {
columns: {
schema: UPDATED_SCHEMA,
},
},
},
},
parameter: {
typeOptions: {
resourceMapper: {
mode: 'add',
parameter: {
typeOptions: {
resourceMapper: {
mode: 'add',
},
},
},
},
},
});
{ merge: true },
);
await waitAllPromises();
// Should have one option in the bottom dropdown for one removed field
expect(getByTestId('add-fields-select').querySelectorAll('li').length).toBe(1);

View File

@@ -1,150 +1,33 @@
import type Vue from 'vue';
import { defineComponent } from 'vue';
import { PiniaVuePlugin } from 'pinia';
import { render, waitFor } from '@testing-library/vue';
import { waitFor } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { createTestingPinia } from '@pinia/testing';
import { merge } from 'lodash-es';
import RunData from '@/components/RunData.vue';
import { STORES, VIEWS } from '@/constants';
import { useSSOStore } from '@/stores/sso.store';
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
import { externalHooks } from '@/mixins/externalHooks';
import { genericHelpers } from '@/mixins/genericHelpers';
import { pinData } from '@/mixins/pinData';
import { useNDVStore, useWorkflowsStore } from '@/stores';
import { createComponentRenderer } from '@/__tests__/render';
let pinia: ReturnType<typeof createTestingPinia>;
let ssoStore: ReturnType<typeof useSSOStore>;
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
let ndvStore: ReturnType<typeof useNDVStore>;
function TelemetryPlugin(vue: typeof Vue): void {
Object.defineProperty(vue, '$telemetry', {
get() {
return {
track: () => {},
};
const renderComponent = createComponentRenderer(RunData, {
props: {
nodeUi: {
name: 'Test Node',
},
});
Object.defineProperty(vue.prototype, '$telemetry', {
get() {
return {
track: () => {},
};
},
global: {
mocks: {
$route: {
name: VIEWS.WORKFLOW,
},
},
});
}
const nodeHelpers = defineComponent({
methods: {
getNodeInputData: vi.fn().mockReturnValue([
{
json: {
id: 1,
name: 'Test 1',
json: {
data: 'Json data 1',
},
},
},
{
json: {
id: 2,
name: 'Test 2',
json: {
data: 'Json data 2',
},
},
},
]),
},
});
const renderComponent = (renderOptions: Parameters<typeof render>[1] = {}) =>
render(
RunData,
merge(
{
pinia,
mocks: {
$route: {
name: VIEWS.WORKFLOW,
},
},
mixins: [externalHooks, genericHelpers, nodeHelpers, pinData],
},
renderOptions,
),
(vue) => {
vue.use(TelemetryPlugin);
vue.use(PiniaVuePlugin);
},
);
describe('RunData', () => {
beforeEach(() => {
pinia = createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
settings: merge({}, SETTINGS_STORE_DEFAULT_STATE.settings),
},
},
});
ssoStore = useSSOStore();
workflowsStore = useWorkflowsStore();
ndvStore = useNDVStore();
vi.spyOn(workflowsStore, 'getWorkflowExecution', 'get').mockReturnValue({
id: '1',
finished: true,
mode: 'trigger',
startedAt: new Date(),
workflowData: {
id: '1',
name: 'Test Workflow',
versionId: '1',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
active: false,
nodes: [],
connections: {},
},
data: {
resultData: {
runData: {
'Test Node': [
{
startTime: new Date().getTime(),
executionTime: new Date().getTime(),
data: {},
source: [null],
},
],
},
},
},
});
});
afterEach(() => {
vi.clearAllMocks();
});
it('should render data correctly even when "item.json" has another "json" key', async () => {
vi.spyOn(ndvStore, 'getPanelDisplayMode').mockReturnValue('schema');
vi.spyOn(ndvStore, 'activeNode', 'get').mockReturnValue({
id: '1',
typeVersion: 1,
name: 'Test Node',
position: [0, 0],
type: 'test',
parameters: {},
});
const { getByText, getAllByTestId, getByTestId } = renderComponent({
const { html, getByText, getAllByTestId, getByTestId } = renderComponent({
props: {
nodeUi: {
id: '1',
name: 'Test Node',
position: [0, 0],
},
@@ -154,10 +37,90 @@ describe('RunData', () => {
mappingEnabled: true,
distanceFromActive: 0,
},
pinia: createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
settings: merge({}, SETTINGS_STORE_DEFAULT_STATE.settings),
},
[STORES.NDV]: {
output: {
displayMode: 'schema',
},
activeNodeName: 'Test Node',
},
[STORES.WORKFLOWS]: {
workflow: {
nodes: [
{
id: '1',
typeVersion: 1,
name: 'Test Node',
position: [0, 0],
type: 'test',
parameters: {},
},
],
},
workflowExecutionData: {
id: '1',
finished: true,
mode: 'trigger',
startedAt: new Date(),
workflowData: {
id: '1',
name: 'Test Workflow',
versionId: '1',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
active: false,
nodes: [],
connections: {},
},
data: {
resultData: {
runData: {
'Test Node': [
{
startTime: new Date().getTime(),
executionTime: new Date().getTime(),
data: {
main: [
[
{
json: {
id: 1,
name: 'Test 1',
json: {
data: 'Json data 1',
},
},
},
{
json: {
id: 2,
name: 'Test 2',
json: {
data: 'Json data 2',
},
},
},
],
],
},
source: [null],
},
],
},
},
},
},
},
},
}),
});
await userEvent.click(getByTestId('ndv-pin-data'));
await waitFor(() => getAllByTestId('run-data-schema-item'));
await waitFor(() => getAllByTestId('run-data-schema-item'), { timeout: 1000 });
expect(getByText('Test 1')).toBeInTheDocument();
expect(getByText('Json data 1')).toBeInTheDocument();
});

View File

@@ -1,44 +1,40 @@
import Vue from 'vue';
import { PiniaVuePlugin } from 'pinia';
import { createTestingPinia } from '@pinia/testing';
import { render, screen, cleanup } from '@testing-library/vue';
import { screen, cleanup } from '@testing-library/vue';
import RunDataJson from '@/components/RunDataJson.vue';
import { createComponentRenderer } from '@/__tests__/render';
Vue.use(PiniaVuePlugin);
describe('RunDataJson.vue', () => {
const DEFAULT_SETUP = {
pinia: createTestingPinia(),
props: {
mappingEnabled: true,
editMode: { enabled: false },
inputData: [
{
json: {
list: [1, 2, 3],
record: { name: 'Joe' },
myNumber: 123,
myStringNumber: '456',
myStringText: 'abc',
nil: null,
d: undefined,
},
const renderComponent = createComponentRenderer(RunDataJson, {
props: {
mappingEnabled: true,
editMode: { enabled: false },
inputData: [
{
json: {
list: [1, 2, 3],
record: { name: 'Joe' },
myNumber: 123,
myStringNumber: '456',
myStringText: 'abc',
nil: null,
d: undefined,
},
],
node: {
parameters: {
keepOnlySet: false,
values: {},
options: {},
},
id: '820ea733-d8a6-4379-8e73-88a2347ea003',
name: 'Set',
type: 'n8n-nodes-base.set',
typeVersion: 1,
position: [380, 1060],
disabled: false,
},
],
node: {
parameters: {
keepOnlySet: false,
values: {},
options: {},
},
id: '820ea733-d8a6-4379-8e73-88a2347ea003',
name: 'Set',
type: 'n8n-nodes-base.set',
typeVersion: 1,
position: [380, 1060],
disabled: false,
},
},
global: {
mocks: {
$locale: {
baseText() {
@@ -49,13 +45,17 @@ describe('RunDataJson.vue', () => {
getters: {},
},
},
};
},
});
describe('RunDataJson.vue', () => {
beforeEach(cleanup);
it('renders json values properly', () => {
const { container } = render(RunDataJson, DEFAULT_SETUP, (vue) => {
vue.use(PiniaVuePlugin);
const { container } = renderComponent({
global: {
plugins: [createTestingPinia()],
},
});
expect(container).toMatchSnapshot();

View File

@@ -1,82 +1,86 @@
import { PiniaVuePlugin } from 'pinia';
import { createTestingPinia } from '@pinia/testing';
import { render, cleanup } from '@testing-library/vue';
import { cleanup } from '@testing-library/vue';
import RunDataJsonSchema from '@/components/RunDataSchema.vue';
import { STORES } from '@/constants';
import { createComponentRenderer } from '@/__tests__/render';
describe('RunDataJsonSchema.vue', () => {
const renderOptions = {
pinia: createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
settings: {
templates: {
enabled: true,
host: 'https://api.n8n.io/api/',
const renderComponent = createComponentRenderer(RunDataJsonSchema, {
global: {
stubs: ['font-awesome-icon'],
plugins: [
createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
settings: {
templates: {
enabled: true,
host: 'https://api.n8n.io/api/',
},
},
},
},
}),
],
},
props: {
mappingEnabled: true,
distanceFromActive: 1,
runIndex: 1,
totalRuns: 2,
paneType: 'input',
node: {
parameters: {
keepOnlySet: false,
values: {},
options: {},
},
}),
stubs: ['font-awesome-icon'],
props: {
mappingEnabled: true,
distanceFromActive: 1,
runIndex: 1,
totalRuns: 2,
paneType: 'input',
node: {
parameters: {
keepOnlySet: false,
values: {},
options: {},
},
id: '820ea733-d8a6-4379-8e73-88a2347ea003',
name: 'Set',
type: 'n8n-nodes-base.set',
typeVersion: 1,
position: [380, 1060],
disabled: false,
},
data: [{}],
id: '820ea733-d8a6-4379-8e73-88a2347ea003',
name: 'Set',
type: 'n8n-nodes-base.set',
typeVersion: 1,
position: [380, 1060],
disabled: false,
},
};
data: [{}],
},
});
describe('RunDataJsonSchema.vue', () => {
beforeEach(cleanup);
it('renders schema for empty data', () => {
const { container } = render(RunDataJsonSchema, renderOptions, (vue) => {
vue.use(PiniaVuePlugin);
});
const { container } = renderComponent();
expect(container).toMatchSnapshot();
});
it('renders schema for data', () => {
renderOptions.props.data = [
{ name: 'John', age: 22, hobbies: ['surfing', 'traveling'] },
{ name: 'Joe', age: 33, hobbies: ['skateboarding', 'gaming'] },
];
const { container } = render(RunDataJsonSchema, renderOptions, (vue) => {
vue.use(PiniaVuePlugin);
const { container } = renderComponent({
props: {
data: [
{ name: 'John', age: 22, hobbies: ['surfing', 'traveling'] },
{ name: 'Joe', age: 33, hobbies: ['skateboarding', 'gaming'] },
],
},
});
expect(container).toMatchSnapshot();
});
it('renders schema with spaces and dots', () => {
renderOptions.props.data = [
{
'hello world': [
const { container } = renderComponent({
props: {
data: [
{
test: {
'more to think about': 1,
},
'test.how': 'ignore',
'hello world': [
{
test: {
'more to think about': 1,
},
'test.how': 'ignore',
},
],
},
],
},
];
const { container } = render(RunDataJsonSchema, renderOptions, (vue) => {
vue.use(PiniaVuePlugin);
});
expect(container).toMatchSnapshot();
});

View File

@@ -1,5 +1,3 @@
import { render } from '@testing-library/vue';
import { PiniaVuePlugin } from 'pinia';
import { SETTINGS_STORE_DEFAULT_STATE, waitAllPromises } from '@/__tests__/utils';
import { STORES } from '@/constants';
import { createTestingPinia } from '@pinia/testing';
@@ -7,6 +5,7 @@ import { createTestingPinia } from '@pinia/testing';
import SqlEditor from '@/components/SqlEditor/SqlEditor.vue';
import { expressionManager } from '@/mixins/expressionManager';
import type { TargetItem } from '@/Interface';
import { renderComponent } from '@/__tests__/render';
const EXPRESSION_OUTPUT_TEST_ID = 'inline-expression-editor-output';
@@ -19,24 +18,23 @@ const RESOLVABLES: { [key: string]: string | number | boolean } = {
};
const DEFAULT_SETUP = {
pinia: createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
settings: SETTINGS_STORE_DEFAULT_STATE.settings,
},
},
}),
props: {
dialect: 'PostgreSQL',
isReadOnly: false,
},
global: {
plugins: [
createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
settings: SETTINGS_STORE_DEFAULT_STATE.settings,
},
},
}),
],
},
};
const renderComponent = (renderOptions: Parameters<typeof render>[1] = {}) =>
render(SqlEditor, { ...DEFAULT_SETUP, ...renderOptions }, (vue) => {
vue.use(PiniaVuePlugin);
});
describe('SQL Editor Preview Tests', () => {
beforeEach(() => {
vi.spyOn(expressionManager.methods, 'resolve').mockImplementation(
@@ -51,9 +49,11 @@ describe('SQL Editor Preview Tests', () => {
});
it('renders basic query', async () => {
const { getByTestId } = renderComponent({
const { getByTestId } = renderComponent(SqlEditor, {
...DEFAULT_SETUP,
props: {
query: 'SELECT * FROM users',
...DEFAULT_SETUP.props,
modelValue: 'SELECT * FROM users',
},
});
await waitAllPromises();
@@ -61,9 +61,11 @@ describe('SQL Editor Preview Tests', () => {
});
it('renders basic query with expression', async () => {
const { getByTestId } = renderComponent({
const { getByTestId } = renderComponent(SqlEditor, {
...DEFAULT_SETUP,
props: {
query: 'SELECT * FROM {{ $json.table }}',
...DEFAULT_SETUP.props,
modelValue: 'SELECT * FROM {{ $json.table }}',
},
});
await waitAllPromises();
@@ -71,9 +73,11 @@ describe('SQL Editor Preview Tests', () => {
});
it('renders resolved expressions with dot between resolvables', async () => {
const { getByTestId } = renderComponent({
const { getByTestId } = renderComponent(SqlEditor, {
...DEFAULT_SETUP,
props: {
query: 'SELECT * FROM {{ $json.schema }}.{{ $json.table }}',
...DEFAULT_SETUP.props,
modelValue: 'SELECT * FROM {{ $json.schema }}.{{ $json.table }}',
},
});
await waitAllPromises();
@@ -81,9 +85,11 @@ describe('SQL Editor Preview Tests', () => {
});
it('renders resolved expressions which resolve to 0', async () => {
const { getByTestId } = renderComponent({
const { getByTestId } = renderComponent(SqlEditor, {
...DEFAULT_SETUP,
props: {
query:
...DEFAULT_SETUP.props,
modelValue:
'SELECT * FROM {{ $json.schema }}.{{ $json.table }} WHERE {{ $json.id }} > {{ $json.limit - 10 }}',
},
});
@@ -94,9 +100,11 @@ describe('SQL Editor Preview Tests', () => {
});
it('keeps query formatting in rendered output', async () => {
const { getByTestId } = renderComponent({
const { getByTestId } = renderComponent(SqlEditor, {
...DEFAULT_SETUP,
props: {
query:
...DEFAULT_SETUP.props,
modelValue:
'SELECT * FROM {{ $json.schema }}.{{ $json.table }}\n WHERE id > {{ $json.limit - 10 }}\n AND active = {{ $json.active }};',
},
});

View File

@@ -1,5 +1,3 @@
import { PiniaVuePlugin } from 'pinia';
import { render } from '@testing-library/vue';
import { createTestingPinia } from '@pinia/testing';
import { merge } from 'lodash-es';
import SSOLogin from '@/components/SSOLogin.vue';
@@ -7,28 +5,20 @@ import { STORES } from '@/constants';
import { useSSOStore } from '@/stores/sso.store';
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
import { afterEach } from 'vitest';
import { createComponentRenderer } from '@/__tests__/render';
let pinia: ReturnType<typeof createTestingPinia>;
let ssoStore: ReturnType<typeof useSSOStore>;
const renderComponent = (renderOptions: Parameters<typeof render>[1] = {}) =>
render(
SSOLogin,
merge(
{
pinia,
stubs: {
'n8n-button': {
template: '<button data-test-id="sso-button"></button>',
},
},
const renderComponent = createComponentRenderer(SSOLogin, {
global: {
stubs: {
'n8n-button': {
template: '<button data-test-id="sso-button"></button>',
},
renderOptions,
),
(vue) => {
vue.use(PiniaVuePlugin);
},
);
},
});
describe('SSOLogin', () => {
beforeEach(() => {
@@ -47,13 +37,13 @@ describe('SSOLogin', () => {
});
it('should not render button if conditions are not met', () => {
const { queryByRole } = renderComponent();
const { queryByRole } = renderComponent({ pinia });
expect(queryByRole('button')).not.toBeInTheDocument();
});
it('should render button if the store returns true for the conditions', () => {
vi.spyOn(ssoStore, 'showSsoLoginButton', 'get').mockReturnValue(true);
const { queryByRole } = renderComponent();
const { queryByRole } = renderComponent({ pinia });
expect(queryByRole('button')).toBeInTheDocument();
});
});

View File

@@ -1,24 +1,38 @@
import VariablesRow from '../VariablesRow.vue';
import type { EnvironmentVariable } from '@/Interface';
import { fireEvent } from '@testing-library/vue';
import { createPinia, setActivePinia } from 'pinia';
import { setupServer } from '@/__tests__/server';
import { afterAll, beforeAll } from 'vitest';
import { useSettingsStore, useUsersStore } from '@/stores';
import { renderComponent } from '@/__tests__/utils';
import { createComponentRenderer } from '@/__tests__/render';
import { createTestingPinia } from '@pinia/testing';
import { STORES } from '@/constants';
const renderComponent = createComponentRenderer(VariablesRow, {
pinia: createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
settings: {
enterprise: {
variables: true,
},
},
},
},
}),
global: {
stubs: ['n8n-tooltip'],
},
});
describe('VariablesRow', () => {
let server: ReturnType<typeof setupServer>;
let pinia: ReturnType<typeof createPinia>;
beforeAll(() => {
server = setupServer();
});
beforeEach(async () => {
pinia = createPinia();
setActivePinia(pinia);
await useSettingsStore().getSettings();
await useUsersStore().loginWithCookie();
});
@@ -27,8 +41,6 @@ describe('VariablesRow', () => {
server.shutdown();
});
const stubs = ['n8n-tooltip'];
const environmentVariable: EnvironmentVariable = {
id: 1,
key: 'key',
@@ -36,25 +48,20 @@ describe('VariablesRow', () => {
};
it('should render correctly', () => {
const wrapper = renderComponent(VariablesRow, {
const wrapper = renderComponent({
props: {
data: environmentVariable,
},
stubs,
pinia,
});
expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.container.querySelectorAll('td')).toHaveLength(4);
});
it('should show edit and delete buttons on hover', async () => {
const wrapper = renderComponent(VariablesRow, {
const wrapper = renderComponent({
props: {
data: environmentVariable,
},
stubs,
pinia,
});
await fireEvent.mouseEnter(wrapper.container);
@@ -64,13 +71,11 @@ describe('VariablesRow', () => {
});
it('should show key and value inputs in edit mode', async () => {
const wrapper = renderComponent(VariablesRow, {
const wrapper = renderComponent({
props: {
data: environmentVariable,
editing: true,
},
stubs,
pinia,
});
await fireEvent.mouseEnter(wrapper.container);
@@ -83,18 +88,14 @@ describe('VariablesRow', () => {
expect(wrapper.getByTestId('variable-row-value-input').querySelector('input')).toHaveValue(
environmentVariable.value,
);
expect(wrapper.html()).toMatchSnapshot();
});
it('should show cancel and save buttons in edit mode', async () => {
const wrapper = renderComponent(VariablesRow, {
const wrapper = renderComponent({
props: {
data: environmentVariable,
editing: true,
},
stubs,
pinia,
});
await fireEvent.mouseEnter(wrapper.container);

View File

@@ -1,284 +0,0 @@
// Vitest Snapshot v1
exports[`PersonalizationModal.vue > should render correctly 1`] = `
"<transition-stub name=\\"dialog-fade\\" data-test-id=\\"personalization-form\\" class=\\"dialog-wrapper\\" style=\\"z-index: 2001;\\">
<div class=\\"el-dialog__wrapper\\">
<div role=\\"dialog\\" aria-modal=\\"true\\" aria-label=\\"dialog\\" class=\\"el-dialog\\" style=\\"margin-top: 15vh; width: 460px;\\">
<div class=\\"el-dialog__header\\">
<div class=\\"centerTitle\\">
<div>
<h1 class=\\"n8n-heading size-xlarge regular\\">Customize n8n to you</h1>
</div>
<div class=\\"subtitle\\">
<h3 class=\\"n8n-heading text-light size-small regular\\">These questions help us tailor n8n to you</h3>
</div>
</div>
<!---->
</div>
<div class=\\"el-dialog__body\\">
<div class=\\"modal-content\\">
<div class=\\"container\\">
<div>
<div class=\\"grid\\">
<div class=\\"\\">
<div data-test-id=\\"companyType\\" class=\\"container\\"><label for=\\"companyType\\" class=\\"n8n-input-label inputLabel heading medium\\">
<div class=\\"title\\"><span class=\\"n8n-text size-medium bold\\"> What best describes your company? <!----></span></div>
<!---->
<!---->
<!---->
</label>
<div class=\\"\\">
<div class=\\"n8n-select container\\" name=\\"companyType\\">
<!---->
<div class=\\"el-select el-select--large\\">
<!---->
<div class=\\"el-input el-input--large el-input--suffix\\">
<!----><input type=\\"text\\" readonly=\\"readonly\\" autocomplete=\\"off\\" placeholder=\\"Select...\\" class=\\"el-input__inner\\">
<!----><span class=\\"el-input__suffix\\"><span class=\\"el-input__suffix-inner\\"><i class=\\"el-select__caret el-input__icon el-icon-arrow-up\\"></i><!----><!----><!----><!----><!----></span>
<!----></span>
<!---->
<!---->
</div>
<transition-stub name=\\"el-zoom-in-top\\">
<div class=\\"el-select-dropdown el-popper\\" style=\\"display: none;\\">
<div class=\\"el-scrollbar\\" style=\\"\\">
<div class=\\"el-select-dropdown__wrap el-scrollbar__wrap el-scrollbar__wrap--hidden-default\\">
<ul class=\\"el-scrollbar__view el-select-dropdown__list\\">
<!---->
<li class=\\"el-select-dropdown__item\\"><span>Software as a service</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Ecommerce</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Marketing agency / consultancy</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Systems integrator / Automation agency</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Education</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Other</span></li>
<li class=\\"el-select-dropdown__item\\"><span>I'm not using n8n for work</span></li>
</ul>
</div>
<div class=\\"el-scrollbar__bar is-horizontal\\">
<div class=\\"el-scrollbar__thumb\\" style=\\"transform: translateX(0%); webkit-transform: translateX(0%);\\"></div>
</div>
<div class=\\"el-scrollbar__bar is-vertical\\">
<div class=\\"el-scrollbar__thumb\\" style=\\"transform: translateY(0%); webkit-transform: translateY(0%);\\"></div>
</div>
</div>
<!---->
</div>
</transition-stub>
</div>
</div>
</div>
<!---->
</div>
</div>
<div class=\\"\\">
<div data-test-id=\\"role\\" class=\\"container\\"><label for=\\"role\\" class=\\"n8n-input-label inputLabel heading medium\\">
<div class=\\"title\\"><span class=\\"n8n-text size-medium bold\\"> Which role best describes you? <!----></span></div>
<!---->
<!---->
<!---->
</label>
<div class=\\"\\">
<div class=\\"n8n-select container\\" name=\\"role\\">
<!---->
<div class=\\"el-select el-select--large\\">
<!---->
<div class=\\"el-input el-input--large el-input--suffix\\">
<!----><input type=\\"text\\" readonly=\\"readonly\\" autocomplete=\\"off\\" placeholder=\\"Select...\\" class=\\"el-input__inner\\">
<!----><span class=\\"el-input__suffix\\"><span class=\\"el-input__suffix-inner\\"><i class=\\"el-select__caret el-input__icon el-icon-arrow-up\\"></i><!----><!----><!----><!----><!----></span>
<!----></span>
<!---->
<!---->
</div>
<transition-stub name=\\"el-zoom-in-top\\">
<div class=\\"el-select-dropdown el-popper\\" style=\\"display: none;\\">
<div class=\\"el-scrollbar\\" style=\\"\\">
<div class=\\"el-select-dropdown__wrap el-scrollbar__wrap el-scrollbar__wrap--hidden-default\\">
<ul class=\\"el-scrollbar__view el-select-dropdown__list\\">
<!---->
<li class=\\"el-select-dropdown__item\\"><span>Business Owner</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Customer support</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Data Science</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Devops</span></li>
<li class=\\"el-select-dropdown__item\\"><span>IT</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Engineering</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Sales and Marketing</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Security</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Other (please specify)</span></li>
</ul>
</div>
<div class=\\"el-scrollbar__bar is-horizontal\\">
<div class=\\"el-scrollbar__thumb\\" style=\\"transform: translateX(0%); webkit-transform: translateX(0%);\\"></div>
</div>
<div class=\\"el-scrollbar__bar is-vertical\\">
<div class=\\"el-scrollbar__thumb\\" style=\\"transform: translateY(0%); webkit-transform: translateY(0%);\\"></div>
</div>
</div>
<!---->
</div>
</transition-stub>
</div>
</div>
</div>
<!---->
</div>
</div>
<div class=\\"\\">
<div data-test-id=\\"automationBeneficiary\\" class=\\"container\\"><label for=\\"automationBeneficiary\\" class=\\"n8n-input-label inputLabel heading medium\\">
<div class=\\"title\\"><span class=\\"n8n-text size-medium bold\\"> Who will your automations mainly be for? <!----></span></div>
<!---->
<!---->
<!---->
</label>
<div class=\\"\\">
<div class=\\"n8n-select container\\" name=\\"automationBeneficiary\\">
<!---->
<div class=\\"el-select el-select--large\\">
<!---->
<div class=\\"el-input el-input--large el-input--suffix\\">
<!----><input type=\\"text\\" readonly=\\"readonly\\" autocomplete=\\"off\\" placeholder=\\"Select...\\" class=\\"el-input__inner\\">
<!----><span class=\\"el-input__suffix\\"><span class=\\"el-input__suffix-inner\\"><i class=\\"el-select__caret el-input__icon el-icon-arrow-up\\"></i><!----><!----><!----><!----><!----></span>
<!----></span>
<!---->
<!---->
</div>
<transition-stub name=\\"el-zoom-in-top\\">
<div class=\\"el-select-dropdown el-popper\\" style=\\"display: none;\\">
<div class=\\"el-scrollbar\\" style=\\"\\">
<div class=\\"el-select-dropdown__wrap el-scrollbar__wrap el-scrollbar__wrap--hidden-default\\">
<ul class=\\"el-scrollbar__view el-select-dropdown__list\\">
<!---->
<li class=\\"el-select-dropdown__item\\"><span>Myself</span></li>
<li class=\\"el-select-dropdown__item\\"><span>My team</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Other teams</span></li>
</ul>
</div>
<div class=\\"el-scrollbar__bar is-horizontal\\">
<div class=\\"el-scrollbar__thumb\\" style=\\"transform: translateX(0%); webkit-transform: translateX(0%);\\"></div>
</div>
<div class=\\"el-scrollbar__bar is-vertical\\">
<div class=\\"el-scrollbar__thumb\\" style=\\"transform: translateY(0%); webkit-transform: translateY(0%);\\"></div>
</div>
</div>
<!---->
</div>
</transition-stub>
</div>
</div>
</div>
<!---->
</div>
</div>
<div class=\\"\\">
<div data-test-id=\\"companySize\\" class=\\"container\\"><label for=\\"companySize\\" class=\\"n8n-input-label inputLabel heading medium\\">
<div class=\\"title\\"><span class=\\"n8n-text size-medium bold\\"> How big is your company? <!----></span></div>
<!---->
<!---->
<!---->
</label>
<div class=\\"\\">
<div class=\\"n8n-select container\\" name=\\"companySize\\">
<!---->
<div class=\\"el-select el-select--large\\">
<!---->
<div class=\\"el-input el-input--large el-input--suffix\\">
<!----><input type=\\"text\\" readonly=\\"readonly\\" autocomplete=\\"off\\" placeholder=\\"Select...\\" class=\\"el-input__inner\\">
<!----><span class=\\"el-input__suffix\\"><span class=\\"el-input__suffix-inner\\"><i class=\\"el-select__caret el-input__icon el-icon-arrow-up\\"></i><!----><!----><!----><!----><!----></span>
<!----></span>
<!---->
<!---->
</div>
<transition-stub name=\\"el-zoom-in-top\\">
<div class=\\"el-select-dropdown el-popper\\" style=\\"display: none;\\">
<div class=\\"el-scrollbar\\" style=\\"\\">
<div class=\\"el-select-dropdown__wrap el-scrollbar__wrap el-scrollbar__wrap--hidden-default\\">
<ul class=\\"el-scrollbar__view el-select-dropdown__list\\">
<!---->
<li class=\\"el-select-dropdown__item\\"><span>Less than 20 people</span></li>
<li class=\\"el-select-dropdown__item\\"><span>20-99 people</span></li>
<li class=\\"el-select-dropdown__item\\"><span>100-499 people</span></li>
<li class=\\"el-select-dropdown__item\\"><span>500-999 people</span></li>
<li class=\\"el-select-dropdown__item\\"><span>1000+ people</span></li>
<li class=\\"el-select-dropdown__item\\"><span>I'm not using n8n for work</span></li>
</ul>
</div>
<div class=\\"el-scrollbar__bar is-horizontal\\">
<div class=\\"el-scrollbar__thumb\\" style=\\"transform: translateX(0%); webkit-transform: translateX(0%);\\"></div>
</div>
<div class=\\"el-scrollbar__bar is-vertical\\">
<div class=\\"el-scrollbar__thumb\\" style=\\"transform: translateY(0%); webkit-transform: translateY(0%);\\"></div>
</div>
</div>
<!---->
</div>
</transition-stub>
</div>
</div>
</div>
<!---->
</div>
</div>
<div class=\\"\\">
<div data-test-id=\\"reportedSource\\" class=\\"container\\"><label for=\\"reportedSource\\" class=\\"n8n-input-label inputLabel heading medium\\">
<div class=\\"title\\"><span class=\\"n8n-text size-medium bold\\"> How did you hear about n8n? <!----></span></div>
<!---->
<!---->
<!---->
</label>
<div class=\\"\\">
<div class=\\"n8n-select container\\" name=\\"reportedSource\\">
<!---->
<div class=\\"el-select el-select--large\\">
<!---->
<div class=\\"el-input el-input--large el-input--suffix\\">
<!----><input type=\\"text\\" readonly=\\"readonly\\" autocomplete=\\"off\\" placeholder=\\"Select...\\" class=\\"el-input__inner\\">
<!----><span class=\\"el-input__suffix\\"><span class=\\"el-input__suffix-inner\\"><i class=\\"el-select__caret el-input__icon el-icon-arrow-up\\"></i><!----><!----><!----><!----><!----></span>
<!----></span>
<!---->
<!---->
</div>
<transition-stub name=\\"el-zoom-in-top\\">
<div class=\\"el-select-dropdown el-popper\\" style=\\"display: none;\\">
<div class=\\"el-scrollbar\\" style=\\"\\">
<div class=\\"el-select-dropdown__wrap el-scrollbar__wrap el-scrollbar__wrap--hidden-default\\">
<ul class=\\"el-scrollbar__view el-select-dropdown__list\\">
<!---->
<li class=\\"el-select-dropdown__item\\"><span>Google</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Twitter</span></li>
<li class=\\"el-select-dropdown__item\\"><span>LinkedIn</span></li>
<li class=\\"el-select-dropdown__item\\"><span>YouTube</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Friend / Word of mouth</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Podcast</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Event</span></li>
<li class=\\"el-select-dropdown__item\\"><span>Other (please specify)</span></li>
</ul>
</div>
<div class=\\"el-scrollbar__bar is-horizontal\\">
<div class=\\"el-scrollbar__thumb\\" style=\\"transform: translateX(0%); webkit-transform: translateX(0%);\\"></div>
</div>
<div class=\\"el-scrollbar__bar is-vertical\\">
<div class=\\"el-scrollbar__thumb\\" style=\\"transform: translateY(0%); webkit-transform: translateY(0%);\\"></div>
</div>
</div>
<!---->
</div>
</transition-stub>
</div>
</div>
</div>
<!---->
</div>
</div>
</div>
</div>
</div>
</div>
<div class=\\"footer\\">
<div><button aria-live=\\"polite\\" class=\\"button button primary medium float-right\\">
<!----><span>Get started</span></button></div>
</div>
</div>
<!---->
</div>
</div>
</transition-stub>"
`;

View File

@@ -8,6 +8,7 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
<div
class=""
>
<div
class="schema"
>
@@ -15,13 +16,14 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
class="item"
data-test-id="run-data-schema-item"
>
<!---->
<!---->
<!---->
<!---->
<!--v-if-->
<!--v-if-->
<!--v-if-->
<!--v-if-->
<div
class="sub"
>
<div
class="item"
data-test-id="run-data-schema-item"
@@ -39,11 +41,23 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
data-target="mappable"
data-value="{{ $json.name }}"
>
<font-awesome-icon-stub
icon="font"
size="sm"
/>
<!---->
<svg
aria-hidden="true"
class="svg-inline--fa fa-font fa-w-14 fa-sm"
data-icon="font"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 448 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M432 416h-23.41L277.88 53.69A32 32 0 0 0 247.58 32h-47.16a32 32 0 0 0-30.3 21.69L39.41 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16h-19.58l23.3-64h152.56l23.3 64H304a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM176.85 272L224 142.51 271.15 272z"
fill="currentColor"
/>
</svg>
<!--v-if-->
<span
class=""
>
@@ -56,9 +70,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
>
John
</span>
<!---->
<!---->
<!---->
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
<div
class="item"
@@ -77,11 +91,23 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
data-target="mappable"
data-value="{{ $json.age }}"
>
<font-awesome-icon-stub
icon="hashtag"
size="sm"
/>
<!---->
<svg
aria-hidden="true"
class="svg-inline--fa fa-hashtag fa-w-14 fa-sm"
data-icon="hashtag"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 448 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M440.667 182.109l7.143-40c1.313-7.355-4.342-14.109-11.813-14.109h-74.81l14.623-81.891C377.123 38.754 371.468 32 363.997 32h-40.632a12 12 0 0 0-11.813 9.891L296.175 128H197.54l14.623-81.891C213.477 38.754 207.822 32 200.35 32h-40.632a12 12 0 0 0-11.813 9.891L132.528 128H53.432a12 12 0 0 0-11.813 9.891l-7.143 40C33.163 185.246 38.818 192 46.289 192h74.81L98.242 320H19.146a12 12 0 0 0-11.813 9.891l-7.143 40C-1.123 377.246 4.532 384 12.003 384h74.81L72.19 465.891C70.877 473.246 76.532 480 84.003 480h40.632a12 12 0 0 0 11.813-9.891L151.826 384h98.634l-14.623 81.891C234.523 473.246 240.178 480 247.65 480h40.632a12 12 0 0 0 11.813-9.891L315.472 384h79.096a12 12 0 0 0 11.813-9.891l7.143-40c1.313-7.355-4.342-14.109-11.813-14.109h-74.81l22.857-128h79.096a12 12 0 0 0 11.813-9.891zM261.889 320h-98.634l22.857-128h98.634l-22.857 128z"
fill="currentColor"
/>
</svg>
<!--v-if-->
<span
class=""
>
@@ -94,9 +120,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
>
22
</span>
<!---->
<!---->
<!---->
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
<div
class="item"
@@ -115,11 +141,23 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
data-target="mappable"
data-value="{{ $json.hobbies }}"
>
<font-awesome-icon-stub
icon="list"
size="sm"
/>
<!---->
<svg
aria-hidden="true"
class="svg-inline--fa fa-list fa-w-16 fa-sm"
data-icon="list"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M80 368H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm0-320H16A16 16 0 0 0 0 64v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16zm0 160H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm416 176H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z"
fill="currentColor"
/>
</svg>
<!--v-if-->
<span
class=""
>
@@ -127,9 +165,8 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
</span>
</span>
</div>
<!---->
<!--v-if-->
<input
checked="checked"
id="input_array-0-2"
type="checkbox"
/>
@@ -137,13 +174,27 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
class="toggle"
for="input_array-0-2"
>
<font-awesome-icon-stub
icon="angle-up"
/>
<svg
aria-hidden="true"
class="svg-inline--fa fa-angle-up fa-w-10"
data-icon="angle-up"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 320 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M177 159.7l136 136c9.4 9.4 9.4 24.6 0 33.9l-22.6 22.6c-9.4 9.4-24.6 9.4-33.9 0L160 255.9l-96.4 96.4c-9.4 9.4-24.6 9.4-33.9 0L7 329.7c-9.4-9.4-9.4-24.6 0-33.9l136-136c9.4-9.5 24.6-9.5 34-.1z"
fill="currentColor"
/>
</svg>
</label>
<div
class="sub"
>
<div
class="item"
data-test-id="run-data-schema-item"
@@ -161,10 +212,22 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
data-target="mappable"
data-value="{{ $json.hobbies[0] }}"
>
<font-awesome-icon-stub
icon="font"
size="sm"
/>
<svg
aria-hidden="true"
class="svg-inline--fa fa-font fa-w-14 fa-sm"
data-icon="font"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 448 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M432 416h-23.41L277.88 53.69A32 32 0 0 0 247.58 32h-47.16a32 32 0 0 0-30.3 21.69L39.41 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16h-19.58l23.3-64h152.56l23.3 64H304a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM176.85 272L224 142.51 271.15 272z"
fill="currentColor"
/>
</svg>
<span>
hobbies
</span>
@@ -180,9 +243,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
>
surfing
</span>
<!---->
<!---->
<!---->
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
<div
class="item"
@@ -201,10 +264,22 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
data-target="mappable"
data-value="{{ $json.hobbies[1] }}"
>
<font-awesome-icon-stub
icon="font"
size="sm"
/>
<svg
aria-hidden="true"
class="svg-inline--fa fa-font fa-w-14 fa-sm"
data-icon="font"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 448 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M432 416h-23.41L277.88 53.69A32 32 0 0 0 247.58 32h-47.16a32 32 0 0 0-30.3 21.69L39.41 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16h-19.58l23.3-64h152.56l23.3 64H304a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM176.85 272L224 142.51 271.15 272z"
fill="currentColor"
/>
</svg>
<span>
hobbies
</span>
@@ -220,19 +295,20 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
>
traveling
</span>
<!---->
<!---->
<!---->
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="teleporter hidden"
data-v-d4e6e290=""
/>
<!--teleport start-->
<!--teleport end-->
</div>
</div>
</div>
@@ -250,15 +326,31 @@ exports[`RunDataJsonSchema.vue > renders schema for empty data 1`] = `
class="iconText"
>
<span
class="n8n-icon n8n-text compact size-medium regular"
class="n8n-text compact size-medium regular n8n-icon n8n-icon"
>
<font-awesome-icon-stub
class="medium"
icon="info-circle"
/>
<svg
aria-hidden="true"
class="svg-inline--fa fa-info-circle fa-w-16 medium"
data-icon="info-circle"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"
fill="currentColor"
/>
</svg>
</span>
<span>
No data to show - item(s) exist, but theyre empty
</span>
</span>
</div>
@@ -274,6 +366,7 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
<div
class=""
>
<div
class="schema"
>
@@ -281,13 +374,14 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
class="item"
data-test-id="run-data-schema-item"
>
<!---->
<!---->
<!---->
<!---->
<!--v-if-->
<!--v-if-->
<!--v-if-->
<!--v-if-->
<div
class="sub"
>
<div
class="item"
data-test-id="run-data-schema-item"
@@ -305,11 +399,23 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
data-target="mappable"
data-value="{{ $json['hello world'] }}"
>
<font-awesome-icon-stub
icon="list"
size="sm"
/>
<!---->
<svg
aria-hidden="true"
class="svg-inline--fa fa-list fa-w-16 fa-sm"
data-icon="list"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M80 368H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm0-320H16A16 16 0 0 0 0 64v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16zm0 160H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm416 176H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z"
fill="currentColor"
/>
</svg>
<!--v-if-->
<span
class=""
>
@@ -317,9 +423,8 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
</span>
</span>
</div>
<!---->
<!--v-if-->
<input
checked="checked"
id="input_array-0-0"
type="checkbox"
/>
@@ -327,13 +432,27 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
class="toggle"
for="input_array-0-0"
>
<font-awesome-icon-stub
icon="angle-up"
/>
<svg
aria-hidden="true"
class="svg-inline--fa fa-angle-up fa-w-10"
data-icon="angle-up"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 320 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M177 159.7l136 136c9.4 9.4 9.4 24.6 0 33.9l-22.6 22.6c-9.4 9.4-24.6 9.4-33.9 0L160 255.9l-96.4 96.4c-9.4 9.4-24.6 9.4-33.9 0L7 329.7c-9.4-9.4-9.4-24.6 0-33.9l136-136c9.4-9.5 24.6-9.5 34-.1z"
fill="currentColor"
/>
</svg>
</label>
<div
class="sub"
>
<div
class="item"
data-test-id="run-data-schema-item"
@@ -351,10 +470,22 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
data-target="mappable"
data-value="{{ $json['hello world'][0] }}"
>
<font-awesome-icon-stub
icon="cube"
size="sm"
/>
<svg
aria-hidden="true"
class="svg-inline--fa fa-cube fa-w-16 fa-sm"
data-icon="cube"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M239.1 6.3l-208 78c-18.7 7-31.1 25-31.1 45v225.1c0 18.2 10.3 34.8 26.5 42.9l208 104c13.5 6.8 29.4 6.8 42.9 0l208-104c16.3-8.1 26.5-24.8 26.5-42.9V129.3c0-20-12.4-37.9-31.1-44.9l-208-78C262 2.2 250 2.2 239.1 6.3zM256 68.4l192 72v1.1l-192 78-192-78v-1.1l192-72zm32 356V275.5l160-65v133.9l-160 80z"
fill="currentColor"
/>
</svg>
<span>
hello world
</span>
@@ -365,9 +496,8 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
</span>
</span>
</div>
<!---->
<!--v-if-->
<input
checked="checked"
id="input_object-1-0"
type="checkbox"
/>
@@ -375,13 +505,27 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
class="toggle"
for="input_object-1-0"
>
<font-awesome-icon-stub
icon="angle-up"
/>
<svg
aria-hidden="true"
class="svg-inline--fa fa-angle-up fa-w-10"
data-icon="angle-up"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 320 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M177 159.7l136 136c9.4 9.4 9.4 24.6 0 33.9l-22.6 22.6c-9.4 9.4-24.6 9.4-33.9 0L160 255.9l-96.4 96.4c-9.4 9.4-24.6 9.4-33.9 0L7 329.7c-9.4-9.4-9.4-24.6 0-33.9l136-136c9.4-9.5 24.6-9.5 34-.1z"
fill="currentColor"
/>
</svg>
</label>
<div
class="sub"
>
<div
class="item"
data-test-id="run-data-schema-item"
@@ -399,11 +543,23 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
data-target="mappable"
data-value="{{ $json['hello world'][0].test }}"
>
<font-awesome-icon-stub
icon="cube"
size="sm"
/>
<!---->
<svg
aria-hidden="true"
class="svg-inline--fa fa-cube fa-w-16 fa-sm"
data-icon="cube"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M239.1 6.3l-208 78c-18.7 7-31.1 25-31.1 45v225.1c0 18.2 10.3 34.8 26.5 42.9l208 104c13.5 6.8 29.4 6.8 42.9 0l208-104c16.3-8.1 26.5-24.8 26.5-42.9V129.3c0-20-12.4-37.9-31.1-44.9l-208-78C262 2.2 250 2.2 239.1 6.3zM256 68.4l192 72v1.1l-192 78-192-78v-1.1l192-72zm32 356V275.5l160-65v133.9l-160 80z"
fill="currentColor"
/>
</svg>
<!--v-if-->
<span
class=""
>
@@ -411,9 +567,8 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
</span>
</span>
</div>
<!---->
<!--v-if-->
<input
checked="checked"
id="input_object-2-0"
type="checkbox"
/>
@@ -421,13 +576,27 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
class="toggle"
for="input_object-2-0"
>
<font-awesome-icon-stub
icon="angle-up"
/>
<svg
aria-hidden="true"
class="svg-inline--fa fa-angle-up fa-w-10"
data-icon="angle-up"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 320 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M177 159.7l136 136c9.4 9.4 9.4 24.6 0 33.9l-22.6 22.6c-9.4 9.4-24.6 9.4-33.9 0L160 255.9l-96.4 96.4c-9.4 9.4-24.6 9.4-33.9 0L7 329.7c-9.4-9.4-9.4-24.6 0-33.9l136-136c9.4-9.5 24.6-9.5 34-.1z"
fill="currentColor"
/>
</svg>
</label>
<div
class="sub"
>
<div
class="item"
data-test-id="run-data-schema-item"
@@ -445,11 +614,23 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
data-target="mappable"
data-value="{{ $json['hello world'][0].test['more to think about'] }}"
>
<font-awesome-icon-stub
icon="hashtag"
size="sm"
/>
<!---->
<svg
aria-hidden="true"
class="svg-inline--fa fa-hashtag fa-w-14 fa-sm"
data-icon="hashtag"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 448 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M440.667 182.109l7.143-40c1.313-7.355-4.342-14.109-11.813-14.109h-74.81l14.623-81.891C377.123 38.754 371.468 32 363.997 32h-40.632a12 12 0 0 0-11.813 9.891L296.175 128H197.54l14.623-81.891C213.477 38.754 207.822 32 200.35 32h-40.632a12 12 0 0 0-11.813 9.891L132.528 128H53.432a12 12 0 0 0-11.813 9.891l-7.143 40C33.163 185.246 38.818 192 46.289 192h74.81L98.242 320H19.146a12 12 0 0 0-11.813 9.891l-7.143 40C-1.123 377.246 4.532 384 12.003 384h74.81L72.19 465.891C70.877 473.246 76.532 480 84.003 480h40.632a12 12 0 0 0 11.813-9.891L151.826 384h98.634l-14.623 81.891C234.523 473.246 240.178 480 247.65 480h40.632a12 12 0 0 0 11.813-9.891L315.472 384h79.096a12 12 0 0 0 11.813-9.891l7.143-40c1.313-7.355-4.342-14.109-11.813-14.109h-74.81l22.857-128h79.096a12 12 0 0 0 11.813-9.891zM261.889 320h-98.634l22.857-128h98.634l-22.857 128z"
fill="currentColor"
/>
</svg>
<!--v-if-->
<span
class=""
>
@@ -462,10 +643,11 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
>
1
</span>
<!---->
<!---->
<!---->
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>
</div>
<div
@@ -485,11 +667,23 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
data-target="mappable"
data-value="{{ $json['hello world'][0]['test.how'] }}"
>
<font-awesome-icon-stub
icon="font"
size="sm"
/>
<!---->
<svg
aria-hidden="true"
class="svg-inline--fa fa-font fa-w-14 fa-sm"
data-icon="font"
data-prefix="fas"
focusable="false"
role="img"
viewBox="0 0 448 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
class=""
d="M432 416h-23.41L277.88 53.69A32 32 0 0 0 247.58 32h-47.16a32 32 0 0 0-30.3 21.69L39.41 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16h-19.58l23.3-64h152.56l23.3 64H304a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM176.85 272L224 142.51 271.15 272z"
fill="currentColor"
/>
</svg>
<!--v-if-->
<span
class=""
>
@@ -502,21 +696,23 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
>
ignore
</span>
<!---->
<!---->
<!---->
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="teleporter hidden"
data-v-d4e6e290=""
/>
<!--teleport start-->
<!--teleport end-->
</div>
</div>
</div>

View File

@@ -1,78 +0,0 @@
// Vitest Snapshot v1
exports[`VariablesRow > should render correctly 1`] = `
"<tr data-test-id=\\"variables-row\\" class=\\"variablesRow\\">
<td class=\\"variables-key-column\\">
<div><span>key</span></div>
</td>
<td class=\\"variables-value-column\\">
<div><span>value</span></div>
</td>
<td class=\\"variables-usage-column\\">
<div>
<n8n-tooltip-stub justifybuttons=\\"flex-end\\" buttons=\\"\\" placement=\\"top\\"><span class=\\"usageSyntax\\">$vars.key</span></n8n-tooltip-stub>
</div>
</td>
<td>
<div class=\\"buttons hoverButtons\\">
<n8n-tooltip-stub justifybuttons=\\"flex-end\\" buttons=\\"\\" placement=\\"top\\">
<div><button disabled=\\"disabled\\" aria-disabled=\\"true\\" aria-live=\\"polite\\" class=\\"mr-xs button button tertiary medium disabled\\" data-test-id=\\"variable-row-edit-button\\">
<!----><span> Edit </span></button></div>
</n8n-tooltip-stub>
<n8n-tooltip-stub justifybuttons=\\"flex-end\\" buttons=\\"\\" placement=\\"top\\">
<div><button disabled=\\"disabled\\" aria-disabled=\\"true\\" aria-live=\\"polite\\" class=\\"button button tertiary medium disabled\\" data-test-id=\\"variable-row-delete-button\\">
<!----><span> Delete </span></button></div>
</n8n-tooltip-stub>
</div>
</td>
</tr>"
`;
exports[`VariablesRow > should show key and value inputs in edit mode 1`] = `
"<tr data-test-id=\\"variables-row\\" class=\\"variablesRow\\">
<td class=\\"variables-key-column\\">
<div>
<div data-test-id=\\"variable-row-key-input\\" class=\\"container\\">
<!---->
<div class=\\"\\">
<div class=\\"el-input el-input--large n8n-input\\">
<!----><input type=\\"text\\" autocomplete=\\"off\\" name=\\"key\\" placeholder=\\"Enter a name\\" class=\\"el-input__inner\\">
<!---->
<!---->
<!---->
<!---->
</div>
</div>
<!---->
</div>
</div>
</td>
<td class=\\"variables-value-column\\">
<div>
<div data-test-id=\\"variable-row-value-input\\" class=\\"container\\">
<!---->
<div class=\\"\\">
<div class=\\"el-input el-input--large n8n-input\\">
<!----><input type=\\"text\\" autocomplete=\\"off\\" name=\\"value\\" placeholder=\\"Enter a value\\" class=\\"el-input__inner\\">
<!---->
<!---->
<!---->
<!---->
</div>
</div>
<!---->
</div>
</div>
</td>
<td class=\\"variables-usage-column\\">
<div>
<n8n-tooltip-stub justifybuttons=\\"flex-end\\" buttons=\\"\\" placement=\\"top\\"><span class=\\"usageSyntax\\">$vars.key</span></n8n-tooltip-stub>
</div>
</td>
<td>
<div class=\\"buttons\\"><button aria-live=\\"polite\\" class=\\"mr-xs button button tertiary medium\\" data-test-id=\\"variable-row-cancel-button\\">
<!----><span> Cancel </span></button><button aria-live=\\"polite\\" class=\\"button button primary medium\\" data-test-id=\\"variable-row-save-button\\">
<!----><span> Save </span></button></div>
</td>
</tr>"
`;

View File

@@ -101,6 +101,7 @@ export const DEFAULT_SETUP = {
dependentParametersValues: 'gid=0',
inputSize: 'small',
labelSize: 'small',
teleported: false,
node: {
parameters: NODE_PARAMETER_VALUES,
id: 'f63efb2d-3cc5-4500-89f9-b39aab19baf5',