Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix(editor): Update the universal create button interaction #12105

Merged
44 changes: 3 additions & 41 deletions cypress/pages/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,10 @@ export class CredentialsPage extends BasePage {
getters = {
emptyListCreateCredentialButton: () => cy.getByTestId('empty-resources-list').find('button'),
createCredentialButton: () => {
cy.getByTestId('resource-add').should('be.visible').click();
cy.getByTestId('resource-add')
.find('.el-sub-menu__title')
.as('menuitem')
.should('have.attr', 'aria-describedby');

cy.get('@menuitem')
.should('be.visible')
.invoke('attr', 'aria-describedby')
.then((el) => cy.get(`[id="${el}"]`))
.as('submenu');

cy.get('@submenu')
.should('be.visible')
.within((submenu) => {
// If submenu has another submenu
if (submenu.find('[data-test-id="navigation-submenu"]').length) {
cy.wrap(submenu)
.find('[data-test-id="navigation-submenu"]')
.should('be.visible')
.filter(':contains("Credential")')
.as('child')
.click();

cy.get('@child')
.should('be.visible')
.find('[data-test-id="navigation-submenu-item"]')
.should('be.visible')
.filter(':contains("Personal")')
.as('button');
} else {
cy.wrap(submenu)
.find('[data-test-id="navigation-menu-item"]')
.filter(':contains("Credential")')
.as('button');
}
});

return cy.get('@button').should('be.visible');
cy.getByTestId('add-resource').should('be.visible').click();
cy.getByTestId('add-resource').getByTestId('action-credential').should('be.visible');
return cy.getByTestId('add-resource').getByTestId('action-credential');
},

// cy.getByTestId('resources-list-add'),
searchInput: () => cy.getByTestId('resources-list-search'),
emptyList: () => cy.getByTestId('resources-list-empty'),
credentialCards: () => cy.getByTestId('resources-list-item'),
Expand Down
41 changes: 2 additions & 39 deletions cypress/pages/workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,8 @@ export class WorkflowsPage extends BasePage {
newWorkflowTemplateCard: () => cy.getByTestId('new-workflow-template-card'),
searchBar: () => cy.getByTestId('resources-list-search'),
createWorkflowButton: () => {
cy.getByTestId('resource-add').should('be.visible').click();
cy.getByTestId('resource-add')
.find('.el-sub-menu__title')
.as('menuitem')
.should('have.attr', 'aria-describedby');

cy.get('@menuitem')
.should('be.visible')
.invoke('attr', 'aria-describedby')
.then((el) => cy.get(`[id="${el}"]`))
.as('submenu');

cy.get('@submenu')
.should('be.visible')
.within((submenu) => {
// If submenu has another submenu
if (submenu.find('[data-test-id="navigation-submenu"]').length) {
cy.wrap(submenu)
.find('[data-test-id="navigation-submenu"]')
.should('be.visible')
.filter(':contains("Workflow")')
.as('child')
.click();

cy.get('@child')
.should('be.visible')
.find('[data-test-id="navigation-submenu-item"]')
.should('be.visible')
.filter(':contains("Personal")')
.as('button');
} else {
cy.wrap(submenu)
.find('[data-test-id="navigation-menu-item"]')
.filter(':contains("Workflow")')
.as('button');
}
});

return cy.get('@button').should('be.visible');
cy.getByTestId('add-resource-workflow').should('be.visible');
return cy.getByTestId('add-resource-workflow');
},
workflowCards: () => cy.getByTestId('resources-list-item'),
workflowCard: (workflowName: string) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,10 @@ const createToastMessagingForNewCredentials = (
toastText = i18n.baseText('credentials.create.personal.toast.text');
}

if (projectsStore.currentProject) {
if (
projectsStore.currentProject &&
projectsStore.currentProject.id !== projectsStore.personalProject?.id
) {
toastTitle = i18n.baseText('credentials.create.project.toast.title', {
interpolate: { projectName: project?.name ?? '' },
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,11 @@ function showCreateWorkflowSuccessToast(id?: string) {
if (!id || ['new', PLACEHOLDER_EMPTY_WORKFLOW_ID].includes(id)) {
let toastTitle = locale.baseText('workflows.create.personal.toast.title');
let toastText = locale.baseText('workflows.create.personal.toast.text');
if (projectsStore.currentProject) {

if (
projectsStore.currentProject &&
projectsStore.currentProject.id !== projectsStore.personalProject?.id
) {
toastTitle = locale.baseText('workflows.create.project.toast.title', {
interpolate: { projectName: projectsStore.currentProject.name ?? '' },
});
Expand Down
3 changes: 2 additions & 1 deletion packages/editor-ui/src/components/MainSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ const {
handleSelect: handleMenuSelect,
createProjectAppendSlotName,
projectsLimitReachedMessage,
upgradeLabel,
} = useGlobalEntityCreation();
onClickOutside(createBtn as Ref<VueInstance>, () => {
createBtn.value?.close();
Expand Down Expand Up @@ -336,7 +337,7 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
type="tertiary"
@click="handleMenuSelect(item.id)"
>
{{ i18n.baseText('generic.upgrade') }}
{{ upgradeLabel }}
</N8nButton>
</N8nTooltip>
</template>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<script lang="ts" setup>
import { N8nIconButton, N8nActionToggle } from 'n8n-design-system';

type Action = {
label: string;
value: string;
disabled: boolean;
};
defineProps<{
actions: Action[];
}>();

const emit = defineEmits<{
action: [id: string];
}>();
</script>

<template>
<div :class="[$style.buttonGroup]">
<slot></slot>
<N8nActionToggle
data-test-id="add-resource"
:actions="actions"
placement="bottom-end"
:teleported="false"
@action="emit('action', $event)"
>
<N8nIconButton :class="[$style.buttonGroupDropdown]" icon="angle-down" />
</N8nActionToggle>
</div>
</template>

<style lang="scss" module>
.buttonGroup {
display: inline-flex;

:global(> .button) {
border-right: 1px solid var(--button-font-color, var(--color-button-primary-font));

&:not(:first-child) {
border-radius: 0;
}

&:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
}

.buttonGroupDropdown {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
</style>
70 changes: 44 additions & 26 deletions packages/editor-ui/src/components/Projects/ProjectHeader.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createTestingPinia } from '@pinia/testing';
import { within } from '@testing-library/dom';
import { createComponentRenderer } from '@/__tests__/render';
import { mockedStore } from '@/__tests__/utils';
import { createTestProject } from '@/__tests__/data/projects';
Expand All @@ -10,13 +9,19 @@ import { useProjectsStore } from '@/stores/projects.store';
import type { Project } from '@/types/projects.types';
import { ProjectTypes } from '@/types/projects.types';
import { VIEWS } from '@/constants';
import userEvent from '@testing-library/user-event';
import { waitFor, within } from '@testing-library/vue';

const mockPush = vi.fn();
vi.mock('vue-router', async () => {
const actual = await vi.importActual('vue-router');
const params = {};
const location = {};
return {
...actual,
useRouter: () => ({
push: mockPush,
}),
useRoute: () => ({
params,
location,
Expand All @@ -32,7 +37,6 @@ const renderComponent = createComponentRenderer(ProjectHeader, {
global: {
stubs: {
ProjectTabs: projectTabsSpy,
N8nNavigationDropdown: true,
},
},
});
Expand Down Expand Up @@ -143,39 +147,53 @@ describe('ProjectHeader', () => {
);
});

test.each([
[null, 'Create'],
[createTestProject({ type: ProjectTypes.Personal }), 'Create in personal'],
[createTestProject({ type: ProjectTypes.Team }), 'Create in project'],
])('in project %s should render correct create button label %s', (project, label) => {
it('should create a workflow', async () => {
const project = createTestProject({
scopes: ['workflow:create'],
});
projectsStore.currentProject = project;
const { getByTestId } = renderComponent({
global: {
stubs: {
N8nNavigationDropdown: {
template: '<div><slot></slot></div>',
},
},
},

const { getByTestId } = renderComponent();

await userEvent.click(getByTestId('add-resource-workflow'));

expect(mockPush).toHaveBeenCalledWith({
name: VIEWS.NEW_WORKFLOW,
query: { projectId: project.id },
});
});

describe('dropdown', () => {
it('should create a credential', async () => {
const project = createTestProject({
scopes: ['credential:create'],
});
projectsStore.currentProject = project;

expect(within(getByTestId('resource-add')).getByRole('button', { name: label })).toBeVisible();
const { getByTestId } = renderComponent();

await userEvent.click(within(getByTestId('add-resource')).getByRole('button'));

await waitFor(() => expect(getByTestId('action-credential')).toBeVisible());

await userEvent.click(getByTestId('action-credential'));

expect(mockPush).toHaveBeenCalledWith({
name: VIEWS.PROJECTS_CREDENTIALS,
params: {
projectId: project.id,
credentialId: 'create',
},
});
});
});

it('should not render creation button in setting page', async () => {
projectsStore.currentProject = createTestProject({ type: ProjectTypes.Personal });
vi.spyOn(router, 'useRoute').mockReturnValueOnce({
name: VIEWS.PROJECT_SETTINGS,
} as RouteLocationNormalizedLoadedGeneric);
const { queryByTestId } = renderComponent({
global: {
stubs: {
N8nNavigationDropdown: {
template: '<div><slot></slot></div>',
},
},
},
});
expect(queryByTestId('resource-add')).not.toBeInTheDocument();
const { queryByTestId } = renderComponent();
expect(queryByTestId('add-resource-buttons')).not.toBeInTheDocument();
});
});
Loading
Loading