mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-24 23:22:38 +08:00
fix(editor): Show correct project in workflow breadcrumb for projectId links (#32103)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
2740c07039
commit
fd23d4da56
+115
-2
@@ -3,14 +3,24 @@ import { reactive } from 'vue';
|
||||
import { vi } from 'vitest';
|
||||
import { useProjectsStore } from './projects.store';
|
||||
import * as projectsApi from './projects.api';
|
||||
import type { Project, ProjectListItem } from './projects.types';
|
||||
import type { Project, ProjectListItem, ProjectType } from './projects.types';
|
||||
import { ProjectTypes } from './projects.types';
|
||||
import type { ProjectRole, Scope } from '@n8n/permissions';
|
||||
|
||||
type MockRoute = {
|
||||
params: Record<string, string>;
|
||||
query: Record<string, string>;
|
||||
path: string;
|
||||
};
|
||||
|
||||
// Minimal router mock to satisfy useRoute usage in the store
|
||||
const { mockRoute } = vi.hoisted(() => ({
|
||||
mockRoute: { params: {}, query: {}, path: '' } as MockRoute,
|
||||
}));
|
||||
|
||||
vi.mock('vue-router', async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
useRoute: () => reactive({ params: {}, query: {}, path: '' }),
|
||||
useRoute: () => reactive(mockRoute),
|
||||
}));
|
||||
|
||||
vi.mock('./projects.api', () => ({
|
||||
@@ -221,3 +231,106 @@ describe('useProjectsStore.updateProject (partial payloads)', () => {
|
||||
expect(store.currentProject?.relations.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useProjectsStore.setProjectNavActiveIdByWorkflowHomeProject', () => {
|
||||
const route = reactive(mockRoute);
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
vi.clearAllMocks();
|
||||
route.params = {};
|
||||
route.query = {};
|
||||
route.path = '';
|
||||
});
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const makeProject = (id: string, type: ProjectType = ProjectTypes.Team): Project => ({
|
||||
id,
|
||||
name: `Project ${id}`,
|
||||
description: null,
|
||||
icon: { type: 'icon', value: 'layers' },
|
||||
type,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
relations: [],
|
||||
scopes: [] as Scope[],
|
||||
});
|
||||
|
||||
const makeStore = () => {
|
||||
const store = useProjectsStore();
|
||||
store.personalProject = makeProject('personal-1', ProjectTypes.Personal);
|
||||
return store;
|
||||
};
|
||||
|
||||
it('fetches the workflow home project when none is loaded', async () => {
|
||||
const store = makeStore();
|
||||
const teamProject = makeProject('team-1');
|
||||
mockedProjectsApi.getProject.mockResolvedValue(teamProject);
|
||||
|
||||
await store.setProjectNavActiveIdByWorkflowHomeProject(teamProject);
|
||||
|
||||
expect(mockedProjectsApi.getProject).toHaveBeenCalledWith(expect.anything(), 'team-1');
|
||||
expect(store.currentProject?.id).toBe('team-1');
|
||||
expect(store.projectNavActiveId).toBe('team-1');
|
||||
});
|
||||
|
||||
it('fetches the workflow home project even when the route has a projectId query param', async () => {
|
||||
route.query = { projectId: 'team-1' };
|
||||
const store = makeStore();
|
||||
const teamProject = makeProject('team-1');
|
||||
mockedProjectsApi.getProject.mockResolvedValue(teamProject);
|
||||
|
||||
await store.setProjectNavActiveIdByWorkflowHomeProject(teamProject);
|
||||
|
||||
expect(mockedProjectsApi.getProject).toHaveBeenCalledWith(expect.anything(), 'team-1');
|
||||
expect(store.currentProject?.id).toBe('team-1');
|
||||
});
|
||||
|
||||
it('replaces a previously loaded project that does not match the workflow home project', async () => {
|
||||
const store = makeStore();
|
||||
store.currentProject = makeProject('team-2');
|
||||
const teamProject = makeProject('team-1');
|
||||
mockedProjectsApi.getProject.mockResolvedValue(teamProject);
|
||||
|
||||
await store.setProjectNavActiveIdByWorkflowHomeProject(teamProject);
|
||||
|
||||
expect(mockedProjectsApi.getProject).toHaveBeenCalledWith(expect.anything(), 'team-1');
|
||||
expect(store.currentProject?.id).toBe('team-1');
|
||||
});
|
||||
|
||||
it('does not refetch when the workflow home project is already loaded', async () => {
|
||||
const store = makeStore();
|
||||
store.currentProject = makeProject('team-1');
|
||||
|
||||
await store.setProjectNavActiveIdByWorkflowHomeProject(makeProject('team-1'));
|
||||
|
||||
expect(mockedProjectsApi.getProject).not.toHaveBeenCalled();
|
||||
expect(store.currentProject?.id).toBe('team-1');
|
||||
});
|
||||
|
||||
it('sets the personal project as current for own personal workflows', async () => {
|
||||
route.query = { projectId: 'personal-1' };
|
||||
const store = makeStore();
|
||||
|
||||
await store.setProjectNavActiveIdByWorkflowHomeProject(
|
||||
makeProject('personal-1', ProjectTypes.Personal),
|
||||
);
|
||||
|
||||
expect(mockedProjectsApi.getProject).not.toHaveBeenCalled();
|
||||
expect(store.currentProject?.id).toBe('personal-1');
|
||||
expect(store.projectNavActiveId).toBe('personal-1');
|
||||
});
|
||||
|
||||
it('marks workflows shared with the user as shared context', async () => {
|
||||
const store = makeStore();
|
||||
|
||||
await store.setProjectNavActiveIdByWorkflowHomeProject(
|
||||
makeProject('other-personal', ProjectTypes.Personal),
|
||||
[makeProject('personal-1', ProjectTypes.Personal)],
|
||||
);
|
||||
|
||||
expect(mockedProjectsApi.getProject).not.toHaveBeenCalled();
|
||||
expect(store.currentProject).toBeNull();
|
||||
expect(store.projectNavActiveId).toBe('shared');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -254,8 +254,10 @@ export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
|
||||
|
||||
// Handle team projects
|
||||
projectNavActiveId.value = workflowHomeProject?.id ?? null;
|
||||
if (workflowHomeProject?.id && !currentProjectId.value) {
|
||||
await getProject(workflowHomeProject?.id);
|
||||
// Compare against the loaded project, not currentProjectId: a `?projectId=` query param
|
||||
// makes currentProjectId truthy without currentProject ever being fetched
|
||||
if (workflowHomeProject?.id && currentProject.value?.id !== workflowHomeProject.id) {
|
||||
await getProject(workflowHomeProject.id);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user