diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml new file mode 100644 index 0000000..673bd33 --- /dev/null +++ b/.github/workflows/node.js.yml @@ -0,0 +1,29 @@ +# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: Node.js CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x, 14.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm run build --if-present + - run: npm test diff --git a/__test__/__snapshots__/game.test.js.snap b/__test__/__snapshots__/game.test.js.snap new file mode 100644 index 0000000..228f65e --- /dev/null +++ b/__test__/__snapshots__/game.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`App Contains the compiled JavaScript 1`] = `"!function(t){var e={};function n(r){if(e[r])return e[r].exports;var l=e[r]={i:r,l:!1,exports:{}};return t[r].call(l.exports,l,l.exports,n),l.l=!0,l.exports}n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){\\"undefined\\"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:\\"Module\\"}),Object.defineProperty(t,\\"__esModule\\",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&\\"object\\"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,\\"default\\",{enumerable:!0,value:t}),2&e&&\\"string\\"!=typeof t)for(var l in t)n.d(r,l,function(e){return t[e]}.bind(null,l));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,\\"a\\",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p=\\"\\",n(n.s=1)}([function(t,e,n){\\"use strict\\";n.r(e),n.d(e,\\"default\\",(function(){return r}));class r{constructor(t,e){this.p1=t,this.p2=e,this.board=[[null,null,null],[null,null,null],[null,null,null]],this.player=Math.random()<.5?this.p1:this.p2,this.sym=\\"X\\"}turn(t,e){e=e||t,this.board[t][e]=this.sym}nextPlayer(){this.player=this.player===this.p1?this.p2:this.p1,this.sym=\\"X\\"===this.sym?\\"O\\":\\"X\\"}hasWinner(){return this.rowWin()||this.colWin()||this.diagWin()}rowWin(){let t=!1;for(let e=0;e<3;e++){const n=this.board[e];null!==n[0]&&(t=t||n[0]===n[1]&&n[0]===n[2])}return t}colWin(){let t=!1;for(let e=0;e<3;e++){const n=this.board;null!==n[0][e]&&(t=t||n[0][e]===n[1][e]&&n[0][e]===n[2][e])}return t}diagWin(){const t=this.board;return null!==t[0][0]&&t[0][0]===t[1][1]&&t[0][0]===t[2][2]||null!==t[0][2]&&t[0][2]===t[1][1]&&t[0][2]===t[2][0]}}},function(t,e,n){n(2),t.exports=n(0)},function(t,e,n){\\"use strict\\";n.r(e);var r=n(0);let l,o;for(;!l;)l=window.prompt(\\"Enter player 1 name:\\");for(;!o&&l!==o;)o=window.prompt(l===o?\`Please enter a different name than \${l}.\`:\\"Enter player 2 name:\\");window.onload=()=>{const t=new r.default(l,o),e=document.getElementById(\\"turn\\"),n=document.getElementById(\\"player\\");n.innerText=t.player,document.querySelectorAll(\\"td\\").forEach(r=>{r.onclick=l=>{r.onclick=void 0,l.target.innerText=t.sym,l.target.onclick=void 0;const[o,i]=l.target.classList;t.turn(o,i),t.hasWinner()?(e.innerText=\`\${t.player} wins!\`,document.querySelectorAll(\\"td\\").forEach(t=>{t.onclick=void 0})):(t.nextPlayer(),n.innerText=t.player)}})}}]);"`; diff --git a/__test__/game.test.js b/__test__/game.test.js new file mode 100644 index 0000000..e8aa3a8 --- /dev/null +++ b/__test__/game.test.js @@ -0,0 +1,108 @@ +const Game = require('../src/game').default +const fs = require('fs') + +describe('App', () => { + it('Contains the compiled JavaScript', async (done) => { + fs.readFile('./public/main.js', 'utf8', (err, data) => { + expect(err).toBe(null) + expect(data).toMatchSnapshot() + done() + }) + }) +}) + +describe('Game', () => { + let game, p1, p2 + beforeEach(() => { + p1 = 'Salem' + p2 = 'Nate' + game = new Game(p1, p2) + }) + + describe('Game', () => { + it('Initializes with two players', async () => { + expect(game.p1).toBe('Salem') + expect(game.p2).toBe('Nate') + }) + + it('Initializes with an empty board', async () => { + for (let r = 0; r < game.board.length; r++) { + for (let c = 0; c < game.board[r].lenght; c++) { + expect(game.board[r][c]).toBeUndefined() + } + } + }) + + it('Starts the game with a random player', async () => { + Math.random = () => 0.4 + expect(new Game(p1, p2).player).toBe('Salem') + + Math.random = () => 0.6 + expect(new Game(p1, p2).player).toBe('Nate') + }) + }) + + describe('turn', () => { + it("Inserts an 'X' into the top center", async () => { + game.turn(0, 1) + expect(game.board[0][1]).toBe('X') + }) + + it("Inserts an 'X' into the top left", async () => { + game.turn(0) + expect(game.board[0][0]).toBe('X') + }) + }) + + describe('nextPlayer', () => { + it('Sets the current player to be whoever it is not', async () => { + Math.random = () => 0.4 + const game = new Game(p1, p2) + expect(game.player).toBe('Salem') + game.nextPlayer() + expect(game.player).toBe('Nate') + }) + }) + + describe('hasWinner', () => { + it('Wins if any row is filled', async () => { + for (let r = 0; r < game.board.length; r++) { + for (let c = 0; c < game.board[r].length; c++) { + game.board[r][c] = 'X' + } + expect(game.hasWinner()).toBe(true) + + for (let c = 0; c < game.board[r].length; c++) { + game.board[r][c] = null + } + } + }) + + it('Wins if any column is filled', async () => { + for (let r = 0; r < game.board.length; r++) { + for (let c = 0; c < game.board[r].length; c++) { + game.board[c][r] = 'X' + } + expect(game.hasWinner()).toBe(true) + + for (let c = 0; c < game.board[r].length; c++) { + game.board[c][r] = null + } + } + }) + + it('Wins if down-left diagonal is filled', async () => { + for (let r = 0; r < game.board.length; r++) { + game.board[r][r] = 'X' + } + expect(game.hasWinner()).toBe(true) + }) + + it('Wins if up-right diagonal is filled', async () => { + for (let r = 0; r < game.board.length; r++) { + game.board[2 - r][r] = 'X' + } + expect(game.hasWinner()).toBe(true) + }) + }) +})