Skip to content

Instantly share code, notes, and snippets.

@dschinkel
Last active May 3, 2017 19:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dschinkel/94db9bd80a556ff4d0ed7e1385cfe02a to your computer and use it in GitHub Desktop.
Save dschinkel/94db9bd80a556ff4d0ed7e1385cfe02a to your computer and use it in GitHub Desktop.
A few examples of React components from WeDoTDD.com
// a "dumb/presentational" React Component
import CompanyHeader from './CompanyHeader';
import CompanyProfile from './CompanyProfile';
import InterviewContentMain from './InterviewContentMain';
import Main from '../Main';
import MainLayout from '../MainLayout';
import React, { Component } from 'react';
export default class Interview extends Component{
render(){
const company = this.props.companies;
return (
<Main>
<MainLayout title={title}>
<div>
<div id='ft-interview'>
<div className="panel vertical-space">
<CompanyHeader company={company}/>
<CompanyProfile company={company}/>
<InterviewContentMain company={company}/>
</div>
</div>
</div>
</MainLayout>
</Main>
)
}
}
@dschinkel
Copy link
Author

dschinkel commented May 3, 2017

this is a very top level component that renders each major part of the TDD Company Interviews that you see on WeDoTDD.com.

import Interviewees from './Interviewees';
import TeamProfile from './TeamProfile';
import TeamCulture from './TeamCulture';
import DevelopmentMethodology from './DevelopmentMethodology';
import DevelopmentWorkflow from './DevelopmentWorkflow';
import QA from './QA';
import TestTypes from './TestTypes';
import TDDPractice from './TDDPractice';
import TDDRefactoring from './TDDRefactoring';
import TDDBenefits from './TDDBenefits';
import TDDChallenges from './TDDChallenges';
import ContinuousIntegration from './ContinuousIntegration';
import Hiring from './Hiring';
import Apprenticeships from './Apprenticeships';
import Learning from './Learning';
import RelatedLinks from './RelatedLinks';
import React, { Component } from 'react';


export default class InterviewContent extends Component {
    render(){
        const {company} = this.props;
        return (
            <div className='ft-interview-content'>
                <Interviewees interviewees={company.interview.interviewees}
                              companyName={company.name}
                              linkedInThumb={company.images.social.linkedin}
                              twitterThumb={company.images.social.twitter}
                />
                <TeamProfile company={company} />
                <TeamCulture company={company}/>
                <DevelopmentMethodology company={company}/>
                <DevelopmentWorkflow company={company}/>
                <QA company={company}/>
                <TestTypes company={company}/>
                <TDDPractice company={company}/>
                <TDDRefactoring company={company}/>
                <TDDBenefits company={company}/>
                <TDDChallenges company={company}/>
                <ContinuousIntegration company={company}/>
                <Apprenticeships company={company}/>
                <Learning company={company}/>
                <Hiring company={company}/>
                <RelatedLinks company={company}/>
            </div>
        )
    }
}

@dschinkel
Copy link
Author

dschinkel commented May 3, 2017

this needs to be cleaned up more but...anyway:

import React, { Component } from 'react';
const ReactDOM = require('react-dom'),
    Scroll  = require('react-scroll'),
    Link = Scroll.Link;

export default class TableOfContents extends Component {

    constructor() {
        super();
        this.state = {rendered: false}
    }

    componentDidMount() {
        this.setState({rendered: true})
        setTimeout(() => {
            const el = ReactDOM.findDOMNode(this);
            if(el && document.getElementById("interviewHeading")) {
                new Ink.UI.Sticky(el, {topElement: "#interviewHeading", bottomElement: "#footer", });
            }
        }, 200);
    }

    render(){
        if (!this.state.rendered) {
            return null;
        }

        const teamProfile = document.getElementById("ft-team-profile"),
            challenges = document.getElementById("ft-tdd-challenges"),
            culture = document.getElementById("ft-team-culture"),
            methodology = document.getElementById("ft-dev-methodology"),
            workflow = document.getElementById("ft-dev-workflow"),
            qa = document.getElementById("ft-qa"),
            testTypes = document.getElementById("ft-test-types"),
            practice = document.getElementById("ft-tdd-practice"),
            benefits = document.getElementById("ft-tdd-benefits"),
            ci = document.getElementById("ft-continuous-integration"),
            apprenticeships = document.getElementById("ft-apprenticeships"),
            learning = document.getElementById("ft-learning"),
            hiring = document.getElementById("ft-hiring"),
            refactoring = document.getElementById("ft-tdd-refactoring"),
            relatedLinks = document.getElementById("ft-relatedLinks");

        return (
            <div className="ft-table-of-contents margin-top-20">
                <p className="margin-8">
                    <span className="blue medium">
                        <a href="/" id="1001">HOME</a>
                    </span>
                </p>
                <p className="margin-8">
                    <span className="medium">
                        <Link activeClass="focused" id="1002" to='ft-company-profile' spy={true} smooth={true} duration={500}>Company Profile</Link>
                    </span>
                </p>
                <p className="margin-8">
                    <span className="medium">
                        <Link activeClass="focused" id="1003" to='interviewee' spy={true} smooth={true} duration={500}>Interviewee</Link>
                    </span>
                </p>
                { teamProfile &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" id="1004" to='ft-team-profile' spy={true} smooth={true}
                                  duration={500}>Team Profile</Link>
                        </span>
                    </p>
                }
                { culture &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" id="1005" to='ft-team-culture' spy={true} smooth={true}
                                  duration={500}>Culture, Principles, Practices</Link>
                       </span>
                    </p>
                }
                { methodology &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" id="1006" to='ft-dev-methodology' spy={true} smooth={true}
                                  duration={500}>Development Methodology</Link>
                        </span>
                    </p>
                }
                { workflow &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" id="1007" to='ft-dev-workflow' spy={true} smooth={true}
                                  duration={500}>Development Workflow</Link>
                        </span>
                    </p>
                }
                { qa &&
                    <p className="margin-8">
                        <span className="medium">
                             <Link activeClass="focused" id="1008" to='ft-qa' spy={true} smooth={true} duration={500}>QA</Link>
                        </span>
                    </p>
                }
                { testTypes &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" id="1009" to='ft-test-types' spy={true} smooth={true}
                                  duration={500}>Types of Tests</Link>
                        </span>
                    </p>
                }
                { practice &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" id="1010" to='ft-tdd-practice' spy={true} smooth={true}
                                  duration={500}>Practice & Style</Link>
                        </span>
                    </p>
                }
                { refactoring &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" id="1010" to='ft-tdd-refactoring' spy={true} smooth={true}
                                  duration={500}>Refactoring</Link>
                        </span>
                    </p>
                }
                { benefits &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" id="1012" to='ft-tdd-benefits' spy={true} smooth={true}
                                  duration={500}>Benefits</Link>
                        </span>
                    </p>
                }
                { challenges &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" to='ft-tdd-challenges' spy={true} smooth={true} duration={500}>Challenges</Link>
                        </span>
                    </p>
                }
                { ci &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" id="1013" to='ft-continuous-integration' spy={true} smooth={true}
                                  duration={500}>Continuous Integration</Link>
                        </span>
                    </p>
                }
                { apprenticeships &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" id="1014" to='ft-apprenticeships' spy={true} smooth={true}  duration={500}>Apprenticeships / Onboarding</Link>
                        </span>
                    </p>
                }
                { learning &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" id="1015" to='ft-learning' spy={true} smooth={true} duration={500}>Learning</Link>
                        </span>
                    </p>
                }
                { hiring &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" id="1016" to='ft-hiring' spy={true} smooth={true} duration={500}>Hiring</Link>
                        </span>
                    </p>
                }
                { relatedLinks &&
                    <p className="margin-8">
                        <span className="medium">
                            <Link activeClass="focused" id="1017" to='relatedLinks' spy={true} smooth={true} duration={500}>Related Links</Link>
                        </span>
                    </p>
                }
                <p className="margin-top-40 margin-left-10"><a href="https://www.patreon.com/WeDoTDD" id="9000" target="_blank"><img src="/lib/assets/social/patreon-small.jpg" alt="logo" /></a></p>
            </div>
        )
    }
}

@dschinkel
Copy link
Author

dschinkel commented May 3, 2017

Example Integration Test. This test is not a unit test because it's testing multiple levels in the App tree.

'use strict';

import chai from 'chai';
import chaiEnzyme from 'chai-enzyme';
chai.use(chaiEnzyme());
var expect = require('chai').expect
require('../../jsdom');
import {shallow, mount} from 'enzyme';
import { ListGroup, LinkList } from "../../../client/Company/CompanyList";

describe('Homepage - Link Sets & Groups', () => {

    it("renders the correct number of Link Sets", () => {
        const country = {
                id: 1,
                name: "United States"
            },
            companies = [
                {id: 1, name: "Company A", active: true, locations: [{primary: true, country: country}]},
                {id: 2, name: "Company B", active: true, locations: [{primary: true, country: country}]},
                {id: 3, name: "Company C", active: true, locations: [{primary: true, country: country}]},
                {id: 4, name: "Company D", active: true, locations: [{primary: true, country: country}]}
            ]
        const group = mount(<ListGroup companies={companies} countries={[country]}/>),
            unitedStatesLinkSets = group.find(".ft-link-set-usa"),
            nonUnitedStatesLinkSets = group.find(".ft-link-set");

        expect(unitedStatesLinkSets.length).to.equal(2);
        expect(nonUnitedStatesLinkSets.length).to.equal(0);
    });

    it("lists headers for each country list", () => {
        const companies = [],
            countries = ["France",
                        "Germany",
                        "United Kingdom",
                        "Canada",
                        "Poland",
                        "Spain"].map((id, name) => ({id: id++, name: name}));


        for(let [index, country] of countries.entries()) {
            const company = {
                id: index + 1,
                name: "Test Company for - " + country.name,
                active: true,
                locations: [{
                    primary: true,
                    country: country
                }]
            };

            companies.push(company);
        }

        const header = shallow(<LinkList {...companies} {...countries}/>).find('.ft-company-header');

        expect(header.length).to.equal(6);

        for(let [index, header] of header.entries()){
            var headerText = header[index].find("span").text();

            expect(headerText).to.equal(countries[index].name);
        }
    });

});

@dschinkel
Copy link
Author

dschinkel commented May 3, 2017

example isolated React "dumb" component unit test

'use strict';

import React from 'react';
import chai from 'chai';
import chaiEnzyme from 'chai-enzyme';
chai.use(chaiEnzyme());
var expect = require('chai').expect
require('../../../jsdom');
import {mount} from 'enzyme';
import { LinkItem,  LinkSet } from "../../../../client/Company/CompanyList";

describe('Company - Link Set', () => {

    it('renders a link set wrapped with a company header for US companies', () => {
        const links = [],
            country = {
                id: 1,
                name: "United States"
            },
            companies = [{id: 1, name: "Company A", active: true, locations: [{primary: true, country: country}]},
                {id: 2, name: "Company B", active: true, locations: [{primary: true, country: country}]},
                {id: 3, name: "Company C", active: true, locations: [{primary: true, country: country}]},
                {id: 4, name: "Company D", active: true, locations: [{primary: true, country: country}]}];

            for(let company of companies){
                links.push(<LinkItem company={company} key={company.id}/>);
            };

            const   linkSet = mount(<LinkSet country={country} links={links} />).find('.ft-link-set-usa'),
                    header = linkSet.find('.ft-company-header-usa');

        expect(linkSet).to.exist;
        expect(header).to.not.exist;
    });
});

@dschinkel
Copy link
Author

dschinkel commented May 3, 2017

I'm in the middle of some refactoring (ripping out a few things locally at the moment) but here's a view of isolated unit tests being run. Since these tests are truly isolated (not coupled to implementation details, only the right tests break for a given bug...that's important, because brittle tests can cause much pain).

screen shot 2017-05-03 at 1 47 46 pm

@dschinkel
Copy link
Author

dschinkel commented May 3, 2017

example integration test making a real API call to my new backend Node REST API:

import React from 'react';
import chai from 'chai';
import chaiEnzyme from 'chai-enzyme';
chai.use(chaiEnzyme());
require('../../jsdom');
import { mount } from 'enzyme';
const expect = require('chai').expect;
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { Provider } from 'react-redux';

import rootReducer from '../../../client/reducers/root';
import FeaturedCompaniesContainer from '../../../client/company/containers/FeaturedCompaniesContainer';

describe('Feature - FeaturedCompanies - Integration', () => {
    let Container;

    before(() => {
        Container = <Provider store={createStore(rootReducer, applyMiddleware(thunk))}>
                        <FeaturedCompaniesContainer />
                    </Provider>
    });

    it('returns featured companies', async () => {
        const container = await new Promise(resolve => resolve(mount(Container))),
        groups = container.find('.ft-featured-company-groups');

        expect(groups.length).to.be.greaterThan(1);
    });
})

@dschinkel
Copy link
Author

React-redux example Connected Container

/*flow*/
import React, { Component } from 'react';
import { connect } from 'react-redux';
import FeaturedCompanies from '../FeaturedCompanies';
import { fetchNewCompanies } from '../../actions/companies';


class FeaturedCompaniesContainer extends Component {
    componentDidMount(){
        if(!this.props.companies){
            fetchNewCompanies();
        }
    }
    render(){
        return(<FeaturedCompanies />)
    }
}

export const mapStateToProps = (state) => ({
    companies: state.featuredCompanies
});
        
export default connect(mapStateToProps)(FeaturedCompaniesContainer);

@dschinkel
Copy link
Author

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <link rel="alternate" type="application/rss+xml" title="We Do TDD - Companies & Teams Practicing Test Driven Development" href="http://feeds.feedburner.com/WeDoTdd-AListOfCompaniesPracticingTestDrivenDevelopment"/>
        <link rel="stylesheet" type="text/css" href="http://fastly.ink.sapo.pt/3.1.10/css/ink.min.css">
        <script type="text/javascript" src="http://fastly.ink.sapo.pt/3.1.10/js/ink-all.min.js"></script>
        <!--[if lt IE 9 ]>
        <link rel="stylesheet" href="ink-ie.min.css" type="text/css">
        <![endif]-->
        <link rel="stylesheet" type="text/css" href="/lib/css/master.css" />
    </head>
    <body>
        <div id="app"></div>
        <script type="text/javascript" src="/scripts/app.bundle.js"></script>
    </body>
</html>

@dschinkel
Copy link
Author

RelatedLinks Component (the Related Links section you see in an interview):

import React, { Component } from 'react';

export default class RelatedLinks extends Component {
    render(){
        const {company} = this.props
        return (
            <div className="all-100 padding-bottom-200">
                { company.interview.relatedLinks &&
                    <div id="ft-relatedLinks">
                        <p id="relatedLinks" className="section-heading bold padding-top-20 font-22">Related Links</p>
                        <div className="all-100 padding-left-30 align-left">
                            <div className="all-100">
                                <Links links={company.interview.relatedLinks}/>
                            </div>
                        </div>
                    </div>
                }
            </div>
        )
    }
}

export class Links extends Component {
    render(){
        const {links} = this.props;
        let formattedLink,
            formattedLinkGroup,
            formattedLinks = [],
            formattedLinkGroups = []

        for (let [i, link] of links.entries()) {
            formattedLink = <li key={i}><a href={link.href} target="_blank"><span className="black small">{link.text}</span></a></li>;
            formattedLinkGroup = <div key={i} className="all-40"><ul className="noStyle line-height-1 margin-0">{formattedLinks}</ul></div>;

            formattedLinks.push(formattedLink);

            if ((++i) % 12 === 0) {
                formattedLinkGroups.push(formattedLinkGroup);
                formattedLinks = [];
            }
        }

        if(formattedLinkGroups.length === 0){
            formattedLinkGroups.push(formattedLinkGroup);
        }

        return(<div className="ft-links column-group">{formattedLinkGroups}</div>)
    }
}

@dschinkel
Copy link
Author

using NGINX

{
  "root": "dist/client/",
  "routes": {
    "/**": "index.html"
  },
  "proxies": {
    "/api/": {
      "origin": "https://wedotdd-service.herokuapp.com"
    }
  }
}

@dschinkel
Copy link
Author

MainLayout component

const DocumentTitle = require('react-document-title');
import React, {Component} from 'react';
import Header from './Header';
import TopNav from './TopNav';


export default class MainLayout extends Component{
    render() {
        return (
            <div className="wrap">
                <div className="ink-grid">
                    <Header />
                    <DocumentTitle title={this.props.title} />
                    <div className='ft-homepage'>
                        <TopNav />
                        {this.props.children}
                    </div>
                </div>
            </div>
        )
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment