feat: render nav conditionally
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
422bacd15d
commit
1a66eac990
9
client/package-lock.json
generated
9
client/package-lock.json
generated
@ -21295,15 +21295,6 @@
|
||||
"camelcase": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"react-identicons": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react-identicons/-/react-identicons-1.2.4.tgz",
|
||||
"integrity": "sha512-my4CFMlO88kWjhX/y5qiGjQE9KgVLLeUKEM2wilUko6UzUTmzHJvl0rjkcftG9bMq3WLkpJkB2qwFMRllS+NmQ==",
|
||||
"requires": {
|
||||
"react": "^16.13.0",
|
||||
"react-dom": "^16.13.0"
|
||||
}
|
||||
},
|
||||
"react-instantsearch-core": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/react-instantsearch-core/-/react-instantsearch-core-6.7.0.tgz",
|
||||
|
@ -56,7 +56,6 @@
|
||||
"react-ga": "^2.7.0",
|
||||
"react-helmet": "^5.2.1",
|
||||
"react-hotkeys": "^2.0.0",
|
||||
"react-identicons": "^1.1.7",
|
||||
"react-instantsearch-dom": "^6.7.0",
|
||||
"react-lazy-load": "^3.1.13",
|
||||
"react-monaco-editor": "^0.36.0",
|
||||
|
91
client/src/assets/icons/DefaultAvatar.js
Normal file
91
client/src/assets/icons/DefaultAvatar.js
Normal file
@ -0,0 +1,91 @@
|
||||
/* eslint-disable max-len */
|
||||
import React from 'react';
|
||||
|
||||
function DefaultAvatar(props) {
|
||||
return (
|
||||
<svg
|
||||
className='default-avatar'
|
||||
height='500px'
|
||||
version='1.1'
|
||||
viewBox='0 0 500 500'
|
||||
width='500px'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
xmlnsXlink='http://www.w3.org/1999/xlink'
|
||||
{...props}
|
||||
>
|
||||
<title>default avatar</title>
|
||||
<desc>an avatar conding with a laptop</desc>
|
||||
<g fill='none' fillRule='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'
|
||||
strokeWidth='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'
|
||||
strokeWidth='16'
|
||||
/>
|
||||
<rect
|
||||
fill='#5F5F8C'
|
||||
height='23'
|
||||
stroke='#D0D0D5'
|
||||
strokeWidth='6'
|
||||
width='339'
|
||||
x='81'
|
||||
y='459'
|
||||
/>
|
||||
<g fillRule='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' fillOpacity='0' stroke='#000' strokeOpacity='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' fillOpacity='0' stroke='#000' strokeOpacity='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' fillOpacity='0' stroke='#000' strokeOpacity='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>
|
||||
);
|
||||
}
|
||||
|
||||
DefaultAvatar.displayName = 'DefaultAvatar';
|
||||
|
||||
export default DefaultAvatar;
|
@ -15,7 +15,7 @@ import {
|
||||
import { submitNewAbout, updateUserFlag, verifyCert } from '../redux/settings';
|
||||
import { createFlashMessage } from '../components/Flash/redux';
|
||||
|
||||
import { FullWidthRow, Link, Loader, Spacer } from '../components/helpers';
|
||||
import { FullWidthRow, Loader, Spacer } from '../components/helpers';
|
||||
import About from '../components/settings/About';
|
||||
import Privacy from '../components/settings/Privacy';
|
||||
import Email from '../components/settings/Email';
|
||||
@ -177,13 +177,7 @@ export function ShowSettings(props) {
|
||||
<Grid>
|
||||
<main>
|
||||
<Spacer size={2} />
|
||||
<FullWidthRow className='button-group'>
|
||||
<Link
|
||||
className='btn-invert btn btn-lg btn-primary btn-block'
|
||||
to={`/${username}`}
|
||||
>
|
||||
Show me my public portfolio
|
||||
</Link>
|
||||
<FullWidthRow>
|
||||
<Button
|
||||
block={true}
|
||||
bsSize='lg'
|
||||
|
@ -1,13 +1,19 @@
|
||||
/* global expect */
|
||||
import React from 'react';
|
||||
import ShallowRenderer from 'react-test-renderer/shallow';
|
||||
import TestRenderer from 'react-test-renderer';
|
||||
import { UniversalNav } from './components/UniversalNav';
|
||||
import NavLinks from './components/NavLinks';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import { forumLocation } from '../../../config/env.json';
|
||||
import { UniversalNav } from './components/UniversalNav';
|
||||
import { AuthOrProfile } from './components/NavLinks';
|
||||
|
||||
describe('<UniversalNav />', () => {
|
||||
const UniversalNavProps = {
|
||||
displayMenu: false,
|
||||
menuButtonRef: {},
|
||||
searchBarRef: {},
|
||||
toggleDisplayMenu: function() {},
|
||||
pathName: '/'
|
||||
};
|
||||
it('renders to the DOM', () => {
|
||||
const shallow = new ShallowRenderer();
|
||||
shallow.render(<UniversalNav {...UniversalNavProps} />);
|
||||
@ -15,34 +21,138 @@ describe('<UniversalNav />', () => {
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('<NavLinks />', () => {
|
||||
const root = TestRenderer.create(<NavLinks />).root;
|
||||
const aTags = root.findAllByType('a');
|
||||
it('shows Curriculum and Sign In buttons on landing page', () => {
|
||||
const landingPageProps = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
picture: 'https://freecodecamp.org/image.png'
|
||||
},
|
||||
pending: false,
|
||||
pathName: '/'
|
||||
};
|
||||
const shallow = new ShallowRenderer();
|
||||
shallow.render(<AuthOrProfile {...landingPageProps} />);
|
||||
const result = shallow.getRenderOutput();
|
||||
// expect(result.props.children).toEqual('Sign In');
|
||||
|
||||
// reduces the aTags to href links
|
||||
const links = aTags.reduce((acc, item) => {
|
||||
acc.push(item._fiber.pendingProps.href);
|
||||
return acc;
|
||||
}, []);
|
||||
expect(deepChildrenProp(result, 0).children === 'Curriculum').toBeTruthy();
|
||||
|
||||
const expectedLinks = ['/learn', '/news', forumLocation];
|
||||
|
||||
it('renders to the DOM', () => {
|
||||
expect(root).toBeTruthy();
|
||||
});
|
||||
it('has 3 links', () => {
|
||||
expect(aTags.length === 3).toBeTruthy();
|
||||
expect(
|
||||
result.props.children[1].props['data-test-label'] === 'landing-small-cta'
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('has links to news, forum, learn and portfolio', () => {
|
||||
// checks if all links in expected links exist in links
|
||||
expect(expectedLinks.every(elem => links.indexOf(elem) > -1)).toBeTruthy();
|
||||
it('has Curriculum and Portfolio links when user signed in on /learn', () => {
|
||||
const defaultUserProps = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
picture: 'https://freecodecamp.org/image.png',
|
||||
isDonating: true
|
||||
},
|
||||
pending: false,
|
||||
pathName: '/learn'
|
||||
};
|
||||
const shallow = new ShallowRenderer();
|
||||
shallow.render(<AuthOrProfile {...defaultUserProps} />);
|
||||
const result = shallow.getRenderOutput();
|
||||
|
||||
expect(hasCurriculumNavItem(result)).toBeTruthy();
|
||||
expect(hasProfileNavItem(result)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('has avatar with default border for default users', () => {
|
||||
const defaultUserProps = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
picture: 'https://freecodecamp.org/image.png'
|
||||
},
|
||||
pending: false,
|
||||
pathName: '/learn'
|
||||
};
|
||||
|
||||
const componentTree = renderer
|
||||
.create(<AuthOrProfile {...defaultUserProps} />)
|
||||
.toJSON();
|
||||
|
||||
expect(avatarHasClass(componentTree, 'default-border')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('has avatar with gold border for donating users', () => {
|
||||
const donatingUserProps = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
picture: 'https://freecodecamp.org/image.png',
|
||||
isDonating: true
|
||||
},
|
||||
pending: false,
|
||||
pathName: '/learn'
|
||||
};
|
||||
const componentTree = renderer
|
||||
.create(<AuthOrProfile {...donatingUserProps} />)
|
||||
.toJSON();
|
||||
|
||||
expect(avatarHasClass(componentTree, 'gold-border')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('has avatar with green border for top contributors', () => {
|
||||
const topContributorUserProps = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
picture: 'https://freecodecamp.org/image.png',
|
||||
yearsTopContributor: [2020]
|
||||
},
|
||||
pending: false,
|
||||
pathName: '/learn'
|
||||
};
|
||||
|
||||
const componentTree = renderer
|
||||
.create(<AuthOrProfile {...topContributorUserProps} />)
|
||||
.toJSON();
|
||||
|
||||
expect(avatarHasClass(componentTree, 'green-border')).toBeTruthy();
|
||||
});
|
||||
it('has avatar with purple border for donating top contributors', () => {
|
||||
const topDonatingContributorUserProps = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
picture: 'https://freecodecamp.org/image.png',
|
||||
isDonating: true,
|
||||
yearsTopContributor: [2020]
|
||||
},
|
||||
pending: false,
|
||||
pathName: '/learn'
|
||||
};
|
||||
const componentTree = renderer
|
||||
.create(<AuthOrProfile {...topDonatingContributorUserProps} />)
|
||||
.toJSON();
|
||||
expect(avatarHasClass(componentTree, 'purple-border')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
const UniversalNavProps = {
|
||||
displayMenu: false,
|
||||
menuButtonRef: {},
|
||||
searchBarRef: {},
|
||||
toggleDisplayMenu: function() {}
|
||||
const deepChildrenProp = (component, childNumber) =>
|
||||
component.props.children[childNumber].props.children.props;
|
||||
|
||||
const hasProfileNavItem = component => {
|
||||
const profileElement = deepChildrenProp(component, 1);
|
||||
return (
|
||||
profileElement.children[0] === 'Profile' &&
|
||||
profileElement.to === '/test-user'
|
||||
);
|
||||
};
|
||||
|
||||
const hasCurriculumNavItem = component => {
|
||||
const curriculumElement = deepChildrenProp(component, 0);
|
||||
return (
|
||||
curriculumElement.children === 'Curriculum' &&
|
||||
curriculumElement.to === '/learn'
|
||||
);
|
||||
};
|
||||
|
||||
const avatarHasClass = (componentTree, classes) => {
|
||||
return (
|
||||
componentTree[1].children[0].children[1].props.className ===
|
||||
'avatar-container ' + classes
|
||||
);
|
||||
};
|
||||
|
65
client/src/components/Header/components/AuthOrProfile.js
Normal file
65
client/src/components/Header/components/AuthOrProfile.js
Normal file
@ -0,0 +1,65 @@
|
||||
/* eslint-disable react/sort-prop-types */
|
||||
import React from 'react';
|
||||
import {
|
||||
Link,
|
||||
borderColorPicker,
|
||||
SkeletonSprite,
|
||||
AvatarRenderer
|
||||
} from '../../helpers';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Login from '../components/Login';
|
||||
|
||||
const propTypes = {
|
||||
pending: PropTypes.bool,
|
||||
pathName: PropTypes.string.isRequired,
|
||||
user: PropTypes.object
|
||||
};
|
||||
|
||||
export function AuthOrProfile({ user, pathName, pending }) {
|
||||
const isUserDonating = user && user.isDonating;
|
||||
const isUserSignedIn = user && user.username;
|
||||
const isTopContributor =
|
||||
user && user.yearsTopContributor && user.yearsTopContributor.length > 0;
|
||||
|
||||
const badgeColorClass = borderColorPicker(isUserDonating, isTopContributor);
|
||||
|
||||
if (pending && pathName !== '/') {
|
||||
return (
|
||||
<div className='nav-skeleton'>
|
||||
<SkeletonSprite />
|
||||
</div>
|
||||
);
|
||||
} else if (pathName === '/' || !isUserSignedIn) {
|
||||
return <Login data-test-label='landing-small-cta'>Sign In</Login>;
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<li>
|
||||
<Link className='nav-link' to='/learn'>
|
||||
Curriculum
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link className='nav-link' to={`/${user.username}`}>
|
||||
Profile
|
||||
</Link>
|
||||
<Link
|
||||
className={`avatar-nav-link ${badgeColorClass}`}
|
||||
to={`/${user.username}`}
|
||||
>
|
||||
<AvatarRenderer
|
||||
picture={user.picture}
|
||||
size='sm'
|
||||
userName={user.username}
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AuthOrProfile.propTypes = propTypes;
|
||||
AuthOrProfile.displayName = 'AuthOrProfile';
|
||||
export default AuthOrProfile;
|
@ -1,30 +1,68 @@
|
||||
import React from 'react';
|
||||
import { Link } from '../../helpers';
|
||||
import { forumLocation } from '../../../../../config/env.json';
|
||||
|
||||
import { Link, SkeletonSprite, AvatarRenderer } from '../../helpers';
|
||||
import PropTypes from 'prop-types';
|
||||
import Login from '../components/Login';
|
||||
|
||||
const propTypes = {
|
||||
displayMenu: PropTypes.bool
|
||||
displayMenu: PropTypes.bool,
|
||||
fetchState: PropTypes.shape({ pending: PropTypes.bool }),
|
||||
pathName: PropTypes.string.isRequired,
|
||||
user: PropTypes.object
|
||||
};
|
||||
|
||||
function NavLinks({ displayMenu }) {
|
||||
export function AuthOrProfile({ user, pathName, pending }) {
|
||||
const isUserDonating = user && user.isDonating;
|
||||
const isUserSignedIn = user && user.username;
|
||||
const isTopContributor =
|
||||
user && user.yearsTopContributor && user.yearsTopContributor.length > 0;
|
||||
|
||||
if (pending && pathName !== '/') {
|
||||
return (
|
||||
<div className='nav-skeleton'>
|
||||
<SkeletonSprite />
|
||||
</div>
|
||||
);
|
||||
} else if (pathName === '/' || !isUserSignedIn) {
|
||||
return (
|
||||
<>
|
||||
<li>
|
||||
<Link className='nav-link' to='/learn'>
|
||||
Curriculum
|
||||
</Link>
|
||||
</li>
|
||||
<Login data-test-label='landing-small-cta'>Sign In</Login>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<li>
|
||||
<Link className='nav-link' to='/learn'>
|
||||
Curriculum
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link className='nav-link' to={`/${user.username}`}>
|
||||
Profile
|
||||
<AvatarRenderer
|
||||
isDonating={isUserDonating}
|
||||
isTopContributor={isTopContributor}
|
||||
picture={user.picture}
|
||||
userName={user.username}
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function NavLinks({ displayMenu, pathName, user, fetchState }) {
|
||||
const { pending } = fetchState;
|
||||
return (
|
||||
<div className='main-nav-group'>
|
||||
<ul className={'nav-list' + (displayMenu ? ' display-flex' : '')}>
|
||||
<li className='nav-news'>
|
||||
<Link external={true} sameTab={true} to='/news'>
|
||||
/news
|
||||
</Link>
|
||||
</li>
|
||||
<li className='nav-forum'>
|
||||
<Link external={true} sameTab={true} to={forumLocation}>
|
||||
/forum
|
||||
</Link>
|
||||
</li>
|
||||
<li className='nav-projects'>
|
||||
<Link to='/learn'>/learn</Link>
|
||||
</li>
|
||||
<AuthOrProfile pathName={pathName} pending={pending} user={user} />
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
@ -12,7 +12,10 @@ export const UniversalNav = ({
|
||||
displayMenu,
|
||||
toggleDisplayMenu,
|
||||
menuButtonRef,
|
||||
searchBarRef
|
||||
searchBarRef,
|
||||
pathName,
|
||||
user,
|
||||
fetchState
|
||||
}) => (
|
||||
<nav
|
||||
className={'universal-nav nav-padding' + (displayMenu ? ' expand-nav' : '')}
|
||||
@ -30,7 +33,12 @@ export const UniversalNav = ({
|
||||
</Link>
|
||||
</div>
|
||||
<div className='universal-nav-right main-nav'>
|
||||
<NavLinks displayMenu={displayMenu} />
|
||||
<NavLinks
|
||||
displayMenu={displayMenu}
|
||||
fetchState={fetchState}
|
||||
pathName={pathName}
|
||||
user={user}
|
||||
/>
|
||||
</div>
|
||||
<MenuButton
|
||||
displayMenu={displayMenu}
|
||||
@ -45,7 +53,10 @@ export default UniversalNav;
|
||||
|
||||
UniversalNav.propTypes = {
|
||||
displayMenu: PropTypes.bool,
|
||||
fetchState: PropTypes.shape({ pending: PropTypes.bool }),
|
||||
menuButtonRef: PropTypes.object,
|
||||
pathName: PropTypes.string.isRequired,
|
||||
searchBarRef: PropTypes.object,
|
||||
toggleDisplayMenu: PropTypes.func
|
||||
toggleDisplayMenu: PropTypes.func,
|
||||
user: PropTypes.object
|
||||
};
|
||||
|
@ -68,10 +68,11 @@
|
||||
margin: 0 0 0 -12px;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-list {
|
||||
height: 38px;
|
||||
height: var(--header-height);
|
||||
}
|
||||
|
||||
.nav-list li {
|
||||
@ -80,37 +81,66 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nav-list li a {
|
||||
display: block;
|
||||
.nav-list li a.nav-link {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
padding: 6px 15px;
|
||||
padding: 0 15px;
|
||||
color: var(--gray-00);
|
||||
opacity: 1;
|
||||
white-space: nowrap;
|
||||
height: 38px;
|
||||
height: var(--header-height);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-list li a:hover {
|
||||
.nav-list li:last-child {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.nav-list li:last-child a {
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.nav-list li a.nav-link:hover {
|
||||
color: var(--theme-color);
|
||||
text-decoration: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.nav-list li a:focus {
|
||||
.nav-list li a.nav-link:focus {
|
||||
background: var(--theme-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-list li a:focus:hover {
|
||||
.nav-list li a.nav-link:focus:hover {
|
||||
color: var(--theme-color);
|
||||
background: white;
|
||||
}
|
||||
|
||||
.nav-list a.btn-cta {
|
||||
padding: 0 20px;
|
||||
height: 30px;
|
||||
margin-left: 19px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.nav-skeleton {
|
||||
height: var(--header-height);
|
||||
margin-right: 15px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.nav-list .fcc-loader {
|
||||
padding: 0 40px;
|
||||
margin-left: 35px;
|
||||
margin-right: 25px;
|
||||
}
|
||||
|
||||
.universal-nav-right {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 38px;
|
||||
height: var(--header-height);
|
||||
}
|
||||
|
||||
.toggle-button-nav {
|
||||
@ -133,8 +163,46 @@
|
||||
border: 1px solid var(--gray-00);
|
||||
}
|
||||
|
||||
.nav-list li .avatar-container {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin-left: 15px;
|
||||
opacity: 1;
|
||||
white-space: nowrap;
|
||||
background: transparent;
|
||||
height: calc(var(--header-height) - 8px);
|
||||
width: calc(var(--header-height) - 8px);
|
||||
}
|
||||
|
||||
.nav-list li .avatar-containersvg {
|
||||
display: inline-block;
|
||||
background: var(--secondary-background);
|
||||
}
|
||||
.nav-list .avatar-container svg,
|
||||
.nav-list .avatar-container img {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.nav-list .gold-border {
|
||||
border: 2px solid var(--yellow-gold);
|
||||
}
|
||||
|
||||
.nav-list .green-border {
|
||||
border: 2px solid var(--green-mid);
|
||||
}
|
||||
|
||||
.nav-list .purple-border {
|
||||
border: 2px solid var(--purple-mid);
|
||||
}
|
||||
|
||||
.nav-list .default-border {
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
@media (max-width: 300px) {
|
||||
.nav-list li a {
|
||||
.nav-list li a.nav-link {
|
||||
width: 50vw;
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -1,11 +1,18 @@
|
||||
import React from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import stripeObserver from './stripeIframesFix';
|
||||
import UniversalNav from './components/UniversalNav';
|
||||
|
||||
import './header.css';
|
||||
|
||||
const propTypes = {
|
||||
fetchState: PropTypes.shape({ pending: PropTypes.bool }),
|
||||
pathName: PropTypes.string.isRequired,
|
||||
user: PropTypes.object
|
||||
};
|
||||
|
||||
export class Header extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -47,6 +54,7 @@ export class Header extends React.Component {
|
||||
}
|
||||
render() {
|
||||
const { displayMenu } = this.state;
|
||||
const { fetchState, pathName, user } = this.props;
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
@ -55,9 +63,12 @@ export class Header extends React.Component {
|
||||
<header>
|
||||
<UniversalNav
|
||||
displayMenu={displayMenu}
|
||||
fetchState={fetchState}
|
||||
menuButtonRef={this.menuButtonRef}
|
||||
pathName={pathName}
|
||||
searchBarRef={this.searchBarRef}
|
||||
toggleDisplayMenu={this.toggleDisplayMenu}
|
||||
user={user}
|
||||
/>
|
||||
</header>
|
||||
</>
|
||||
@ -65,5 +76,7 @@ export class Header extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
Header.propTypes = propTypes;
|
||||
Header.displayName = 'Header';
|
||||
|
||||
export default Header;
|
||||
|
@ -21,7 +21,6 @@ const propTypes = {
|
||||
|
||||
function Intro({
|
||||
isSignedIn,
|
||||
username,
|
||||
name,
|
||||
pending,
|
||||
complete,
|
||||
@ -49,13 +48,7 @@ function Intro({
|
||||
<Spacer />
|
||||
</Col>
|
||||
</Row>
|
||||
<FullWidthRow className='button-group'>
|
||||
<Link
|
||||
className='btn btn-lg btn-primary btn-block'
|
||||
to={`/${username}`}
|
||||
>
|
||||
View my Portfolio
|
||||
</Link>
|
||||
<FullWidthRow>
|
||||
<Link className='btn btn-lg btn-primary btn-block' to='/settings'>
|
||||
Update my account settings
|
||||
</Link>
|
||||
|
44
client/src/components/helpers/AvatarRenderer.js
Normal file
44
client/src/components/helpers/AvatarRenderer.js
Normal file
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Image } from '@freecodecamp/react-bootstrap';
|
||||
import DefaultAvatar from '../../assets/icons/DefaultAvatar';
|
||||
import { defaultUserImage } from '../../../../config/misc';
|
||||
|
||||
const propTypes = {
|
||||
isDonating: PropTypes.bool,
|
||||
isTopContributor: PropTypes.bool,
|
||||
picture: PropTypes.any.isRequired,
|
||||
userName: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
function borderColorPicker(isDonating, isTopContributor) {
|
||||
if (isDonating && isTopContributor) return 'purple-border';
|
||||
else if (isTopContributor) return 'green-border';
|
||||
else if (isDonating) return 'gold-border';
|
||||
else return 'default-border';
|
||||
}
|
||||
|
||||
function AvatarRenderer({ picture, userName, isDonating, isTopContributor }) {
|
||||
let borderColor = borderColorPicker(isDonating, isTopContributor);
|
||||
let isPlaceHolderImage =
|
||||
/example.com|identicon.org/.test(picture) || picture === defaultUserImage;
|
||||
|
||||
return (
|
||||
<div className={`avatar-container ${borderColor}`}>
|
||||
{isPlaceHolderImage ? (
|
||||
<DefaultAvatar className='avatar default-avatar' />
|
||||
) : (
|
||||
<Image
|
||||
alt={userName + "'s avatar"}
|
||||
className='avatar'
|
||||
responsive={true}
|
||||
src={picture}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
AvatarRenderer.propTypes = propTypes;
|
||||
AvatarRenderer.displayName = 'AvatarRenderer';
|
||||
export default AvatarRenderer;
|
@ -9,9 +9,9 @@ function SkeletonSprite() {
|
||||
<svg className='sprite-svg'>
|
||||
<rect
|
||||
className='sprite'
|
||||
fill='#ccc'
|
||||
fill='var(--gray-75)'
|
||||
height='100%'
|
||||
stroke='#ccc'
|
||||
stroke='var(--gray-75)'
|
||||
width='2px'
|
||||
x='0'
|
||||
y='0'
|
||||
|
6
client/src/components/helpers/borderColorPicker.js
Normal file
6
client/src/components/helpers/borderColorPicker.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default function borderColorPicker(isDonating, isTopContributor) {
|
||||
if (isDonating && isTopContributor) return 'purple-border';
|
||||
else if (isTopContributor) return 'green-border';
|
||||
else if (isDonating) return 'gold-border';
|
||||
else return 'default-border';
|
||||
}
|
@ -7,3 +7,5 @@ export { default as Spacer } from './Spacer';
|
||||
export { default as Link } from './Link';
|
||||
export { default as CurrentChallengeLink } from './CurrentChallengeLink';
|
||||
export { default as ImageLoader } from './ImageLoader';
|
||||
export { default as AvatarRenderer } from './AvatarRenderer';
|
||||
export { default as borderColorPicker } from './borderColorPicker';
|
||||
|
@ -8,7 +8,7 @@ export default `
|
||||
.sprite-svg {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: #aaa;
|
||||
background: var(--theme-color);
|
||||
|
||||
}
|
||||
|
||||
@ -18,8 +18,15 @@ export default `
|
||||
transform: translateX(0%);
|
||||
stroke-width: 2px;
|
||||
}
|
||||
5%{
|
||||
opacity:100%;
|
||||
}
|
||||
35% {
|
||||
stroke-width: 30px;
|
||||
opacity:100%;
|
||||
}
|
||||
65%{
|
||||
opacity:100%;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateX(100%);
|
||||
@ -36,6 +43,10 @@ export default `
|
||||
}
|
||||
35% {
|
||||
stroke-width: 30px;
|
||||
opacity:100%;
|
||||
}
|
||||
65%{
|
||||
opacity:100%;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateX(100%);
|
||||
@ -45,8 +56,10 @@ export default `
|
||||
}
|
||||
|
||||
.sprite {
|
||||
opacity:0%;
|
||||
-webkit-animation-name: shimmer;
|
||||
animation-name: shimmer;
|
||||
animation-delay: 1s;
|
||||
width: 2px;
|
||||
-webkit-animation-duration: 2s;
|
||||
animation-duration: 2s;
|
||||
|
@ -11,7 +11,9 @@ import {
|
||||
isSignedInSelector,
|
||||
onlineStatusChange,
|
||||
isOnlineSelector,
|
||||
userFetchStateSelector,
|
||||
userSelector,
|
||||
usernameSelector,
|
||||
executeGA
|
||||
} from '../../redux';
|
||||
import { flashMessageSelector, removeFlashMessage } from '../Flash/redux';
|
||||
@ -69,6 +71,7 @@ const metaKeywords = [
|
||||
const propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
executeGA: PropTypes.func,
|
||||
fetchState: PropTypes.shape({ pending: PropTypes.bool }),
|
||||
fetchUser: PropTypes.func.isRequired,
|
||||
flashMessage: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
@ -82,21 +85,27 @@ const propTypes = {
|
||||
pathname: PropTypes.string.isRequired,
|
||||
removeFlashMessage: PropTypes.func.isRequired,
|
||||
showFooter: PropTypes.bool,
|
||||
signedInUserName: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
useTheme: PropTypes.bool
|
||||
useTheme: PropTypes.bool,
|
||||
user: PropTypes.object
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
isSignedInSelector,
|
||||
flashMessageSelector,
|
||||
isOnlineSelector,
|
||||
userFetchStateSelector,
|
||||
userSelector,
|
||||
(isSignedIn, flashMessage, isOnline, user) => ({
|
||||
usernameSelector,
|
||||
(isSignedIn, flashMessage, isOnline, fetchState, user) => ({
|
||||
isSignedIn,
|
||||
flashMessage,
|
||||
hasMessage: !!flashMessage.message,
|
||||
isOnline,
|
||||
theme: user.theme
|
||||
fetchState,
|
||||
theme: user.theme,
|
||||
user
|
||||
})
|
||||
);
|
||||
|
||||
@ -142,14 +151,18 @@ class DefaultLayout extends Component {
|
||||
const {
|
||||
children,
|
||||
hasMessage,
|
||||
fetchState,
|
||||
flashMessage,
|
||||
isOnline,
|
||||
isSignedIn,
|
||||
removeFlashMessage,
|
||||
showFooter = true,
|
||||
theme = 'default',
|
||||
useTheme = true
|
||||
user,
|
||||
useTheme = true,
|
||||
pathname
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet
|
||||
@ -213,7 +226,7 @@ class DefaultLayout extends Component {
|
||||
<style>{fontawesome.dom.css()}</style>
|
||||
</Helmet>
|
||||
<WithInstantSearch>
|
||||
<Header />
|
||||
<Header fetchState={fetchState} pathName={pathname} user={user} />
|
||||
<div className={`default-layout`}>
|
||||
<OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} />
|
||||
{hasMessage && flashMessage ? (
|
||||
|
@ -225,7 +225,7 @@ fieldset[disabled] .btn-primary.focus {
|
||||
.btn-cta:hover,
|
||||
.btn-cta:focus,
|
||||
.btn-cta:active:hover {
|
||||
background-color: #fecc4c;
|
||||
background-color: #fecc4c !important;
|
||||
border-width: 3px;
|
||||
border-color: #f1a02a;
|
||||
background-image: none;
|
||||
|
@ -17,6 +17,8 @@
|
||||
--blue-light: #99c9ff;
|
||||
--blue-dark: #002ead;
|
||||
--green-light: #acd157;
|
||||
--green-mid: green;
|
||||
--purple-mid: darkviolet;
|
||||
--green-dark: #00471b;
|
||||
--red-light: #ffadad;
|
||||
--red-dark: #850000;
|
||||
|
@ -21,7 +21,7 @@ exports[`<Profile/> renders correctly 1`] = `
|
||||
class="avatar-container col-xs-12"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
class="avatar-container default-border"
|
||||
>
|
||||
<img
|
||||
alt="string's avatar"
|
||||
|
@ -1,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Col, Row, Image } from '@freecodecamp/react-bootstrap';
|
||||
import { Col, Row } from '@freecodecamp/react-bootstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
faAward,
|
||||
faHeart,
|
||||
faCalendar
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import Identicon from 'react-identicons';
|
||||
|
||||
import { AvatarRenderer } from '../../helpers';
|
||||
|
||||
import SocialIcons from './SocialIcons';
|
||||
|
||||
@ -77,29 +78,16 @@ function Camper({
|
||||
twitter,
|
||||
website
|
||||
}) {
|
||||
// A lot of the user-profiles are still using the defunct service.
|
||||
const avatar = /example.com|identicon.org/.test(picture) ? (
|
||||
<Identicon
|
||||
bg={'#858591'}
|
||||
count={5}
|
||||
fg={'#0A0A23'}
|
||||
padding={5}
|
||||
size={256}
|
||||
string={username}
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
alt={username + "'s avatar"}
|
||||
className='avatar'
|
||||
responsive={true}
|
||||
src={picture}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<Row>
|
||||
<Col className='avatar-container' xs={12}>
|
||||
<div className={isDonating ? 'supporter-img' : ''}>{avatar}</div>
|
||||
<AvatarRenderer
|
||||
isDonating={isDonating}
|
||||
isTopContributor={yearsTopContributor.length > 0}
|
||||
picture={picture}
|
||||
userName={username}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<SocialIcons
|
||||
|
@ -1,5 +1,7 @@
|
||||
.avatar-container .avatar {
|
||||
height: 180px;
|
||||
width: 180px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
@ -14,6 +16,22 @@
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.supporter-img {
|
||||
.avatar-container div {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.avatar-container .gold-border {
|
||||
border: 10px solid var(--yellow-gold);
|
||||
}
|
||||
|
||||
.avatar-container .green-border {
|
||||
border: 10px solid var(--green-mid);
|
||||
}
|
||||
|
||||
.avatar-container .purple-border {
|
||||
border: 10px solid var(--purple-mid);
|
||||
}
|
||||
|
||||
.avatar-container .default-border {
|
||||
border: 10px solid transparent;
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ export const LearnPage = ({
|
||||
location: { hash = '', state = '' },
|
||||
isSignedIn,
|
||||
fetchState: { pending, complete },
|
||||
user: { name = '', username = '', completedChallengeCount = 0 },
|
||||
user: { name = '', completedChallengeCount = 0 },
|
||||
data: {
|
||||
challengeNode: {
|
||||
fields: { slug }
|
||||
@ -86,7 +86,6 @@ export const LearnPage = ({
|
||||
name={name}
|
||||
pending={pending}
|
||||
slug={slug}
|
||||
username={username}
|
||||
/>
|
||||
<Map
|
||||
hash={hashValue}
|
||||
|
@ -1 +1,2 @@
|
||||
exports.oldDataVizId = '561add10cb82ac38a17513b3';
|
||||
exports.defaultUserImage = 'https://freecodecamp.com/sample-image.png';
|
||||
|
@ -1,5 +1,14 @@
|
||||
/* global cy */
|
||||
|
||||
const selectors = {
|
||||
heading: "[data-test-label='landing-header']",
|
||||
smallCallToAction: "[data-test-label='landing-small-cta']",
|
||||
firstNavigationLink: '.nav-list .nav-link:first-child',
|
||||
lastNavigationLink: '.nav-list .nav-link:last-child',
|
||||
avatarContainer: '.avatar-container',
|
||||
defaultAvatar: '.avatar-container svg'
|
||||
};
|
||||
|
||||
describe('Navbar', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/');
|
||||
@ -10,32 +19,6 @@ describe('Navbar', () => {
|
||||
cy.get('#universal-nav').should('have.class', 'universal-nav nav-padding');
|
||||
});
|
||||
|
||||
it('Should take user to news page when clicked on `/news`', () => {
|
||||
cy.get('.nav-news').within(() => {
|
||||
cy.contains('/news').click();
|
||||
});
|
||||
cy.url().should('include', '/news');
|
||||
});
|
||||
|
||||
it('Should take user to forum page when clicked on `/forum`', () => {
|
||||
cy.get('.nav-forum').within(() => {
|
||||
// Can't click on it in test due to CORS policy
|
||||
// So check the link instead
|
||||
cy.contains('/forum').should(
|
||||
'have.attr',
|
||||
'href',
|
||||
'https://forum.freecodecamp.org'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should take user to learn page when clicked on `/learn`', () => {
|
||||
cy.get('.nav-projects').within(() => {
|
||||
cy.contains('/learn').click();
|
||||
});
|
||||
cy.url().should('include', '/learn');
|
||||
});
|
||||
|
||||
it(
|
||||
'Should take user to learn page when clicked on ' + 'the freeCodeCamp logo',
|
||||
() => {
|
||||
@ -53,7 +36,7 @@ describe('Navbar', () => {
|
||||
|
||||
cy.get('.ais-Hits-list')
|
||||
.children()
|
||||
.should('have.length', 1);
|
||||
.should('to.have.length.of.at.least', 1);
|
||||
|
||||
cy.get('.ais-SearchBox').within(() => {
|
||||
cy.get('input').clear();
|
||||
@ -61,4 +44,48 @@ describe('Navbar', () => {
|
||||
|
||||
cy.get('div.ais-Hits').should('not.be.visible');
|
||||
});
|
||||
|
||||
it('Should have a Sign In button', () => {
|
||||
cy.contains("[data-test-label='landing-small-cta']", 'Sign In');
|
||||
});
|
||||
|
||||
// have the curriculum and CTA on landing and /learn pages.
|
||||
it(
|
||||
'Should have `Curriculum` link on landing and learn pages' +
|
||||
'page when not signed in',
|
||||
() => {
|
||||
cy.get(selectors.firstNavigationLink)
|
||||
.contains('Curriculum')
|
||||
.click();
|
||||
cy.url().should('include', '/learn');
|
||||
cy.get(selectors.firstNavigationLink).contains('Curriculum');
|
||||
}
|
||||
);
|
||||
|
||||
it(
|
||||
'Should have `Sign In` link on landing and learn pages' +
|
||||
'page when not signed in',
|
||||
() => {
|
||||
cy.contains(selectors.smallCallToAction, 'Sign In');
|
||||
cy.get(selectors.firstNavigationLink)
|
||||
.contains('Curriculum')
|
||||
.click();
|
||||
cy.contains(selectors.smallCallToAction, 'Sign In');
|
||||
}
|
||||
);
|
||||
|
||||
it('Should have `Profile` link when user is signed in', () => {
|
||||
cy.login()
|
||||
.get(selectors.lastNavigationLink)
|
||||
.contains('Profile')
|
||||
.click();
|
||||
cy.url().should('include', '/developmentuser');
|
||||
});
|
||||
|
||||
it('Should have a profile image with class `default-border`', () => {
|
||||
cy.login()
|
||||
.get(selectors.avatarContainer)
|
||||
.should('have.class', 'default-border');
|
||||
cy.get(selectors.defaultAvatar).should('exist');
|
||||
});
|
||||
});
|
||||
|
@ -2,18 +2,7 @@
|
||||
|
||||
describe('The `Update my account settings` button works properly', function() {
|
||||
beforeEach(() => {
|
||||
cy.visit('/');
|
||||
|
||||
cy.contains("Get started (it's free)").click();
|
||||
});
|
||||
|
||||
it('Should get rendered', function() {
|
||||
cy.contains('View my Portfolio').should(
|
||||
'have.class',
|
||||
'btn btn-lg btn-primary btn-block'
|
||||
);
|
||||
|
||||
cy.contains('View my Portfolio').should('be.visible');
|
||||
cy.login();
|
||||
});
|
||||
|
||||
it('Should take user to their account settings when clicked', function() {
|
||||
|
@ -1,23 +0,0 @@
|
||||
/* global cy */
|
||||
|
||||
describe('The `View my Portfolio` button works properly', function() {
|
||||
beforeEach(() => {
|
||||
cy.visit('/');
|
||||
|
||||
cy.contains("Get started (it's free)").click();
|
||||
});
|
||||
|
||||
it('Button gets rendered', function() {
|
||||
cy.contains('View my Portfolio').should(
|
||||
'have.class',
|
||||
'btn btn-lg btn-primary btn-block'
|
||||
);
|
||||
|
||||
cy.contains('View my Portfolio').should('be.visible');
|
||||
});
|
||||
|
||||
it('Button takes user to their portfolio when clicked', function() {
|
||||
cy.contains('View my Portfolio').click();
|
||||
cy.url().should('include', '/developmentuser');
|
||||
});
|
||||
});
|
@ -1,3 +1,4 @@
|
||||
/* global cy Cypress*/
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
@ -31,3 +32,8 @@
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => {});
|
||||
|
||||
Cypress.Commands.add('login', () => {
|
||||
cy.visit('/');
|
||||
cy.contains("Get started (it's free)").click({ force: true });
|
||||
});
|
||||
|
@ -7,6 +7,8 @@ const debug = require('debug');
|
||||
const log = debug('fcc:tools:seedLocalAuthUser');
|
||||
const { MONGOHQ_URL } = process.env;
|
||||
|
||||
const defaulUserImage = require('../../../config/misc').defaulUserImage;
|
||||
|
||||
function handleError(err, client) {
|
||||
if (err) {
|
||||
console.error('Oh noes!! Error seeding local auth user.');
|
||||
@ -48,7 +50,7 @@ MongoClient.connect(MONGOHQ_URL, { useNewUrlParser: true }, function(
|
||||
about: '',
|
||||
name: 'Development User',
|
||||
location: '',
|
||||
picture: 'https://github.com/identicons/camperbot.png',
|
||||
picture: defaulUserImage,
|
||||
acceptedPrivacyTerms: true,
|
||||
sendQuincyEmail: false,
|
||||
currentChallengeId: '',
|
||||
|
Reference in New Issue
Block a user