From 49adb4b1cc638ab33fcb63c2548d98777ae30bed Mon Sep 17 00:00:00 2001 From: SteinsHeir <60748159+SteinsHeir@users.noreply.github.com> Date: Sun, 11 Jun 2023 21:41:41 +0200 Subject: [PATCH] tests --- babel.config.js | 3 + jest.config.js | 197 +++++++++++++ package.json | 9 +- static/js/2048game.js | 464 ++++++++++++++++-------------- static/tests/2048game.test.js | 206 +++++++++++++ static/tests/2048gameWin.test.js | 107 +++++++ static/tests/connect-four.test.js | 206 +++++++++++++ static/tests/tic-tac-toe.test.js | 122 ++++++++ 8 files changed, 1096 insertions(+), 218 deletions(-) create mode 100644 babel.config.js create mode 100644 jest.config.js create mode 100644 static/tests/2048game.test.js create mode 100644 static/tests/2048gameWin.test.js create mode 100644 static/tests/connect-four.test.js create mode 100644 static/tests/tic-tac-toe.test.js diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..a76dfe6 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [['@babel/preset-env', { targets: { node: 'current' } }]], +} diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..201ff16 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,197 @@ +/* + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +module.exports = { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "C:\\Users\\Predator\\AppData\\Local\\Temp\\jest", + + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + coverageDirectory: 'coverage', + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // Indicates which provider should be used to instrument code for coverage + // coverageProvider: "babel", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // The default configuration for fake timers + // fakeTimers: { + // "enableGlobally": false + // }, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + /*globals: {},*/ + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "mjs", + // "cjs", + // "jsx", + // "ts", + // "tsx", + // "json", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: undefined, + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state before every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state and implementation before every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + //setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: 'node', + + //setupFilesAfterEnv: ['./static/js/2048game.js'], + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + // testMatch: [ + // "**/__tests__/**/*.[jt]s?(x)", + // "**/?(*.)+(spec|test).[tj]s?(x)" + // ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // A map from regular expressions to paths to transformers + // transform: undefined, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "\\\\node_modules\\\\", + // "\\.pnp\\.[^\\\\]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +} diff --git a/package.json b/package.json index 59b6ca6..b0b58cc 100644 --- a/package.json +++ b/package.json @@ -6,17 +6,24 @@ "scripts": { "start": "npm run lite", "lite": "lite-server", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "jest" }, "author": "Srijan Gupta", "license": "ISC", "dependencies": { + "@testing-library/dom": "^9.3.0", "bootstrap": "^4.6.0", + "dom-storage": "^2.1.0", "font-awesome": "^4.7.0", + "jest": "^29.5.0", "jquery": "^3.5.1", + "jsdom": "^22.0.0", "popper.js": "^1.16.1" }, "devDependencies": { + "@babel/core": "^7.21.8", + "@babel/preset-env": "^7.21.5", + "babel-jest": "^29.5.0", "lite-server": "^2.6.1" } } diff --git a/static/js/2048game.js b/static/js/2048game.js index 886e2ed..59a00fd 100644 --- a/static/js/2048game.js +++ b/static/js/2048game.js @@ -1,236 +1,266 @@ document.addEventListener('DOMContentLoaded', () => { - const grid = document.getElementById("grid"); - const scoreDisplay = document.getElementById("score"); - const resultDisplay = document.getElementById("result"); - const highScoreDisplay = document.getElementById("high-score"); - const squares = []; - const width = 4; - let score = 0; - let highscore = localStorage.getItem('highscore'); - highScoreDisplay.innerHTML = highscore; - - //create board by adding 16 squares with 0 in them - function createBoard() { - for(let i=0; i num); //this will return only non-zero elements from thisRow and store them in thisRowFiltered - let numberOfZeroes = width - thisRowFiltered.length; //number of zeroes in the row - let zeroesArray = Array(numberOfZeroes).fill(0); //Array(zeroes) makes an array of length 'numberOfZeroes' and then we fill it with the number 0 - let newRow = zeroesArray.concat(thisRowFiltered); //new array with all zeroes at the beginning - squares[i].innerHTML = newRow[0]; - squares[i+1].innerHTML = newRow[1]; - squares[i+2].innerHTML = newRow[2]; - squares[i+3].innerHTML = newRow[3]; - } + } + + //move right + function moveRight() { + for (let i = 0; i < width * width; i++) { + if (i % width === 0) { + //0, 4, 8, 12 (first square of each row only) + let first = squares[i].innerHTML + let second = squares[i + 1].innerHTML + let third = squares[i + 2].innerHTML + let fourth = squares[i + 3].innerHTML + let thisRow = [ + parseInt(first), + parseInt(second), + parseInt(third), + parseInt(fourth), + ] + let thisRowFiltered = thisRow.filter((num) => num) //this will return only non-zero elements from thisRow and store them in thisRowFiltered + let numberOfZeroes = width - thisRowFiltered.length //number of zeroes in the row + let zeroesArray = Array(numberOfZeroes).fill(0) //Array(zeroes) makes an array of length 'numberOfZeroes' and then we fill it with the number 0 + let newRow = zeroesArray.concat(thisRowFiltered) //new array with all zeroes at the beginning + squares[i].innerHTML = newRow[0] + squares[i + 1].innerHTML = newRow[1] + squares[i + 2].innerHTML = newRow[2] + squares[i + 3].innerHTML = newRow[3] } } - - //move left - function moveLeft(){ - for(let i=0; i num); //this will return only non-zero elements from thisRow and store them in thisRowFiltered - let numberOfZeroes = width - thisRowFiltered.length; //number of zeroes in the row - let zeroesArray = Array(numberOfZeroes).fill(0); //Array(zeroes) makes an array of length 'numberOfZeroes' and then we fill it with the number 0 - let newRow = thisRowFiltered.concat(zeroesArray); //new array with all zeroes at the end - squares[i].innerHTML = newRow[0]; - squares[i+1].innerHTML = newRow[1]; - squares[i+2].innerHTML = newRow[2]; - squares[i+3].innerHTML = newRow[3]; - } + } + + //move left + function moveLeft() { + for (let i = 0; i < width * width; i++) { + if (i % width === 0) { + //0, 4, 8, 12 (first square of each row only) + let first = squares[i].innerHTML + let second = squares[i + 1].innerHTML + let third = squares[i + 2].innerHTML + let fourth = squares[i + 3].innerHTML + let thisRow = [ + parseInt(first), + parseInt(second), + parseInt(third), + parseInt(fourth), + ] + let thisRowFiltered = thisRow.filter((num) => num) //this will return only non-zero elements from thisRow and store them in thisRowFiltered + let numberOfZeroes = width - thisRowFiltered.length //number of zeroes in the row + let zeroesArray = Array(numberOfZeroes).fill(0) //Array(zeroes) makes an array of length 'numberOfZeroes' and then we fill it with the number 0 + let newRow = thisRowFiltered.concat(zeroesArray) //new array with all zeroes at the end + squares[i].innerHTML = newRow[0] + squares[i + 1].innerHTML = newRow[1] + squares[i + 2].innerHTML = newRow[2] + squares[i + 3].innerHTML = newRow[3] } } - - //move down - function moveDown(){ - for(let i=0; i num); //this will return only non-zero elements from thisColumn and store them in thisColumnFiltered - let numberOfZeroes = width - thisColumnFiltered.length; //number of zeroes in the column - let zeroesArray = Array(numberOfZeroes).fill(0); //Array(zeroes) makes an array of length 'numberOfZeroes' and then we fill it with the number 0 - let newColumn = zeroesArray.concat(thisColumnFiltered); - squares[i].innerHTML = newColumn[0]; - squares[i+width].innerHTML = newColumn[1]; - squares[i+2*width].innerHTML = newColumn[2]; - squares[i+3*width].innerHTML = newColumn[3]; - } + } + + //move down + function moveDown() { + for (let i = 0; i < width; i++) { + let first = squares[i].innerHTML + let second = squares[i + width].innerHTML + let third = squares[i + 2 * width].innerHTML + let fourth = squares[i + 3 * width].innerHTML + let thisColumn = [ + parseInt(first), + parseInt(second), + parseInt(third), + parseInt(fourth), + ] + let thisColumnFiltered = thisColumn.filter((num) => num) //this will return only non-zero elements from thisColumn and store them in thisColumnFiltered + let numberOfZeroes = width - thisColumnFiltered.length //number of zeroes in the column + let zeroesArray = Array(numberOfZeroes).fill(0) //Array(zeroes) makes an array of length 'numberOfZeroes' and then we fill it with the number 0 + let newColumn = zeroesArray.concat(thisColumnFiltered) + squares[i].innerHTML = newColumn[0] + squares[i + width].innerHTML = newColumn[1] + squares[i + 2 * width].innerHTML = newColumn[2] + squares[i + 3 * width].innerHTML = newColumn[3] } - - //move up - function moveUp(){ - for(let i=0; i num); //this will return only non-zero elements from thisColumn and store them in thisColumnFiltered - let numberOfZeroes = width - thisColumnFiltered.length; //number of zeroes in the column - let zeroesArray = Array(numberOfZeroes).fill(0); //Array(zeroes) makes an array of length 'numberOfZeroes' and then we fill it with the number 0 - let newColumn = thisColumnFiltered.concat(zeroesArray); - squares[i].innerHTML = newColumn[0]; - squares[i+width].innerHTML = newColumn[1]; - squares[i+2*width].innerHTML = newColumn[2]; - squares[i+3*width].innerHTML = newColumn[3]; - } + } + + //move up + function moveUp() { + for (let i = 0; i < width; i++) { + let first = squares[i].innerHTML + let second = squares[i + width].innerHTML + let third = squares[i + 2 * width].innerHTML + let fourth = squares[i + 3 * width].innerHTML + let thisColumn = [ + parseInt(first), + parseInt(second), + parseInt(third), + parseInt(fourth), + ] + let thisColumnFiltered = thisColumn.filter((num) => num) //this will return only non-zero elements from thisColumn and store them in thisColumnFiltered + let numberOfZeroes = width - thisColumnFiltered.length //number of zeroes in the column + let zeroesArray = Array(numberOfZeroes).fill(0) //Array(zeroes) makes an array of length 'numberOfZeroes' and then we fill it with the number 0 + let newColumn = thisColumnFiltered.concat(zeroesArray) + squares[i].innerHTML = newColumn[0] + squares[i + width].innerHTML = newColumn[1] + squares[i + 2 * width].innerHTML = newColumn[2] + squares[i + 3 * width].innerHTML = newColumn[3] } - - function combineRow(){ - for(let i=0; ihighscore) { - highscore = score; - } - highScoreDisplay.innerHTML = highscore; + } + + function combineRow() { + for (let i = 0; i < width * width - 1; i++) { + if (squares[i].innerHTML === squares[i + 1].innerHTML) { + let combinedTotal = + parseInt(squares[i + 1].innerHTML) + parseInt(squares[i].innerHTML) + squares[i].innerHTML = 0 + squares[i + 1].innerHTML = combinedTotal + score += combinedTotal + scoreDisplay.innerHTML = score + if (score > highscore) { + highscore = score } + highScoreDisplay.innerHTML = highscore } - checkForWin(); } - - function combineColumn(){ - for(let i=0; i<12; i++){ - if(squares[i].innerHTML === squares[i+width].innerHTML) { - let combinedTotal = parseInt(squares[i+width].innerHTML) + parseInt(squares[i].innerHTML); - squares[i].innerHTML = 0; - squares[i+width].innerHTML = combinedTotal; - score += combinedTotal; - scoreDisplay.innerHTML = score; - if(score>highscore) { - highscore = score; - } - highScoreDisplay.innerHTML = highscore; + checkForWin() + } + + function combineColumn() { + for (let i = 0; i < 12; i++) { + if (squares[i].innerHTML === squares[i + width].innerHTML) { + let combinedTotal = + parseInt(squares[i + width].innerHTML) + + parseInt(squares[i].innerHTML) + squares[i].innerHTML = 0 + squares[i + width].innerHTML = combinedTotal + score += combinedTotal + scoreDisplay.innerHTML = score + if (score > highscore) { + highscore = score } - } - checkForWin(); - } - - //keypress functions - document.addEventListener("keyup", gameplayControls); - - function gameplayControls(event){ - if(event.keyCode === 68){ - moveRight(); - combineRow(); - moveRight(); - generate(); - } - else if(event.keyCode === 65){ - moveLeft(); - combineRow(); - moveLeft(); - generate(); - } - else if(event.keyCode === 83){ - moveDown(); - combineColumn(); - moveDown(); - generate(); - } - else if(event.keyCode === 87){ - moveUp(); - combineColumn(); - moveUp(); - generate(); + highScoreDisplay.innerHTML = highscore } } - - - //check for win - function checkForWin(){ - squares.forEach((item, i) => { - if(item.innerHTML == 2048){ - resultDisplay.innerHTML = "YOU WON!"; - localStorage.setItem('highscore', highscore); - document.removeEventListener("keyup", gameplayControls); - clearInterval(colorsInterval); - addColors(); - } - }); + checkForWin() + } + + //keypress functions + document.addEventListener('keyup', gameplayControls) + + function gameplayControls(event) { + if (event.keyCode === 68) { + moveRight() + combineRow() + moveRight() + generate() + } else if (event.keyCode === 65) { + moveLeft() + combineRow() + moveLeft() + generate() + } else if (event.keyCode === 83) { + moveDown() + combineColumn() + moveDown() + generate() + } else if (event.keyCode === 87) { + moveUp() + combineColumn() + moveUp() + generate() } - - //check for gameOver - function gameOver(){ - let zeroes = 0; - squares.forEach((item, i) => { - if(item.innerHTML == 0) { - zeroes++; - } - }); - if(zeroes === 0){ - resultDisplay.innerHTML = "GAME OVER"; - alert("GAME OVER, Your Score ="+score); - localStorage.setItem('highscore', highscore); - document.removeEventListener("keyup", gameplayControls); - clearInterval(colorsInterval); - addColors(); + } + + //check for win + function checkForWin() { + squares.forEach((item, i) => { + if (item.innerHTML == 2048) { + resultDisplay.innerHTML = 'YOU WON!' + localStorage.setItem('highscore', highscore) + document.removeEventListener('keyup', gameplayControls) + clearInterval(colorsInterval) + addColors() } - } - - //add colors - function addColors() { - for (let i=0; i { + if (item.innerHTML == 0) { + zeroes++ } + }) + if (zeroes === 0) { + resultDisplay.innerHTML = 'GAME OVER' + window.alert('GAME OVER, Your Score =' + score) + localStorage.setItem('highscore', highscore) + document.removeEventListener('keyup', gameplayControls) + clearInterval(colorsInterval) + addColors() + } + } + + //add colors + function addColors() { + for (let i = 0; i < squares.length; i++) { + if (squares[i].innerHTML == 0) + squares[i].style.backgroundColor = '#CAD5E2' + else if (squares[i].innerHTML == 2) + squares[i].style.backgroundColor = '#50DBB4' + else if (squares[i].innerHTML == 4) + squares[i].style.backgroundColor = '#6AC47E' + else if (squares[i].innerHTML == 8) + squares[i].style.backgroundColor = '#38CC77' + else if (squares[i].innerHTML == 16) + squares[i].style.backgroundColor = '#66AD47' + else if (squares[i].innerHTML == 32) + squares[i].style.backgroundColor = '#1FAA59' + else if (squares[i].innerHTML == 64) + squares[i].style.backgroundColor = '#22CB5C' + else if (squares[i].innerHTML == 128) + squares[i].style.backgroundColor = '#6EC72D' + else if (squares[i].innerHTML == 256) + squares[i].style.backgroundColor = '#00D84A' + else if (squares[i].innerHTML == 512) + squares[i].style.backgroundColor = '#4DD637' + else if (squares[i].innerHTML == 1024) + squares[i].style.backgroundColor = '#1C8D73' + else if (squares[i].innerHTML == 2048) + squares[i].style.backgroundColor = '#3DBE29' } - addColors() - - var colorsInterval = setInterval(addColors, 50) - - }); \ No newline at end of file + } + addColors() + + var colorsInterval = setInterval(addColors, 50) +}) diff --git a/static/tests/2048game.test.js b/static/tests/2048game.test.js new file mode 100644 index 0000000..2bd0588 --- /dev/null +++ b/static/tests/2048game.test.js @@ -0,0 +1,206 @@ +import { findAllByText, fireEvent } from '@testing-library/dom' + +import { JSDOM } from 'jsdom' +const fs = require('fs') +const Storage = require('dom-storage') + +const html = fs.readFileSync('./pages/2048game.html') +const dom = new JSDOM(html, { + resources: 'usable', + runScripts: 'dangerously', +}) + +global.document = dom.window.document +global.window = dom.window +global.localStorage = new Storage(null, { strict: true }) +global.sessionStorage = new Storage(null, { strict: true }) + +describe('2048game test suite', () => { + jest.useFakeTimers() + /** changes the board as to get a specific state for testing + * @param {number[]} squares array of index where squares should be initiated to 2 + */ + function initBoard(squares) { + for (let i = 1; i < 17; i++) { + if (!squares.includes(i)) + document.getElementById('grid').children[i].innerHTML = 0 + else { + document.getElementById('grid').children[i].innerHTML = 2 + } + } + } + + /** + * changes the value of a specific grid square to a specified value + * @param {int} square index value of a grid square + * @param {int} val value that should be added + */ + function addBoard(square, val) { + document.getElementById('grid').children[square].innerHTML = val + } + + /** + * checks that the board is in a specific state after an action has been invoked + * @param {int[]} squares array of index where squares should have a value > 0 + * @param {int[]} vals array of values for the squares array parameter + */ + function checkBoardState(squares, vals) { + let additional2 = false + for (let i = 1; i < 17; i++) { + if (!squares.includes(i)) { + if ( + document.getElementById('grid').children[i].innerHTML !== '0' && + !additional2 + ) { + additional2 = true + } else { + if (document.getElementById('grid').children[i].innerHTML !== '0') + expect(document.getElementById('grid').children[i].innerHTML).toBe( + '0' + ) + } + } else { + expect(document.getElementById('grid').children[i].innerHTML).toBe( + vals[squares.indexOf(i)].toString() + ) + } + } + } + + test('checks if board was created correctly with createBoard function', async () => { + jest.requireActual('../js/2048game') + await new Promise((resolve) => document.addEventListener('load', resolve)) + const squares2 = await findAllByText(document, 2) + expect(document.getElementById('grid').children.length).toBe(17) + expect(squares2.length).toBe(2) + }) + + test('checks up movement', async () => { + initBoard([6, 10]) + checkBoardState([6, 10], [2, 2]) + + fireEvent.keyUp(document, { + key: 'w', + keyCode: 87, + }) + checkBoardState([2], [4]) + initBoard([]) + checkBoardState([], []) + addBoard(2, 8) + addBoard(14, 2) + fireEvent.keyUp(document, { + key: 'w', + keyCode: 87, + }) + checkBoardState([2, 6], [8, 2]) + + initBoard([]) + checkBoardState([], []) + addBoard(2, 128) + addBoard(6, 128) + addBoard(11, 16) + addBoard(15, 16) + fireEvent.keyUp(document, { + key: 'w', + keyCode: 87, + }) + checkBoardState([2, 3], [256, 32]) + }) + + test('checks down movement', async () => { + initBoard([6, 10]) + checkBoardState([6, 10], [2, 2]) + + fireEvent.keyUp(document, { + key: 's', + keyCode: 83, + }) + checkBoardState([14], [4]) + initBoard([]) + checkBoardState([], []) + addBoard(2, 8) + addBoard(14, 2) + fireEvent.keyUp(document, { + key: 's', + keyCode: 83, + }) + checkBoardState([10, 14], [8, 2]) + + initBoard([]) + checkBoardState([], []) + addBoard(2, 128) + addBoard(6, 128) + addBoard(11, 16) + addBoard(15, 16) + fireEvent.keyUp(document, { + key: 's', + keyCode: 83, + }) + checkBoardState([14, 15], [256, 32]) + }) + + test('checks left movement', async () => { + initBoard([6, 7]) + checkBoardState([6, 7], [2, 2]) + + fireEvent.keyUp(document, { key: 'a', keyCode: 65 }) + //printBoard() + checkBoardState([5], [4]) + + initBoard([]) + addBoard(9, 8) + addBoard(12, 2) + fireEvent.keyUp(document, { key: 'a', keyCode: 65 }) + checkBoardState([9, 10], [8, 2]) + + initBoard([]) + checkBoardState([], []) + addBoard(1, 128) + addBoard(3, 128) + addBoard(6, 16) + addBoard(8, 16) + fireEvent.keyUp(document, { key: 'a', keyCode: 65 }) + checkBoardState([1, 5], [256, 32]) + }) + + test('checks right movement', async () => { + initBoard([6, 7]) + checkBoardState([6, 7], [2, 2]) + + fireEvent.keyUp(document, { key: 'd', keyCode: 68 }) + //printBoard() + checkBoardState([8], [4]) + + initBoard([]) + addBoard(9, 8) + addBoard(12, 2) + fireEvent.keyUp(document, { key: 'd', keyCode: 68 }) + checkBoardState([11, 12], [8, 2]) + + initBoard([]) + checkBoardState([], []) + addBoard(1, 128) + addBoard(3, 128) + addBoard(6, 16) + addBoard(8, 16) + fireEvent.keyUp(document, { key: 'd', keyCode: 68 }) + checkBoardState([4, 8], [256, 32]) + }) + + test('checks game loss ending', () => { + initBoard([]) + let val = 2 + for (let i = 1; i < 17; i++) { + if (i === 16) val = 0 + addBoard(i, val) + if (i === 10) val = 2 + val = val * 2 + } + + //mocks alert because of an undefined error due to the mocking of window + window.alert = jest.fn() + fireEvent.keyUp(document, { key: 'd', keyCode: 68 }) + + expect(document.getElementById('result').innerHTML).toBe('GAME OVER') + }) +}) diff --git a/static/tests/2048gameWin.test.js b/static/tests/2048gameWin.test.js new file mode 100644 index 0000000..c89d666 --- /dev/null +++ b/static/tests/2048gameWin.test.js @@ -0,0 +1,107 @@ +import { fireEvent } from '@testing-library/dom' + +import { JSDOM } from 'jsdom' +const fs = require('fs') +const Storage = require('dom-storage') + +const html = fs.readFileSync('./pages/2048game.html') +const dom = new JSDOM(html, { + resources: 'usable', + runScripts: 'dangerously', +}) + +global.document = dom.window.document +global.window = dom.window +global.localStorage = new Storage(null, { strict: true }) +global.sessionStorage = new Storage(null, { strict: true }) + +describe('my test suite', () => { + jest.useFakeTimers() + /** changes the board as to get a specific state for testing + * @param {number[]} squares array of index where squares should be initiated to 2 + */ + function initBoard(squares) { + for (let i = 1; i < 17; i++) { + if (!squares.includes(i)) + document.getElementById('grid').children[i].innerHTML = 0 + else { + document.getElementById('grid').children[i].innerHTML = 2 + } + } + } + + /** + * changes the value of a specific grid square to a specified value + * @param {int} square index value of a grid square + * @param {int} val value that should be added + */ + function addBoard(square, val) { + document.getElementById('grid').children[square].innerHTML = val + } + + function printBoard() { + let row = [] + for (let i = 0; i < 16; i++) { + row.push(document.getElementById('grid').children[i + 1].innerHTML) + if ((i + 1) % 4 === 0) { + console.log('---\n' + row + '\n---') + row = [] + } + } + } + + /** + * checks that the board is in a specific state after an action has been invoked + * @param {int[]} squares array of index where squares should have a value > 0 + * @param {int[]} vals array of values for the squares array parameter + */ + function checkBoardState(squares, vals) { + let additional2 = false + for (let i = 1; i < 17; i++) { + if (!squares.includes(i)) { + if ( + document.getElementById('grid').children[i].innerHTML !== '0' && + !additional2 + ) { + additional2 = true + } else { + if (document.getElementById('grid').children[i].innerHTML !== '0') + console.log( + 'error at ' + + i + + ' => ' + + document.getElementById('grid').children[i].innerHTML + ) + expect(document.getElementById('grid').children[i].innerHTML).toBe( + '0' + ) + } + } else { + /*console.log( + 'square ' + + i + + ' => ' + + document.getElementById('grid').children[i].innerHTML + )*/ + + expect(document.getElementById('grid').children[i].innerHTML).toBe( + vals[squares.indexOf(i)].toString() + ) + } + } + } + test('checks game win ending', async () => { + jest.requireActual('../js/2048game') + await new Promise((resolve) => document.addEventListener('load', resolve)) + initBoard([]) + addBoard(5, 1024) + addBoard(6, 1024) + + //mocks alert because of an undefined error due to the mocking of window + jest.spyOn(window, 'alert').mockImplementation(() => {}) + fireEvent.keyUp(document, { key: 'd', keyCode: 68 }) + checkBoardState([8], [2048]) + + expect(document.getElementById('result').innerHTML).toBe('YOU WON!') + }) +}) diff --git a/static/tests/connect-four.test.js b/static/tests/connect-four.test.js new file mode 100644 index 0000000..b48e737 --- /dev/null +++ b/static/tests/connect-four.test.js @@ -0,0 +1,206 @@ +import { findAllByText, fireEvent } from '@testing-library/dom' + +import { JSDOM } from 'jsdom' +const fs = require('fs') +const Storage = require('dom-storage') + +const html = fs.readFileSync('./pages/connect-four-game.html') +const dom = new JSDOM(html, { + resources: 'usable', + runScripts: 'dangerously', +}) + +global.document = dom.window.document +global.window = dom.window +global.localStorage = new Storage(null, { strict: true }) +global.sessionStorage = new Storage(null, { strict: true }) +const msg = jest.fn() +global.swal = jest.fn(async () => { + msg() + fireEvent.click(document.getElementById('reset-btn')) +}) + +describe('connect-four test suite', () => { + jest.useFakeTimers() + jest + .spyOn(window.HTMLMediaElement.prototype, 'play') + .mockImplementation(() => {}) + window.clicksound = jest.fn(() => {}) + + /** + * checks if the cases with index values contained in the indexes array contains the values contained in the vals array. + * The rest of the grid will check if the values are empty + * @param {int[]} indexes list of indexes + * @param {int[]} vals list of wanted values + */ + function checkGrid(indexes, vals) { + expect(document.getElementsByClassName('row').length).toBe(6) + expect(document.getElementsByClassName('col').length).toBe(7 * 6) + for (let i = 0; i < document.getElementsByClassName('col').length; i++) { + if (!indexes.includes(i)) { + expect( + document.getElementsByClassName('col')[i].children[0].className + ).toBe(`btn btn-${i + 1} grow`) + } else { + expect( + document.getElementsByClassName('col')[i].children[0].className + ).toBe(`btn btn-${i + 1} grow btn-player-${vals[indexes.indexOf(i)]}`) + expect( + document.getElementsByClassName('col')[i].children[0] + ).toHaveProperty('disabled') + } + } + } + + test('testing game init', async () => { + jest.requireActual('../js/connect-four') + await new Promise((resolve) => document.addEventListener('load', resolve)) + expect(document.getElementById('player-type').innerHTML).toBe('Player - 1') + checkGrid([], []) + }) + + test('testing user inputs', () => { + expect(document.getElementById('player-type').innerHTML).toBe('Player - 1') + fireEvent.click(document.getElementsByClassName('col')[10].children[0]) + checkGrid([10], [1]) + expect(document.getElementById('player-type').innerHTML).toBe('Player - 2') + + fireEvent.click(document.getElementsByClassName('col')[18].children[0]) + checkGrid([10, 18], [1, 2]) + expect(document.getElementById('player-type').innerHTML).toBe('Player - 1') + fireEvent.click(document.getElementsByClassName('col')[19].children[0]) + expect(document.getElementById('player-type').innerHTML).toBe('Player - 2') + }) + + test('testing board reset button', () => { + fireEvent.click(document.getElementById('reset-btn')) + checkGrid([], []) + expect(document.getElementById('player-type').innerHTML).toBe('Player - 1') + }) + + test('testing player 1 win', async () => { + //horizontal + fireEvent.click(document.getElementsByClassName('col')[0].children[0]) + fireEvent.click(document.getElementsByClassName('col')[8].children[0]) + fireEvent.click(document.getElementsByClassName('col')[1].children[0]) + fireEvent.click(document.getElementsByClassName('col')[9].children[0]) + fireEvent.click(document.getElementsByClassName('col')[2].children[0]) + fireEvent.click(document.getElementsByClassName('col')[10].children[0]) + fireEvent.click(document.getElementsByClassName('col')[3].children[0]) + checkGrid([], []) + expect(msg).toHaveBeenCalledTimes(1) + + //vertical + fireEvent.click(document.getElementsByClassName('col')[0].children[0]) + fireEvent.click(document.getElementsByClassName('col')[8].children[0]) + fireEvent.click(document.getElementsByClassName('col')[7].children[0]) + fireEvent.click(document.getElementsByClassName('col')[9].children[0]) + fireEvent.click(document.getElementsByClassName('col')[14].children[0]) + fireEvent.click(document.getElementsByClassName('col')[10].children[0]) + fireEvent.click(document.getElementsByClassName('col')[21].children[0]) + checkGrid([], []) + expect(msg).toHaveBeenCalledTimes(2) + + //diagonal1 + fireEvent.click(document.getElementsByClassName('col')[0].children[0]) + fireEvent.click(document.getElementsByClassName('col')[1].children[0]) + fireEvent.click(document.getElementsByClassName('col')[8].children[0]) + fireEvent.click(document.getElementsByClassName('col')[2].children[0]) + fireEvent.click(document.getElementsByClassName('col')[16].children[0]) + fireEvent.click(document.getElementsByClassName('col')[3].children[0]) + fireEvent.click(document.getElementsByClassName('col')[24].children[0]) + checkGrid([], []) + expect(msg).toHaveBeenCalledTimes(3) + + //diagonal0 + fireEvent.click(document.getElementsByClassName('col')[6].children[0]) + fireEvent.click(document.getElementsByClassName('col')[1].children[0]) + fireEvent.click(document.getElementsByClassName('col')[12].children[0]) + fireEvent.click(document.getElementsByClassName('col')[2].children[0]) + fireEvent.click(document.getElementsByClassName('col')[18].children[0]) + fireEvent.click(document.getElementsByClassName('col')[3].children[0]) + fireEvent.click(document.getElementsByClassName('col')[24].children[0]) + checkGrid([], []) + expect(msg).toHaveBeenCalledTimes(4) + }) + + test('testing player 2 win', async () => { + //horizontal + fireEvent.click(document.getElementsByClassName('col')[8].children[0]) + fireEvent.click(document.getElementsByClassName('col')[0].children[0]) + fireEvent.click(document.getElementsByClassName('col')[9].children[0]) + fireEvent.click(document.getElementsByClassName('col')[1].children[0]) + fireEvent.click(document.getElementsByClassName('col')[10].children[0]) + fireEvent.click(document.getElementsByClassName('col')[2].children[0]) + fireEvent.click(document.getElementsByClassName('col')[40].children[0]) + fireEvent.click(document.getElementsByClassName('col')[3].children[0]) + checkGrid([], []) + expect(msg).toHaveBeenCalledTimes(1) + + //vertical + fireEvent.click(document.getElementsByClassName('col')[8].children[0]) + fireEvent.click(document.getElementsByClassName('col')[0].children[0]) + fireEvent.click(document.getElementsByClassName('col')[9].children[0]) + fireEvent.click(document.getElementsByClassName('col')[7].children[0]) + fireEvent.click(document.getElementsByClassName('col')[10].children[0]) + fireEvent.click(document.getElementsByClassName('col')[14].children[0]) + fireEvent.click(document.getElementsByClassName('col')[40].children[0]) + fireEvent.click(document.getElementsByClassName('col')[21].children[0]) + checkGrid([], []) + expect(msg).toHaveBeenCalledTimes(2) + + //diagonal1 + fireEvent.click(document.getElementsByClassName('col')[1].children[0]) + fireEvent.click(document.getElementsByClassName('col')[0].children[0]) + fireEvent.click(document.getElementsByClassName('col')[2].children[0]) + fireEvent.click(document.getElementsByClassName('col')[8].children[0]) + fireEvent.click(document.getElementsByClassName('col')[3].children[0]) + fireEvent.click(document.getElementsByClassName('col')[16].children[0]) + fireEvent.click(document.getElementsByClassName('col')[40].children[0]) + fireEvent.click(document.getElementsByClassName('col')[24].children[0]) + checkGrid([], []) + expect(msg).toHaveBeenCalledTimes(3) + + //diagonal0 + fireEvent.click(document.getElementsByClassName('col')[1].children[0]) + fireEvent.click(document.getElementsByClassName('col')[6].children[0]) + fireEvent.click(document.getElementsByClassName('col')[2].children[0]) + fireEvent.click(document.getElementsByClassName('col')[12].children[0]) + fireEvent.click(document.getElementsByClassName('col')[3].children[0]) + fireEvent.click(document.getElementsByClassName('col')[18].children[0]) + fireEvent.click(document.getElementsByClassName('col')[40].children[0]) + fireEvent.click(document.getElementsByClassName('col')[24].children[0]) + checkGrid([], []) + expect(msg).toHaveBeenCalledTimes(4) + }) + + test('testing draw', () => { + for (let i = 0; i < 21; i++) { + fireEvent.click(document.getElementsByClassName('col')[i].children[0]) + } + fireEvent.click(document.getElementsByClassName('col')[22].children[0]) + fireEvent.click(document.getElementsByClassName('col')[21].children[0]) + fireEvent.click(document.getElementsByClassName('col')[24].children[0]) + fireEvent.click(document.getElementsByClassName('col')[23].children[0]) + fireEvent.click(document.getElementsByClassName('col')[26].children[0]) + fireEvent.click(document.getElementsByClassName('col')[25].children[0]) + + fireEvent.click(document.getElementsByClassName('col')[29].children[0]) + fireEvent.click(document.getElementsByClassName('col')[27].children[0]) + fireEvent.click(document.getElementsByClassName('col')[31].children[0]) + fireEvent.click(document.getElementsByClassName('col')[28].children[0]) + fireEvent.click(document.getElementsByClassName('col')[33].children[0]) + fireEvent.click(document.getElementsByClassName('col')[30].children[0]) + fireEvent.click(document.getElementsByClassName('col')[41].children[0]) + fireEvent.click(document.getElementsByClassName('col')[32].children[0]) + fireEvent.click(document.getElementsByClassName('col')[39].children[0]) + fireEvent.click(document.getElementsByClassName('col')[40].children[0]) + fireEvent.click(document.getElementsByClassName('col')[37].children[0]) + fireEvent.click(document.getElementsByClassName('col')[38].children[0]) + fireEvent.click(document.getElementsByClassName('col')[35].children[0]) + fireEvent.click(document.getElementsByClassName('col')[36].children[0]) + fireEvent.click(document.getElementsByClassName('col')[34].children[0]) + checkGrid([], []) + expect(msg).toHaveBeenCalledTimes(1) + }) +}) diff --git a/static/tests/tic-tac-toe.test.js b/static/tests/tic-tac-toe.test.js new file mode 100644 index 0000000..a5e5356 --- /dev/null +++ b/static/tests/tic-tac-toe.test.js @@ -0,0 +1,122 @@ +import { findAllByText, fireEvent } from '@testing-library/dom' + +import { JSDOM } from 'jsdom' +const fs = require('fs') +const Storage = require('dom-storage') + +const html = fs.readFileSync('./pages/tic-tac-toe.html') +const dom = new JSDOM(html, { + resources: 'usable', + runScripts: 'dangerously', +}) + +global.document = dom.window.document +global.window = dom.window +global.localStorage = new Storage(null, { strict: true }) +global.sessionStorage = new Storage(null, { strict: true }) +global.swal = jest.fn() + +describe('tic-tac-toe game test suite', () => { + jest.useFakeTimers() + jest + .spyOn(window.HTMLMediaElement.prototype, 'play') + .mockImplementation(() => {}) + + function resetGrid() { + for (let i = 0; i < document.getElementsByTagName('td').length; i++) { + document.getElementsByTagName('td')[i].innerHTML = ' ' + } + } + + /** + * checks if the cases with index values contained in the indexes array contains the symbols contained in the symbols array. + * The rest of the grid will check if the symbols are empty + * @param {int[]} indexes list of indexes + * @param {int[]} symbols list of wanted symbols + */ + function checkSymbols(indexes, symbols) { + for (let i = 0; i < document.getElementsByTagName('td').length; i++) { + if (!indexes.includes(i)) + expect(document.getElementsByTagName('td')[i].innerHTML).toBe(' ') + else { + expect(document.getElementsByTagName('td')[i].innerHTML).toBe( + symbols[indexes.indexOf(i)] + ) + } + } + } + + /** + * Adds the symbols contained in symbols to the grid positions whose indexes are referenced in the indexes array + * @param {int[]} indexes list of indexes + * @param {int[]} symbols list of wanted symbols + */ + function addSymbols(indexes, symbols) { + for (let i = 0; i < indexes.length; i++) { + document.getElementsByTagName('td')[indexes[i]].innerHTML = symbols[i] + } + } + + test('test game grid initialisation', async () => { + jest.requireActual('../js/tic-tac-toe') + await new Promise((resolve) => document.addEventListener('load', resolve)) + + expect(document.getElementsByTagName('table')[0].children.length).toBe(3) + expect(document.getElementsByTagName('td').length).toBe(9) + let row = 0 + let col = 0 + for (let i = 0; i < 9; i++) { + if (i > 0 && i % 3 === 0) { + col = 0 + row += 1 + } + let cName = `col${col} row${row}` + if (i === 0 || i === 4 || i === 8) cName += ` diagonal0` + if (i === 2 || i === 4 || i === 6) cName += ` diagonal1` + expect(document.getElementsByTagName('td')[i].className).toBe(cName) + col += 1 + } + }) + + test('test user input', () => { + fireEvent.click(document.getElementsByTagName('td')[0]) + fireEvent.click(document.getElementsByTagName('td')[7]) + fireEvent.click(document.getElementsByTagName('td')[7]) + fireEvent.click(document.getElementsByTagName('td')[8]) + fireEvent.click(document.getElementsByTagName('td')[6]) + checkSymbols([0, 7, 8, 6], ['X', 'O', 'X', 'O']) + expect((document.getElementById('turn').textContent = 'Player ' + 'X')) + }) + + test('test user0 win', () => { + resetGrid() + addSymbols([0, 1], ['X', 'X']) + fireEvent.click(document.getElementsByTagName('td')[2]) + checkSymbols([], []) + }) + + test('test user1 win', () => { + fireEvent.click(document.getElementsByTagName('td')[5]) + expect((document.getElementById('turn').textContent = 'Player ' + 'O')) + addSymbols([0, 1], ['O', 'O']) + fireEvent.click(document.getElementsByTagName('td')[2]) + checkSymbols([], []) + }) + + test('test draw', () => { + fireEvent.click(document.getElementsByTagName('td')[0]) + fireEvent.click(document.getElementsByTagName('td')[1]) + fireEvent.click(document.getElementsByTagName('td')[2]) + fireEvent.click(document.getElementsByTagName('td')[4]) + fireEvent.click(document.getElementsByTagName('td')[3]) + fireEvent.click(document.getElementsByTagName('td')[5]) + fireEvent.click(document.getElementsByTagName('td')[8]) + fireEvent.click(document.getElementsByTagName('td')[6]) + + expect((document.getElementById('turn').textContent = 'Player ' + 'X')) + fireEvent.click(document.getElementsByTagName('td')[5]) + fireEvent.click(document.getElementsByTagName('td')[7]) + checkSymbols([], []) + expect((document.getElementById('turn').textContent = 'Player ' + 'X')) + }) +})