Skip to content

Commit 6fbee27

Browse files
Rajgupta36kasyaarkid15r
committed
UI/ux mentorship program update (#2244)
* fix ui bugs * update format * fix test cases * fix test cases * update UI * update UI * update line clamp property * fix test case * Update styling --------- Co-authored-by: Kate Golovanova <[email protected]> Co-authored-by: Arkadii Yakovets <[email protected]>
1 parent f6a3dbe commit 6fbee27

File tree

16 files changed

+126
-260
lines changed

16 files changed

+126
-260
lines changed

frontend/__tests__/unit/components/ProgramCard.test.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ jest.mock('components/ActionButton', () => ({
2424
),
2525
}))
2626

27+
jest.mock('@heroui/tooltip', () => ({
28+
Tooltip: ({ children, content }: { children: React.ReactNode; content: string }) => (
29+
<div data-testid="tooltip" title={content}>
30+
{children}
31+
</div>
32+
),
33+
}))
34+
2735
describe('ProgramCard', () => {
2836
const mockOnEdit = jest.fn()
2937
const mockOnView = jest.fn()
@@ -178,23 +186,31 @@ describe('ProgramCard', () => {
178186
})
179187

180188
describe('Description Handling', () => {
181-
it('truncates long descriptions to 100 characters', () => {
182-
const longDescription = 'A'.repeat(150)
189+
it('renders long descriptions with line-clamp-6 CSS class', () => {
190+
const longDescription = 'A'.repeat(300) // Long enough to trigger line clamping
183191
const longDescProgram = { ...baseMockProgram, description: longDescription }
184192

185193
render(<ProgramCard program={longDescProgram} onView={mockOnView} accessLevel="user" />)
186194

187-
const expectedText = 'A'.repeat(100) + '...'
188-
expect(screen.getByText(expectedText)).toBeInTheDocument()
195+
// Check that the full description is rendered (CSS handles the visual truncation)
196+
expect(screen.getByText(longDescription)).toBeInTheDocument()
197+
198+
// Check that the paragraph has the line-clamp-6 class
199+
const descriptionElement = screen.getByText(longDescription)
200+
expect(descriptionElement).toHaveClass('line-clamp-6')
189201
})
190202

191-
it('shows full description when under 100 characters', () => {
203+
it('shows full description when short', () => {
192204
const shortDescription = 'Short description'
193205
const shortDescProgram = { ...baseMockProgram, description: shortDescription }
194206

195207
render(<ProgramCard program={shortDescProgram} onView={mockOnView} accessLevel="user" />)
196208

197209
expect(screen.getByText('Short description')).toBeInTheDocument()
210+
211+
// Check that it still has line-clamp-6 class for consistency
212+
const descriptionElement = screen.getByText('Short description')
213+
expect(descriptionElement).toHaveClass('line-clamp-6')
198214
})
199215

200216
it('shows fallback text when description is empty', () => {

frontend/__tests__/unit/components/SingleModuleCard.test.tsx

Lines changed: 31 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { faUsers } from '@fortawesome/free-solid-svg-icons'
2-
import { fireEvent, screen, waitFor } from '@testing-library/react'
2+
import { screen } from '@testing-library/react'
33
import { useRouter } from 'next/navigation'
44
import { useSession } from 'next-auth/react'
55
import React from 'react'
66
import { render } from 'wrappers/testUtil'
7-
import type { ExtendedSession } from 'types/auth'
87
import type { Module } from 'types/mentorship'
98
import { ExperienceLevelEnum, ProgramStatusEnum } from 'types/mentorship'
109
import SingleModuleCard from 'components/SingleModuleCard'
@@ -111,16 +110,6 @@ const mockModule: Module = {
111110

112111
const mockAdmins = [{ login: 'admin1' }, { login: 'admin2' }]
113112

114-
const mockSessionData: ExtendedSession = {
115-
user: {
116-
login: 'admin1',
117-
isLeader: true,
118-
119-
image: 'https://example.com/admin-avatar.jpg',
120-
},
121-
expires: '2024-12-31T23:59:59Z',
122-
}
123-
124113
describe('SingleModuleCard', () => {
125114
beforeEach(() => {
126115
jest.clearAllMocks()
@@ -146,7 +135,6 @@ describe('SingleModuleCard', () => {
146135
expect(screen.getByText('Test Module')).toBeInTheDocument()
147136
expect(screen.getByText('This is a test module description')).toBeInTheDocument()
148137
expect(screen.getByTestId('icon-users')).toBeInTheDocument()
149-
expect(screen.getByTestId('icon-ellipsis')).toBeInTheDocument()
150138
})
151139

152140
it('renders module details correctly', () => {
@@ -183,128 +171,31 @@ describe('SingleModuleCard', () => {
183171
})
184172
})
185173

186-
describe('Dropdown Menu', () => {
187-
it('opens dropdown when ellipsis button is clicked', () => {
188-
render(<SingleModuleCard module={mockModule} />)
189-
190-
const ellipsisButton = screen.getByRole('button')
191-
fireEvent.click(ellipsisButton)
192-
193-
expect(screen.getByText('View Module')).toBeInTheDocument()
194-
})
195-
196-
it('closes dropdown when clicking outside', async () => {
197-
render(<SingleModuleCard module={mockModule} />)
198-
199-
const ellipsisButton = screen.getByRole('button')
200-
fireEvent.click(ellipsisButton)
201-
202-
expect(screen.getByText('View Module')).toBeInTheDocument()
203-
204-
// Click outside the dropdown
205-
fireEvent.mouseDown(document.body)
206-
207-
await waitFor(() => {
208-
expect(screen.queryByText('View Module')).not.toBeInTheDocument()
209-
})
210-
})
211-
212-
it('navigates to view module when View Module is clicked', () => {
174+
describe('Simplified Interface', () => {
175+
it('focuses on content display only', () => {
213176
render(<SingleModuleCard module={mockModule} />)
214177

215-
const ellipsisButton = screen.getByRole('button')
216-
fireEvent.click(ellipsisButton)
217-
218-
const viewButton = screen.getByText('View Module')
219-
fireEvent.click(viewButton)
220-
221-
expect(mockPush).toHaveBeenCalledWith('//modules/test-module')
222-
})
223-
224-
it('shows only View Module option for non-admin users', () => {
225-
render(
226-
<SingleModuleCard
227-
module={mockModule}
228-
showEdit={true}
229-
accessLevel="user"
230-
admins={mockAdmins}
231-
/>
232-
)
233-
234-
const ellipsisButton = screen.getByRole('button')
235-
fireEvent.click(ellipsisButton)
178+
// Should display core content
179+
expect(screen.getByText('Test Module')).toBeInTheDocument()
180+
expect(screen.getByText('This is a test module description')).toBeInTheDocument()
181+
expect(screen.getByText('Experience Level:')).toBeInTheDocument()
236182

237-
expect(screen.getByText('View Module')).toBeInTheDocument()
238-
expect(screen.queryByText('Edit Module')).not.toBeInTheDocument()
239-
expect(screen.queryByText('Create Module')).not.toBeInTheDocument()
183+
// Should have clickable title for navigation
184+
const moduleLink = screen.getByTestId('module-link')
185+
expect(moduleLink).toHaveAttribute('href', '//modules/test-module')
240186
})
241187
})
242188

243-
describe('Admin Functionality', () => {
244-
beforeEach(() => {
245-
mockUseSession.mockReturnValue({
246-
data: mockSessionData,
247-
status: 'authenticated',
248-
update: jest.fn(),
249-
})
250-
})
251-
252-
it('shows Edit Module option for admin users when showEdit is true', () => {
253-
render(
254-
<SingleModuleCard
255-
module={mockModule}
256-
showEdit={true}
257-
accessLevel="admin"
258-
admins={mockAdmins}
259-
/>
260-
)
261-
262-
const ellipsisButton = screen.getByRole('button')
263-
fireEvent.click(ellipsisButton)
264-
265-
expect(screen.getByText('View Module')).toBeInTheDocument()
266-
expect(screen.getByText('Edit Module')).toBeInTheDocument()
267-
expect(screen.getByText('Create Module')).toBeInTheDocument()
268-
})
269-
270-
it('does not show Edit Module option when showEdit is false', () => {
271-
render(
272-
<SingleModuleCard
273-
module={mockModule}
274-
showEdit={false}
275-
accessLevel="admin"
276-
admins={mockAdmins}
277-
/>
278-
)
279-
280-
const ellipsisButton = screen.getByRole('button')
281-
fireEvent.click(ellipsisButton)
282-
283-
expect(screen.getByText('View Module')).toBeInTheDocument()
284-
expect(screen.queryByText('Edit Module')).not.toBeInTheDocument()
285-
expect(screen.getByText('Create Module')).toBeInTheDocument()
286-
})
287-
288-
it('navigates to edit module when Edit Module is clicked', () => {
289-
render(
290-
<SingleModuleCard
291-
module={mockModule}
292-
showEdit={true}
293-
accessLevel="admin"
294-
admins={mockAdmins}
295-
/>
296-
)
297-
298-
const ellipsisButton = screen.getByRole('button')
299-
fireEvent.click(ellipsisButton)
300-
301-
const editButton = screen.getByText('Edit Module')
302-
fireEvent.click(editButton)
189+
describe('Props Handling', () => {
190+
it('renders correctly with minimal props', () => {
191+
render(<SingleModuleCard module={mockModule} />)
303192

304-
expect(mockPush).toHaveBeenCalledWith('//modules/test-module/edit')
193+
expect(screen.getByText('Test Module')).toBeInTheDocument()
194+
expect(screen.getByText('This is a test module description')).toBeInTheDocument()
305195
})
306196

307-
it('navigates to create module when Create Module is clicked', () => {
197+
it('ignores admin-related props since menu is removed', () => {
198+
// These props are now ignored but should not cause errors
308199
render(
309200
<SingleModuleCard
310201
module={mockModule}
@@ -314,13 +205,7 @@ describe('SingleModuleCard', () => {
314205
/>
315206
)
316207

317-
const ellipsisButton = screen.getByRole('button')
318-
fireEvent.click(ellipsisButton)
319-
320-
const createButton = screen.getByText('Create Module')
321-
fireEvent.click(createButton)
322-
323-
expect(mockPush).toHaveBeenCalledWith('//modules/create')
208+
expect(screen.getByText('Test Module')).toBeInTheDocument()
324209
})
325210
})
326211

@@ -338,67 +223,31 @@ describe('SingleModuleCard', () => {
338223
expect(screen.queryByTestId('top-contributors-list')).not.toBeInTheDocument()
339224
})
340225

341-
it('handles undefined admins array', () => {
226+
it('handles undefined admins array gracefully', () => {
342227
render(<SingleModuleCard module={mockModule} showEdit={true} accessLevel="admin" />)
343228

344-
const ellipsisButton = screen.getByRole('button')
345-
fireEvent.click(ellipsisButton)
346-
347-
expect(screen.getByText('View Module')).toBeInTheDocument()
348-
expect(screen.queryByText('Edit Module')).not.toBeInTheDocument()
349-
})
350-
351-
it('handles null session data', () => {
352-
mockUseSession.mockReturnValue({
353-
data: null,
354-
status: 'unauthenticated',
355-
update: jest.fn(),
356-
})
357-
358-
render(
359-
<SingleModuleCard
360-
module={mockModule}
361-
showEdit={true}
362-
accessLevel="admin"
363-
admins={mockAdmins}
364-
/>
365-
)
366-
367-
const ellipsisButton = screen.getByRole('button')
368-
fireEvent.click(ellipsisButton)
369-
370-
expect(screen.getByText('View Module')).toBeInTheDocument()
371-
expect(screen.queryByText('Edit Module')).not.toBeInTheDocument()
229+
// Should render without errors even with admin props
230+
expect(screen.getByText('Test Module')).toBeInTheDocument()
372231
})
373232
})
374233

375234
describe('Accessibility', () => {
376-
it('has proper button roles and interactions', () => {
235+
it('has accessible link for module navigation', () => {
377236
render(<SingleModuleCard module={mockModule} />)
378237

379-
const ellipsisButton = screen.getByRole('button')
380-
expect(ellipsisButton).toBeInTheDocument()
381-
382-
fireEvent.click(ellipsisButton)
383-
384-
const viewButton = screen.getByText('View Module')
385-
expect(viewButton.closest('button')).toBeInTheDocument()
238+
const moduleLink = screen.getByTestId('module-link')
239+
expect(moduleLink).toBeInTheDocument()
240+
expect(moduleLink).toHaveAttribute('href', '//modules/test-module')
241+
expect(moduleLink).toHaveAttribute('target', '_blank')
242+
expect(moduleLink).toHaveAttribute('rel', 'noopener noreferrer')
386243
})
387244

388-
it('supports keyboard navigation', () => {
245+
it('has proper heading structure', () => {
389246
render(<SingleModuleCard module={mockModule} />)
390247

391-
const ellipsisButton = screen.getByRole('button')
392-
393-
// Focus the button
394-
ellipsisButton.focus()
395-
expect(ellipsisButton).toHaveFocus()
396-
397-
// Press Enter to open dropdown
398-
fireEvent.keyDown(ellipsisButton, { key: 'Enter', code: 'Enter' })
399-
fireEvent.click(ellipsisButton) // Simulate the click that would happen
400-
401-
expect(screen.getByText('View Module')).toBeInTheDocument()
248+
const moduleTitle = screen.getByRole('heading', { level: 1 })
249+
expect(moduleTitle).toBeInTheDocument()
250+
expect(moduleTitle).toHaveTextContent('Test Module')
402251
})
403252
})
404253

frontend/__tests__/unit/pages/ProgramDetails.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ describe('ProgramDetailsPage', () => {
6767
expect(screen.getByText('Jan 1, 2025')).toBeInTheDocument()
6868
expect(screen.getByText('Dec 31, 2025')).toBeInTheDocument()
6969
expect(screen.getByText('20')).toBeInTheDocument()
70-
expect(screen.getByText('beginner, intermediate')).toBeInTheDocument()
70+
expect(screen.getByText('Beginner, Intermediate')).toBeInTheDocument()
7171
})
7272
})
7373
})

frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ describe('ProgramDetailsPage', () => {
6767
expect(screen.getByText('Jan 1, 2025')).toBeInTheDocument()
6868
expect(screen.getByText('Dec 31, 2025')).toBeInTheDocument()
6969
expect(screen.getByText('20')).toBeInTheDocument()
70-
expect(screen.getByText('beginner, intermediate')).toBeInTheDocument()
70+
expect(screen.getByText('Beginner, Intermediate')).toBeInTheDocument()
7171
})
7272
})
7373
})

frontend/src/app/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
'use client'
22

33
import { useQuery } from '@apollo/client'
4-
import upperFirst from 'lodash/upperFirst'
54
import { useParams } from 'next/navigation'
65
import { useEffect, useState } from 'react'
76
import { ErrorDisplay, handleAppError } from 'app/global-error'
87
import { GET_PROGRAM_ADMINS_AND_MODULES } from 'server/queries/moduleQueries'
98
import type { Module } from 'types/mentorship'
9+
import { titleCaseWord } from 'utils/capitalize'
1010
import { formatDate } from 'utils/dateFormatter'
1111
import DetailsCard from 'components/CardDetailsPage'
1212
import LoadingSpinner from 'components/LoadingSpinner'
@@ -49,7 +49,7 @@ const ModuleDetailsPage = () => {
4949
}
5050

5151
const moduleDetails = [
52-
{ label: 'Experience Level', value: upperFirst(module.experienceLevel) },
52+
{ label: 'Experience Level', value: titleCaseWord(module.experienceLevel) },
5353
{ label: 'Start Date', value: formatDate(module.startedAt) },
5454
{ label: 'End Date', value: formatDate(module.endedAt) },
5555
{

0 commit comments

Comments
 (0)