Files
freeCodeCamp/client/src/templates/Introduction/components/super-block-accordion.test.tsx
T

254 lines
7.3 KiB
TypeScript

import React from 'react';
import { render, screen, within } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import '@testing-library/jest-dom/vitest';
import { SuperBlocks } from '@freecodecamp/shared/config/curriculum';
import { SuperBlockAccordion } from './super-block-accordion';
import { BlockLabel, BlockLayouts } from '@freecodecamp/shared/config/blocks';
vi.mock('./block', () => ({
default: ({ block }: { block: string }) =>
React.createElement('div', { 'data-testid': `block-${block}` })
}));
const mockStructure = {
superBlock: SuperBlocks.RespWebDesign,
chapters: [
{
dashedName: 'test-chapter',
modules: [
{
dashedName: 'test-module',
blocks: ['test-block']
}
]
}
]
};
// Representative challenge used to populate module content.
const mockChallenge = {
id: 'test-challenge-id',
block: 'test-block',
blockLabel: BlockLabel.lecture,
title: 'Test Challenge',
fields: { slug: '/test-slug' },
dashedName: 'test-challenge',
challengeType: 0,
blockLayout: BlockLayouts.ChallengeList,
superBlock: SuperBlocks.RespWebDesign
};
describe('SuperBlockAccordion', () => {
it('does not show completed checkmark when there are zero challenges in a chapter', () => {
render(
<SuperBlockAccordion
challenges={[]}
superBlock={SuperBlocks.RespWebDesign}
structure={mockStructure}
chosenBlock={''}
completedChallengeIds={[]}
/>
);
expect(screen.queryByTestId('green-pass')).not.toBeInTheDocument();
const chapterButtons = screen.getAllByRole('button', {
name: /test-chapter/i
});
const chapterButton = chapterButtons[0];
const checkmark = within(chapterButton).getByTestId('green-not-completed');
expect(checkmark).toBeInTheDocument();
});
// With progress data present, the module button should render the steps label.
it('shows module progress when totalSteps > 0 and not comingSoon', () => {
const structureWithProgress = {
superBlock: SuperBlocks.RespWebDesign,
chapters: [
{
dashedName: 'test-chapter',
modules: [
{
dashedName: 'test-module',
blocks: ['test-block'],
comingSoon: false,
totalSteps: 10,
completedSteps: 5
}
]
}
]
};
render(
<SuperBlockAccordion
challenges={[mockChallenge]}
superBlock={SuperBlocks.RespWebDesign}
structure={structureWithProgress}
chosenBlock={''}
completedChallengeIds={[]}
/>
);
const moduleButtons = screen.getAllByRole('button', {
name: /test-module/i
});
const moduleButton = moduleButtons[0];
const moduleRight = within(moduleButton).getByTestId('module-button-right');
const moduleSteps = within(moduleRight).getByText(
/learn\.steps-completed/i
);
expect(moduleSteps).toBeInTheDocument();
expect(moduleSteps).toHaveClass('module-steps');
});
// A coming-soon module should hide the steps summary even if progress exists.
it('does not show module progress when comingSoon is true', () => {
const structureComingSoon = {
superBlock: SuperBlocks.RespWebDesign,
chapters: [
{
dashedName: 'test-chapter',
modules: [
{
dashedName: 'test-module',
blocks: ['test-block'],
comingSoon: true,
totalSteps: 10,
completedSteps: 5
}
]
}
]
};
render(
<SuperBlockAccordion
challenges={[mockChallenge]}
superBlock={SuperBlocks.RespWebDesign}
structure={structureComingSoon}
chosenBlock={''}
completedChallengeIds={[]}
/>
);
const moduleButtons = screen.getAllByRole('button', {
name: /test-module/i
});
const moduleButton = moduleButtons[0];
const moduleRight = within(moduleButton).getByTestId('module-button-right');
expect(within(moduleRight).queryByText(/steps/i)).not.toBeInTheDocument();
});
// Modules with zero total steps should not display any progress text.
it('does not show module progress when totalSteps is zero', () => {
const structureZeroSteps = {
superBlock: SuperBlocks.RespWebDesign,
chapters: [
{
dashedName: 'test-chapter',
modules: [
{
dashedName: 'test-module',
blocks: ['test-block'],
comingSoon: false,
totalSteps: 0,
completedSteps: 0
}
]
}
]
};
render(
<SuperBlockAccordion
challenges={[]}
superBlock={SuperBlocks.RespWebDesign}
structure={structureZeroSteps}
chosenBlock={''}
completedChallengeIds={[]}
/>
);
const moduleButtons = screen.getAllByRole('button', {
name: /test-module/i
});
const moduleButton = moduleButtons[0];
const moduleRight = within(moduleButton).getByTestId('module-button-right');
expect(within(moduleRight).queryByText(/steps/i)).not.toBeInTheDocument();
});
it('should expand all chapters when expandAll is true', () => {
const multiChapterStructure = {
superBlock: SuperBlocks.RespWebDesign,
chapters: [
{
dashedName: 'chapter-one',
modules: [{ dashedName: 'mod-one', blocks: ['block-one'] }]
},
{
dashedName: 'chapter-two',
modules: [{ dashedName: 'mod-two', blocks: ['block-two'] }]
}
]
};
render(
<SuperBlockAccordion
challenges={[
{ ...mockChallenge, block: 'block-one', id: 'id-1' },
{ ...mockChallenge, block: 'block-two', id: 'id-2' }
]}
superBlock={SuperBlocks.RespWebDesign}
structure={multiChapterStructure}
chosenBlock={''}
completedChallengeIds={[]}
expandAll={true}
/>
);
// When expandAll=true, both chapters are open so their module buttons are visible
const moduleButtons = screen.getAllByRole('button', { name: /mod/i });
expect(moduleButtons).toHaveLength(2);
});
it('should not render a module when all its challenges are filtered out', () => {
render(
<SuperBlockAccordion
// Only challenges for block-one are passed; mod-two has no challenges
challenges={[{ ...mockChallenge, block: 'block-one', id: 'id-1' }]}
superBlock={SuperBlocks.RespWebDesign}
structure={{
superBlock: SuperBlocks.RespWebDesign,
chapters: [
{
dashedName: 'test-chapter',
modules: [
{ dashedName: 'mod-one', blocks: ['block-one'] },
{ dashedName: 'mod-two', blocks: ['block-two'] }
]
}
]
}}
chosenBlock={'block-one'}
completedChallengeIds={[]}
expandAll={true}
/>
);
// mod-one has a challenge — its button should render
expect(
screen.getByRole('button', { name: /mod-one/i })
).toBeInTheDocument();
// mod-two has no challenges — its button should not render
expect(
screen.queryByRole('button', { name: /mod-two/i })
).not.toBeInTheDocument();
});
});