diff --git a/README.md b/README.md
index b915f90..da32a7f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
# js-code
+
Useful code and practice of JavaScript
## TOC
-* [Test React components using component tests](./testing-js/react-components/test-app/)
+- [Test React components using component tests](./testing-js/react-components/test-app/)
+- [Tic tac toe game](./tic-tac-toe/README.md)
diff --git a/tic-tac-toe/.env b/tic-tac-toe/.env
new file mode 100644
index 0000000..98a4f13
--- /dev/null
+++ b/tic-tac-toe/.env
@@ -0,0 +1 @@
+CHOKIDAR_USEPOLLING=true
\ No newline at end of file
diff --git a/tic-tac-toe/.gitignore b/tic-tac-toe/.gitignore
new file mode 100644
index 0000000..4d29575
--- /dev/null
+++ b/tic-tac-toe/.gitignore
@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/tic-tac-toe/README.md b/tic-tac-toe/README.md
new file mode 100644
index 0000000..50ad7cf
--- /dev/null
+++ b/tic-tac-toe/README.md
@@ -0,0 +1,13 @@
+# Tic Tac Toe Game
+
+[Following ReactJs tutorial](https://reactjs.org/tutorial/tutorial.html)
+
+## Testing
+
+- visual tests with [wdio](https://webdriver.io/docs/gettingstarted/)
+
+```js
+npm init wdio .
+```
+
+1. What are all the tests that we need to write to ensure bug free code?
diff --git a/tic-tac-toe/package.json b/tic-tac-toe/package.json
new file mode 100644
index 0000000..2616c50
--- /dev/null
+++ b/tic-tac-toe/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "tic-tac-toe",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@testing-library/jest-dom": "^5.16.1",
+ "@testing-library/react": "^11.2.7",
+ "@testing-library/user-event": "^12.8.3",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2",
+ "react-scripts": "4.0.3",
+ "web-vitals": "^1.1.2"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject",
+ "wdio": "wdio run wdio.conf.js"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "@wdio/cli": "^7.16.11",
+ "@wdio/local-runner": "^7.16.11",
+ "@wdio/mocha-framework": "^7.16.11",
+ "@wdio/sauce-service": "^7.16.11",
+ "@wdio/spec-reporter": "^7.16.11"
+ }
+}
\ No newline at end of file
diff --git a/tic-tac-toe/public/favicon.ico b/tic-tac-toe/public/favicon.ico
new file mode 100644
index 0000000..a11777c
Binary files /dev/null and b/tic-tac-toe/public/favicon.ico differ
diff --git a/tic-tac-toe/public/index.html b/tic-tac-toe/public/index.html
new file mode 100644
index 0000000..aa069f2
--- /dev/null
+++ b/tic-tac-toe/public/index.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ React App
+
+
+
+
+
+
+
diff --git a/tic-tac-toe/public/logo192.png b/tic-tac-toe/public/logo192.png
new file mode 100644
index 0000000..fc44b0a
Binary files /dev/null and b/tic-tac-toe/public/logo192.png differ
diff --git a/tic-tac-toe/public/logo512.png b/tic-tac-toe/public/logo512.png
new file mode 100644
index 0000000..a4e47a6
Binary files /dev/null and b/tic-tac-toe/public/logo512.png differ
diff --git a/tic-tac-toe/public/manifest.json b/tic-tac-toe/public/manifest.json
new file mode 100644
index 0000000..080d6c7
--- /dev/null
+++ b/tic-tac-toe/public/manifest.json
@@ -0,0 +1,25 @@
+{
+ "short_name": "React App",
+ "name": "Create React App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ },
+ {
+ "src": "logo192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "logo512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/tic-tac-toe/public/robots.txt b/tic-tac-toe/public/robots.txt
new file mode 100644
index 0000000..e9e57dc
--- /dev/null
+++ b/tic-tac-toe/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/tic-tac-toe/src/index.css b/tic-tac-toe/src/index.css
new file mode 100644
index 0000000..ac7a816
--- /dev/null
+++ b/tic-tac-toe/src/index.css
@@ -0,0 +1,51 @@
+body {
+ font: 14px "Century Gothic", Futura, sans-serif;
+ margin: 20px;
+}
+
+ol,
+ul {
+ padding-left: 30px;
+}
+
+.board-row:after {
+ clear: both;
+ content: "";
+ display: table;
+}
+
+.status {
+ margin-bottom: 10px;
+}
+
+.square {
+ background: #fff;
+ border: 1px solid #999;
+ float: left;
+ font-size: 24px;
+ font-weight: bold;
+ line-height: 34px;
+ height: 34px;
+ margin-right: -1px;
+ margin-top: -1px;
+ padding: 0;
+ text-align: center;
+ width: 34px;
+}
+
+.square:focus {
+ outline: none;
+}
+
+.kbd-navigation .square:focus {
+ background: #ddd;
+}
+
+.game {
+ display: flex;
+ flex-direction: row;
+}
+
+.game-info {
+ margin-left: 20px;
+}
diff --git a/tic-tac-toe/src/index.js b/tic-tac-toe/src/index.js
new file mode 100644
index 0000000..2a8cec2
--- /dev/null
+++ b/tic-tac-toe/src/index.js
@@ -0,0 +1,134 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import "./index.css";
+
+function Square(props) {
+ //no more render()
+ return (
+ //no more this.props and no more ()=> onClick(), instead onClick
+
+ );
+}
+
+/**
+ * the best approach is to store the game’s state in the parent Board
+ * component instead of in each Square.
+ * The Board component can tell each Square what to display
+ * by passing a prop
+ * To collect data from multiple children,
+ * or to have two child components communicate with each other,
+ * you need to declare the shared state in their parent component instead.
+ * The parent component can pass the state back down to the children
+ * by using props;
+ * this keeps the child components in sync with each other
+ * and with the parent component.
+ */
+class Board extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ squares: Array(9).fill(null),
+ xIsNext: true,
+ };
+ }
+ handleClick(i) {
+ const squares = this.state.squares.slice();
+ if (calculateWinner(squares) || squares[i]) {
+ return;
+ }
+ squares[i] = this.state.xIsNext ? "X" : "O";
+ this.setState({ squares: squares, xIsNext: !this.state.xIsNext });
+ }
+ renderSquare(i) {
+ return (
+ // Now we’re passing down two props from Board to Square:
+ // value and onClick.
+ // The onClick prop is a function that Square can call when clicked.
+ this.handleClick(i)}
+ testid={i}
+ />
+ );
+ }
+
+ //The render method returns a description of what you want to see on the screen
+ render() {
+ const winner = calculateWinner(this.state.squares);
+ let status;
+ if (winner) {
+ status = "Winner " + winner;
+ } else {
+ status = "Next player: " + (this.state.xIsNext ? "X" : "O");
+ }
+
+ return (
+
+
{status}
+
+ {this.renderSquare(0)}
+ {this.renderSquare(1)}
+ {this.renderSquare(2)}
+
+
+ {this.renderSquare(3)}
+ {this.renderSquare(4)}
+ {this.renderSquare(5)}
+
+
+ {this.renderSquare(6)}
+ {this.renderSquare(7)}
+ {this.renderSquare(8)}
+
+
+ );
+ }
+}
+
+class Game extends React.Component {
+ render() {
+ return (
+
+
+
+
Built, tested, and deployed with ❤️ by Nikolay Advolodkin
+
ultimateqa
+
+
+
{/* status */}
+
{/* TODO */}
+
+
+ );
+ }
+}
+
+// ========================================
+
+ReactDOM.render(, document.getElementById("root"));
+
+function 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;
+}
diff --git a/tic-tac-toe/test/specs/visual.spec.js b/tic-tac-toe/test/specs/visual.spec.js
new file mode 100644
index 0000000..87be53a
--- /dev/null
+++ b/tic-tac-toe/test/specs/visual.spec.js
@@ -0,0 +1,17 @@
+describe("Tic tac toe game", () => {
+ it("should look correct", async () => {
+ await browser.url(``);
+ await browser.execute("/*@visual.init*/", "Tic Tac Toe");
+ await browser.execute("/*@visual.snapshot*/", "Empty State");
+
+ await $('[data-testid="0"]').click();
+ await $('[data-testid="3"]').click();
+ await $('[data-testid="1"]').click();
+ await $('[data-testid="4"]').click();
+ await $('[data-testid="2"]').click();
+ await browser.execute("/*@visual.snapshot*/", "X is Winner");
+
+ const result = await browser.execute("/*@visual.end*/");
+ expect(result.message).toBeNull();
+ });
+});
diff --git a/tic-tac-toe/wdio.conf.js b/tic-tac-toe/wdio.conf.js
new file mode 100644
index 0000000..97445d3
--- /dev/null
+++ b/tic-tac-toe/wdio.conf.js
@@ -0,0 +1,74 @@
+const visualOptions = {
+ apiKey: process.env.SCREENER_API_KEY,
+ projectName: "tic-tac-toe",
+ failOnNewStates: false,
+};
+const sauceOptions = {
+ username: process.env.SAUCE_USERNAME,
+ accesskey: process.env.SAUCE_ACCESS_KEY,
+};
+exports.config = {
+ region: "us",
+ services: [
+ [
+ "sauce",
+ {
+ sauceConnect: true,
+ },
+ ],
+ ],
+ specs: ["./test/specs/**/*.js"],
+ // Patterns to exclude.
+ exclude: [
+ // 'path/to/excluded/files'
+ ],
+ maxInstances: 10,
+ hostname: "hub.screener.io",
+ port: 443,
+ protocol: "https",
+ path: "/wd/hub",
+ capabilities: [
+ //Desktop A 28%: https://www.w3schools.com/browsers/browsers_display.asp
+ {
+ browserName: "chrome",
+ platformName: "windows 10",
+ browserVersion: "latest",
+ "sauce:options": {
+ ...sauceOptions,
+ },
+ "sauce:visual": {
+ ...visualOptions,
+ viewportSize: "1366x768",
+ },
+ },
+ {
+ browserName: "safari",
+ platformName: "macOS 11.00",
+ browserVersion: "14",
+ "sauce:options": {
+ ...sauceOptions,
+ },
+ "sauce:visual": {
+ ...visualOptions,
+ viewportSize: "1366x768",
+ },
+ },
+ ],
+ //
+ // ===================
+ // Test Configurations
+ // ===================
+ // Level of logging verbosity: trace | debug | info | warn | error | silent
+ logLevel: "info",
+ bail: 0,
+ baseUrl: "http://localhost:3000",
+ waitforTimeout: 10000,
+ connectionRetryTimeout: 120000,
+ connectionRetryCount: 3,
+ framework: "mocha",
+ reporters: ["spec"],
+ mochaOpts: {
+ ui: "bdd",
+ timeout: 60000,
+ },
+};