feat: improve landing page (#36826)
* feat/ add smooth scroll from landing to map * feat: scroll to currentChallengeId if it exist * fix: update tests * refactor: migrate from componentWillMount * fix: update Map and Block's tests and mocks Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
committed by
mrugesh
parent
f9a112b43e
commit
203ca92a20
@@ -4,12 +4,13 @@ import React from 'react';
|
||||
import ShallowRenderer from 'react-test-renderer/shallow';
|
||||
import Enzyme, { shallow } from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
import store from 'store';
|
||||
|
||||
import { Map } from './';
|
||||
import mockChallengeNodes from '../../__mocks__/challenge-nodes';
|
||||
import mockIntroNodes from '../../__mocks__/intro-nodes';
|
||||
|
||||
import { dasherize } from '../../../../utils/slugs';
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
const renderer = new ShallowRenderer();
|
||||
|
||||
@@ -21,11 +22,15 @@ const baseProps = {
|
||||
resetExpansion: () => {}
|
||||
};
|
||||
|
||||
// set .scrollTo to avoid errors in default test environment
|
||||
window.scrollTo = jest.fn();
|
||||
|
||||
test('<Map /> snapshot', () => {
|
||||
const componentToRender = (
|
||||
<Map
|
||||
introNodes={mockIntroNodes}
|
||||
nodes={mockChallengeNodes}
|
||||
resetExpansion={() => {}}
|
||||
toggleBlock={() => {}}
|
||||
toggleSuperBlock={() => {}}
|
||||
/>
|
||||
@@ -36,42 +41,74 @@ test('<Map /> snapshot', () => {
|
||||
|
||||
describe('<Map/>', () => {
|
||||
describe('after reload', () => {
|
||||
let initializeSpy = null;
|
||||
beforeEach(() => {
|
||||
initializeSpy = jest.spyOn(Map.prototype, 'initializeExpandedState');
|
||||
});
|
||||
afterEach(() => {
|
||||
initializeSpy.mockRestore();
|
||||
store.clearAll();
|
||||
});
|
||||
// 7 was chosen because it has a different superblock from the first node.
|
||||
const currentChallengeId = mockChallengeNodes[7].id;
|
||||
const defaultNode = mockChallengeNodes[0];
|
||||
const idNode = mockChallengeNodes[7];
|
||||
const hashNode = mockChallengeNodes[9];
|
||||
const currentChallengeId = idNode.id;
|
||||
const hash = dasherize(hashNode.superBlock);
|
||||
|
||||
it('should expand the block with the most recent challenge', () => {
|
||||
const initializeSpy = jest.spyOn(
|
||||
Map.prototype,
|
||||
'initializeExpandedState'
|
||||
);
|
||||
|
||||
const blockSpy = jest.fn();
|
||||
const superSpy = jest.fn();
|
||||
const props = {
|
||||
...baseProps,
|
||||
toggleBlock: blockSpy,
|
||||
toggleSuperBlock: superSpy
|
||||
};
|
||||
const mapToRender = <Map {...props} />;
|
||||
shallow(mapToRender);
|
||||
expect(blockSpy).toHaveBeenCalledTimes(1);
|
||||
expect(superSpy).toHaveBeenCalledTimes(1);
|
||||
expect(initializeSpy).toHaveBeenCalledTimes(1);
|
||||
initializeSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should use the hash prop if it exists', () => {
|
||||
const blockSpy = jest.fn();
|
||||
const superSpy = jest.fn();
|
||||
const props = {
|
||||
...baseProps,
|
||||
hash,
|
||||
toggleBlock: blockSpy,
|
||||
toggleSuperBlock: superSpy,
|
||||
currentChallengeId
|
||||
};
|
||||
|
||||
const mapToRender = <Map {...props} />;
|
||||
shallow(mapToRender);
|
||||
|
||||
expect(blockSpy).toHaveBeenCalledTimes(1);
|
||||
// the block here should always be the first block of the superblock
|
||||
// this is tested implicitly, as there is a second block in the mock nodes
|
||||
expect(blockSpy).toHaveBeenCalledWith(hashNode.block);
|
||||
|
||||
expect(superSpy).toHaveBeenCalledTimes(1);
|
||||
expect(superSpy).toHaveBeenCalledWith(hashNode.superBlock);
|
||||
});
|
||||
|
||||
it('should use the currentChallengeId prop if there is no hash', () => {
|
||||
const blockSpy = jest.fn();
|
||||
const superSpy = jest.fn();
|
||||
const props = {
|
||||
...baseProps,
|
||||
toggleBlock: blockSpy,
|
||||
toggleSuperBlock: superSpy,
|
||||
currentChallengeId: currentChallengeId
|
||||
currentChallengeId
|
||||
};
|
||||
|
||||
const mapToRender = <Map {...props} />;
|
||||
shallow(mapToRender);
|
||||
|
||||
expect(blockSpy).toHaveBeenCalledTimes(1);
|
||||
expect(blockSpy).toHaveBeenCalledWith(mockChallengeNodes[7].block);
|
||||
expect(blockSpy).toHaveBeenCalledWith(idNode.block);
|
||||
|
||||
expect(superSpy).toHaveBeenCalledTimes(1);
|
||||
expect(superSpy).toHaveBeenCalledWith(mockChallengeNodes[7].superBlock);
|
||||
});
|
||||
|
||||
it('should use the currentChallengeId prop if it exists', () => {
|
||||
const props = { ...baseProps, currentChallengeId };
|
||||
const mapToRender = <Map {...props} />;
|
||||
shallow(mapToRender);
|
||||
|
||||
expect(initializeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(initializeSpy).toHaveBeenCalledWith(currentChallengeId);
|
||||
expect(superSpy).toHaveBeenCalledWith(idNode.superBlock);
|
||||
});
|
||||
|
||||
it('should default to the first challenge otherwise', () => {
|
||||
@@ -85,10 +122,10 @@ describe('<Map/>', () => {
|
||||
const mapToRender = <Map {...props} />;
|
||||
shallow(mapToRender);
|
||||
expect(blockSpy).toHaveBeenCalledTimes(1);
|
||||
expect(blockSpy).toHaveBeenCalledWith(mockChallengeNodes[0].block);
|
||||
expect(blockSpy).toHaveBeenCalledWith(defaultNode.block);
|
||||
|
||||
expect(superSpy).toHaveBeenCalledTimes(1);
|
||||
expect(superSpy).toHaveBeenCalledWith(mockChallengeNodes[0].superBlock);
|
||||
expect(superSpy).toHaveBeenCalledWith(defaultNode.superBlock);
|
||||
});
|
||||
|
||||
it('calls resetExpansion when initializing', () => {
|
||||
|
@@ -120,7 +120,7 @@ exports[`<Map /> snapshot: Map 1`] = `
|
||||
"dashedName": "challenge-one",
|
||||
"fields": Object {
|
||||
"blockName": "Block A",
|
||||
"slug": "/super-block-one/block-a/challenge-one",
|
||||
"slug": "/super-block-two/block-a/challenge-one",
|
||||
},
|
||||
"id": "f",
|
||||
"isPrivate": false,
|
||||
@@ -133,7 +133,7 @@ exports[`<Map /> snapshot: Map 1`] = `
|
||||
"dashedName": "challenge-two",
|
||||
"fields": Object {
|
||||
"blockName": "Block A",
|
||||
"slug": "/super-block-one/block-a/challenge-two",
|
||||
"slug": "/super-block-two/block-a/challenge-two",
|
||||
},
|
||||
"id": "g",
|
||||
"isPrivate": false,
|
||||
@@ -146,7 +146,7 @@ exports[`<Map /> snapshot: Map 1`] = `
|
||||
"dashedName": "challenge-one",
|
||||
"fields": Object {
|
||||
"blockName": "Block B",
|
||||
"slug": "/super-block-one/block-b/challenge-one",
|
||||
"slug": "/super-block-two/block-b/challenge-one",
|
||||
},
|
||||
"id": "h",
|
||||
"isPrivate": false,
|
||||
@@ -159,7 +159,7 @@ exports[`<Map /> snapshot: Map 1`] = `
|
||||
"dashedName": "challenge-two",
|
||||
"fields": Object {
|
||||
"blockName": "Block B",
|
||||
"slug": "/super-block-one/block-b/challenge-two",
|
||||
"slug": "/super-block-two/block-b/challenge-two",
|
||||
},
|
||||
"id": "i",
|
||||
"isPrivate": false,
|
||||
@@ -167,6 +167,32 @@ exports[`<Map /> snapshot: Map 1`] = `
|
||||
"superBlock": "Super Block Two",
|
||||
"title": "Challenge Two",
|
||||
},
|
||||
Object {
|
||||
"block": "block-a",
|
||||
"dashedName": "challenge-one",
|
||||
"fields": Object {
|
||||
"blockName": "Block A",
|
||||
"slug": "/super-block-three/block-a/challenge-one",
|
||||
},
|
||||
"id": "j",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block Three",
|
||||
"title": "Challenge One",
|
||||
},
|
||||
Object {
|
||||
"block": "block-c",
|
||||
"dashedName": "challenge-two",
|
||||
"fields": Object {
|
||||
"blockName": "Block C",
|
||||
"slug": "/super-block-three/block-c/challenge-two",
|
||||
},
|
||||
"id": "k",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block Three",
|
||||
"title": "Challenge Two",
|
||||
},
|
||||
]
|
||||
}
|
||||
superBlock="Super Block One"
|
||||
@@ -275,7 +301,7 @@ exports[`<Map /> snapshot: Map 1`] = `
|
||||
"dashedName": "challenge-one",
|
||||
"fields": Object {
|
||||
"blockName": "Block A",
|
||||
"slug": "/super-block-one/block-a/challenge-one",
|
||||
"slug": "/super-block-two/block-a/challenge-one",
|
||||
},
|
||||
"id": "f",
|
||||
"isPrivate": false,
|
||||
@@ -288,7 +314,7 @@ exports[`<Map /> snapshot: Map 1`] = `
|
||||
"dashedName": "challenge-two",
|
||||
"fields": Object {
|
||||
"blockName": "Block A",
|
||||
"slug": "/super-block-one/block-a/challenge-two",
|
||||
"slug": "/super-block-two/block-a/challenge-two",
|
||||
},
|
||||
"id": "g",
|
||||
"isPrivate": false,
|
||||
@@ -301,7 +327,7 @@ exports[`<Map /> snapshot: Map 1`] = `
|
||||
"dashedName": "challenge-one",
|
||||
"fields": Object {
|
||||
"blockName": "Block B",
|
||||
"slug": "/super-block-one/block-b/challenge-one",
|
||||
"slug": "/super-block-two/block-b/challenge-one",
|
||||
},
|
||||
"id": "h",
|
||||
"isPrivate": false,
|
||||
@@ -314,7 +340,7 @@ exports[`<Map /> snapshot: Map 1`] = `
|
||||
"dashedName": "challenge-two",
|
||||
"fields": Object {
|
||||
"blockName": "Block B",
|
||||
"slug": "/super-block-one/block-b/challenge-two",
|
||||
"slug": "/super-block-two/block-b/challenge-two",
|
||||
},
|
||||
"id": "i",
|
||||
"isPrivate": false,
|
||||
@@ -322,10 +348,217 @@ exports[`<Map /> snapshot: Map 1`] = `
|
||||
"superBlock": "Super Block Two",
|
||||
"title": "Challenge Two",
|
||||
},
|
||||
Object {
|
||||
"block": "block-a",
|
||||
"dashedName": "challenge-one",
|
||||
"fields": Object {
|
||||
"blockName": "Block A",
|
||||
"slug": "/super-block-three/block-a/challenge-one",
|
||||
},
|
||||
"id": "j",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block Three",
|
||||
"title": "Challenge One",
|
||||
},
|
||||
Object {
|
||||
"block": "block-c",
|
||||
"dashedName": "challenge-two",
|
||||
"fields": Object {
|
||||
"blockName": "Block C",
|
||||
"slug": "/super-block-three/block-c/challenge-two",
|
||||
},
|
||||
"id": "k",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block Three",
|
||||
"title": "Challenge Two",
|
||||
},
|
||||
]
|
||||
}
|
||||
superBlock="Super Block Two"
|
||||
/>
|
||||
<Connect(SuperBlock)
|
||||
introNodes={
|
||||
Array [
|
||||
Object {
|
||||
"fields": Object {
|
||||
"slug": "/super-block-one/block-a",
|
||||
},
|
||||
"frontmatter": Object {
|
||||
"block": "Block A",
|
||||
"title": "Introduction to Block A",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"fields": Object {
|
||||
"slug": "/super-block-one/block-b",
|
||||
},
|
||||
"frontmatter": Object {
|
||||
"block": "Block B",
|
||||
"title": "Introduction to Block B",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"fields": Object {
|
||||
"slug": "/super-block-one/block-c",
|
||||
},
|
||||
"frontmatter": Object {
|
||||
"block": "Block C",
|
||||
"title": "Introduction to Block C",
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
nodes={
|
||||
Array [
|
||||
Object {
|
||||
"block": "block-a",
|
||||
"dashedName": "challenge-one",
|
||||
"fields": Object {
|
||||
"blockName": "Block A",
|
||||
"slug": "/super-block-one/block-a/challenge-one",
|
||||
},
|
||||
"id": "a",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block One",
|
||||
"title": "Challenge One",
|
||||
},
|
||||
Object {
|
||||
"block": "block-a",
|
||||
"dashedName": "challenge-two",
|
||||
"fields": Object {
|
||||
"blockName": "Block A",
|
||||
"slug": "/super-block-one/block-a/challenge-two",
|
||||
},
|
||||
"id": "b",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block One",
|
||||
"title": "Challenge Two",
|
||||
},
|
||||
Object {
|
||||
"block": "block-b",
|
||||
"dashedName": "challenge-one",
|
||||
"fields": Object {
|
||||
"blockName": "Block B",
|
||||
"slug": "/super-block-one/block-b/challenge-one",
|
||||
},
|
||||
"id": "c",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block One",
|
||||
"title": "Challenge One",
|
||||
},
|
||||
Object {
|
||||
"block": "block-b",
|
||||
"dashedName": "challenge-two",
|
||||
"fields": Object {
|
||||
"blockName": "Block B",
|
||||
"slug": "/super-block-one/block-b/challenge-two",
|
||||
},
|
||||
"id": "d",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block One",
|
||||
"title": "Challenge Two",
|
||||
},
|
||||
Object {
|
||||
"block": "block-c",
|
||||
"dashedName": "challenge-one",
|
||||
"fields": Object {
|
||||
"blockName": "Block C",
|
||||
"slug": "/super-block-one/block-c/challenge-one",
|
||||
},
|
||||
"id": "e",
|
||||
"isPrivate": true,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block One",
|
||||
"title": "Challenge One",
|
||||
},
|
||||
Object {
|
||||
"block": "block-a",
|
||||
"dashedName": "challenge-one",
|
||||
"fields": Object {
|
||||
"blockName": "Block A",
|
||||
"slug": "/super-block-two/block-a/challenge-one",
|
||||
},
|
||||
"id": "f",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block Two",
|
||||
"title": "Challenge One",
|
||||
},
|
||||
Object {
|
||||
"block": "block-a",
|
||||
"dashedName": "challenge-two",
|
||||
"fields": Object {
|
||||
"blockName": "Block A",
|
||||
"slug": "/super-block-two/block-a/challenge-two",
|
||||
},
|
||||
"id": "g",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block Two",
|
||||
"title": "Challenge Two",
|
||||
},
|
||||
Object {
|
||||
"block": "block-b",
|
||||
"dashedName": "challenge-one",
|
||||
"fields": Object {
|
||||
"blockName": "Block B",
|
||||
"slug": "/super-block-two/block-b/challenge-one",
|
||||
},
|
||||
"id": "h",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block Two",
|
||||
"title": "Challenge One",
|
||||
},
|
||||
Object {
|
||||
"block": "block-b",
|
||||
"dashedName": "challenge-two",
|
||||
"fields": Object {
|
||||
"blockName": "Block B",
|
||||
"slug": "/super-block-two/block-b/challenge-two",
|
||||
},
|
||||
"id": "i",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block Two",
|
||||
"title": "Challenge Two",
|
||||
},
|
||||
Object {
|
||||
"block": "block-a",
|
||||
"dashedName": "challenge-one",
|
||||
"fields": Object {
|
||||
"blockName": "Block A",
|
||||
"slug": "/super-block-three/block-a/challenge-one",
|
||||
},
|
||||
"id": "j",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block Three",
|
||||
"title": "Challenge One",
|
||||
},
|
||||
Object {
|
||||
"block": "block-c",
|
||||
"dashedName": "challenge-two",
|
||||
"fields": Object {
|
||||
"blockName": "Block C",
|
||||
"slug": "/super-block-three/block-c/challenge-two",
|
||||
},
|
||||
"id": "k",
|
||||
"isPrivate": false,
|
||||
"isRequired": false,
|
||||
"superBlock": "Super Block Three",
|
||||
"title": "Challenge Two",
|
||||
},
|
||||
]
|
||||
}
|
||||
superBlock="Super Block Three"
|
||||
/>
|
||||
<Spacer />
|
||||
</ul>
|
||||
</div>
|
||||
|
@@ -13,6 +13,7 @@ import { blockNameify } from '../../../../utils/blockNameify';
|
||||
import GreenPass from '../../../assets/icons/GreenPass';
|
||||
import GreenNotCompleted from '../../../assets/icons/GreenNotCompleted';
|
||||
import IntroInformation from '../../../assets/icons/IntroInformation';
|
||||
import { dasherize } from '../../../../../utils/slugs';
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const expandedSelector = makeExpandedBlockSelector(ownProps.blockDashedName);
|
||||
@@ -92,6 +93,11 @@ export class Block extends Component {
|
||||
return (
|
||||
<li
|
||||
className={'map-challenge-title' + completedClass}
|
||||
id={
|
||||
challenge.title
|
||||
? dasherize(challenge.title)
|
||||
: dasherize(challenge.frontmatter.title)
|
||||
}
|
||||
key={'map-challenge' + challenge.fields.slug}
|
||||
>
|
||||
<span className='badge map-badge'>
|
||||
|
@@ -4,6 +4,7 @@ import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { uniq, find } from 'lodash';
|
||||
import { dasherize } from '../../../../../utils/slugs';
|
||||
|
||||
import Block from './Block';
|
||||
|
||||
@@ -88,7 +89,10 @@ export class SuperBlock extends Component {
|
||||
render() {
|
||||
const { superBlock, isExpanded, toggleSuperBlock } = this.props;
|
||||
return (
|
||||
<li className={`superblock ${isExpanded ? 'open' : ''}`}>
|
||||
<li
|
||||
className={`superblock ${isExpanded ? 'open' : ''}`}
|
||||
id={dasherize(superBlock)}
|
||||
>
|
||||
<button
|
||||
aria-expanded={isExpanded}
|
||||
className='map-title'
|
||||
|
@@ -28,7 +28,7 @@ exports[`<Block /> not expanded snapshot: block-not-expanded 1`] = `
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
2/4
|
||||
2/5
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
@@ -64,13 +64,14 @@ exports[`<Block expanded snapshot: block-expanded 1`] = `
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
2/4
|
||||
2/5
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
<ul>
|
||||
<li
|
||||
className="map-challenge-title"
|
||||
id="introduction-to-block-a"
|
||||
>
|
||||
<span
|
||||
className="badge map-badge"
|
||||
@@ -94,6 +95,7 @@ exports[`<Block expanded snapshot: block-expanded 1`] = `
|
||||
</li>
|
||||
<li
|
||||
className="map-challenge-title map-challenge-title-completed"
|
||||
id="challenge-one"
|
||||
>
|
||||
<span
|
||||
className="badge map-badge"
|
||||
@@ -117,6 +119,7 @@ exports[`<Block expanded snapshot: block-expanded 1`] = `
|
||||
</li>
|
||||
<li
|
||||
className="map-challenge-title"
|
||||
id="challenge-two"
|
||||
>
|
||||
<span
|
||||
className="badge map-badge"
|
||||
@@ -140,6 +143,7 @@ exports[`<Block expanded snapshot: block-expanded 1`] = `
|
||||
</li>
|
||||
<li
|
||||
className="map-challenge-title"
|
||||
id="challenge-one"
|
||||
>
|
||||
<span
|
||||
className="badge map-badge"
|
||||
@@ -156,13 +160,14 @@ exports[`<Block expanded snapshot: block-expanded 1`] = `
|
||||
</span>
|
||||
<mockConstructor
|
||||
onClick={[Function]}
|
||||
to="/super-block-one/block-a/challenge-one"
|
||||
to="/super-block-two/block-a/challenge-one"
|
||||
>
|
||||
Challenge One
|
||||
</mockConstructor>
|
||||
</li>
|
||||
<li
|
||||
className="map-challenge-title map-challenge-title-completed"
|
||||
id="challenge-two"
|
||||
>
|
||||
<span
|
||||
className="badge map-badge"
|
||||
@@ -179,11 +184,35 @@ exports[`<Block expanded snapshot: block-expanded 1`] = `
|
||||
</span>
|
||||
<mockConstructor
|
||||
onClick={[Function]}
|
||||
to="/super-block-one/block-a/challenge-two"
|
||||
to="/super-block-two/block-a/challenge-two"
|
||||
>
|
||||
Challenge Two
|
||||
</mockConstructor>
|
||||
</li>
|
||||
<li
|
||||
className="map-challenge-title"
|
||||
id="challenge-one"
|
||||
>
|
||||
<span
|
||||
className="badge map-badge"
|
||||
>
|
||||
<GreenNotCompleted
|
||||
style={
|
||||
Object {
|
||||
"height": "15px",
|
||||
"marginRight": "10px",
|
||||
"width": "15px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
<mockConstructor
|
||||
onClick={[Function]}
|
||||
to="/super-block-three/block-a/challenge-one"
|
||||
>
|
||||
Challenge One
|
||||
</mockConstructor>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
`;
|
||||
|
@@ -3,6 +3,7 @@
|
||||
exports[`<SuperBlock /> expanded snapshot: superBlock-expanded 1`] = `
|
||||
<li
|
||||
className="superblock open"
|
||||
id="super-block-one"
|
||||
>
|
||||
<button
|
||||
aria-expanded={true}
|
||||
@@ -141,6 +142,7 @@ exports[`<SuperBlock /> expanded snapshot: superBlock-expanded 1`] = `
|
||||
exports[`<SuperBlock /> not expanded snapshot: superBlock-not-expanded 1`] = `
|
||||
<li
|
||||
className="superblock "
|
||||
id="super-block-one"
|
||||
>
|
||||
<button
|
||||
aria-expanded={false}
|
||||
|
@@ -5,6 +5,7 @@ import { bindActionCreators } from 'redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import uniq from 'lodash/uniq';
|
||||
import { createSelector } from 'reselect';
|
||||
import { scroller } from 'react-scroll';
|
||||
|
||||
import SuperBlock from './components/SuperBlock';
|
||||
import Spacer from '../helpers/Spacer';
|
||||
@@ -13,9 +14,11 @@ import './map.css';
|
||||
import { ChallengeNode } from '../../redux/propTypes';
|
||||
import { toggleSuperBlock, toggleBlock, resetExpansion } from './redux';
|
||||
import { currentChallengeIdSelector } from '../../redux';
|
||||
import { dasherize } from '../../../../utils/slugs';
|
||||
|
||||
const propTypes = {
|
||||
currentChallengeId: PropTypes.string,
|
||||
hash: PropTypes.string,
|
||||
introNodes: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
fields: PropTypes.shape({ slug: PropTypes.string.isRequired }),
|
||||
@@ -52,17 +55,56 @@ function mapDispatchToProps(dispatch) {
|
||||
}
|
||||
|
||||
export class Map extends Component {
|
||||
componentDidMount() {
|
||||
this.initializeExpandedState(this.props.currentChallengeId);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { idToScrollto: null };
|
||||
this.initializeExpandedState();
|
||||
}
|
||||
|
||||
initializeExpandedState(currentChallengeId) {
|
||||
this.props.resetExpansion();
|
||||
const { superBlock, block } = currentChallengeId
|
||||
? this.props.nodes.find(node => node.id === currentChallengeId)
|
||||
: this.props.nodes[0];
|
||||
this.props.toggleBlock(block);
|
||||
this.props.toggleSuperBlock(superBlock);
|
||||
componentDidMount() {
|
||||
if (this.state.idToScrollto) {
|
||||
window.scrollTo(0, 0);
|
||||
scroller.scrollTo(this.state.idToScrollto, {
|
||||
duration: 1500,
|
||||
smooth: 'easeInOutQuint',
|
||||
offset: -35
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// As this happens in the constructor, it's necessary to manipulate state
|
||||
// directly.
|
||||
initializeExpandedState() {
|
||||
const {
|
||||
currentChallengeId,
|
||||
hash,
|
||||
nodes,
|
||||
resetExpansion,
|
||||
toggleBlock,
|
||||
toggleSuperBlock
|
||||
} = this.props;
|
||||
resetExpansion();
|
||||
let node;
|
||||
|
||||
// find the challenge that has the same superblock with hash
|
||||
if (hash) {
|
||||
node = nodes.find(node => dasherize(node.superBlock) === hash);
|
||||
// eslint-disable-next-line react/no-direct-mutation-state
|
||||
if (node) this.state = { idToScrollto: dasherize(node.superBlock) };
|
||||
}
|
||||
|
||||
// if there is no hash or the hash did not match any challenge superblock
|
||||
// and there was a currentChallengeId
|
||||
if (!node && currentChallengeId) {
|
||||
node = nodes.find(node => node.id === currentChallengeId);
|
||||
// eslint-disable-next-line react/no-direct-mutation-state
|
||||
if (node) this.state = { idToScrollto: dasherize(node.title) };
|
||||
}
|
||||
|
||||
if (!node) node = nodes[0];
|
||||
|
||||
toggleBlock(node.block);
|
||||
toggleSuperBlock(node.superBlock);
|
||||
}
|
||||
|
||||
renderSuperBlocks(superBlocks) {
|
||||
|
@@ -3,6 +3,7 @@ import React from 'react';
|
||||
import ShallowRenderer from 'react-test-renderer/shallow';
|
||||
|
||||
import { IndexPage } from '../../pages';
|
||||
import mockChallengeNodes from '../../__mocks__/challenge-nodes';
|
||||
|
||||
describe('<Landing />', () => {
|
||||
it('renders when visiting index page and logged out', () => {
|
||||
@@ -14,6 +15,7 @@ describe('<Landing />', () => {
|
||||
});
|
||||
|
||||
const loggedOutProps = {
|
||||
data: { allChallengeNode: { edges: mockChallengeNodes } },
|
||||
fetchState: {
|
||||
complete: true,
|
||||
error: null,
|
||||
|
@@ -1,28 +1,36 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Grid, Row, Col, Image } from '@freecodecamp/react-bootstrap';
|
||||
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
import Login from '../Header/components/Login';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'gatsby';
|
||||
import { uniq } from 'lodash';
|
||||
import { Spacer } from '../helpers';
|
||||
import Login from '../Header/components/Login';
|
||||
|
||||
import './landing.css';
|
||||
import '../Map/map.css';
|
||||
|
||||
const propTypes = {
|
||||
edges: PropTypes.array
|
||||
};
|
||||
|
||||
const BigCallToAction = () => (
|
||||
<Row>
|
||||
<Col sm={10} smOffset={1} xs={12}>
|
||||
<Login block={true}>Sign in and get started.</Login>
|
||||
<Login block={true}>Sign in and get started (it's free)</Login>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
||||
function Landing() {
|
||||
export const Landing = ({ edges }) => {
|
||||
const superBlocks = uniq(edges.map(element => element.node.superBlock));
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet>
|
||||
<title>Learn to code | freeCodeCamp.org</title>
|
||||
</Helmet>
|
||||
<main className='index-page'>
|
||||
<Spacer size={2} />
|
||||
<main className='landing-page'>
|
||||
<Spacer />
|
||||
<Grid>
|
||||
<Row>
|
||||
<Col sm={10} smOffset={1} xs={12}>
|
||||
@@ -31,33 +39,36 @@ function Landing() {
|
||||
</h1>
|
||||
<Spacer />
|
||||
<h2 className='medium-heading'>Learn to code.</h2>
|
||||
<h2 className='medium-heading'>Build projects.</h2>
|
||||
<h2 className='medium-heading'>Earn certifications.</h2>
|
||||
<h2 className='medium-heading'>
|
||||
Build projects and earn certifications.
|
||||
</h2>
|
||||
<h2 className='medium-heading'>
|
||||
Grow your portfolio and get a developer job.
|
||||
</h2>
|
||||
<h2 className='medium-heading'>
|
||||
It's all 100% free thanks to our nonprofit's donors.
|
||||
Since 2014, more than 40,000 freeCodeCamp.org graduates have
|
||||
gotten jobs at tech companies including:
|
||||
</h2>
|
||||
<div className='logo-row'>
|
||||
<h2 className='medium-heading'>Apple</h2>
|
||||
<h2 className='medium-heading'>Google</h2>
|
||||
<h2 className='medium-heading'>Amazon</h2>
|
||||
<h2 className='medium-heading'>Microsoft</h2>
|
||||
<h2 className='medium-heading'>Spotify</h2>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Spacer />
|
||||
<BigCallToAction />
|
||||
<Spacer size={2} />
|
||||
<Image
|
||||
alt='companies featuring freeCodeCamp.org'
|
||||
className='img-center'
|
||||
responsive={true}
|
||||
src='https://cdn-media-1.freecodecamp.org/learn/as-seen-on.png'
|
||||
/>
|
||||
<Spacer />
|
||||
<Row>
|
||||
<Col sm={10} smOffset={1} xs={12}>
|
||||
<h2 className='medium-heading'>
|
||||
Since 2014, more than 40,000 freeCodeCamp.org graduates have
|
||||
gotten jobs in tech.
|
||||
</h2>
|
||||
<h2 className='medium-heading'>Certifications:</h2>
|
||||
<ul>
|
||||
{superBlocks.map((superBlock, i) => (
|
||||
<li className={'superblock'} key={i}>
|
||||
<Link state={{ superBlock: superBlock }} to={`/learn`}>
|
||||
<h2 className='medium-heading'>{superBlock}</h2>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Col>
|
||||
</Row>
|
||||
<Spacer />
|
||||
@@ -65,8 +76,8 @@ function Landing() {
|
||||
</main>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Landing.displayName = 'Landing';
|
||||
|
||||
Landing.propTypes = propTypes;
|
||||
export default Landing;
|
||||
|
@@ -1,47 +1,7 @@
|
||||
.black-text {
|
||||
color: var(--secondary-color);
|
||||
font-weight: 400;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.large-p {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.img-center {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.landing-icon {
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.landing-skill-icon {
|
||||
color: var(--secondary-color);
|
||||
margin-top: -15px;
|
||||
padding-bottom: 15px;
|
||||
height: 150px;
|
||||
margin-bottom: 1.45rem;
|
||||
}
|
||||
|
||||
.testimonial-image {
|
||||
border-radius: 5px;
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.testimonial-copy {
|
||||
text-align: center;
|
||||
font-size: 18px !important;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.underlined-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Buttons with a lot of text can overflow and mess up formatting on small
|
||||
screens, this stops that unless the word itself is too large. */
|
||||
|
||||
@@ -49,13 +9,20 @@
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
@media (min-width: 991px) and (max-width: 1199px) {
|
||||
.testimonial-copy {
|
||||
height: 150px;
|
||||
}
|
||||
.logo-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
align-content: center;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.testimonial-copy {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.logo-row h2 {
|
||||
height: 35px;
|
||||
padding: 0 10px 0 10px;
|
||||
}
|
||||
|
||||
.landing-page ul {
|
||||
list-style: none;
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
@@ -46,6 +46,7 @@ const loggedInProps = {
|
||||
user: {
|
||||
name: 'Development User'
|
||||
},
|
||||
location: { hash: '' },
|
||||
data: {
|
||||
challengeNode: {
|
||||
fields: {
|
||||
|
Reference in New Issue
Block a user