Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

refactor(editor): Migrate header WorkflowDetails to composition api (no-changelog) #9186

Merged
merged 11 commits into from
Apr 29, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,13 @@ import { ref, useCssModule, useAttrs, computed } from 'vue';
import { ElDropdown, ElDropdownMenu, ElDropdownItem, type Placement } from 'element-plus';
import N8nIcon from '../N8nIcon';
import { N8nKeyboardShortcut } from '../N8nKeyboardShortcut';
import type { KeyboardShortcut } from '../../types';
import type { ActionDropdownItem } from '../../types';
import type { IconSize } from '@/types/icon';

interface IActionDropdownItem {
id: string;
label: string;
icon?: string;
divided?: boolean;
disabled?: boolean;
shortcut?: KeyboardShortcut;
customClass?: string;
}

const TRIGGER = ['click', 'hover'] as const;

interface ActionDropdownProps {
items: IActionDropdownItem[];
items: ActionDropdownItem[];
placement?: Placement;
activatorIcon?: string;
activatorSize?: IconSize;
Expand All @@ -99,7 +89,7 @@ const $attrs = useAttrs();
const testIdPrefix = $attrs['data-test-id'];

const $style = useCssModule();
const getItemClasses = (item: IActionDropdownItem): Record<string, boolean> => {
const getItemClasses = (item: ActionDropdownItem): Record<string, boolean> => {
return {
[$style.itemContainer]: true,
[$style.disabled]: !!item.disabled,
Expand Down
6 changes: 3 additions & 3 deletions packages/design-system/src/css/tag.scss
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
border-radius: var.$tag-border-radius;
box-sizing: border-box;
white-space: nowrap;
line-height: 1;

.el-icon.el-tag__close {
border-radius: 50%;
Expand All @@ -137,9 +138,8 @@
height: 16px;
width: 16px;
line-height: 16px;
vertical-align: middle;
top: -1px;
right: -5px;
margin-top: 0;
margin-right: 0;

&::before {
display: block;
Expand Down
11 changes: 11 additions & 0 deletions packages/design-system/src/types/action-dropdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { KeyboardShortcut } from '@/types/keyboardshortcut';

export interface ActionDropdownItem {
id: string;
label: string;
icon?: string;
divided?: boolean;
disabled?: boolean;
shortcut?: KeyboardShortcut;
customClass?: string;
}
1 change: 1 addition & 0 deletions packages/design-system/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './action-dropdown';
export * from './button';
export * from './datatable';
export * from './form';
Expand Down
25 changes: 25 additions & 0 deletions packages/editor-ui/src/__tests__/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,28 @@ Range.prototype.getClientRects = vi.fn(() => ({
length: 0,
[Symbol.iterator]: vi.fn(),
}));

export class IntersectionObserver {
root = null;
rootMargin = '';
thresholds = [];

disconnect() {
return null;
}

observe() {
return null;
}

takeRecords() {
return [];
}

unobserve() {
return null;
}
}

window.IntersectionObserver = IntersectionObserver;
global.IntersectionObserver = IntersectionObserver;
7 changes: 5 additions & 2 deletions packages/editor-ui/src/components/MainHeader/MainHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div>
<div :class="{ 'main-header': true, expanded: !uiStore.sidebarMenuCollapsed }">
<div v-show="!hideMenuBar" class="top-menu">
<WorkflowDetails :read-only="readOnly" />
<WorkflowDetails v-if="workflow?.name" :workflow="workflow" :read-only="readOnly" />
<TabBar
v-if="onWorkflowPage"
:items="tabBarItems"
Expand All @@ -27,7 +27,7 @@ import {
STICKY_NODE_TYPE,
VIEWS,
} from '@/constants';
import type { INodeUi, ITabBarItem } from '@/Interface';
import type { INodeUi, ITabBarItem, IWorkflowDb } from '@/Interface';
import { useNDVStore } from '@/stores/ndv.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useUIStore } from '@/stores/ui.store';
Expand Down Expand Up @@ -75,6 +75,9 @@ export default defineComponent({
hideMenuBar(): boolean {
return Boolean(this.activeNode && this.activeNode.type !== STICKY_NODE_TYPE);
},
workflow(): IWorkflowDb {
return this.workflowsStore.workflow;
},
workflowName(): string {
return this.workflowsStore.workflowName;
},
Expand Down
115 changes: 115 additions & 0 deletions packages/editor-ui/src/components/MainHeader/WorkflowDetails.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import WorkflowDetails from '@/components/MainHeader/WorkflowDetails.vue';
MiloradFilipovic marked this conversation as resolved.
Show resolved Hide resolved
import { createComponentRenderer } from '@/__tests__/render';
import { STORES } from '@/constants';
import { createTestingPinia } from '@pinia/testing';
import { fireEvent } from '@testing-library/vue';

vi.mock('vue-router', async () => {
const actual = await import('vue-router');

return {
...actual,
useRoute: () => ({
value: {
params: {
id: '1',
},
},
}),
};
});

const initialState = {
[STORES.SETTINGS]: {
settings: {
enterprise: {
sharing: true,
},
},
areTagsEnabled: true,
},
[STORES.TAGS]: {
tags: {
1: {
id: '1',
name: 'tag1',
},
2: {
id: '2',
name: 'tag2',
},
},
},
};

const renderComponent = createComponentRenderer(WorkflowDetails, {
pinia: createTestingPinia({ initialState }),
});

describe('WorkflowDetails', () => {
it('renders workflow name and tags', async () => {
const workflow = {
id: '1',
name: 'Test Workflow',
tags: ['1', '2'],
};

const { getByTestId, getByText } = renderComponent({
props: {
workflow,
readOnly: false,
},
});

const workflowName = getByTestId('workflow-name-input');
const workflowNameInput = workflowName.querySelector('input');

expect(workflowNameInput).toHaveValue('Test Workflow');
expect(getByText('tag1')).toBeInTheDocument();
expect(getByText('tag2')).toBeInTheDocument();
});

it('calls save function on save button click', async () => {
const onSaveButtonClick = vi.fn();
const { getByTestId } = renderComponent({
props: {
workflow: {
id: '1',
name: 'Test Workflow',
tags: [],
},
readOnly: false,
},
global: {
mocks: {
onSaveButtonClick,
},
},
});

await fireEvent.click(getByTestId('workflow-save-button'));
expect(onSaveButtonClick).toHaveBeenCalled();
});

it('opens share modal on share button click', async () => {
const onShareButtonClick = vi.fn();
const { getByTestId } = renderComponent({
props: {
workflow: {
id: '1',
name: 'Test Workflow',
tags: [],
},
readOnly: false,
},
global: {
mocks: {
onShareButtonClick,
},
},
});

await fireEvent.click(getByTestId('workflow-share-button'));
expect(onShareButtonClick).toHaveBeenCalled();
});
});
Loading
Loading