Merge pull request #73 from Bouncey/fix/introsInMap

Add intro pages to map
This commit is contained in:
Stuart Taylor
2018-05-19 23:56:26 +01:00
committed by Mrugesh Mohapatra
parent b28b0d9d4f
commit d7bddb46b0
13 changed files with 233 additions and 13 deletions

View 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'
}
}
];

View File

@ -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}
/>
));
}

View File

@ -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');
});

View File

@ -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 {

View File

@ -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>
);
}

View File

@ -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

View File

@ -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}
/>
))}

View File

@ -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',

View File

@ -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"
>

View File

@ -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>

View File

@ -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>

View File

@ -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
}
}
}
}
}
`;

View File

@ -1,3 +1,7 @@
.intro-layout {
margin-top: 1.45rem;
}
.intro-toc .list-group-item:hover {
background-color: #eee;
}