Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 111 additions & 19 deletions src/renderer/src/components/App/OrgHead.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,72 @@ import { OrganizationD } from '@model/organization';
// Mock memory with query function that can return organization data
// The findRecord function uses: memory.cache.query((q) => q.findRecord({ type, id }))
// The findRecords function uses: memory.cache.query((q) => q.findRecords(type))
const createMockMemory = (orgData?: OrganizationD): Memory => {
const createMockMemory = (
orgData?: OrganizationD,
isAdmin: boolean = false,
userId: string = 'test-user-id'
): Memory => {
// Store organizations in an array for findRecords queries
const organizations = orgData ? [orgData] : [];

// Create role records
const adminRoleId = 'admin-role-id';
const memberRoleId = 'member-role-id';
const roles = [
{
id: adminRoleId,
type: 'role',
attributes: {
roleName: 'Admin',
orgRole: true,
},
keys: { remoteId: 'admin-role-remote-id' },
},
{
id: memberRoleId,
type: 'role',
attributes: {
roleName: 'Member',
orgRole: true,
},
keys: { remoteId: 'member-role-remote-id' },
},
];

// Create organization membership record if orgData exists
const orgMemberships = orgData
? [
{
id: 'org-membership-id',
type: 'organizationmembership',
relationships: {
user: { data: { type: 'user', id: userId } },
organization: { data: { type: 'organization', id: orgData.id } },
role: {
data: {
type: 'role',
id: isAdmin ? adminRoleId : memberRoleId,
},
},
},
keys: { remoteId: 'org-membership-remote-id' },
},
]
: [];

// Create a mock query builder with both findRecord and findRecords
const createMockQueryBuilder = () => ({
findRecord: ({ type, id }: { type: string; id: string }) => {
// Return the organization data if it matches
if (type === 'organization' && id === orgData?.id && orgData) {
return orgData;
}
// Return undefined for other record types (user, role, etc.)
// Return role records
if (type === 'role') {
const role = roles.find((r) => r.id === id);
if (role) return role;
}
// Return undefined for other record types (user, etc.)
return undefined;
},
findRecords: (type: string) => {
Expand All @@ -39,8 +93,15 @@ const createMockMemory = (orgData?: OrganizationD): Memory => {
if (type === 'organization') {
return organizations;
}
// Return empty array for other types (roles, organizationmembership, etc.)
// This is used by useRole for roles, organizationmembership, etc.
// Return roles array when querying for 'role' type
if (type === 'role') {
return roles;
}
// Return organization memberships when querying for 'organizationmembership' type
if (type === 'organizationmembership') {
return orgMemberships;
}
// Return empty array for other types
return [];
},
});
Expand Down Expand Up @@ -180,7 +241,8 @@ describe('OrgHead', () => {
initialState: ReturnType<typeof createInitialState>,
initialEntries: string[] = ['/team'],
orgId?: string,
orgData?: OrganizationD
orgData?: OrganizationD,
isAdmin: boolean = false
) => {
// Set organization ID in localStorage if provided
if (orgId) {
Expand All @@ -189,8 +251,10 @@ describe('OrgHead', () => {
});
}

// Create memory with org data if provided
const memory = orgData ? createMockMemory(orgData) : createMockMemory();
// Create memory with org data and admin status if provided
const memory = orgData
? createMockMemory(orgData, isAdmin, initialState.user)
: createMockMemory(undefined, false, initialState.user);

// Create stubs for TeamContext methods
const mockTeamUpdate = cy.stub().as('teamUpdate');
Expand Down Expand Up @@ -316,21 +380,34 @@ describe('OrgHead', () => {
cy.get('h6, [variant="h6"]').should('be.visible');
});

it('should show settings and members buttons when on team screen', () => {
it('should show settings and members buttons when on team screen and user is admin', () => {
const orgId = 'test-org-id';
const orgName = 'Test Organization';
const orgData = createMockOrganization(orgId, orgName);

mountOrgHead(createInitialState(), ['/team'], orgId, orgData);
mountOrgHead(createInitialState(), ['/team'], orgId, orgData, true);

// Check for settings and members icon buttons
// Check for settings and members icon buttons (both should be visible for admin)
// MUI IconButtons contain SVG icons as children
cy.get('button').should('have.length.at.least', 2);
// Verify that buttons contain SVG elements (icon buttons should have SVG children)
cy.get('button').should('be.visible');
cy.get('button svg').should('have.length.at.least', 2);
});

it('should show only members button (not settings) when on team screen and user is not admin', () => {
const orgId = 'test-org-id';
const orgName = 'Test Organization';
const orgData = createMockOrganization(orgId, orgName);

mountOrgHead(createInitialState(), ['/team'], orgId, orgData, false);

// Should only have members button, not settings button
cy.get('button').should('have.length', 1);
cy.get('button').should('be.visible');
cy.get('button svg').should('have.length', 1);
});

it('should not show settings and members buttons when not on team screen', () => {
const orgId = 'test-org-id';
const orgName = 'Test Organization';
Expand All @@ -343,45 +420,60 @@ describe('OrgHead', () => {
cy.get('button').should('not.exist');
});

it('should open TeamDialog when settings button is clicked', () => {
it('should open TeamDialog when settings button is clicked (admin only)', () => {
const orgId = 'test-org-id';
const orgName = 'Test Organization';
const orgData = createMockOrganization(orgId, orgName);

mountOrgHead(createInitialState(), ['/team'], orgId, orgData);
mountOrgHead(createInitialState(), ['/team'], orgId, orgData, true);

// Find and click the first button (settings button)
// The settings button is the first IconButton rendered
// The settings button is the first IconButton rendered (only visible for admin)
cy.get('button').first().click();

// TeamDialog should be open (check for a dialog or form element)
cy.get('[role="dialog"]').should('be.visible');
});

it('should open members dialog when members button is clicked', () => {
it('should open members dialog when members button is clicked (admin)', () => {
const orgId = 'test-org-id';
const orgName = 'Test Organization';
const orgData = createMockOrganization(orgId, orgName);

mountOrgHead(createInitialState(), ['/team'], orgId, orgData);
mountOrgHead(createInitialState(), ['/team'], orgId, orgData, true);

// Find and click the second button (members button)
// The members button is the second IconButton rendered
// The members button is the second IconButton rendered (when admin)
cy.get('button').eq(1).click();

// BigDialog should be open with members title
cy.contains('Members of Test Organization').should('be.visible');
cy.get('[role="dialog"]').should('be.visible');
});

it('should close TeamDialog when editOpen is set to false', () => {
it('should open members dialog when members button is clicked (non-admin)', () => {
const orgId = 'test-org-id';
const orgName = 'Test Organization';
const orgData = createMockOrganization(orgId, orgName);

mountOrgHead(createInitialState(), ['/team'], orgId, orgData);
mountOrgHead(createInitialState(), ['/team'], orgId, orgData, false);

// Find and click the button (members button - only button for non-admin)
cy.get('button').first().click();

// BigDialog should be open with members title
cy.contains('Members of Test Organization').should('be.visible');
cy.get('[role="dialog"]').should('be.visible');
});

it('should close TeamDialog when editOpen is set to false (admin only)', () => {
const orgId = 'test-org-id';
const orgName = 'Test Organization';
const orgData = createMockOrganization(orgId, orgName);

mountOrgHead(createInitialState(), ['/team'], orgId, orgData, true);

// Open the dialog
// Open the dialog (settings button is first for admin)
cy.get('button').first().click();

cy.get('[role="dialog"]').should('be.visible');
Expand Down
15 changes: 11 additions & 4 deletions src/renderer/src/components/App/OrgHead.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ export const OrgHead = () => {
const { pathname } = useLocation();
const isTeamScreen = pathname.includes('/team');
const isSwitchTeamsScreen = pathname.includes('/switch-teams');
const { userIsOrgAdmin, setMyOrgRole } = useRole();
const ctx = useContext(TeamContext);
const { teamDelete } = ctx?.state ?? {};
const { setMyOrgRole } = useRole();

const orgId = useMemo(
() => localStorage.getItem(localUserKey(LocalKey.team)),
Expand All @@ -52,6 +52,11 @@ export const OrgHead = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [orgId, organizations]);

const isAdmin = useMemo(
() => userIsOrgAdmin(orgId ?? ''),
[orgId, userIsOrgAdmin]
);

const handleSettings = () => {
setEditOpen(true);
};
Expand Down Expand Up @@ -99,9 +104,11 @@ export const OrgHead = () => {
</Typography>
{isTeamScreen && (
<>
<IconButton onClick={handleSettings}>
<SettingsIcon />
</IconButton>
{isAdmin && (
<IconButton onClick={handleSettings}>
<SettingsIcon />
</IconButton>
)}
{orgRec && (
<IconButton onClick={handleMembers(orgRec)}>
<UsersIcon />
Expand Down