test(client): stabilize UI regression tests

Replace brittle client snapshot coverage with explicit assertions so the updated test stack no longer depends on snapshot state initialization. Update the react-i18next mock to avoid mutating function component defaultProps while preserving WrappedComponent metadata for layout tests.
This commit is contained in:
Mrugesh Mohapatra
2026-04-20 10:01:26 +05:30
parent e88d5609e5
commit 983b249de0
17 changed files with 122 additions and 1538 deletions
+10 -2
View File
@@ -35,8 +35,16 @@ const renderNodes = reactNodes => {
};
const withTranslation = () => Component => {
Component.defaultProps = { ...Component.defaultProps, t: str => str };
return Component;
const WrappedComponent = props =>
React.createElement(Component, {
...props,
t: props.t ?? (str => str)
});
WrappedComponent.WrappedComponent = Component;
WrappedComponent.displayName = `withTranslation(${Component.displayName || Component.name || 'Component'})`;
return WrappedComponent;
};
const useTranslation = () => {
@@ -1,458 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<Footer /> > matches snapshot 1`] = `
<footer
className="site-footer"
>
<div
className="footer-top"
>
<div
className="footer-desc-col"
>
<p>
footer.tax-exempt-status
</p>
<p>
footer.mission-statement
</p>
<p>
footer.donation-initiatives
</p>
<p
className="footer-donation"
>
You can
<a
className="inline"
href="/donate"
>
make a tax-deductible donation here
</a>
.
</p>
</div>
<div
className="trending-guides"
>
<h2
className="col-header"
id="trending-guides"
>
footer.trending-guides
</h2>
<ul
aria-labelledby="trending-guides"
className="trending-guides-articles"
>
<li>
<a
href="trending:article0link"
rel="noopener noreferrer"
target="_blank"
>
trending:article0title
</a>
</li>
<li>
<a
href="trending:article1link"
rel="noopener noreferrer"
target="_blank"
>
trending:article1title
</a>
</li>
<li>
<a
href="trending:article2link"
rel="noopener noreferrer"
target="_blank"
>
trending:article2title
</a>
</li>
<li>
<a
href="trending:article3link"
rel="noopener noreferrer"
target="_blank"
>
trending:article3title
</a>
</li>
<li>
<a
href="trending:article4link"
rel="noopener noreferrer"
target="_blank"
>
trending:article4title
</a>
</li>
<li>
<a
href="trending:article5link"
rel="noopener noreferrer"
target="_blank"
>
trending:article5title
</a>
</li>
<li>
<a
href="trending:article6link"
rel="noopener noreferrer"
target="_blank"
>
trending:article6title
</a>
</li>
<li>
<a
href="trending:article7link"
rel="noopener noreferrer"
target="_blank"
>
trending:article7title
</a>
</li>
<li>
<a
href="trending:article8link"
rel="noopener noreferrer"
target="_blank"
>
trending:article8title
</a>
</li>
<li>
<a
href="trending:article9link"
rel="noopener noreferrer"
target="_blank"
>
trending:article9title
</a>
</li>
<li>
<a
href="trending:article10link"
rel="noopener noreferrer"
target="_blank"
>
trending:article10title
</a>
</li>
<li>
<a
href="trending:article11link"
rel="noopener noreferrer"
target="_blank"
>
trending:article11title
</a>
</li>
<li>
<a
href="trending:article12link"
rel="noopener noreferrer"
target="_blank"
>
trending:article12title
</a>
</li>
<li>
<a
href="trending:article13link"
rel="noopener noreferrer"
target="_blank"
>
trending:article13title
</a>
</li>
<li>
<a
href="trending:article14link"
rel="noopener noreferrer"
target="_blank"
>
trending:article14title
</a>
</li>
<li>
<a
href="trending:article15link"
rel="noopener noreferrer"
target="_blank"
>
trending:article15title
</a>
</li>
<li>
<a
href="trending:article16link"
rel="noopener noreferrer"
target="_blank"
>
trending:article16title
</a>
</li>
<li>
<a
href="trending:article17link"
rel="noopener noreferrer"
target="_blank"
>
trending:article17title
</a>
</li>
<li>
<a
href="trending:article18link"
rel="noopener noreferrer"
target="_blank"
>
trending:article18title
</a>
</li>
<li>
<a
href="trending:article19link"
rel="noopener noreferrer"
target="_blank"
>
trending:article19title
</a>
</li>
<li>
<a
href="trending:article20link"
rel="noopener noreferrer"
target="_blank"
>
trending:article20title
</a>
</li>
<li>
<a
href="trending:article21link"
rel="noopener noreferrer"
target="_blank"
>
trending:article21title
</a>
</li>
<li>
<a
href="trending:article22link"
rel="noopener noreferrer"
target="_blank"
>
trending:article22title
</a>
</li>
<li>
<a
href="trending:article23link"
rel="noopener noreferrer"
target="_blank"
>
trending:article23title
</a>
</li>
<li>
<a
href="trending:article24link"
rel="noopener noreferrer"
target="_blank"
>
trending:article24title
</a>
</li>
<li>
<a
href="trending:article25link"
rel="noopener noreferrer"
target="_blank"
>
trending:article25title
</a>
</li>
<li>
<a
href="trending:article26link"
rel="noopener noreferrer"
target="_blank"
>
trending:article26title
</a>
</li>
<li>
<a
href="trending:article27link"
rel="noopener noreferrer"
target="_blank"
>
trending:article27title
</a>
</li>
<li>
<a
href="trending:article28link"
rel="noopener noreferrer"
target="_blank"
>
trending:article28title
</a>
</li>
<li>
<a
href="trending:article29link"
rel="noopener noreferrer"
target="_blank"
>
trending:article29title
</a>
</li>
</ul>
<div
className="h-[30px]"
/>
<div>
<h2
className="col-header"
id="mobile-app"
>
footer.mobile-app
</h2>
<div
className=" min-h-[1px] px-[15px] md:w-2/3 md:ms-[16.6%] "
>
<ul
aria-labelledby="mobile-app"
className="mobile-app-container"
>
<li>
<a
href="https://apps.apple.com/us/app/freecodecamp/id6446908151?itsct=apps_box_link&itscg=30200"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Download on the App Store"
lang="en"
src="/src/assets/images/footer-ads/apple-store-badge.svg"
/>
</a>
</li>
<li>
<a
href="https://play.google.com/store/apps/details?id=org.freecodecamp"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Get it on Google Play"
lang="en"
src="/src/assets/images/footer-ads/google-play-badge.svg"
/>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div
className="footer-bottom"
>
<h2
className="col-header"
>
footer.our-nonprofit
</h2>
<div
className="our-nonprofit"
>
<a
href="links:footer.about-url"
rel="noopener noreferrer"
target="_blank"
>
footer.links.about
</a>
<a
href="https://www.linkedin.com/school/free-code-camp/people/"
rel="noopener noreferrer"
target="_blank"
>
footer.links.alumni
</a>
<a
href="https://github.com/freeCodeCamp/"
rel="noopener noreferrer"
target="_blank"
>
footer.links.open-source
</a>
<a
href="links:footer.shop-url"
rel="noopener noreferrer"
target="_blank"
>
footer.links.shop
</a>
<a
href="links:footer.support-url"
rel="noopener noreferrer"
target="_blank"
>
footer.links.support
</a>
<a
href="links:footer.sponsors-url"
rel="noopener noreferrer"
target="_blank"
>
footer.links.sponsors
</a>
<a
href="links:footer.honesty-url"
rel="noopener noreferrer"
target="_blank"
>
footer.links.honesty
</a>
<a
href="links:footer.coc-url"
rel="noopener noreferrer"
target="_blank"
>
footer.links.coc
</a>
<a
href="links:footer.privacy-url"
rel="noopener noreferrer"
target="_blank"
>
footer.links.privacy
</a>
<a
href="links:footer.tos-url"
rel="noopener noreferrer"
target="_blank"
>
footer.links.tos
</a>
<a
href="links:footer.copyright-url"
rel="noopener noreferrer"
target="_blank"
>
footer.links.copyright
</a>
</div>
</div>
</footer>
`;
+30 -4
View File
@@ -1,12 +1,38 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { render, screen, within } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import Footer from '.';
describe('<Footer />', () => {
it('matches snapshot', () => {
const tree = renderer.create(<Footer />).toJSON();
expect(tree).toMatchSnapshot();
it('renders footer sections and primary links', () => {
render(<Footer />);
expect(screen.getByText('footer.tax-exempt-status')).toBeInTheDocument();
expect(
screen.getByRole('link', { name: 'make a tax-deductible donation here' })
).toHaveAttribute('href', '/donate');
const trendingList = screen.getByRole('list', {
name: 'footer.trending-guides'
});
const trendingLinks = within(trendingList).getAllByRole('link');
expect(trendingLinks).toHaveLength(30);
expect(trendingLinks[0]).toHaveAttribute('href', 'trending:article0link');
expect(
screen.getByRole('heading', { name: 'footer.mobile-app' })
).toBeInTheDocument();
expect(
screen.getByAltText('Download on the App Store')
).toBeInTheDocument();
expect(screen.getByAltText('Get it on Google Play')).toBeInTheDocument();
expect(
screen.getByRole('heading', { name: 'footer.our-nonprofit' })
).toBeInTheDocument();
expect(
screen.getByRole('link', { name: 'footer.links.about' })
).toHaveAttribute('href', 'links:footer.about-url');
});
});
@@ -1,23 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<Loader /> > matches the fullScreen render snapshot 1`] = `
<div
class="fcc-loader full-screen-wrapper"
data-testid="fcc-loader"
>
<div>
Spinner
</div>
</div>
`;
exports[`<Loader /> > matches to the default render snapshot 1`] = `
<div
class="fcc-loader "
data-testid="fcc-loader"
>
<div>
Spinner
</div>
</div>
`;
@@ -1,12 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<BlockSaveButton /> > <BlockSaveButton /> snapshot 1`] = `
<div>
<button
class=" relative inline-block mt-[0.5px] border-solid border-3 active:before:w-full active:before:h-full active:before:absolute active:before:inset-0 active:before:border-3 active:before:border-transparent active:before:bg-gray-900 active:before:opacity-20 text-center cursor-pointer no-underline block w-full bg-background-quaternary text-foreground-secondary border-foreground-secondary hover:bg-foreground-primary hover:text-background-primary hover:border-foreground-secondary dark:hover:bg-background-primary dark:hover:text-foreground-primary aria-expanded:border-foreground-secondary aria-expanded:bg-foreground-primary aria-expanded:text-background-primary dark:aria-expanded:bg-background-primary dark:aria-expanded:text-foreground-primary px-3 py-1.5 text-md"
type="submit"
>
buttons.save
</button>
</div>
`;
@@ -5,10 +5,10 @@ import React from 'react';
import BlockSaveButton from './block-save-button';
describe('<BlockSaveButton />', () => {
test('<BlockSaveButton /> snapshot', () => {
const { container } = render(<BlockSaveButton />);
test('renders a submit button by default', () => {
render(<BlockSaveButton />);
expect(container).toMatchSnapshot();
expect(screen.getByRole('button')).toHaveAttribute('type', 'submit');
});
test('Button text should default to the correct translation key', () => {
@@ -21,15 +21,17 @@ describe('<Loader />', () => {
* there is nothing much to test except snapshots
*/
it('matches to the default render snapshot', () => {
it('shows spinner in default state', () => {
render(<Loader />);
const fccLoader = screen.getByTestId('fcc-loader');
expect(fccLoader).toMatchSnapshot();
expect(fccLoader).not.toHaveClass('full-screen-wrapper');
expect(screen.getByText('Spinner')).toBeInTheDocument();
});
it('matches the fullScreen render snapshot', () => {
it('shows spinner in fullScreen state', () => {
render(<Loader fullScreen={true} />);
const fccLoader = screen.getByTestId('fcc-loader');
expect(fccLoader).toMatchSnapshot();
expect(fccLoader).toHaveClass('full-screen-wrapper');
expect(screen.getByText('Spinner')).toBeInTheDocument();
});
});
@@ -1,328 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<Profile/> > renders correctly 1`] = `
<div>
<div
class="h-[30px]"
/>
<div
class="mx-auto px-[15px] my-0 md:w-[750px] min-[992px]:w-[970px] min-[1200px]:w-[1170px] "
>
<div
class="h-[30px]"
/>
<div
class="bio-container"
>
<div
class="h-[30px]"
/>
<div
class="mx-[-15px] "
>
<div
class=" min-h-[1px] px-[15px] w-full md:w-2/3 md:ms-[16.6%] "
>
<div
class="h-[90px]"
/>
<section
class="card card-header"
>
<div
class="avatar-camper"
>
<div
class="avatar-container default-border"
>
<span
class="sr-only"
>
buttons.profile
</span>
<svg
aria-hidden="true"
class="avatar"
height="500px"
version="1.1"
viewBox="0 0 500 500"
width="500px"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<title>
icons.avatar
</title>
<desc>
icons.avatar-2
</desc>
<g
fill="none"
fill-rule="evenodd"
>
<g
id="g"
>
<rect
fill="#D0D0D5"
height="500"
width="500"
/>
<path
d="m251.34 49c23.859 58.47 34.222 90.121 31.088 94.954-4.701 7.2493-15.381 32.924 0 50.968s77.487 6.439 92.029 23.897c14.542 17.458 12.196 68.184 14.542 135.56-22.154 0.54208-68.154 1.0837-138 1.6248l0.062-56h-0.124l0.062 56c-69.846-0.54109-115.85-1.0827-138-1.6248 2.3463-67.372 0-118.1 14.542-135.56 14.542-17.458 76.649-5.852 92.029-23.897s4.701-43.719 0-50.968c-3.134-4.8329 7.2285-36.484 31.088-94.954l0.13247 120h0.415z"
fill="#242440"
/>
<path
d="m250.13 185c47.577 0 66.872-66.034 66.872-90.434 0-42.286-29.773-76.566-66.5-76.566s-66.5 34.28-66.5 76.566c0 24.7 18.552 90.434 66.128 90.434z"
fill="#242440"
id="c"
stroke="#D0D0D5"
stroke-width="17"
/>
<path
d="m77.011 459c-19.341-119.95-29.011-183.79-29.011-191.53 0-11.605 6.2167-16.473 17.298-16.473h370.4c11.082 0 17.298 4.8681 17.298 16.473 0 7.7366-9.6704 71.579-29.011 191.53z"
fill="#5F5F8C"
stroke="#D0D0D5"
stroke-width="16"
/>
<rect
fill="#5F5F8C"
height="23"
stroke="#D0D0D5"
stroke-width="6"
width="339"
x="81"
y="459"
/>
<g
fill-rule="nonzero"
transform="translate(162 283)"
>
<ellipse
cx="88.5"
cy="79"
fill="#0A0A23"
rx="88.5"
ry="79"
/>
<g
transform="translate(20 40)"
>
<g
id="Group"
transform="translate(42.462 4)"
>
<g
fill="#fff"
id="e"
>
<path
d="m38.312 39.042c-3.9186-0.91476 12.174-18.263-16.43-39.034 0 0 3.7555 10.879-15.169 35.157-18.933 24.27 8.418 38.725 8.418 38.725s-12.834-6.24 2.0846-28.459c2.6734-4.0329 6.1663-7.6847 10.507-15.899 0 0 3.839 4.9441 1.834 15.671-2.9996 16.208 13.005 11.569 13.256 11.794 5.5895 6.0077-4.6307 16.564-5.2513 16.894-0.62061 0.32307 29.185-16.36 8.0083-41.469-1.4521 1.325-3.3338 7.5359-7.2564 6.6212z"
id="i"
/>
</g>
<g
fill="#000"
fill-opacity="0"
stroke="#000"
stroke-opacity="0"
>
<path
d="m38.312 39.042c-3.9186-0.91476 12.174-18.263-16.43-39.034 0 0 3.7555 10.879-15.169 35.157-18.933 24.27 8.418 38.725 8.418 38.725s-12.834-6.24 2.0846-28.459c2.6734-4.0329 6.1663-7.6847 10.507-15.899 0 0 3.839 4.9441 1.834 15.671-2.9996 16.208 13.005 11.569 13.256 11.794 5.5895 6.0077-4.6307 16.564-5.2513 16.894-0.62061 0.32307 29.185-16.36 8.0083-41.469-1.4521 1.325-3.3338 7.5359-7.2564 6.6212z"
/>
</g>
</g>
<g
id="b"
transform="translate(110.13)"
>
<g
fill="#fff"
id="d"
>
<path
d="m0.96996 0.62339c-0.47786 0.41439-0.95166 1.0162-0.95166 1.6215-0.0040664 1.045 1.3846 2.4611 3.9577 4.7889 10.713 9.1022 16.104 20.251 16.068 33.692-0.040843 14.875-5.7099 26.82-16.729 36.077-2.3158 1.8305-3.2674 3.2647-3.2715 4.4935 0 0.60537 0.4697 1.2324 0.94347 1.8341 0.44519 0.4216 1.3927 0.83962 2.0748 0.83962 2.5486 0.0071777 6.1183-2.6521 10.774-7.8266 9.0712-9.8085 13.172-20.64 13.401-35.4 0.21238-14.77-5.0319-24.784-15.308-35.126-3.6963-3.6935-6.7759-5.6141-8.8834-5.6177-0.68208 0-1.3927 0.20539-2.0748 0.62339z"
id="a"
/>
</g>
<g
fill="#000"
fill-opacity="0"
stroke="#000"
stroke-opacity="0"
>
<path
d="m0.96996 0.62339c-0.47786 0.41439-0.95166 1.0162-0.95166 1.6215-0.0040664 1.045 1.3846 2.4611 3.9577 4.7889 10.713 9.1022 16.104 20.251 16.068 33.692-0.040843 14.875-5.7099 26.82-16.729 36.077-2.3158 1.8305-3.2674 3.2647-3.2715 4.4935 0 0.60537 0.4697 1.2324 0.94347 1.8341 0.44519 0.4216 1.3927 0.83962 2.0748 0.83962 2.5486 0.0071777 6.1183-2.6521 10.774-7.8266 9.0712-9.8085 13.172-20.64 13.401-35.4 0.21238-14.77-5.0319-24.784-15.308-35.126-3.6963-3.6935-6.7759-5.6141-8.8834-5.6177-0.68208 0-1.3927 0.20539-2.0748 0.62339z"
/>
</g>
</g>
<g
fill="#fff"
id="h"
>
<path
d="m26.367 0.6342c0.47409 0.41439 0.9482 1.0162 0.9482 1.6215 0.004069 1.045-1.3855 2.4611-3.9603 4.7889-10.72 9.1022-16.111 20.251-16.078 33.692 0.04087 14.875 5.7136 26.82 16.74 36.077 2.3173 1.8305 3.2696 3.2647 3.2737 4.4935 0 0.60537-0.47001 1.2324-0.9441 1.8341-0.44548 0.4216-1.3896 0.83962-2.0762 0.83962-2.5503 0.0071777-6.1223-2.6521-10.782-7.8266-9.0773-9.8085-13.181-20.64-13.409-35.4-0.21252-14.77 5.0352-24.784 15.318-35.126 3.6987-3.6935 6.7844-5.6141 8.8892-5.6177 0.68253 0 1.3937 0.20539 2.0803 0.62339z"
id="f"
/>
</g>
<g
fill="#000"
fill-opacity="0"
stroke="#000"
stroke-opacity="0"
>
<path
d="m26.367 0.6342c0.47409 0.41439 0.9482 1.0162 0.9482 1.6215 0.004069 1.045-1.3855 2.4611-3.9603 4.7889-10.72 9.1022-16.111 20.251-16.078 33.692 0.04087 14.875 5.7136 26.82 16.74 36.077 2.3173 1.8305 3.2696 3.2647 3.2737 4.4935 0 0.60537-0.47001 1.2324-0.9441 1.8341-0.44548 0.4216-1.3896 0.83962-2.0762 0.83962-2.5503 0.0071777-6.1223-2.6521-10.782-7.8266-9.0773-9.8085-13.181-20.64-13.409-35.4-0.21252-14.77 5.0352-24.784 15.318-35.126 3.6987-3.6935 6.7844-5.6141 8.8892-5.6177 0.68253 0 1.3937 0.20539 2.0803 0.62339z"
/>
</g>
</g>
</g>
</g>
</g>
</svg>
</div>
</div>
<div
class="profile-edit-container"
>
<h1>
@
string
</h1>
</div>
<div
class="h-[20px]"
/>
<div
class="profile-meta-container"
/>
<div
class="mx-[-15px] "
>
<div
class="social-icons-row min-h-[1px] px-[15px] "
>
<a
aria-label="aria.linkedin"
href="string"
rel="noopener noreferrer"
target="_blank"
>
<svg
aria-hidden="true"
class="svg-inline--fa fa-linkedin fa-2x"
data-icon="linkedin"
data-prefix="fab"
role="img"
viewBox="0 0 448 512"
>
<path
d="M416 32L31.9 32C14.3 32 0 46.5 0 64.3L0 447.7C0 465.5 14.3 480 31.9 480L416 480c17.6 0 32-14.5 32-32.3l0-383.4C448 46.5 433.6 32 416 32zM135.4 416l-66.4 0 0-213.8 66.5 0 0 213.8-.1 0zM102.2 96a38.5 38.5 0 1 1 0 77 38.5 38.5 0 1 1 0-77zM384.3 416l-66.4 0 0-104c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9l0 105.8-66.4 0 0-213.8 63.7 0 0 29.2 .9 0c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9l0 117.2z"
fill="currentColor"
/>
</svg>
</a>
<a
aria-label="aria.github"
href="string"
rel="noopener noreferrer"
target="_blank"
>
<svg
aria-hidden="true"
class="svg-inline--fa fa-github fa-2x"
data-icon="github"
data-prefix="fab"
role="img"
viewBox="0 0 512 512"
>
<path
d="M173.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM252.8 8c-138.7 0-244.8 105.3-244.8 244 0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1 100-33.2 167.8-128.1 167.8-239 0-138.7-112.5-244-251.2-244zM105.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"
fill="currentColor"
/>
</svg>
</a>
<a
aria-label="aria.website"
href="string"
rel="noopener noreferrer"
target="_blank"
>
<svg
aria-hidden="true"
class="svg-inline--fa fa-link fa-2x"
data-icon="link"
data-prefix="fas"
role="img"
viewBox="0 0 576 512"
>
<path
d="M419.5 96c-16.6 0-32.7 4.5-46.8 12.7-15.8-16-34.2-29.4-54.5-39.5 28.2-24 64.1-37.2 101.3-37.2 86.4 0 156.5 70 156.5 156.5 0 41.5-16.5 81.3-45.8 110.6l-71.1 71.1c-29.3 29.3-69.1 45.8-110.6 45.8-86.4 0-156.5-70-156.5-156.5 0-1.5 0-3 .1-4.5 .5-17.7 15.2-31.6 32.9-31.1s31.6 15.2 31.1 32.9c0 .9 0 1.8 0 2.6 0 51.1 41.4 92.5 92.5 92.5 24.5 0 48-9.7 65.4-27.1l71.1-71.1c17.3-17.3 27.1-40.9 27.1-65.4 0-51.1-41.4-92.5-92.5-92.5zM275.2 173.3c-1.9-.8-3.8-1.9-5.5-3.1-12.6-6.5-27-10.2-42.1-10.2-24.5 0-48 9.7-65.4 27.1L91.1 258.2c-17.3 17.3-27.1 40.9-27.1 65.4 0 51.1 41.4 92.5 92.5 92.5 16.5 0 32.6-4.4 46.7-12.6 15.8 16 34.2 29.4 54.6 39.5-28.2 23.9-64 37.2-101.3 37.2-86.4 0-156.5-70-156.5-156.5 0-41.5 16.5-81.3 45.8-110.6l71.1-71.1c29.3-29.3 69.1-45.8 110.6-45.8 86.6 0 156.5 70.6 156.5 156.9 0 1.3 0 2.6 0 3.9-.4 17.7-15.1 31.6-32.8 31.2s-31.6-15.1-31.2-32.8c0-.8 0-1.5 0-2.3 0-33.7-18-63.3-44.8-79.6z"
fill="currentColor"
/>
</svg>
</a>
<a
aria-label="aria.twitter"
href="string"
rel="noopener noreferrer"
target="_blank"
>
<svg
aria-hidden="true"
class="svg-inline--fa fa-x-twitter fa-2x"
data-icon="x-twitter"
data-prefix="fab"
role="img"
viewBox="0 0 448 512"
>
<path
d="M357.2 48L427.8 48 273.6 224.2 455 464 313 464 201.7 318.6 74.5 464 3.8 464 168.7 275.5-5.2 48 140.4 48 240.9 180.9 357.2 48zM332.4 421.8l39.1 0-252.4-333.8-42 0 255.3 333.8z"
fill="currentColor"
/>
</svg>
</a>
<a
aria-label="aria.bluesky"
href="string"
rel="noopener noreferrer"
target="_blank"
>
<svg
aria-hidden="true"
class="svg-inline--fa fa-bluesky fa-2x"
data-icon="bluesky"
data-prefix="fab"
role="img"
viewBox="0 0 576 512"
>
<path
d="M407.8 294.7c-3.3-.4-6.7-.8-10-1.3 3.4 .4 6.7 .9 10 1.3zM288 227.1C261.9 176.4 190.9 81.9 124.9 35.3 61.6-9.4 37.5-1.7 21.6 5.5 3.3 13.8 0 41.9 0 58.4S9.1 194 15 213.9c19.5 65.7 89.1 87.9 153.2 80.7 3.3-.5 6.6-.9 10-1.4-3.3 .5-6.6 1-10 1.4-93.9 14-177.3 48.2-67.9 169.9 120.3 124.6 164.8-26.7 187.7-103.4 22.9 76.7 49.2 222.5 185.6 103.4 102.4-103.4 28.1-156-65.8-169.9-3.3-.4-6.7-.8-10-1.3 3.4 .4 6.7 .9 10 1.3 64.1 7.1 133.6-15.1 153.2-80.7 5.9-19.9 15-138.9 15-155.5s-3.3-44.7-21.6-52.9c-15.8-7.1-40-14.9-103.2 29.8-66.1 46.6-137.1 141.1-163.2 191.8z"
fill="currentColor"
/>
</svg>
</a>
</div>
</div>
</section>
</div>
</div>
</div>
<div
class="h-[30px]"
/>
<div
class="mx-[-15px] text-center"
>
<a
href="/user/string/report-user"
>
buttons.flag-user
</a>
</div>
<div
class="h-[30px]"
/>
</div>
</div>
`;
@@ -1,451 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Timeline buttons test > should check certification page consistency 1`] = `
<div
className="mx-[-15px] "
>
<div
className=" min-h-[1px] px-[15px] w-full md:w-2/3 md:ms-[16.6%] "
>
<section
className="card"
>
<h2>
profile.timeline
</h2>
<div
className="h-[20px]"
/>
<table
className="table-auto w-full max-w-full border-collapse text-start text-foreground-tertiary [&_th]:font-normal [&_td]:p-1 [&_th]:p-1 [&>tbody>tr:nth-of-type(odd)]:bg-background-tertiary"
>
<thead>
<tr>
<th>
profile.challenge
</th>
<th>
settings.labels.solution
</th>
<th
className="text-center"
>
profile.completed
</th>
</tr>
</thead>
<tbody>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/2022/responsive-web-design/learn-accessibility-by-building-a-quiz/step-41"
>
Step 41
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/build-a-number-guessing-game-project/build-a-number-guessing-game"
>
Build a Number Guessing Game
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/build-a-periodic-table-database-project/build-a-periodic-table-database"
>
Build a Periodic Table Database
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/learn-git-by-building-an-sql-reference-object/build-an-sql-reference-object"
>
Build an SQL Reference Object
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/learn-nano-by-building-a-castle/build-a-castle"
>
Build a Castle
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/build-a-salon-appointment-scheduler-project/build-a-salon-appointment-scheduler"
>
Build a Salon Appointment Scheduler
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/learn-bash-and-sql-by-building-a-bike-rental-shop/build-a-bike-rental-shop"
>
Build a Bike Rental Shop
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/learn-advanced-bash-by-building-a-kitty-ipsum-translator/build-a-kitty-ipsum-translator"
>
Build a Kitty Ipsum Translator
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/build-a-world-cup-database-project/build-a-world-cup-database"
>
Build a World Cup Database
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/learn-sql-by-building-a-student-database-part-2/build-a-student-database-part-2"
>
Build a Student Database: Part 2
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/learn-sql-by-building-a-student-database-part-1/build-a-student-database-part-1"
>
Build a Student Database: Part 1
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/learn-bash-scripting-by-building-five-programs/build-five-programs"
>
Build Five Programs
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/build-a-celestial-bodies-database-project/build-a-celestial-bodies-database"
>
Build a Celestial Bodies Database
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/learn-relational-databases-by-building-a-mario-database/build-a-mario-database"
>
Build a Mario Database
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
<tr
className="timeline-row"
>
<td>
<a
gatsby="true"
href="/learn/relational-database/learn-bash-by-building-a-boilerplate/build-a-boilerplate"
>
Build a Boilerplate
</a>
</td>
<td />
<td
className="text-center"
>
<time
dateTime="2016-09-28T20:31:56.730Z"
>
Dec 29, 2022
</time>
</td>
</tr>
</tbody>
</table>
<nav
aria-label="aria.timeline-pagination-nav"
>
<ul
aria-hidden="true"
className="timeline-pagination_list"
>
<li
className="timeline-pagination_list_item"
style={
{
"visibility": "hidden",
}
}
>
<button
aria-label="aria.first-page"
disabled={true}
onClick={[Function]}
>
&lt;&lt;
</button>
</li>
<li
className="timeline-pagination_list_item"
style={
{
"visibility": "hidden",
}
}
>
<button
aria-label="aria.previous-page"
disabled={true}
onClick={[Function]}
>
&lt;
</button>
</li>
<li
className="timeline-pagination_list_item"
>
profile.page-number
</li>
<li
className="timeline-pagination_list_item"
style={
{
"visibility": "unset",
}
}
>
<button
aria-label="aria.next-page"
disabled={false}
onClick={[Function]}
>
&gt;
</button>
</li>
<li
className="timeline-pagination_list_item"
style={
{
"visibility": "unset",
}
}
>
<button
aria-label="aria.last-page"
disabled={false}
onClick={[Function]}
>
&gt;&gt;
</button>
</li>
</ul>
</nav>
</section>
</div>
</div>
`;
@@ -1,5 +1,5 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { render, screen } from '@testing-library/react';
import { Provider } from 'react-redux';
import { describe, it, expect, vi } from 'vitest';
@@ -37,19 +37,28 @@ vi.mock('gatsby', async () => {
});
describe('Timeline buttons test', () => {
it('should check certification page consistency', () => {
const tree = renderer
.create(
<Provider store={createStore()}>
<Timeline
completedMap={completedChallenges}
username='CeritifedUser'
onPress={() => {}}
/>
</Provider>
)
.toJSON();
it('renders first page of timeline entries', () => {
render(
<Provider store={createStore()}>
<Timeline
completedMap={completedChallenges}
username='CeritifedUser'
onPress={() => {}}
/>
</Provider>
);
expect(tree).toMatchSnapshot();
expect(
screen.getByRole('heading', { name: 'profile.timeline' })
).toBeInTheDocument();
expect(screen.getAllByRole('row')).toHaveLength(16);
expect(
screen.getByRole('columnheader', { name: 'settings.labels.solution' })
).toBeInTheDocument();
expect(screen.getAllByText('Dec 29, 2022')).toHaveLength(15);
expect(screen.getByRole('link', { name: 'Step 41' })).toHaveAttribute(
'href',
'/learn/2022/responsive-web-design/learn-accessibility-by-building-a-quiz/step-41'
);
});
});
+17 -3
View File
@@ -107,10 +107,24 @@ describe('<Profile/>', () => {
expect(reportButton).toHaveAttribute('href', '/user/string/report-user');
});
it('renders correctly', () => {
it('renders profile heading and social links', () => {
// @ts-expect-error - quick hack to mollify TS.
const { container } = renderWithRedux(<Profile {...notMyProfileProps} />);
renderWithRedux(<Profile {...notMyProfileProps} />);
expect(container).toMatchSnapshot();
expect(
screen.getByRole('heading', { name: '@string' })
).toBeInTheDocument();
expect(screen.getByLabelText('aria.linkedin')).toHaveAttribute(
'href',
'string'
);
expect(screen.getByLabelText('aria.github')).toHaveAttribute(
'href',
'string'
);
expect(screen.getByLabelText('aria.website')).toHaveAttribute(
'href',
'string'
);
});
});
@@ -1,135 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<Honesty /> > <Honesty /> snapshot when isHonest is false > Honesty 1`] = `
<DocumentFragment>
<section
id="honesty-policy"
>
<div
class="mx-[-15px] "
>
<div
class=" min-h-[1px] px-[15px] w-full md:w-2/3 md:ms-[16.6%] "
>
<h2
class="text-center"
>
settings.headings.honesty
</h2>
<hr />
</div>
</div>
<div
class="mx-[-15px] "
>
<div
class=" min-h-[1px] px-[15px] w-full md:w-2/3 md:ms-[16.6%] "
>
<div
class="border-1 border-solid shadow-sm mb-6 border-background-tertiary honesty-panel"
>
<p>
settings.honesty.p1
</p>
<p>
settings.honesty.p2
</p>
<p>
settings.honesty.p3
</p>
<p>
settings.honesty.p4
</p>
<p>
settings.honesty.p5
</p>
<p>
settings.honesty.p6
</p>
<p>
<a
href="mailto:support@freecodecamp.org"
>
{{email}}
</a>
</p>
</div>
<button
aria-disabled="false"
class=" relative inline-block mt-[0.5px] border-solid border-3 active:before:w-full active:before:h-full active:before:absolute active:before:inset-0 active:before:border-3 active:before:border-transparent active:before:bg-gray-900 active:before:opacity-20 text-center cursor-pointer no-underline block w-full bg-background-quaternary text-foreground-secondary border-foreground-secondary hover:bg-foreground-primary hover:text-background-primary hover:border-foreground-secondary dark:hover:bg-background-primary dark:hover:text-foreground-primary aria-expanded:border-foreground-secondary aria-expanded:bg-foreground-primary aria-expanded:text-background-primary dark:aria-expanded:bg-background-primary dark:aria-expanded:text-foreground-primary px-3 py-1.5 text-md"
type="button"
>
buttons.agree-honesty
</button>
</div>
</div>
</section>
</DocumentFragment>
`;
exports[`<Honesty /> > <Honesty /> snapshot when isHonest is true > HonestyAccepted 1`] = `
<DocumentFragment>
<section
id="honesty-policy"
>
<div
class="mx-[-15px] "
>
<div
class=" min-h-[1px] px-[15px] w-full md:w-2/3 md:ms-[16.6%] "
>
<h2
class="text-center"
>
settings.headings.honesty
</h2>
<hr />
</div>
</div>
<div
class="mx-[-15px] "
>
<div
class=" min-h-[1px] px-[15px] w-full md:w-2/3 md:ms-[16.6%] "
>
<div
class="border-1 border-solid shadow-sm mb-6 border-background-tertiary honesty-panel"
>
<p>
settings.honesty.p1
</p>
<p>
settings.honesty.p2
</p>
<p>
settings.honesty.p3
</p>
<p>
settings.honesty.p4
</p>
<p>
settings.honesty.p5
</p>
<p>
settings.honesty.p6
</p>
<p>
<a
href="mailto:support@freecodecamp.org"
>
{{email}}
</a>
</p>
</div>
<button
aria-disabled="true"
class=" relative inline-block mt-[0.5px] border-solid border-3 active:before:w-full active:before:h-full active:before:absolute active:before:inset-0 active:before:border-3 active:before:border-transparent active:before:bg-gray-900 active:before:opacity-20 text-center cursor-pointer no-underline block w-full bg-background-quaternary text-foreground-secondary border-gray-450 active:before:hidden aria-disabled:cursor-not-allowed aria-disabled:opacity-80 px-3 py-1.5 text-md"
type="button"
>
buttons.accepted-honesty
</button>
</div>
</div>
</section>
</DocumentFragment>
`;
@@ -6,18 +6,27 @@ import Honesty from './honesty';
describe('<Honesty />', () => {
const updateIsHonestMock = vi.fn();
test('<Honesty /> snapshot when isHonest is false', () => {
const { asFragment } = render(
<Honesty isHonest={false} updateIsHonest={updateIsHonestMock} />
test('renders pending honesty state', () => {
render(<Honesty isHonest={false} updateIsHonest={updateIsHonestMock} />);
expect(
screen.getByRole('heading', { name: 'settings.headings.honesty' })
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'buttons.agree-honesty' })
).toBeEnabled();
expect(screen.getByRole('link', { name: '{{email}}' })).toHaveAttribute(
'href',
'mailto:support@freecodecamp.org'
);
expect(asFragment()).toMatchSnapshot('Honesty');
});
test('<Honesty /> snapshot when isHonest is true', () => {
const { asFragment } = render(
<Honesty isHonest={true} updateIsHonest={updateIsHonestMock} />
);
expect(asFragment()).toMatchSnapshot('HonestyAccepted');
test('renders accepted honesty state', () => {
render(<Honesty isHonest={true} updateIsHonest={updateIsHonestMock} />);
expect(
screen.getByRole('button', { name: 'buttons.accepted-honesty' })
).toHaveAttribute('aria-disabled', 'true');
});
test('should call updateIsHonest method on clicking agree button', () => {
@@ -1,62 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<ChallengeTitle/> > renders correctly 1`] = `
<div
className="challenge-title-wrap"
>
<div
className="challenge-title"
>
<h1
data-playwright-test-label="challenge-title"
id="content-start"
>
title text
</h1>
<svg
aria-label="icons.passed"
data-testid="green-pass"
height="15"
viewBox="0 0 200 200"
width="15"
xmlns="http://www.w3.org/2000/svg"
>
<g
aria-hidden="true"
>
<title>
icons.passed
</title>
<circle
cx="100"
cy="99"
fill="var(--primary-color)"
r="95"
stroke="var(--primary-color)"
strokeDasharray="null"
/>
<rect
fill="var(--primary-background)"
height="30"
stroke="var(--primary-background)"
strokeDasharray="null"
transform="rotate(-45, 120, 106.321)"
width="128.85878"
x="55.57059"
y="91.32089"
/>
<rect
fill="var(--primary-background)"
height="30"
stroke="var(--primary-background)"
strokeDasharray="null"
transform="rotate(45, 66.75, 123.75)"
width="80.66548"
x="26.41726"
y="108.75"
/>
</g>
</svg>
</div>
</div>
`;
@@ -1,5 +1,5 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import ChallengeTitle from './challenge-title';
@@ -11,8 +11,12 @@ const baseProps = {
};
describe('<ChallengeTitle/>', () => {
it('renders correctly', () => {
const tree = renderer.create(<ChallengeTitle {...baseProps} />).toJSON();
expect(tree).toMatchSnapshot();
it('renders heading and completion icon', () => {
render(<ChallengeTitle {...baseProps} />);
expect(
screen.getByRole('heading', { level: 1, name: 'title text' })
).toBeInTheDocument();
expect(screen.getByTestId('green-pass')).toBeInTheDocument();
});
});
@@ -1,22 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<BlockIntros /> > matches snapshot 1`] = `
<div
className="block-description"
>
<p
dangerouslySetInnerHTML={
{
"__html": "first paragraph",
}
}
/>
<p
dangerouslySetInnerHTML={
{
"__html": "second paragraph",
}
}
/>
</div>
`;
@@ -1,13 +1,16 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import BlockIntros from './block-intros';
const arrString = ['first paragraph', 'second paragraph'];
describe('<BlockIntros />', () => {
it('matches snapshot', () => {
const tree = renderer.create(<BlockIntros intros={arrString} />);
expect(tree).toMatchSnapshot();
it('renders each intro paragraph', () => {
render(<BlockIntros intros={arrString} />);
expect(screen.getAllByText(/paragraph/)).toHaveLength(2);
expect(screen.getByText('first paragraph')).toBeInTheDocument();
expect(screen.getByText('second paragraph')).toBeInTheDocument();
});
});