Skip to content

Commit 50e49c6

Browse files
feat: refactor tests
1 parent 2a2d78b commit 50e49c6

File tree

7 files changed

+259
-323
lines changed

7 files changed

+259
-323
lines changed

src/course-home/live-tab/LiveTab.jsx

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const LiveTab = () => {
1313
return (
1414
<div
1515
id="live_tab"
16+
role="region"
1617
// eslint-disable-next-line react/no-danger
1718
dangerouslySetInnerHTML={{ __html: liveModel[courseId]?.iframe }}
1819
/>
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import React from 'react';
2+
import { render } from '@testing-library/react';
3+
import { useSelector } from 'react-redux';
4+
import LiveTab from './LiveTab';
5+
6+
jest.mock('react-redux', () => ({
7+
useSelector: jest.fn(),
8+
}));
9+
10+
describe('LiveTab', () => {
11+
afterEach(() => {
12+
jest.clearAllMocks();
13+
document.body.innerHTML = '';
14+
});
15+
16+
it('renders iframe from liveModel using dangerouslySetInnerHTML', () => {
17+
useSelector.mockImplementation((selector) => selector({
18+
courseHome: { courseId: 'course-v1:test+id+2024' },
19+
models: {
20+
live: {
21+
'course-v1:test+id+2024': {
22+
iframe: '<iframe id="lti-tab-embed" src="about:blank"></iframe>',
23+
},
24+
},
25+
},
26+
}));
27+
28+
render(<LiveTab />);
29+
30+
const iframe = document.getElementById('lti-tab-embed');
31+
expect(iframe).toBeInTheDocument();
32+
expect(iframe.src).toBe('about:blank');
33+
});
34+
35+
it('adds classes to iframe after mount', () => {
36+
document.body.innerHTML = `
37+
<div id="live_tab">
38+
<iframe id="lti-tab-embed" class=""></iframe>
39+
</div>
40+
`;
41+
42+
useSelector.mockImplementation((selector) => selector({
43+
courseHome: { courseId: 'course-v1:test+id+2024' },
44+
models: {
45+
live: {
46+
'course-v1:test+id+2024': {
47+
iframe: '<iframe id="lti-tab-embed"></iframe>',
48+
},
49+
},
50+
},
51+
}));
52+
53+
render(<LiveTab />);
54+
55+
const iframe = document.getElementById('lti-tab-embed');
56+
expect(iframe.className).toContain('vh-100');
57+
expect(iframe.className).toContain('w-100');
58+
expect(iframe.className).toContain('border-0');
59+
});
60+
61+
it('does not throw if iframe is not found in DOM', () => {
62+
useSelector.mockImplementation((selector) => selector({
63+
courseHome: { courseId: 'course-v1:test+id+2024' },
64+
models: {
65+
live: {
66+
'course-v1:test+id+2024': {
67+
iframe: '<div>No iframe here</div>',
68+
},
69+
},
70+
},
71+
}));
72+
73+
expect(() => render(<LiveTab />)).not.toThrow();
74+
const iframe = document.getElementById('lti-tab-embed');
75+
expect(iframe).toBeNull();
76+
});
77+
});

src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx

+29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import { Factory } from 'rosie';
3+
import { useWindowSize, breakpoints } from '@openedx/paragon';
34
import {
45
render, screen, fireEvent, getByText, initializeTestStore,
56
} from '../../../../setupTest';
@@ -10,6 +11,15 @@ import useIndexOfLastVisibleChild from '../../../../generic/tabs/useIndexOfLastV
1011
jest.mock('../../../../generic/tabs/useIndexOfLastVisibleChild');
1112
useIndexOfLastVisibleChild.mockReturnValue([0, null, null]);
1213

14+
jest.mock('@openedx/paragon', () => {
15+
const original = jest.requireActual('@openedx/paragon');
16+
return {
17+
...original,
18+
breakpoints: original.breakpoints,
19+
useWindowSize: jest.fn(),
20+
};
21+
});
22+
1323
describe('Sequence Navigation', () => {
1424
let mockData;
1525
const courseMetadata = Factory.build('courseMetadata');
@@ -29,6 +39,7 @@ describe('Sequence Navigation', () => {
2939
onNavigate: () => {},
3040
nextHandler: () => {},
3141
};
42+
useWindowSize.mockReturnValue({ width: 1024, height: 800 });
3243
});
3344

3445
it('is empty while loading', async () => {
@@ -209,4 +220,22 @@ describe('Sequence Navigation', () => {
209220
);
210221
expect(screen.queryByRole('link', { name: /next/i })).not.toBeInTheDocument();
211222
});
223+
224+
it('shows previous button without label when screen is small', () => {
225+
useWindowSize.mockReturnValue({ width: breakpoints.small.minWidth - 1 });
226+
227+
render(<SequenceNavigation {...mockData} />, { wrapWithRouter: true });
228+
229+
const prevButton = screen.getByRole('button');
230+
expect(prevButton.textContent).toBe('2 of 3');
231+
});
232+
233+
it('does not set width when screen is large', () => {
234+
useWindowSize.mockReturnValue({ width: breakpoints.small.minWidth });
235+
236+
render(<SequenceNavigation {...mockData} />, { wrapWithRouter: true });
237+
238+
const nav = screen.getByRole('navigation', { name: /course sequence tabs/i });
239+
expect(nav).not.toHaveStyle({ width: '90%' });
240+
});
212241
});

src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.test.jsx

+82-1
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,23 @@ import userEvent from '@testing-library/user-event';
66
import { initializeTestStore, render, screen } from '../../../../setupTest';
77
import SequenceNavigationTabs from './SequenceNavigationTabs';
88
import useIndexOfLastVisibleChild from '../../../../generic/tabs/useIndexOfLastVisibleChild';
9+
import {
10+
useIsSidebarOpen,
11+
useIsOnXLDesktop,
12+
useIsOnLargeDesktop,
13+
useIsOnMediumDesktop,
14+
} from './hooks';
915

1016
// Mock the hook to avoid relying on its implementation and mocking `getBoundingClientRect`.
1117
jest.mock('../../../../generic/tabs/useIndexOfLastVisibleChild');
1218

19+
jest.mock('./hooks', () => ({
20+
useIsSidebarOpen: jest.fn(),
21+
useIsOnXLDesktop: jest.fn(),
22+
useIsOnLargeDesktop: jest.fn(),
23+
useIsOnMediumDesktop: jest.fn(),
24+
}));
25+
1326
describe('Sequence Navigation Tabs', () => {
1427
let mockData;
1528

@@ -54,7 +67,7 @@ describe('Sequence Navigation Tabs', () => {
5467
const booyah = render(<SequenceNavigationTabs {...mockData} />, { wrapWithRouter: true });
5568

5669
// wait for links to appear so we aren't testing an empty div
57-
await screen.findAllByRole('link');
70+
await screen.findAllByRole('tab');
5871

5972
container = booyah.container;
6073

@@ -67,4 +80,72 @@ describe('Sequence Navigation Tabs', () => {
6780
expect(screen.getByRole('button', { name: `${activeBlockNumber} of ${unitBlocks.length}` }))
6881
.toHaveClass('dropdown-toggle');
6982
});
83+
84+
it('adds class navigation-tab-width-xl when isOnXLDesktop and sidebar is open', () => {
85+
useIsSidebarOpen.mockReturnValue(true);
86+
useIsOnXLDesktop.mockReturnValue(true);
87+
useIsOnLargeDesktop.mockReturnValue(false);
88+
useIsOnMediumDesktop.mockReturnValue(false);
89+
useIndexOfLastVisibleChild.mockReturnValue([0, null, null]);
90+
91+
const { container } = render(<SequenceNavigationTabs {...mockData} />, { wrapWithRouter: true });
92+
93+
const wrapperDiv = container.querySelector('.navigation-tab-width-xl');
94+
expect(wrapperDiv).toBeInTheDocument();
95+
});
96+
97+
it('adds class navigation-tab-width-large when isOnLargeDesktop and sidebar is open', () => {
98+
useIsSidebarOpen.mockReturnValue(true);
99+
useIsOnXLDesktop.mockReturnValue(false);
100+
useIsOnLargeDesktop.mockReturnValue(true);
101+
useIsOnMediumDesktop.mockReturnValue(false);
102+
useIndexOfLastVisibleChild.mockReturnValue([0, null, null]);
103+
104+
const { container } = render(<SequenceNavigationTabs {...mockData} />, { wrapWithRouter: true });
105+
106+
const wrapperDiv = container.querySelector('.navigation-tab-width-large');
107+
expect(wrapperDiv).toBeInTheDocument();
108+
});
109+
110+
it('adds class navigation-tab-width-medium when isOnMediumDesktop and sidebar is open', () => {
111+
useIsSidebarOpen.mockReturnValue(true);
112+
useIsOnXLDesktop.mockReturnValue(false);
113+
useIsOnLargeDesktop.mockReturnValue(false);
114+
useIsOnMediumDesktop.mockReturnValue(true);
115+
useIndexOfLastVisibleChild.mockReturnValue([0, null, null]);
116+
117+
const { container } = render(<SequenceNavigationTabs {...mockData} />, { wrapWithRouter: true });
118+
119+
const wrapperDiv = container.querySelector('.navigation-tab-width-medium');
120+
expect(wrapperDiv).toBeInTheDocument();
121+
});
122+
123+
it('applies invisibleStyle when shouldDisplayDropdown is true', () => {
124+
useIsSidebarOpen.mockReturnValue(true);
125+
useIsOnXLDesktop.mockReturnValue(false);
126+
useIsOnLargeDesktop.mockReturnValue(false);
127+
useIsOnMediumDesktop.mockReturnValue(false);
128+
129+
useIndexOfLastVisibleChild.mockReturnValue([
130+
0,
131+
null,
132+
{
133+
position: 'absolute',
134+
left: '0px',
135+
pointerEvents: 'none',
136+
visibility: 'hidden',
137+
maxWidth: '100%',
138+
},
139+
]);
140+
141+
render(<SequenceNavigationTabs {...mockData} />, { wrapWithRouter: true });
142+
143+
const tabList = screen.getByRole('tablist');
144+
145+
expect(tabList.style.position).toBe('absolute');
146+
expect(tabList.style.left).toBe('0px');
147+
expect(tabList.style.pointerEvents).toBe('none');
148+
expect(tabList.style.visibility).toBe('hidden');
149+
expect(tabList.style.maxWidth).toBe('100%');
150+
});
70151
});

src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx

+44
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ import {
55
} from '../../../../setupTest';
66
import UnitButton from './UnitButton';
77

8+
jest.mock('react-router-dom', () => {
9+
const actual = jest.requireActual('react-router-dom');
10+
return {
11+
...actual,
12+
useLocation: () => ({ pathname: '/preview/anything' }),
13+
};
14+
});
15+
816
describe('Unit Button', () => {
917
let mockData;
1018
const courseMetadata = Factory.build('courseMetadata');
@@ -31,6 +39,10 @@ describe('Unit Button', () => {
3139
};
3240
});
3341

42+
afterEach(() => {
43+
jest.resetModules();
44+
});
45+
3446
it('hides title by default', () => {
3547
render(<UnitButton {...mockData} />, { wrapWithRouter: true });
3648
expect(screen.getByRole('tab')).not.toHaveTextContent(unit.display_name);
@@ -82,4 +94,36 @@ describe('Unit Button', () => {
8294
fireEvent.click(screen.getByRole('tab'));
8395
expect(onClick).toHaveBeenCalledTimes(1);
8496
});
97+
98+
it('calls onClick with correct unitId when clicked', () => {
99+
const onClick = jest.fn();
100+
render(<UnitButton {...mockData} onClick={onClick} />, { wrapWithRouter: true });
101+
102+
fireEvent.click(screen.getByRole('tab'));
103+
104+
expect(onClick).toHaveBeenCalledWith(mockData.unitId);
105+
});
106+
107+
it('renders with no unitId and does not crash', async () => {
108+
render(<UnitButton unitId={undefined} onClick={jest.fn()} contentType="video" title="Unit" />, {
109+
wrapWithRouter: true,
110+
});
111+
112+
expect(screen.getByRole('tab')).toBeInTheDocument();
113+
});
114+
115+
it('prepends /preview to the unit path if in preview mode', () => {
116+
const onClick = jest.fn();
117+
118+
render(
119+
<UnitButton {...mockData} onClick={onClick} />,
120+
{
121+
wrapWithRouter: true,
122+
initialEntries: ['/preview/some/path'],
123+
},
124+
);
125+
126+
const button = screen.getByRole('tab');
127+
expect(button.closest('a')).toHaveAttribute('href', expect.stringContaining('/preview/course/'));
128+
});
85129
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import { IntlProvider } from '@edx/frontend-platform/i18n';
4+
import ShareButton from './ShareButton';
5+
import messages from './messages';
6+
7+
describe('ShareButton', () => {
8+
const url = 'https://example.com/course';
9+
10+
const renderComponent = () => render(
11+
<IntlProvider locale="en" messages={{}}>
12+
<ShareButton url={url} />
13+
</IntlProvider>,
14+
);
15+
16+
it('renders the button with correct text', () => {
17+
renderComponent();
18+
expect(screen.getByText(messages.shareButton.defaultMessage)).toBeInTheDocument();
19+
});
20+
21+
it('renders the Twitter icon', () => {
22+
renderComponent();
23+
const svg = screen.getByText(messages.shareButton.defaultMessage).querySelector('svg');
24+
expect(svg).toBeInTheDocument();
25+
});
26+
});

0 commit comments

Comments
 (0)