diff --git a/solutions/testing-react-component/Button/Exercise1.test.js b/solutions/testing-react-component/Button/Exercise1.test.js new file mode 100644 index 0000000..5b4308b --- /dev/null +++ b/solutions/testing-react-component/Button/Exercise1.test.js @@ -0,0 +1,57 @@ +import React from "react"; +import { shallow } from "enzyme"; +import renderer from "react-test-renderer"; +import Button from "."; + +// Exasice #1 +describe("Rendering", () => { + // A sample test. + it("Should render a button", () => { + const wrapper = shallow(); + expect(wrapper.find('button').length).toEqual(1); + }); + + it("Should render defaults", () => { + const tree = renderer.create().toJSON(); + expect(tree).toMatchSnapshot(); + }); + + describe("Render based on size", () => { + it("Small Button", () => { + const wrapper = shallow(); + expect(wrapper.find('.btn-sm').length).toEqual(1); + }); + + it("Medium Button", () => { + const wrapper = shallow(); + expect(wrapper.find('.btn-md').length).toEqual(1); + }); + + it("Large Button", () => { + const wrapper = shallow(); + expect(wrapper.find('.btn-lg').length).toEqual(1); + }); + }); + + describe("Button disabled based on props", () => { + it("Button should have disabled attribute", () => { + const wrapper = shallow(); + expect(wrapper.find('button').prop('disabled')).toBeTruthy(); + }); + + it("Button should not have disabled attribute", () => { + const wrapper = shallow(); + expect(wrapper.find('button').prop('disabled')).toBeFalsy(); + }); + + it("Button should be disabled class", () => { + const wrapper = shallow(); + expect(wrapper.find('.disabled').length).toEqual(1); + }); + + it("Button should be disabled class", () => { + const wrapper = shallow(); + expect(wrapper.find('.disabled').length).toEqual(0); + }); + }); +}); diff --git a/solutions/testing-react-component/Button/Exercise2.test.js b/solutions/testing-react-component/Button/Exercise2.test.js new file mode 100644 index 0000000..003ab2b --- /dev/null +++ b/solutions/testing-react-component/Button/Exercise2.test.js @@ -0,0 +1,13 @@ +import React from "react"; +import { shallow } from "enzyme"; +import Button from "."; + +// Exasice #2 +describe("Interaction", () => { + it("Callback props onClick", () => { + const spyFunc = jest.fn() + const component = shallow(); + component.find('button').simulate('click') + expect(spyFunc).toHaveBeenCalled(); + }) +}); diff --git a/solutions/testing-react-component/Button/index.js b/solutions/testing-react-component/Button/index.js new file mode 100644 index 0000000..c482dfe --- /dev/null +++ b/solutions/testing-react-component/Button/index.js @@ -0,0 +1,34 @@ +import React from "react"; +import PropTypes from "prop-types"; +import classnames from "classnames"; + +const Button = (props) => { + const btnClasses = classnames("btn", { + "btn-sm": props.size === "small", + "btn-md": props.size === "medium", + "btn-lg": props.size === "large", + "disabled": props.disabled === true + }); + + return ( + + { props.children } + + ); +} + +Button.propTypes = { + disabled: PropTypes.bool, + children: PropTypes.string, + size: PropTypes.oneOf(["small", "medium", "large"]), + onClick: PropTypes.func +}; + +Button.defaultProps = { + disabled: false, + children: "Button", + size: "medium", + onClick: () => {} +}; + +export default Button; \ No newline at end of file diff --git a/solutions/testing-react-component/Counter/Exercise3.test.js b/solutions/testing-react-component/Counter/Exercise3.test.js new file mode 100644 index 0000000..5af25b4 --- /dev/null +++ b/solutions/testing-react-component/Counter/Exercise3.test.js @@ -0,0 +1,18 @@ +import React from "react"; +import { shallow } from "enzyme"; +import Counter from "."; + +// Exasice #3 +describe("Initial state of the counter", () => { + + // A Sample bad test + it("initial state in bad way", () => { + const wrapper = shallow(); + expect(wrapper.state().count).toEqual(0); + }); + + it("initial state should be 0", () => { + const wrapper = shallow(); + expect(wrapper.find(".count").text()).toEqual("0"); + }); +}); diff --git a/solutions/testing-react-component/Counter/Exercise4.test.js b/solutions/testing-react-component/Counter/Exercise4.test.js new file mode 100644 index 0000000..f0e48a1 --- /dev/null +++ b/solutions/testing-react-component/Counter/Exercise4.test.js @@ -0,0 +1,19 @@ +import React from "react"; +import { shallow } from "enzyme"; +import Counter from "."; + +// Exasice #4 +describe("User interactions on counter", () => { + + it("increment the count when the increment button is clicked", () => { + const wrapper = shallow(); + wrapper.find('Button.increment').simulate('click'); + expect(wrapper.find(".count").text()).toEqual("1"); + }); + + it("decrement the count when the decrement button is clicked", () => { + const wrapper = shallow(); + wrapper.find('Button.decrement').simulate('click'); + expect(wrapper.find(".count").text()).toEqual("-1"); + }); +}); diff --git a/solutions/testing-react-component/Counter/index.js b/solutions/testing-react-component/Counter/index.js new file mode 100644 index 0000000..822ece4 --- /dev/null +++ b/solutions/testing-react-component/Counter/index.js @@ -0,0 +1,43 @@ +import React, { Component } from "react"; + +import Button from "../Button"; + +class Counter extends Component { + constructor(props) { + super(props); + this.state = { + count: 0 + } + + this.increment = this.increment.bind(this); + this.decrement = this.decrement.bind(this); + } + + increment() { + this.setState(prevState => ({ count: prevState.count + 1 })); + }; + + decrement() { + this.setState(prevState => ({ count: prevState.count - 1 })); + }; + + render() { + return ( + + {this.state.count} + + Increment + + + Decrement + + + ); + } +} + +export default Counter; \ No newline at end of file diff --git a/solutions/testing-react-component/CounterStore/Exercise5.test.js b/solutions/testing-react-component/CounterStore/Exercise5.test.js new file mode 100644 index 0000000..fe1590d --- /dev/null +++ b/solutions/testing-react-component/CounterStore/Exercise5.test.js @@ -0,0 +1,43 @@ +import React from 'react' +import { shallow } from 'enzyme' +import { counterIncrement, counterDecrement } from './actions' + +import { Counter } from './index' + +describe('Counter component connected with redux store', () => { + + // A sample test + it('should renders the count', () => { + const props = { + dispatch: jest.fn(), + count: 1 + } + + const wrapper = shallow() + expect(wrapper.find('.count').text()).toEqual('1') + }) + + it('dispatches the right action for incrementing', () => { + const props = { + dispatch: jest.fn(), + count: 3 + } + + const wrapper = shallow(); + wrapper.find('Button.increment').simulate('click') + + expect(props.dispatch).toHaveBeenCalledWith(counterIncrement()) + }) + + it('dispatches the right action for decrementing', () => { + const props = { + dispatch: jest.fn(), + count: 3 + } + + const wrapper = shallow() + wrapper.find('Button.decrement').simulate('click') + + expect(props.dispatch).toHaveBeenCalledWith(counterDecrement()) + }) +}) diff --git a/solutions/testing-react-component/CounterStore/actions.js b/solutions/testing-react-component/CounterStore/actions.js new file mode 100644 index 0000000..478df96 --- /dev/null +++ b/solutions/testing-react-component/CounterStore/actions.js @@ -0,0 +1,10 @@ +export const COUNTER_INCREMENT = '@@Counter/COUNTER_INCREMENT'; +export const COUNTER_DECREMENT = '@@Counter/COUNTER_DECREMENT'; + +export const counterIncrement = () => ({ + type: COUNTER_INCREMENT +}); + +export const counterDecrement = () => ({ + type: COUNTER_DECREMENT +}); \ No newline at end of file diff --git a/solutions/testing-react-component/CounterStore/index.js b/solutions/testing-react-component/CounterStore/index.js new file mode 100644 index 0000000..070e269 --- /dev/null +++ b/solutions/testing-react-component/CounterStore/index.js @@ -0,0 +1,49 @@ +import React, { Component } from "react"; +import { connect } from "react-redux"; +import { counterIncrement, counterDecrement } from "./actions"; +import Button from "../Button"; + +export class Counter extends Component { + constructor(props) { + super(props); + this.increment = this.increment.bind(this); + this.decrement = this.decrement.bind(this); + } + + increment() { + this.props.dispatch(counterIncrement()); + }; + + decrement() { + this.props.dispatch(counterDecrement()); + }; + + render() { + return ( + + {this.props.count} + + Increment + + + Decrement + + + ); + } +} + +const mapStateToProps = (state) => ({ + count: state.count, +}) + +export default connect(mapStateToProps, { + counterIncrement, + counterDecrement +})(Counter) \ No newline at end of file diff --git a/solutions/testing-react-component/CounterStore/reducer.js b/solutions/testing-react-component/CounterStore/reducer.js new file mode 100644 index 0000000..2ac0fa1 --- /dev/null +++ b/solutions/testing-react-component/CounterStore/reducer.js @@ -0,0 +1,23 @@ +import { + COUNTER_INCREMENT, + COUNTER_DECREMENT +} from "./actions"; + +const INITIAL_STATE = { count: 0 }; + +export default (state = INITIAL_STATE, action) => { + switch (action.type) { + case COUNTER_INCREMENT: + return { + ...state, + count : state.count + 1, + } + case COUNTER_DECREMENT: + return { + ...state, + count : state.count - 1, + } + default: + return state; + } +}