Skip to content

Commit 467aa10

Browse files
gtryusGreg Trihus
andauthored
TT-7006 OrgHead component displays project name when in project (#177)
- Updated OrgHead component to conditionally display project names based on the current route and project context. - Refactored createMockMemory and createInitialState functions to include project data for testing. - Added tests to verify correct rendering of project names in various scenarios, improving test coverage and user experience. These changes ensure that the OrgHead component accurately reflects the current project context, enhancing usability across different screens. Update src/renderer/src/components/App/OrgHead.tsx Co-authored-by: Copilot <[email protected]> update cypress tests Co-authored-by: Greg Trihus <[email protected]>
1 parent 5bb677b commit 467aa10

File tree

2 files changed

+260
-55
lines changed

2 files changed

+260
-55
lines changed

src/renderer/src/components/App/OrgHead.cy.tsx

Lines changed: 249 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,21 @@ import { UnsavedProvider } from '../../context/UnsavedContext';
1515
import { TeamContext } from '../../context/TeamContext';
1616
import { TokenContext } from '../../context/TokenProvider';
1717
import { OrganizationD } from '@model/organization';
18+
import { ProjectD } from '../../model';
1819

1920
// Mock memory with query function that can return organization data
2021
// The findRecord function uses: memory.cache.query((q) => q.findRecord({ type, id }))
2122
// The findRecords function uses: memory.cache.query((q) => q.findRecords(type))
2223
const createMockMemory = (
2324
orgData?: OrganizationD,
2425
isAdmin: boolean = false,
25-
userId: string = 'test-user-id'
26+
userId: string = 'test-user-id',
27+
projectData?: ProjectD[]
2628
): Memory => {
2729
// Store organizations in an array for findRecords queries
2830
const organizations = orgData ? [orgData] : [];
31+
// Store projects in an array for findRecords queries
32+
const projects = projectData || [];
2933

3034
// Create role records
3135
const adminRoleId = 'admin-role-id';
@@ -79,6 +83,11 @@ const createMockMemory = (
7983
if (type === 'organization' && id === orgData?.id && orgData) {
8084
return orgData;
8185
}
86+
// Return project data if it matches
87+
if (type === 'project') {
88+
const project = projects.find((p) => p.id === id);
89+
if (project) return project;
90+
}
8291
// Return role records
8392
if (type === 'role') {
8493
const role = roles.find((r) => r.id === id);
@@ -93,6 +102,11 @@ const createMockMemory = (
93102
if (type === 'organization') {
94103
return organizations;
95104
}
105+
// Return projects array when querying for 'project' type
106+
// This is used by useOrbitData('project')
107+
if (type === 'project') {
108+
return projects;
109+
}
96110
// Return roles array when querying for 'role' type
97111
if (type === 'role') {
98112
return roles;
@@ -197,43 +211,82 @@ describe('OrgHead', () => {
197211
relationships: {},
198212
}) as OrganizationD;
199213

200-
const createInitialState = (overrides = {}, orgData?: OrganizationD) => ({
201-
coordinator: mockCoordinator,
202-
errorReporter: bugsnagClient,
203-
fingerprint: 'test-fingerprint',
204-
memory: createMockMemory(orgData),
205-
lang: 'en',
206-
latestVersion: '',
207-
loadComplete: false,
208-
offlineOnly: false,
209-
organization: '',
210-
releaseDate: '',
211-
user: 'test-user-id',
212-
alertOpen: false,
213-
autoOpenAddMedia: false,
214-
changed: false,
215-
connected: true,
216-
dataChangeCount: 0,
217-
developer: false,
218-
enableOffsite: false,
219-
home: false,
220-
importexportBusy: false,
221-
orbitRetries: 0,
222-
orgRole: undefined,
223-
plan: '',
224-
playingMediaId: '',
225-
progress: 0,
226-
project: '',
227-
projectsLoaded: [],
228-
projType: '',
229-
remoteBusy: false,
230-
saveResult: undefined,
231-
snackAlert: undefined,
232-
snackMessage: (<></>) as React.JSX.Element,
233-
offline: false,
234-
mobileView: false,
235-
...overrides,
236-
});
214+
const createMockProject = (id: string, name: string): ProjectD =>
215+
({
216+
id,
217+
type: 'project',
218+
attributes: {
219+
name,
220+
slug: name.toLowerCase().replace(/\s+/g, '-'),
221+
description: null,
222+
uilanguagebcp47: null,
223+
language: 'und',
224+
languageName: null,
225+
defaultFont: null,
226+
defaultFontSize: null,
227+
rtl: false,
228+
spellCheck: false,
229+
allowClaim: false,
230+
isPublic: false,
231+
dateCreated: new Date().toISOString(),
232+
dateUpdated: new Date().toISOString(),
233+
dateArchived: '',
234+
lastModifiedBy: 0,
235+
defaultParams: '{}',
236+
},
237+
relationships: {},
238+
}) as ProjectD;
239+
240+
const createInitialState = (
241+
overrides = {},
242+
orgData?: OrganizationD,
243+
projectData?: ProjectD[]
244+
) => {
245+
// Create memory with orgData and projectData if provided
246+
const memory = createMockMemory(
247+
orgData,
248+
false,
249+
'test-user-id',
250+
projectData
251+
);
252+
return {
253+
coordinator: mockCoordinator,
254+
errorReporter: bugsnagClient,
255+
fingerprint: 'test-fingerprint',
256+
memory,
257+
lang: 'en',
258+
latestVersion: '',
259+
loadComplete: false,
260+
offlineOnly: false,
261+
organization: '',
262+
releaseDate: '',
263+
user: 'test-user-id',
264+
alertOpen: false,
265+
autoOpenAddMedia: false,
266+
changed: false,
267+
connected: true,
268+
dataChangeCount: 0,
269+
developer: false,
270+
enableOffsite: false,
271+
home: false,
272+
importexportBusy: false,
273+
orbitRetries: 0,
274+
orgRole: undefined,
275+
plan: '',
276+
playingMediaId: '',
277+
progress: 0,
278+
project: '',
279+
projectsLoaded: [],
280+
projType: '',
281+
remoteBusy: false,
282+
saveResult: undefined,
283+
snackAlert: undefined,
284+
snackMessage: (<></>) as React.JSX.Element,
285+
offline: false,
286+
mobileView: false,
287+
...overrides,
288+
};
289+
};
237290

238291
// Helper function to mount OrgHead with all required providers
239292
const mountOrgHead = (
@@ -242,7 +295,8 @@ describe('OrgHead', () => {
242295
orgId?: string,
243296
orgData?: OrganizationD,
244297
isAdmin: boolean = false,
245-
personalTeam?: string
298+
personalTeam?: string,
299+
projectData?: ProjectD[]
246300
) => {
247301
// Set organization ID in localStorage if provided
248302
if (orgId) {
@@ -251,10 +305,13 @@ describe('OrgHead', () => {
251305
});
252306
}
253307

254-
// Create memory with org data and admin status if provided
255-
const memory = orgData
256-
? createMockMemory(orgData, isAdmin, initialState.user)
257-
: createMockMemory(undefined, false, initialState.user);
308+
// Create memory with org data, admin status, and project data if provided
309+
// If orgData or projectData is provided to mountOrgHead, create new memory with those
310+
// Otherwise use memory from initialState
311+
const memoryToUse =
312+
orgData !== undefined || projectData !== undefined
313+
? createMockMemory(orgData, isAdmin, initialState.user, projectData)
314+
: initialState.memory;
258315

259316
// Create stubs for TeamContext methods
260317
const mockTeamUpdate = cy.stub().as('teamUpdate');
@@ -335,14 +392,14 @@ describe('OrgHead', () => {
335392
// Create state with memory
336393
const stateWithMemory = {
337394
...initialState,
338-
memory,
395+
memory: memoryToUse,
339396
};
340397

341398
cy.mount(
342399
<MemoryRouter initialEntries={initialEntries}>
343400
<Provider store={mockStore}>
344401
<GlobalProvider init={stateWithMemory}>
345-
<DataProvider dataStore={memory}>
402+
<DataProvider dataStore={memoryToUse}>
346403
<UnsavedProvider>
347404
<TokenContext.Provider value={mockTokenContextValue as any}>
348405
<TeamContext.Provider value={mockTeamContextValue as any}>
@@ -369,15 +426,24 @@ describe('OrgHead', () => {
369426

370427
it('should render product name fallback when organization does not exist', () => {
371428
mountOrgHead(createInitialState(), ['/team']);
372-
// The component should render - it will show product name from API_CONFIG
373-
// Note: The actual product name depends on API_CONFIG, which should be available
374-
cy.get('h6, [variant="h6"]').should('be.visible');
429+
// When on team screen and orgRec is undefined, cleanOrgName returns empty string
430+
// So the Typography will render but may be empty. Check that the element exists.
431+
cy.get('h6, [variant="h6"]').should('exist');
432+
// The text will be empty when orgRec doesn't exist on team screen
433+
cy.get('h6, [variant="h6"]')
434+
.contains('Audio Project Manager')
435+
.should('be.visible');
375436
});
376437

377438
it('should render product name fallback when orgId is not set', () => {
378439
mountOrgHead(createInitialState(), ['/team'], undefined);
379-
// The component should render - it will show product name from API_CONFIG
380-
cy.get('h6, [variant="h6"]').should('be.visible');
440+
// When on team screen and orgId is not set, orgRec will be undefined
441+
// So cleanOrgName returns empty string. Check that the element exists.
442+
cy.get('h6, [variant="h6"]').should('exist');
443+
// The text will be empty when orgId is not set on team screen
444+
cy.get('h6, [variant="h6"]')
445+
.contains('Audio Project Manager')
446+
.should('be.visible');
381447
});
382448

383449
it('should show settings and members buttons when on team screen and user is admin', () => {
@@ -412,11 +478,27 @@ describe('OrgHead', () => {
412478
const orgId = 'test-org-id';
413479
const orgName = 'Test Organization';
414480
const orgData = createMockOrganization(orgId, orgName);
481+
const projectId = 'test-project-id';
482+
const projectName = 'Test Project';
483+
const projectData = createMockProject(projectId, projectName);
415484

416-
mountOrgHead(createInitialState(), ['/project'], orgId, orgData);
485+
// When not on team/switch-teams screen, it shows project name if project is set,
486+
// otherwise product name. In this test we set the project, so it should show the project name.
487+
mountOrgHead(
488+
createInitialState({ project: projectId }, orgData, [projectData]),
489+
['/project'],
490+
orgId,
491+
orgData,
492+
false,
493+
undefined,
494+
[projectData]
495+
);
417496

418-
// Should only render the Typography, no buttons
419-
cy.contains(orgName).should('be.visible');
497+
// Should display project name (not organization name) when not on team screen
498+
cy.contains(projectName).should('be.visible');
499+
// Should NOT show organization name
500+
cy.contains(orgName).should('not.exist');
501+
// Should not have buttons
420502
cy.get('button').should('not.exist');
421503
});
422504

@@ -641,4 +723,118 @@ describe('OrgHead', () => {
641723
cy.get('button').should('have.length', 1);
642724
cy.get('button svg').should('have.length', 1);
643725
});
726+
727+
it('should display project name when not on team or switch-teams screen and project is set', () => {
728+
const projectId = 'test-project-id';
729+
const projectName = 'Test Project';
730+
const projectData = createMockProject(projectId, projectName);
731+
732+
// Mount on a route that is not /team or /switch-teams (e.g., /plan)
733+
mountOrgHead(
734+
createInitialState({ project: projectId }, undefined, [projectData]),
735+
['/plan/123/0'],
736+
undefined,
737+
undefined,
738+
false,
739+
undefined,
740+
[projectData]
741+
);
742+
743+
// Should display the project name
744+
cy.contains(projectName).should('be.visible');
745+
});
746+
747+
it('should display project name from projects table using global project id', () => {
748+
const projectId1 = 'test-project-id-1';
749+
const projectName1 = 'First Project';
750+
const projectId2 = 'test-project-id-2';
751+
const projectName2 = 'Second Project';
752+
const projectData = [
753+
createMockProject(projectId1, projectName1),
754+
createMockProject(projectId2, projectName2),
755+
];
756+
757+
// Set global project to the second project
758+
mountOrgHead(
759+
createInitialState({ project: projectId2 }, undefined, projectData),
760+
['/plan/456/0'],
761+
undefined,
762+
undefined,
763+
false,
764+
undefined,
765+
projectData
766+
);
767+
768+
// Should display the second project name (matching the global project id)
769+
cy.contains(projectName2).should('be.visible');
770+
// Should NOT display the first project name
771+
cy.contains(projectName1).should('not.exist');
772+
});
773+
774+
it('should display product name fallback when project is not found in projects table', () => {
775+
const projectId = 'non-existent-project-id';
776+
const projectName = 'Existing Project';
777+
const projectData = [createMockProject('other-project-id', projectName)];
778+
779+
// Set global project to an id that doesn't exist in the projects array
780+
mountOrgHead(
781+
createInitialState({ project: projectId }, undefined, projectData),
782+
['/plan/789/0'],
783+
undefined,
784+
undefined,
785+
false,
786+
undefined,
787+
projectData
788+
);
789+
790+
// Should display product name fallback since project is not found
791+
cy.contains('Audio Project Manager').should('be.visible');
792+
// Should NOT display the existing project name
793+
cy.contains(projectName).should('not.exist');
794+
});
795+
796+
it('should display product name fallback when project global is not set', () => {
797+
const projectName = 'Some Project';
798+
const projectData = [createMockProject('some-project-id', projectName)];
799+
800+
// Don't set the project global
801+
mountOrgHead(
802+
createInitialState({ project: '' }, undefined, projectData),
803+
['/plan/999/0'],
804+
undefined,
805+
undefined,
806+
false,
807+
undefined,
808+
projectData
809+
);
810+
811+
// Should display product name fallback since project is not set
812+
cy.contains('Audio Project Manager').should('be.visible');
813+
// Should NOT display the project name
814+
cy.contains(projectName).should('not.exist');
815+
});
816+
817+
it('should display project name on routes other than team and switch-teams', () => {
818+
const projectId = 'test-project-id';
819+
const projectName = 'My Project';
820+
const projectData = createMockProject(projectId, projectName);
821+
822+
// Test various routes that are not /team or /switch-teams
823+
const routes = ['/plan/123/0', '/projects', '/some-other-route'];
824+
825+
routes.forEach((route) => {
826+
mountOrgHead(
827+
createInitialState({ project: projectId }, undefined, [projectData]),
828+
[route],
829+
undefined,
830+
undefined,
831+
false,
832+
undefined,
833+
[projectData]
834+
);
835+
836+
// Should display the project name
837+
cy.contains(projectName).should('be.visible');
838+
});
839+
});
644840
});

0 commit comments

Comments
 (0)