Merge pull request #73 from Bouncey/fix/introsInMap
Add intro pages to map
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
b28b0d9d4f
commit
d7bddb46b0
29
packages/learn/src/__mocks__/intro-nodes.js
Normal file
29
packages/learn/src/__mocks__/intro-nodes.js
Normal file
@ -0,0 +1,29 @@
|
||||
export default [
|
||||
{
|
||||
fields: {
|
||||
slug: '/super-block-one/block-a'
|
||||
},
|
||||
frontmatter: {
|
||||
block: 'Block A',
|
||||
title: 'Introduction to Block A'
|
||||
}
|
||||
},
|
||||
{
|
||||
fields: {
|
||||
slug: '/super-block-one/block-b'
|
||||
},
|
||||
frontmatter: {
|
||||
block: 'Block B',
|
||||
title: 'Introduction to Block B'
|
||||
}
|
||||
},
|
||||
{
|
||||
fields: {
|
||||
slug: '/super-block-one/block-c'
|
||||
},
|
||||
frontmatter: {
|
||||
block: 'Block C',
|
||||
title: 'Introduction to Block C'
|
||||
}
|
||||
}
|
||||
];
|
@ -9,14 +9,28 @@ import './map.css';
|
||||
import { ChallengeNode } from '../../redux/propTypes';
|
||||
|
||||
const propTypes = {
|
||||
introNodes: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
fields: PropTypes.shape({ slug: PropTypes.string.isRequired }),
|
||||
frontmatter: PropTypes.shape({
|
||||
title: PropTypes.string.isRequired,
|
||||
block: PropTypes.string.isRequired
|
||||
})
|
||||
})
|
||||
),
|
||||
nodes: PropTypes.arrayOf(ChallengeNode)
|
||||
};
|
||||
|
||||
class ShowMap extends PureComponent {
|
||||
renderSuperBlocks(superBlocks) {
|
||||
const { nodes } = this.props;
|
||||
const { nodes, introNodes } = this.props;
|
||||
return superBlocks.map(superBlock => (
|
||||
<SuperBlock key={superBlock} nodes={nodes} superBlock={superBlock} />
|
||||
<SuperBlock
|
||||
introNodes={introNodes}
|
||||
key={superBlock}
|
||||
nodes={nodes}
|
||||
superBlock={superBlock}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,14 @@ import Adapter from 'enzyme-adapter-react-16';
|
||||
|
||||
import Map from './Map';
|
||||
import mockNodes from '../../__mocks__/map-nodes';
|
||||
import mockIntroNodes from '../../__mocks__/intro-nodes';
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
const renderer = new ShallowRenderer();
|
||||
|
||||
test('<Map /> snapshot', () => {
|
||||
const component = renderer.render(<Map nodes={mockNodes} />);
|
||||
const component = renderer.render(
|
||||
<Map introNodes={mockIntroNodes} nodes={mockNodes} />
|
||||
);
|
||||
expect(component).toMatchSnapshot('Map');
|
||||
});
|
||||
|
@ -6,6 +6,37 @@ exports[`<Map /> snapshot: Map 1`] = `
|
||||
>
|
||||
<ul>
|
||||
<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 {
|
||||
@ -121,6 +152,37 @@ exports[`<Map /> snapshot: Map 1`] = `
|
||||
superBlock="Super Block One"
|
||||
/>
|
||||
<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 {
|
||||
|
@ -24,6 +24,13 @@ const mapDispatchToProps = dispatch =>
|
||||
const propTypes = {
|
||||
blockDashedName: PropTypes.string,
|
||||
challenges: PropTypes.array,
|
||||
intro: PropTypes.shape({
|
||||
fields: PropTypes.shape({ slug: PropTypes.string.isRequired }),
|
||||
frontmatter: PropTypes.shape({
|
||||
title: PropTypes.string.isRequired,
|
||||
block: PropTypes.string.isRequired
|
||||
})
|
||||
}),
|
||||
isExpanded: PropTypes.bool,
|
||||
toggleBlock: PropTypes.func.isRequired,
|
||||
toggleMapModal: PropTypes.func.isRequired
|
||||
@ -62,22 +69,25 @@ export class Block extends PureComponent {
|
||||
};
|
||||
}
|
||||
|
||||
renderChallenges(challenges) {
|
||||
renderChallenges(intro, challenges) {
|
||||
// TODO: Split this into a Challenge Component and add tests
|
||||
return challenges.map(challenge => (
|
||||
<li className='map-challenge-title' key={challenge.dashedName}>
|
||||
return [intro].concat(challenges).map(challenge => (
|
||||
<li
|
||||
className='map-challenge-title'
|
||||
key={'map-challenge' + challenge.fields.slug}
|
||||
>
|
||||
<Link
|
||||
onClick={this.handleChallengeClick(challenge.fields.slug)}
|
||||
to={challenge.fields.slug}
|
||||
>
|
||||
{challenge.title}
|
||||
{challenge.title || challenge.frontmatter.title}
|
||||
</Link>
|
||||
</li>
|
||||
));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { challenges, isExpanded } = this.props;
|
||||
const { challenges, isExpanded, intro } = this.props;
|
||||
const { blockName } = challenges[0].fields;
|
||||
return (
|
||||
<li className={`block ${isExpanded ? 'open' : ''}`}>
|
||||
@ -85,7 +95,7 @@ export class Block extends PureComponent {
|
||||
<Caret />
|
||||
<h5>{blockName}</h5>
|
||||
</div>
|
||||
<ul>{isExpanded ? this.renderChallenges(challenges) : null}</ul>
|
||||
<ul>{isExpanded ? this.renderChallenges(intro, challenges) : null}</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import sinon from 'sinon';
|
||||
|
||||
import { Block } from './Block';
|
||||
import mockNodes from '../../../__mocks__/map-nodes';
|
||||
import mockIntroNodes from '../../../__mocks__/intro-nodes';
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
const renderer = new ShallowRenderer();
|
||||
@ -19,6 +20,7 @@ test('<Block /> not expanded snapshot', () => {
|
||||
<Block
|
||||
blockDashedName='block-a'
|
||||
challenges={mockNodes.filter(node => node.block === 'block-a')}
|
||||
intro={mockIntroNodes[0]}
|
||||
isExpanded={false}
|
||||
toggleBlock={toggleSpy}
|
||||
toggleMapModal={toggleMapSpy}
|
||||
@ -36,6 +38,7 @@ test('<Block expanded snapshot', () => {
|
||||
<Block
|
||||
blockDashedName='block-a'
|
||||
challenges={mockNodes.filter(node => node.block === 'block-a')}
|
||||
intro={mockIntroNodes[0]}
|
||||
isExpanded={true}
|
||||
toggleBlock={toggleSpy}
|
||||
toggleMapModal={toggleMapSpy}
|
||||
@ -53,6 +56,7 @@ test('<Block /> should handle toggle clicks correctly', () => {
|
||||
const props = {
|
||||
blockDashedName: 'block-a',
|
||||
challenges: mockNodes.filter(node => node.block === 'block-a'),
|
||||
intro: mockIntroNodes[0],
|
||||
isExpanded: false,
|
||||
toggleBlock: toggleSpy,
|
||||
toggleMapModal: toggleMapSpy
|
||||
|
@ -4,6 +4,7 @@ import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import uniq from 'lodash/uniq';
|
||||
import find from 'lodash/find';
|
||||
|
||||
import Block from './Block';
|
||||
|
||||
@ -29,6 +30,15 @@ function mapDispatchToProps(dispatch) {
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
introNodes: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
fields: PropTypes.shape({ slug: PropTypes.string.isRequired }),
|
||||
frontmatter: PropTypes.shape({
|
||||
title: PropTypes.string.isRequired,
|
||||
block: PropTypes.string.isRequired
|
||||
})
|
||||
})
|
||||
),
|
||||
isExpanded: PropTypes.bool,
|
||||
nodes: PropTypes.arrayOf(ChallengeNode),
|
||||
superBlock: PropTypes.string,
|
||||
@ -37,7 +47,7 @@ const propTypes = {
|
||||
|
||||
export class SuperBlock extends PureComponent {
|
||||
renderBlock(superBlock) {
|
||||
const { nodes } = this.props;
|
||||
const { nodes, introNodes } = this.props;
|
||||
const blocksForSuperBlock = nodes.filter(
|
||||
node => node.superBlock === superBlock
|
||||
);
|
||||
@ -53,6 +63,14 @@ export class SuperBlock extends PureComponent {
|
||||
challenges={blocksForSuperBlock.filter(
|
||||
node => node.block === blockDashedName
|
||||
)}
|
||||
intro={find(
|
||||
introNodes,
|
||||
({ frontmatter: { block } }) =>
|
||||
block
|
||||
.toLowerCase()
|
||||
.split(' ')
|
||||
.join('-') === blockDashedName
|
||||
)}
|
||||
key={blockDashedName}
|
||||
/>
|
||||
))}
|
||||
|
@ -8,6 +8,7 @@ import sinon from 'sinon';
|
||||
|
||||
import { SuperBlock } from './SuperBlock';
|
||||
import mockNodes from '../../../__mocks__/map-nodes';
|
||||
import mockIntroNodes from '../../../__mocks__/intro-nodes';
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
const renderer = new ShallowRenderer();
|
||||
@ -15,6 +16,7 @@ const renderer = new ShallowRenderer();
|
||||
test('<SuperBlock /> not expanded snapshot', () => {
|
||||
const toggleSpy = sinon.spy();
|
||||
const props = {
|
||||
introNodes: mockIntroNodes,
|
||||
isExpanded: false,
|
||||
nodes: mockNodes,
|
||||
superBlock: 'Super Block One',
|
||||
@ -29,6 +31,7 @@ test('<SuperBlock /> not expanded snapshot', () => {
|
||||
test('<SuperBlock /> expanded snapshot', () => {
|
||||
const toggleSpy = sinon.spy();
|
||||
const props = {
|
||||
introNodes: mockIntroNodes,
|
||||
isExpanded: true,
|
||||
nodes: mockNodes,
|
||||
superBlock: 'Super Block One',
|
||||
@ -43,6 +46,7 @@ test('<SuperBlock /> expanded snapshot', () => {
|
||||
test('<SuperBlock should handle toggle clicks correctly', () => {
|
||||
const toggleSpy = sinon.spy();
|
||||
const props = {
|
||||
introNodes: mockIntroNodes,
|
||||
isExpanded: false,
|
||||
nodes: mockNodes,
|
||||
superBlock: 'Super Block One',
|
||||
|
@ -31,6 +31,16 @@ exports[`<Block expanded snapshot: block-expanded 1`] = `
|
||||
</h5>
|
||||
</div>
|
||||
<ul>
|
||||
<li
|
||||
className="map-challenge-title"
|
||||
>
|
||||
<Unknown
|
||||
onClick={[Function]}
|
||||
to="/super-block-one/block-a"
|
||||
>
|
||||
Introduction to Block A
|
||||
</Unknown>
|
||||
</li>
|
||||
<li
|
||||
className="map-challenge-title"
|
||||
>
|
||||
|
@ -44,6 +44,17 @@ exports[`<SuperBlock /> expanded snapshot: superBlock-expanded 1`] = `
|
||||
},
|
||||
]
|
||||
}
|
||||
intro={
|
||||
Object {
|
||||
"fields": Object {
|
||||
"slug": "/super-block-one/block-a",
|
||||
},
|
||||
"frontmatter": Object {
|
||||
"block": "Block A",
|
||||
"title": "Introduction to Block A",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(Block)
|
||||
blockDashedName="block-b"
|
||||
@ -75,6 +86,17 @@ exports[`<SuperBlock /> expanded snapshot: superBlock-expanded 1`] = `
|
||||
},
|
||||
]
|
||||
}
|
||||
intro={
|
||||
Object {
|
||||
"fields": Object {
|
||||
"slug": "/super-block-one/block-b",
|
||||
},
|
||||
"frontmatter": Object {
|
||||
"block": "Block B",
|
||||
"title": "Introduction to Block B",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(Block)
|
||||
blockDashedName="block-c"
|
||||
@ -94,6 +116,17 @@ exports[`<SuperBlock /> expanded snapshot: superBlock-expanded 1`] = `
|
||||
},
|
||||
]
|
||||
}
|
||||
intro={
|
||||
Object {
|
||||
"fields": Object {
|
||||
"slug": "/super-block-one/block-c",
|
||||
},
|
||||
"frontmatter": Object {
|
||||
"block": "Block C",
|
||||
"title": "Introduction to Block C",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</ul>
|
||||
</li>
|
||||
|
@ -21,12 +21,21 @@ const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ toggleMapModal }, dispatch);
|
||||
|
||||
const propTypes = {
|
||||
introNodes: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
fields: PropTypes.shape({ slug: PropTypes.string.isRequired }),
|
||||
frontmatter: PropTypes.shape({
|
||||
title: PropTypes.string.isRequired,
|
||||
block: PropTypes.string.isRequired
|
||||
})
|
||||
})
|
||||
),
|
||||
nodes: PropTypes.arrayOf(ChallengeNode),
|
||||
show: PropTypes.bool,
|
||||
toggleMapModal: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
function MapModal({ nodes, show, toggleMapModal }) {
|
||||
function MapModal({ introNodes, nodes, show, toggleMapModal }) {
|
||||
return (
|
||||
<Modal
|
||||
bsSize='lg'
|
||||
@ -41,7 +50,7 @@ function MapModal({ nodes, show, toggleMapModal }) {
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Spacer />
|
||||
<Map nodes={nodes} />
|
||||
<Map introNodes={introNodes} nodes={nodes} />
|
||||
<Spacer />
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
|
@ -64,7 +64,13 @@ class Layout extends PureComponent {
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const { children, data: { allChallengeNode: { edges } } } = this.props;
|
||||
const {
|
||||
children,
|
||||
data: {
|
||||
allChallengeNode: { edges },
|
||||
allMarkdownRemark: { edges: mdEdges }
|
||||
}
|
||||
} = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet
|
||||
@ -83,6 +89,7 @@ class Layout extends PureComponent {
|
||||
<main>{children()}</main>
|
||||
</div>
|
||||
<MapModal
|
||||
introNodes={mdEdges.map(({ node }) => node)}
|
||||
nodes={edges
|
||||
.map(({ node }) => node)
|
||||
.filter(({ isPrivate }) => !isPrivate)}
|
||||
@ -120,5 +127,18 @@ export const query = graphql`
|
||||
}
|
||||
}
|
||||
}
|
||||
allMarkdownRemark(filter: { frontmatter: { block: { ne: null } } }) {
|
||||
edges {
|
||||
node {
|
||||
frontmatter {
|
||||
title
|
||||
block
|
||||
}
|
||||
fields {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -1,3 +1,7 @@
|
||||
.intro-layout {
|
||||
margin-top: 1.45rem;
|
||||
}
|
||||
|
||||
.intro-toc .list-group-item:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
Reference in New Issue
Block a user