fix(editor): Render fallback icon for projects without one in sidebar (#30572)

This commit is contained in:
Ricardo Espinoza 2026-05-18 11:52:09 -04:00 committed by GitHub
parent cebb78d515
commit cd1bae84e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 31 additions and 7 deletions

View File

@ -176,6 +176,23 @@ describe('ProjectsNavigation', () => {
expect(getByText('Projects')).toBeVisible();
});
it('should render a fallback icon for projects with no icon set', async () => {
projectsStore.teamProjectsLimit = -1;
const projectWithoutIcon = createProjectListItem('team');
projectWithoutIcon.icon = null;
projectsStore.myProjects = [projectWithoutIcon];
const { getAllByTestId } = renderComponent({
props: {
collapsed: false,
},
});
const items = getAllByTestId('project-menu-item');
expect(items).toHaveLength(1);
expect(items[0].querySelector('svg')).toBeInTheDocument();
});
it('should not render shared menu item when only one verified user', async () => {
// Only one verified user
usersStore.allUsers = [

View File

@ -9,6 +9,7 @@ import type { IMenuItem } from '@n8n/design-system/types';
import { useI18n } from '@n8n/i18n';
import { computed, onBeforeMount, onBeforeUnmount, ref, watch } from 'vue';
import { useProjectsStore } from '../projects.store';
import { DEFAULT_PROJECT_ICON } from '../projects.constants';
import type { ProjectListItem } from '../projects.types';
import { CHAT_VIEW } from '@/features/ai/chatHub/constants';
import { useFavoritesStore } from '@/app/stores/favorites.store';
@ -91,7 +92,7 @@ const shared = computed<IMenuItem>(() => ({
const getProjectMenuItem = (project: ProjectListItem): IMenuItem => ({
id: project.id,
label: project.name ?? '',
icon: project.icon as IMenuItem['icon'],
icon: (project.icon as IMenuItem['icon']) ?? DEFAULT_PROJECT_ICON,
route: {
to: {
name: VIEWS.PROJECTS_WORKFLOWS,

View File

@ -99,7 +99,10 @@ describe('useFavoriteNavItems', () => {
const { favoriteProjectItems } = useFavoriteNavItems();
expect(favoriteProjectItems.value[0].menuItem.icon).toBe('layers');
expect(favoriteProjectItems.value[0].menuItem.icon).toEqual({
type: 'icon',
value: 'layers',
});
});
it('should use raw resourceId as item id (no prefix)', () => {

View File

@ -3,6 +3,7 @@ import type { IMenuItem } from '@n8n/design-system/types';
import { VIEWS } from '@/app/constants';
import { useFavoritesStore } from '@/app/stores/favorites.store';
import { useProjectsStore } from '../projects.store';
import { DEFAULT_PROJECT_ICON } from '../projects.constants';
import type { Project } from '../projects.types';
import { DATA_TABLE_DETAILS } from '@/features/core/dataTable/constants';
import type { FavoriteResourceType } from '@/app/api/favorites';
@ -46,7 +47,7 @@ export function useFavoriteNavItems() {
menuItem: {
id: f.resourceId,
label: f.resourceName,
icon: (project?.icon as IMenuItem['icon']) ?? ('layers' as IMenuItem['icon']),
icon: (project?.icon as IMenuItem['icon']) ?? DEFAULT_PROJECT_ICON,
route: { to: { name: VIEWS.PROJECTS_WORKFLOWS, params: { projectId: f.resourceId } } },
},
resourceId: f.resourceId,

View File

@ -1 +1,5 @@
import type { IconOrEmoji } from '@n8n/design-system/components/N8nIconPicker/types';
export const PROJECT_MOVE_RESOURCE_MODAL = 'projectMoveResourceModal';
export const DEFAULT_PROJECT_ICON: IconOrEmoji = { type: 'icon', value: 'layers' };

View File

@ -7,6 +7,7 @@ import { useDebounceFn } from '@vueuse/core';
import { useUsersStore } from '@/features/settings/users/users.store';
import { useI18n } from '@n8n/i18n';
import { type ResourceCounts, useProjectsStore } from '../projects.store';
import { DEFAULT_PROJECT_ICON } from '../projects.constants';
import type { Project, ProjectRelation, ProjectMemberData } from '../projects.types';
import { useToast } from '@/app/composables/useToast';
import { DEBOUNCE_TIME, getDebounceTime, VIEWS } from '@/app/constants';
@ -84,10 +85,7 @@ const suppressNextSync = ref(false);
const nameInput = ref<InstanceType<typeof N8nFormInput> | null>(null);
const projectIcon = ref<IconOrEmoji>({
type: 'icon',
value: 'layers',
});
const projectIcon = ref<IconOrEmoji>({ ...DEFAULT_PROJECT_ICON });
const search = ref('');
const membersTableState = ref<TableOptions>({