diff --git a/package.json b/package.json
index 87d386e..899b096 100644
--- a/package.json
+++ b/package.json
@@ -21,5 +21,9 @@
"not dead",
"not ie <= 11",
"not op_mini all"
- ]
+ ],
+ "devDependencies": {
+ "enzyme": "^3.8.0",
+ "enzyme-adapter-react-16": "^1.9.0"
+ }
}
diff --git a/src/App.css b/src/App.css
index 92f956e..41a8699 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1,32 +1,41 @@
-.App {
- text-align: center;
-}
-.App-logo {
- animation: App-logo-spin infinite 20s linear;
- height: 40vmin;
-}
+.game {
+ position: absolute;
+ z-index: 15;
+ top: 25%;
+ left: 50%;
+ margin: -100px 0 0 -150px;}
+
+
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
+.cell{
+
+ border: 1px solid #999;
+ float: left;
+ font-size: 60px;
+ font-family: Ubuntu, sans-serif;
+ color:white;
+ font-weight: bold;
+ line-height: 34px;
+ height: 100px;
+ margin-right: -1px;
+ margin-top: -1px;
+ padding: 0;
+ text-align: center;
+ width: 100px;
+ border-radius: 14px;
+ background: #78bec5;
}
-.App-link {
- color: #61dafb;
+
+.game-info {
+ margin-left: 40px;
}
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
+.cell:focus {
+ outline: none;
}
+
+.kbd-navigation .cell:focus {
+ background: #ddd;
+}
\ No newline at end of file
diff --git a/src/App.js b/src/App.js
deleted file mode 100644
index 7e261ca..0000000
--- a/src/App.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import React, { Component } from 'react';
-import logo from './logo.svg';
-import './App.css';
-
-class App extends Component {
- render() {
- return (
-
- );
- }
-}
-
-export default App;
diff --git a/src/App.test.js b/src/App.test.js
index a754b20..6614dc1 100644
--- a/src/App.test.js
+++ b/src/App.test.js
@@ -1,9 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import App from './App';
+import Game from './Game';
it('renders without crashing', () => {
const div = document.createElement('div');
- ReactDOM.render( , div);
+ ReactDOM.render( , div);
ReactDOM.unmountComponentAtNode(div);
});
diff --git a/src/Board.jsx b/src/Board.jsx
new file mode 100644
index 0000000..b649f19
--- /dev/null
+++ b/src/Board.jsx
@@ -0,0 +1,41 @@
+import React, { Component } from 'react';
+import Cell from './Cell';
+
+export default class Board extends Component {
+
+
+ renderCell(i) {
+ return this.props.onClick(i)}/>;
+ }
+
+
+
+ render() {
+
+ return (
+
+
+
+
+
+ {this.renderCell(0)}
+ {this.renderCell(1)}
+ {this.renderCell(2)}
+
+
+ {this.renderCell(3)}
+ {this.renderCell(4)}
+ {this.renderCell(5)}
+
+
+ {this.renderCell(6)}
+ {this.renderCell(7)}
+ {this.renderCell(8)}
+
+
+
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/BoardGrid.test.jsx b/src/BoardGrid.test.jsx
new file mode 100644
index 0000000..1d28a7d
--- /dev/null
+++ b/src/BoardGrid.test.jsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import Board from './Board';
+
+describe('BoardGrid tests', () => {
+ let wrapper;
+ beforeEach(() => {
+ wrapper = shallow( );
+ });
+
+ describe('isBoardFull', () => {
+ it('Should return false at startup', () => {
+ expect(wrapper.instance().isBoardFull()).toBe(false);
+ });
+
+ it('Should return true if board is full', () => {
+ wrapper.setState({ grid: Array(9).fill('Y') });
+
+ expect(wrapper.instance().isBoardFull()).toBe(true);
+ });
+ });
+
+ describe('getWinner', () => {
+ it('Should return null at startup', () => {
+ expect(wrapper.instance().getWinner()).toBe(null);
+ });
+
+ it('Should return X when first player has fullfill firt row', () => {
+ wrapper.setState({
+ grid: Array(3).fill('X')
+ });
+
+ expect(wrapper.instance().getWinner()).toBe('X');
+ });
+
+ it('Should return O when second player has fullfill firt column', () => {
+ wrapper.setState({
+ grid: [
+ 'O', null, null,
+ 'O', null, null,
+ 'O', null, null
+ ]
+ });
+
+ expect(wrapper.instance().getWinner()).toBe('O');
+ });
+
+ it('Should return X when first player has fullfill diagonal', () => {
+ wrapper.setState({
+ grid: [
+ 'O', null, 'X',
+ 'O', 'X', null,
+ 'X', null, 'O'
+ ]
+ });
+
+ expect(wrapper.instance().getWinner()).toBe('X');
+ });
+ });
+});
diff --git a/src/Cell.jsx b/src/Cell.jsx
new file mode 100644
index 0000000..5c63274
--- /dev/null
+++ b/src/Cell.jsx
@@ -0,0 +1,13 @@
+import React, { Component } from 'react';
+
+
+export default class Cell extends Component {
+
+ render() {
+ return (
+ this.props.onClick()}>
+ {this.props.value}
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/Game.jsx b/src/Game.jsx
new file mode 100644
index 0000000..8b0f7e5
--- /dev/null
+++ b/src/Game.jsx
@@ -0,0 +1,101 @@
+import React, { Component } from 'react';
+import './App.css';
+import Board from './Board'
+
+class Game extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ history: [{
+ cells: Array(9).fill(null),
+ }],
+ xIsNext: true,
+ stepNumber: 0,
+ };
+ }
+
+ handleClick(i) {
+ const history = this.state.history.slice(0, this.state.stepNumber + 1);
+ const current = history[history.length - 1];
+ const cells = current.cells.slice();
+ if (this.calculateWinner(cells) || cells[i])
+ return;
+ cells[i] = this.state.xIsNext ? 'X' : 'O';
+ this.setState({
+ history: history.concat([{
+ cells: cells,
+ }]),
+ xIsNext: !this.state.xIsNext,
+ stepNumber: history.length,
+ });
+ }
+
+ jumpTo(step) {
+ this.setState({
+ stepNumber: step,
+ xIsNext: (step % 2) === 0,
+ });
+ }
+
+
+ calculateWinner(squares) {
+ const lines = [
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 8],
+ [0, 3, 6],
+ [1, 4, 7],
+ [2, 5, 8],
+ [0, 4, 8],
+ [2, 4, 6],
+ ];
+ for (let i = 0; i < lines.length; i++) {
+ const [a, b, c] = lines[i];
+ if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
+ return squares[a];
+ }
+ }
+ return null;
+ }
+
+ render() {
+ const history = this.state.history;
+ const current = history[this.state.stepNumber];
+ const winner = this.calculateWinner(current.cells);
+
+ const moves = history.map((step, move) => {
+ const desc = move ?
+ 'Go to move #' + move :
+ 'Go to game start';
+ return (
+
+ this.jumpTo(move)}>{desc}
+
+ );
+ });
+
+ let status;
+ if (winner) {
+ status = 'Winner: ' + winner;
+ } else {
+ status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
+ }
+
+
+ return (
+
+
+ this.handleClick(i)}/>
+
+
+
+ );
+ }
+}
+
+export default Game;
diff --git a/src/index.js b/src/index.js
index 0c5e75d..97b317b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,10 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
-import App from './App';
+import Game from './Game';
import * as serviceWorker from './serviceWorker';
-ReactDOM.render( , document.getElementById('root'));
+ReactDOM.render( , document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
diff --git a/src/setupTests.js b/src/setupTests.js
new file mode 100644
index 0000000..2d30267
--- /dev/null
+++ b/src/setupTests.js
@@ -0,0 +1,3 @@
+import {configure} from 'enzyme';
+import Adapter from 'enzyme-adapter-react-16';
+configure({adapter: new Adapter()});
\ No newline at end of file
|