Skip to content

Commit 1bacf09

Browse files
authored
fix(client): use Link component for block links (freeCodeCamp#63559)
1 parent 9bc0f84 commit 1bacf09

2 files changed

Lines changed: 213 additions & 32 deletions

File tree

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import { describe, it, expect, vi } from 'vitest';
4+
5+
import BlockHeader from './block-header';
6+
7+
const defaultProps = {
8+
blockDashed: 'test-block',
9+
blockTitle: 'Test Block Title',
10+
blockLabel: null,
11+
courseCompletionStatus: '50% completed',
12+
completedCount: 5,
13+
handleClick: vi.fn(),
14+
isCompleted: false,
15+
isExpanded: false,
16+
percentageCompleted: 50,
17+
blockIntroArr: ['Introduction paragraph 1', 'Introduction paragraph 2'],
18+
accordion: false
19+
};
20+
21+
describe('<BlockHeader />', () => {
22+
it('should render as a button with aria-expanded and aria-controls when not a link', () => {
23+
render(<BlockHeader {...defaultProps} />);
24+
25+
const button = screen.getByRole('button');
26+
expect(button).toBeInTheDocument();
27+
expect(button).toHaveAttribute('aria-expanded', 'false');
28+
expect(button).toHaveAttribute('aria-controls', 'test-block-panel');
29+
});
30+
31+
it('should render as a link without aria-expanded and aria-controls when blockUrl is provided in accordion mode', () => {
32+
render(
33+
<BlockHeader
34+
{...defaultProps}
35+
accordion={true}
36+
blockUrl='/learn/test-block'
37+
/>
38+
);
39+
40+
const link = screen.getByRole('link');
41+
expect(link).toBeInTheDocument();
42+
expect(link).toHaveAttribute('href', '/learn/test-block');
43+
expect(link).not.toHaveAttribute('aria-expanded');
44+
expect(link).not.toHaveAttribute('aria-controls');
45+
});
46+
47+
it('should set aria-expanded to true when isExpanded is true', () => {
48+
render(<BlockHeader {...defaultProps} isExpanded={true} />);
49+
50+
const button = screen.getByRole('button');
51+
expect(button).toHaveAttribute('aria-expanded', 'true');
52+
});
53+
54+
it('should display the block title', () => {
55+
render(<BlockHeader {...defaultProps} />);
56+
57+
expect(screen.getByText('Test Block Title')).toBeInTheDocument();
58+
});
59+
60+
it('should display the course completion status in sr-only text', () => {
61+
render(<BlockHeader {...defaultProps} />);
62+
63+
expect(screen.getByText(', 50% completed')).toBeInTheDocument();
64+
});
65+
66+
it('should show progress percentage when not expanded, not completed, and has completed challenges', () => {
67+
render(
68+
<BlockHeader
69+
{...defaultProps}
70+
isExpanded={false}
71+
isCompleted={false}
72+
completedCount={5}
73+
/>
74+
);
75+
76+
expect(screen.getByText('50%')).toBeInTheDocument();
77+
});
78+
79+
it('should not show progress percentage when in accordion mode', () => {
80+
render(
81+
<BlockHeader
82+
{...defaultProps}
83+
accordion={true}
84+
isExpanded={false}
85+
isCompleted={false}
86+
completedCount={5}
87+
/>
88+
);
89+
90+
expect(screen.queryByText('50%')).not.toBeInTheDocument();
91+
});
92+
93+
it('should not show progress percentage when expanded', () => {
94+
render(
95+
<BlockHeader
96+
{...defaultProps}
97+
isExpanded={true}
98+
isCompleted={false}
99+
completedCount={5}
100+
/>
101+
);
102+
103+
expect(screen.queryByText('50%')).not.toBeInTheDocument();
104+
});
105+
106+
it('should not show progress percentage when completed', () => {
107+
render(
108+
<BlockHeader
109+
{...defaultProps}
110+
isExpanded={false}
111+
isCompleted={true}
112+
completedCount={10}
113+
/>
114+
);
115+
116+
expect(screen.queryByText('50%')).not.toBeInTheDocument();
117+
});
118+
119+
it('should not show progress percentage when no challenges completed', () => {
120+
render(
121+
<BlockHeader
122+
{...defaultProps}
123+
isExpanded={false}
124+
isCompleted={false}
125+
completedCount={0}
126+
/>
127+
);
128+
129+
expect(screen.queryByText('50%')).not.toBeInTheDocument();
130+
});
131+
132+
it('should render BlockIntros when expanded and blockIntroArr is provided', () => {
133+
render(<BlockHeader {...defaultProps} isExpanded={true} />);
134+
135+
expect(screen.getByText('Introduction paragraph 1')).toBeInTheDocument();
136+
expect(screen.getByText('Introduction paragraph 2')).toBeInTheDocument();
137+
});
138+
139+
it('should not render BlockIntros when not expanded', () => {
140+
render(<BlockHeader {...defaultProps} isExpanded={false} />);
141+
142+
expect(
143+
screen.queryByText('Introduction paragraph 1')
144+
).not.toBeInTheDocument();
145+
expect(
146+
screen.queryByText('Introduction paragraph 2')
147+
).not.toBeInTheDocument();
148+
});
149+
150+
it('should not render BlockIntros when blockIntroArr is empty', () => {
151+
render(
152+
<BlockHeader {...defaultProps} isExpanded={true} blockIntroArr={[]} />
153+
);
154+
155+
expect(
156+
screen.queryByText('Introduction paragraph 1')
157+
).not.toBeInTheDocument();
158+
});
159+
160+
it('should not render BlockIntros when blockIntroArr is undefined', () => {
161+
render(
162+
<BlockHeader
163+
{...defaultProps}
164+
isExpanded={true}
165+
blockIntroArr={undefined}
166+
/>
167+
);
168+
169+
expect(
170+
screen.queryByText('Introduction paragraph 1')
171+
).not.toBeInTheDocument();
172+
});
173+
});

client/src/templates/Introduction/components/block-header.tsx

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { isEmpty } from 'lodash';
33
import { Button } from '@freecodecamp/ui';
4+
import { Link } from '../../../components/helpers';
45

56
import type { BlockLabel as BlockLabelType } from '../../../../../shared-dist/config/blocks';
67
import { ProgressBar } from '../../../components/Progress/progress-bar';
@@ -38,41 +39,48 @@ function BlockHeader({
3839
accordion,
3940
blockUrl
4041
}: BlockHeaderProps): JSX.Element {
42+
const InnerBlockHeader = () => (
43+
<>
44+
<span className='block-header-button-text map-title'>
45+
{accordion &&
46+
(blockUrl ? <span className='aligner-dash'></span> : <DropDown />)}
47+
<CheckMark isCompleted={isCompleted} />
48+
{!accordion && blockLabel && <BlockLabel blockLabel={blockLabel} />}
49+
<span>
50+
{blockTitle}
51+
<span className='sr-only'>, {courseCompletionStatus}</span>
52+
</span>
53+
{accordion && blockLabel && <BlockLabel blockLabel={blockLabel} />}
54+
{!accordion && <DropDown />}
55+
</span>
56+
{!accordion && !isExpanded && !isCompleted && completedCount > 0 && (
57+
<div aria-hidden='true' className='progress-wrapper'>
58+
<div>
59+
<ProgressBar now={percentageCompleted} />
60+
</div>
61+
<span>{`${percentageCompleted}%`}</span>
62+
</div>
63+
)}
64+
</>
65+
);
66+
4167
return (
4268
<>
4369
<h3 className='block-grid-title'>
44-
<Button
45-
aria-expanded={isExpanded ? 'true' : 'false'}
46-
aria-controls={`${blockDashed}-panel`}
47-
className='block-header'
48-
onClick={handleClick}
49-
{...(accordion && blockUrl ? { href: blockUrl } : {})}
50-
>
51-
<span className='block-header-button-text map-title'>
52-
{accordion &&
53-
(blockUrl ? (
54-
<span className='aligner-dash'></span>
55-
) : (
56-
<DropDown />
57-
))}
58-
<CheckMark isCompleted={isCompleted} />
59-
{!accordion && blockLabel && <BlockLabel blockLabel={blockLabel} />}
60-
<span>
61-
{blockTitle}
62-
<span className='sr-only'>, {courseCompletionStatus}</span>
63-
</span>
64-
{accordion && blockLabel && <BlockLabel blockLabel={blockLabel} />}
65-
{!accordion && <DropDown />}
66-
</span>
67-
{!accordion && !isExpanded && !isCompleted && completedCount > 0 && (
68-
<div aria-hidden='true' className='progress-wrapper'>
69-
<div>
70-
<ProgressBar now={percentageCompleted} />
71-
</div>
72-
<span>{`${percentageCompleted}%`}</span>
73-
</div>
74-
)}
75-
</Button>
70+
{accordion && blockUrl ? (
71+
<Link className='block-header' to={blockUrl}>
72+
<InnerBlockHeader />
73+
</Link>
74+
) : (
75+
<Button
76+
aria-expanded={isExpanded ? 'true' : 'false'}
77+
aria-controls={`${blockDashed}-panel`}
78+
className='block-header'
79+
onClick={handleClick}
80+
>
81+
<InnerBlockHeader />
82+
</Button>
83+
)}
7684
</h3>
7785
{isExpanded && !isEmpty(blockIntroArr) && (
7886
<BlockIntros intros={blockIntroArr as string[]} />

0 commit comments

Comments
 (0)