diff --git a/archived/jest/demo/README.md b/archived/jest/demo/README.md deleted file mode 100644 index 60467a9..0000000 --- a/archived/jest/demo/README.md +++ /dev/null @@ -1,128 +0,0 @@ -# Step 2.4: Testing TypeScript code with Jest (Demo) - -[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/) - -[Jest](https://jestjs.io/) is a test framework made by Facebook and is very popular in the React and wider JS ecosystems. - -In this exercise, we will work on implementing simple unit tests using Jest. - -## Jest Features - -- Multi-threaded and isolated test runner -- Provides a fake browser-like environment if needed (window, document, DOM, etc) using jsdom -- Snapshots: Jest can create text-based snapshots of rendered components. These snapshots can be checked in and show API or large object changes alongside code changes in pull requests. -- Code coverage is integrated (`--coverage`) -- Very clear error messages showing where a test failure occurred - -## How to use Jest - -- Using `create-react-app` or other project generators, Jest should already be pre-configured. Running `npm test` usually will trigger it! -- A `jest.config.js` file is used for configuration -- `jsdom` might not have enough API from real browsers, for those cases, polyfills are required. Place these inside `jest.setup.js` and hook up the setup file in `jest.config.js` -- in order to use `enzyme` library to test React Components, more config bits are needed inside `jest.setup.js` - -## What does a test look like? - -```ts -// describe(), it() and expect() are globally exported, so they don't need to be imported when jest runs these tests -describe('Something to be tested', () => { - it('should describe the behavior', () => { - expect(true).toBe(true); - }); -}); -``` - -## Testing React components using Enzyme - -[Enzyme](https://airbnb.io/enzyme/) is made by Airbnb and provides utilities to help test React components. - -In a real app using ReactDOM, the top-level component will be rendered on the page using `ReactDOM.render()`. Enzyme provides a lighter-weight `mount()` function which is usually adequate for testing purposes. - -`mount()` returns a wrapper that can be inspected and provides functionality like `find()`, simulating clicks, etc. - -The following code demonstrates how Enzyme can be used to help test React components. - -```jsx -import React from 'react'; -import { mount } from 'enzyme'; -import { TestMe } from './TestMe'; - -describe('TestMe Component', () => { - it('should have a non-clickable component when the original InnerMe is clicked', () => { - const wrapper = mount(); - wrapper.find('#innerMe').simulate('click'); - expect(wrapper.find('#innerMe').text()).toBe('Clicked'); - }); -}); - -describe('Foo Component Tests', () => { - it('allows us to set props', () => { - const wrapper = mount(); - expect(wrapper.props().bar).toBe('baz'); - wrapper.setProps({ bar: 'foo' }); - expect(wrapper.props().bar).toBe('foo'); - - wrapper.find('button').simulate('click'); - }); -}); -``` - -## Advanced topics - -### Mocking - -Mocking functions is a large part of what makes Jest a powerful testing library. Jest actually intercepts the module loading process in Node.js, allowing it to mock entire modules if needed. - -There are many ways to mock, as you'd imagine in a language as flexible as JS. We only look at the simplest case, but there's a lot of depth here. - -To mock a function: - -```ts -it('some test function', () => { - const mockCallback = jest.fn(x => 42 + x); - mockCallback(1); - mockCallback(2); - expect(mockCallback).toHaveBeenCalledTimes(2); -}); -``` - -Read more about jest mocking [here](https://jestjs.io/docs/en/mock-functions.html). - -### Async Testing - -For testing async scenarios, the test runner needs some way to know when the scenario is finished. Jest tests can handle async scenarios using callbacks, promises, or async/await. - -```ts -// Callback -it('tests callback functions', (done) => { - setTimeout(() => { - done(); - }, 1000); -}); - -// Returning a promise -it('tests promise functions', () => { - return someFunctionThatReturnsPromise()); -}); - -// Async/await (recommended) -it('tests async functions', async () => { - expect(await someFunction()).toBe(5); -}); -``` - -# Demo - -## Jest basics - -In this repo, we can start an inner loop development of tests by running `npm test` from the root of the `frontend-bootcamp` folder. - -Take a look at code inside `demo/src`: - -1. `index.ts` exports a few functions for a counter as well as a function for squaring numbers. We'll use this last function to demonstrate how mocks work. - -2. `multiply.ts` is a contrived example of a function that is exported - -3. `index.spec.ts` is the test file - -Note how tests are re-run when either test files or source files under `src` are saved. diff --git a/archived/jest/demo/index.html b/archived/jest/demo/index.html deleted file mode 100644 index 92a9499..0000000 --- a/archived/jest/demo/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - -
-
- For this step, we look at unit testing. Run -
npm test
- in the command line. -
- - - diff --git a/archived/jest/demo/src/TestMe.spec.tsx b/archived/jest/demo/src/TestMe.spec.tsx deleted file mode 100644 index 70452b6..0000000 --- a/archived/jest/demo/src/TestMe.spec.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import { TestMe } from './TestMe'; - -describe('TestMe Component', () => { - it('should have a non-clickable component when the original InnerMe is clicked', () => { - const wrapper = mount(); - wrapper.find('#innerMe').simulate('click'); - expect(wrapper.find('#innerMe').text()).toBe('Clicked'); - }); -}); diff --git a/archived/jest/demo/src/TestMe.tsx b/archived/jest/demo/src/TestMe.tsx deleted file mode 100644 index 3d68f34..0000000 --- a/archived/jest/demo/src/TestMe.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; - -export interface TestMeProps { - name: string; -} - -export interface TestMeState { - clicked: boolean; -} - -export const TestMe = (props: TestMeProps) => { - return ( -
- -
- ); -}; - -export class InnerMe extends React.Component { - state = { - clicked: false - }; - - onClick = () => { - this.setState({ clicked: true }); - }; - - render() { - return !this.state.clicked ? ( -
- Hello {this.props.name}, Click Me -
- ) : ( -
Clicked
- ); - } -} diff --git a/archived/jest/demo/src/index.spec.tsx b/archived/jest/demo/src/index.spec.tsx deleted file mode 100644 index 6fa5c3e..0000000 --- a/archived/jest/demo/src/index.spec.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; - -describe('index', () => { - it('placeholder', () => { - }); -}); diff --git a/archived/jest/demo/src/index.ts b/archived/jest/demo/src/index.ts deleted file mode 100644 index ecdd946..0000000 --- a/archived/jest/demo/src/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { multiply } from './multiply'; - -let counter = 0; - -export function getCount() { - return counter; -} - -export function increment() { - return ++counter; -} - -export function decrement() { - return --counter; -} - -export function square(x: number) { - return multiply(x, x); -} diff --git a/archived/jest/demo/src/multiply.ts b/archived/jest/demo/src/multiply.ts deleted file mode 100644 index aa528c0..0000000 --- a/archived/jest/demo/src/multiply.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function multiply(x: number, y: number) { - return x * y; -} diff --git a/archived/jest/exercise/README.md b/archived/jest/exercise/README.md deleted file mode 100644 index aed944e..0000000 --- a/archived/jest/exercise/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Step 2.4: Testing TypeScript code with Jest (Exercise) - -[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/) - -Start the test runner by running `npm test` in the root of the `frontend-bootcamp` folder. - -## Basic testing - -1. Look at `exercise/src/stack.ts` for a sample implementation of a stack - -2. Follow the instructions inside `stack.spec.ts` file to complete the two tests - -## Enzyme Testing - -1. Open up `exercise/src/TestMe.spec.tsx` - -2. Fill in the test using Enzyme concepts introduced in the demo diff --git a/archived/jest/exercise/index.html b/archived/jest/exercise/index.html deleted file mode 100644 index e908cc3..0000000 --- a/archived/jest/exercise/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - -
-
- For this step, we look at unit testing. Run -
npm test
- in the command line. -
- - - diff --git a/archived/jest/exercise/src/TestMe.spec.tsx b/archived/jest/exercise/src/TestMe.spec.tsx deleted file mode 100644 index afd5164..0000000 --- a/archived/jest/exercise/src/TestMe.spec.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import { TestMe } from './TestMe'; - -describe('TestMe Component', () => { - it('should render correctly when hovered', () => { - // TODO: - // 1. mount a Component here - // 2. use enzyme wrapper's find() method to retrieve the #innerMe element - // 3. simulate a hover with "mouseover" event via the simulate() API - // 4. make assertions with expect on the text() of the #innerMe element - }); -}); diff --git a/archived/jest/exercise/src/TestMe.tsx b/archived/jest/exercise/src/TestMe.tsx deleted file mode 100644 index 8f39d8d..0000000 --- a/archived/jest/exercise/src/TestMe.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; - -export interface TestMeProps { - name: string; -} - -export interface TestMeState { - enabled: boolean; -} - -export const TestMe = (props: TestMeProps) => { - return ( -
- -
- ); -}; - -export class InnerMe extends React.Component { - state = { - enabled: false - }; - - onMouseOver = () => { - this.setState({ enabled: true }); - }; - - render() { - return !this.state.enabled ? ( -
- Hello {this.props.name}, Hover Over Me -
- ) : ( -
Enabled
- ); - } -} diff --git a/archived/jest/exercise/src/index.ts b/archived/jest/exercise/src/index.ts deleted file mode 100644 index 8b6ab17..0000000 --- a/archived/jest/exercise/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { Stack } from './stack'; -export { TestMe } from './TestMe'; diff --git a/archived/jest/exercise/src/stack.spec.ts b/archived/jest/exercise/src/stack.spec.ts deleted file mode 100644 index 03b7d9a..0000000 --- a/archived/jest/exercise/src/stack.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -// TODO: Import the stack here - -describe('Stack', () => { - it('should push item to the top of the stack', () => { - // TODO: implement test here: - // 1. Instantiate a new Stack - i.e. const stack = new Stack(); - // 2. Use stack push calls to add some items to the stack - // 3. Write assertions via the expect() API - }); - - it('should pop the item from the top of stack', () => { - // TODO: implement test here: - // 1. Instantiate a new Stack - i.e. const stack = new Stack(); - // 2. Use stack push calls to add some items to the stack - // 3. pop a few items off the stack - // 4. write assertions via the expect() API - }); -}); diff --git a/archived/jest/exercise/src/stack.ts b/archived/jest/exercise/src/stack.ts deleted file mode 100644 index 7f62d2d..0000000 --- a/archived/jest/exercise/src/stack.ts +++ /dev/null @@ -1,27 +0,0 @@ -export class Stack { - private _items: T[] = []; - - /** Add an item to the top of the stack. */ - push(item: T) { - this._items.push(item); - } - - /** Remove the top item from the stack and return it. */ - pop(): T { - if (this._items.length > 0) { - return this._items.pop(); - } - } - - /** Return the top item from the stack without removing it. */ - peek(): T { - if (this._items.length > 0) { - return this._items[this._items.length - 1]; - } - } - - /** Get the number of items in the stack/ */ - get count(): number { - return this._items.length; - } -} diff --git a/step2-04/exercise/README.md b/step2-04/exercise/README.md index 507edbf..b7118c6 100644 --- a/step2-04/exercise/README.md +++ b/step2-04/exercise/README.md @@ -20,4 +20,8 @@ If you don't already have the app running, start it by running `npm start` from 1. Open `exercise/src/components/TodoHeader.tsx` -2. Replace the couple of TODO +2. Replace the `onAdd` with a real implementation using the `this.context` object + +3. Replace the `onFilter` with a real implementation using the `this.context` object + +4. Be sure to set the `contextType` of the TodoHeader component diff --git a/step2-06/exercise/README.md b/step2-06/exercise/README.md index b8d8031..8638161 100644 --- a/step2-06/exercise/README.md +++ b/step2-06/exercise/README.md @@ -1,25 +1,17 @@ -# Step 2.6 - Redux: Dispatching actions and examining state (Exercise) +# Step 2.6: Redux: React Binding (Exercise) [Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/) -## Visualize state changes with Chrome extension +## Bind Redux Store with Class Component -If you still have `npm test` running from the previous step, stop it with `ctrl+C`. Start the app by running `npm start` from the root of the `frontend-bootcamp` folder. Click the "exercise" link under day 2 step 6. +1. Open `exercise/src/components/TodoHeader.tsx`. -1. Install the [Redux DevTools extension](https://github.com/zalmoxisus/redux-devtools-extension) - - [Chrome](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) - - [Firefox](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/) +2. Just like the 2.4 exercise, implement `onAdd` and `onFilter` using `this.context.dispatch()` calls to dispatch actions. -2. Hit F12 (`cmd+option+I` on Mac) and open the inspector panel entitled **Redux** +## Bind Redux Store with Functional Component -3. Modify `exercise/src/index.tsx` to dispatch actions (you're not limited to adding todos; you can also remove and clear) +1. Open `exercise/src/components/TodoFooter.tsx`. -4. Explore the actions' effects using the extension +2. Follow the instructions in the file to replace the `todos` const using the `useMappedState()` hook. -## Playing with dispatching actions inside tests - -Stop the app using `ctrl+C` and start the tests by running `npm test`. - -1. Open `exercise/src/reducers/reducer.spec.ts` - -2. Follow the instructions to fill out the reducer tests +3. Retrieve the dispatch function with `useDispatch()` hook. diff --git a/step2-06/exercise/src/actions/index.ts b/step2-06/exercise/src/actions/index.ts index d75c2d9..a483a82 100644 --- a/step2-06/exercise/src/actions/index.ts +++ b/step2-06/exercise/src/actions/index.ts @@ -1,5 +1,10 @@ import uuid from 'uuid/v4'; export const actions = { - addTodo: (label: string) => ({ type: 'addTodo', id: uuid(), label }) + addTodo: (label: string) => ({ type: 'addTodo', id: uuid(), label }), + remove: (id: string) => ({ type: 'remove', id }), + complete: (id: string) => ({ type: 'complete', id }), + clear: () => ({ type: 'clear' }), + setFilter: (filter: string) => ({ type: 'setFilter', filter }), + edit: (id: string, label: string) => ({ type: 'edit', id, label }) }; diff --git a/step2-06/exercise/src/components/TodoApp.tsx b/step2-06/exercise/src/components/TodoApp.tsx new file mode 100644 index 0000000..4425e1d --- /dev/null +++ b/step2-06/exercise/src/components/TodoApp.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Stack } from 'office-ui-fabric-react'; +import { TodoFooter } from './TodoFooter'; +import { TodoHeader } from './TodoHeader'; +import { TodoList } from './TodoList'; + +export const TodoApp = () => { + return ( + + + + + + + + ); +}; diff --git a/step2-06/exercise/src/components/TodoFooter.tsx b/step2-06/exercise/src/components/TodoFooter.tsx new file mode 100644 index 0000000..fc5307e --- /dev/null +++ b/step2-06/exercise/src/components/TodoFooter.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { DefaultButton, Stack, Text } from 'office-ui-fabric-react'; +import { actions } from '../actions'; +import { useMappedState, useDispatch } from 'redux-react-hook'; + +export const TodoFooter = () => { + // TODO: make use of useMappedState(state => state) and the useDispatch functions to get + // the Redux store and dispatching actions + // HINT: const { todos } = useMappedState(...); + // HINT: useDispatch() here too. + const todos = {}; + const dispatch = (...args: any[]) => {}; + + const itemCount = Object.keys(todos).filter(id => !todos[id].completed).length; + + return ( + + + {itemCount} item{itemCount === 1 ? '' : 's'} left + + dispatch(actions.clear())}>Clear Completed + + ); +}; diff --git a/step2-06/exercise/src/components/TodoHeader.tsx b/step2-06/exercise/src/components/TodoHeader.tsx new file mode 100644 index 0000000..e447dad --- /dev/null +++ b/step2-06/exercise/src/components/TodoHeader.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Stack, Text, Pivot, PivotItem, TextField, PrimaryButton } from 'office-ui-fabric-react'; +import { FilterTypes } from '../store'; +import { actions } from '../actions'; +import { StoreContext } from 'redux-react-hook'; + +interface TodoHeaderState { + labelInput: string; +} + +export class TodoHeader extends React.Component<{}, TodoHeaderState> { + constructor(props: {}) { + super(props); + this.state = { labelInput: undefined }; + } + + render() { + return ( + + + todos + + + + + ({ + ...(props.focused && { + field: { + backgroundColor: '#c7e0f4' + } + }) + })} + /> + + Add + + + + + + + + + ); + } + + private onAdd = () => { + // TODO: Fill in a dispatch call to add the todo item + // HINT: this.context.dispatch(...); + this.setState({ labelInput: undefined }); + }; + + private onChange = (evt: React.FormEvent, newValue: string) => { + this.setState({ labelInput: newValue }); + }; + + private onFilter = (item: PivotItem) => { + // TODO: Fill in the dispatch call to set the filter + // HINT: this.context.dispatch(...); + }; +} + +// TODO: set the context type of this Class to StoreContext diff --git a/step2-06/exercise/src/components/TodoList.tsx b/step2-06/exercise/src/components/TodoList.tsx new file mode 100644 index 0000000..b577baa --- /dev/null +++ b/step2-06/exercise/src/components/TodoList.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Stack } from 'office-ui-fabric-react'; +import { TodoListItem } from './TodoListItem'; +import { useMappedState } from 'redux-react-hook'; + +export const TodoList = () => { + const { filter, todos } = useMappedState(state => state); + const filteredTodos = Object.keys(todos).filter(id => { + return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed); + }); + + return ( + + {filteredTodos.map(id => ( + + ))} + + ); +}; diff --git a/step2-06/exercise/src/components/TodoListItem.tsx b/step2-06/exercise/src/components/TodoListItem.tsx new file mode 100644 index 0000000..e105851 --- /dev/null +++ b/step2-06/exercise/src/components/TodoListItem.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { Stack, Checkbox, IconButton, TextField, DefaultButton } from 'office-ui-fabric-react'; +import { actions } from '../actions'; +import { StoreContext } from 'redux-react-hook'; + +interface TodoListItemProps { + id: string; +} + +interface TodoListItemState { + editing: boolean; + editLabel: string; +} + +export class TodoListItem extends React.Component { + constructor(props: TodoListItemProps) { + super(props); + this.state = { editing: false, editLabel: undefined }; + } + + render() { + const { id } = this.props; + const { todos } = this.context.getState(); + const dispatch = this.context.dispatch; + + const item = todos[id]; + + return ( + + {!this.state.editing && ( + <> + dispatch(actions.complete(id))} /> +
+ + dispatch(actions.remove(id))} /> +
+ + )} + + {this.state.editing && ( + + + + + + Save + + + )} +
+ ); + } + + private onEdit = () => { + const { id } = this.props; + const { todos } = this.context.getState(); + const { label } = todos[id]; + + this.setState({ + editing: true, + editLabel: this.state.editLabel || label + }); + }; + + private onDoneEdit = () => { + this.context.dispatch(actions.edit(this.props.id, this.state.editLabel)); + this.setState({ + editing: false, + editLabel: undefined + }); + }; + + private onChange = (evt: React.FormEvent, newValue: string) => { + this.setState({ editLabel: newValue }); + }; +} + +TodoListItem.contextType = StoreContext; diff --git a/step2-06/exercise/src/index.tsx b/step2-06/exercise/src/index.tsx index 566917b..d994e52 100644 --- a/step2-06/exercise/src/index.tsx +++ b/step2-06/exercise/src/index.tsx @@ -1,16 +1,19 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; import { reducer } from './reducers'; import { createStore } from 'redux'; +import { TodoApp } from './components/TodoApp'; +import { initializeIcons } from '@uifabric/icons'; import { composeWithDevTools } from 'redux-devtools-extension'; -import { actions } from './actions'; +import { StoreContext } from 'redux-react-hook'; const store = createStore(reducer, {}, composeWithDevTools()); -console.log(store.getState()); +initializeIcons(); -// TODO: dispatch several actions and see the effects on state inside the Redux devtools - -// store.dispatch(actions.???); -// store.dispatch(actions.???); -// store.dispatch(actions.???); - -console.log(store.getState()); +ReactDOM.render( + + + , + document.getElementById('app') +); diff --git a/step2-06/exercise/src/reducers/index.ts b/step2-06/exercise/src/reducers/index.ts index 6eed3c5..757ac54 100644 --- a/step2-06/exercise/src/reducers/index.ts +++ b/step2-06/exercise/src/reducers/index.ts @@ -1,27 +1,43 @@ import { Store } from '../store'; -import { addTodo, remove, complete, clear } from './pureFunctions'; +import { combineReducers } from 'redux'; +import { createReducer } from 'redux-starter-kit'; -function todoReducer(state: Store['todos'] = {}, action: any): Store['todos'] { - switch (action.type) { - case 'addTodo': - return addTodo(state, action.id, action.label); +export const todosReducer = createReducer( + {}, + { + addTodo(state, action) { + state[action.id] = { label: action.label, completed: false }; + }, - case 'remove': - return remove(state, action.id); + remove(state, action) { + delete state[action.id]; + }, - case 'clear': - return clear(state); + clear(state, action) { + Object.keys(state).forEach(key => { + if (state[key].completed) { + delete state[key]; + } + }); + }, - case 'complete': - return complete(state, action.id); + complete(state, action) { + state[action.id].completed = !state[action.id].completed; + }, + + edit(state, action) { + state[action.id].label = action.label; + } } +); - return state; -} +export const filterReducer = createReducer('all', { + setFilter(state, action) { + return action.filter; + } +}); -export function reducer(state: Store, action: any): Store { - return { - todos: todoReducer(state.todos, action), - filter: 'all' - }; -} +export const reducer = combineReducers({ + todos: todosReducer, + filter: filterReducer +}); diff --git a/step2-06/exercise/src/reducers/pureFunctions.spec.ts b/step2-06/exercise/src/reducers/pureFunctions.spec.ts deleted file mode 100644 index b3815cf..0000000 --- a/step2-06/exercise/src/reducers/pureFunctions.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { addTodo, complete } from './pureFunctions'; -import { Store } from '../store'; - -describe('TodoApp reducers', () => { - it('can add an item', () => { - const state = {}; - - const newState = addTodo(state, '0', 'item1'); - - const keys = Object.keys(newState); - - expect(newState).not.toBe(state); - expect(keys.length).toBe(1); - expect(newState[keys[0]].label).toBe('item1'); - expect(newState[keys[0]].completed).toBeFalsy(); - }); - - it('can complete an item', () => { - const state = {}; - - let newState = addTodo(state, '0', 'item1'); - - const key = Object.keys(newState)[0]; - - newState = complete(newState, key); - - expect(newState[key].completed).toBeTruthy(); - }); -}); diff --git a/step2-06/exercise/src/reducers/pureFunctions.ts b/step2-06/exercise/src/reducers/pureFunctions.ts deleted file mode 100644 index e1954e5..0000000 --- a/step2-06/exercise/src/reducers/pureFunctions.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Store, FilterTypes } from '../store'; - -export function addTodo(state: Store['todos'], id: string, label: string): Store['todos'] { - return { ...state, [id]: { label, completed: false } }; -} - -export function remove(state: Store['todos'], id: string) { - const newTodos = { ...state }; - - delete newTodos[id]; - - return newTodos; -} - -export function complete(state: Store['todos'], id: string) { - // Clone the todo, overriding - const newTodo = { ...state[id], completed: !state[id].completed }; - return { ...state, [id]: newTodo }; -} - -export function clear(state: Store['todos']) { - const newTodos = { ...state }; - - Object.keys(state).forEach(key => { - if (state[key].completed) { - delete newTodos[key]; - } - }); - - return newTodos; -} - -export function setFilter(state: Store['filter'], filter: FilterTypes) { - return filter; -} diff --git a/step2-06/exercise/src/reducers/reducer.spec.ts b/step2-06/exercise/src/reducers/reducer.spec.ts deleted file mode 100644 index 86b9272..0000000 --- a/step2-06/exercise/src/reducers/reducer.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { createStore } from 'redux'; -import { reducer } from '.'; -import { actions } from '../actions'; - -describe('reducers', () => { - it('should add items', () => { - // 1. Use Redux's createStore() to create a store. Pass in a reducer along with the initial state. - // - // 2. Call store.dispatch() with some action messages to indicate the kind of - // action to perform (in this case, addTodo) - // - // 3. Assert with expect() on the resultant store.getState().todos - }); - - // Tests left for you to write: - // - remove - // - clear - // - complete -});