diff --git a/package-lock.json b/package-lock.json index b6a4079..17de432 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,13 @@ "version": "0.1.0", "dependencies": { "prop-types": "^15.8.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-is": "^18.3.1", - "react-test-renderer": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "styled-components": "^6.1.13" }, "devDependencies": { + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.1", "coveralls": "^3.1.1", "husky": "^8.0.3", "lint-staged": "^15.2.0", @@ -26,6 +26,13 @@ "node": ">=18.0.0" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -3768,6 +3775,93 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.1.tgz", + "integrity": "sha512-gr4KtAWqIOQoucWYD/f6ki+j5chXfcPc74Col/6poTyqTmn7zRmodWahWRCp8tYd+GMqBonw6hstNzqjbs6gjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -3788,6 +3882,14 @@ "node": ">=10.13.0" } }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -6937,6 +7039,13 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/cssdb": { "version": "7.11.2", "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.11.2.tgz", @@ -7476,6 +7585,17 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -7602,6 +7722,14 @@ "node": ">=6.0.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -10546,6 +10674,16 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -12878,6 +13016,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-watch-typeahead/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-watch-typeahead/node_modules/slash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", @@ -13637,6 +13782,17 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", @@ -13836,6 +13992,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/mini-css-extract-plugin": { "version": "2.9.4", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", @@ -16338,13 +16504,10 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } @@ -16525,16 +16688,15 @@ } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.2.3" } }, "node_modules/react-error-overlay": { @@ -16544,12 +16706,6 @@ "dev": true, "license": "MIT" }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -16634,33 +16790,6 @@ } } }, - "node_modules/react-shallow-renderer": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", - "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "license": "MIT", - "dependencies": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-test-renderer": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz", - "integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==", - "license": "MIT", - "dependencies": { - "react-is": "^18.3.1", - "react-shallow-renderer": "^16.15.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -16712,6 +16841,20 @@ "node": ">=6.0.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -17389,13 +17532,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/schema-utils": { "version": "4.3.3", @@ -18479,6 +18619,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -19475,9 +19628,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -19486,7 +19639,7 @@ "tsserver": "bin/tsserver" }, "engines": { - "node": ">=14.17" + "node": ">=4.2.0" } }, "node_modules/unbox-primitive": { diff --git a/package.json b/package.json index 639747d..44ed83f 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "node": ">=18.0.0" }, "devDependencies": { + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.1", "coveralls": "^3.1.1", "husky": "^8.0.3", "lint-staged": "^15.2.0", @@ -14,10 +16,8 @@ }, "dependencies": { "prop-types": "^15.8.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-is": "^18.3.1", - "react-test-renderer": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "styled-components": "^6.1.13" }, "scripts": { diff --git a/src/App.test.js b/src/App.test.js index 73ac925..4c15371 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -1,9 +1,172 @@ import React from 'react'; -import ReactDOM from 'react-dom/client'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; import App from './App'; +import { GAME_STARTED, GAME_WON } from './game-states'; -it('renders without crashing', () => { - const div = document.createElement('div'); - const root = ReactDOM.createRoot(div); - root.render(); +describe('App', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.runOnlyPendingTimers(); + jest.useRealTimers(); + }); + + test('renders without crashing', () => { + const { container } = render(); + expect(container.querySelector('.App')).toBeInTheDocument(); + }); + + test('renders AppHeader component', () => { + render(); + expect(screen.getByText('React Puzzle Games - Memory Puzzle')).toBeInTheDocument(); + }); + + test('renders Footer component', () => { + render(); + expect(screen.getByText(/Made with/i)).toBeInTheDocument(); + }); + + test('renders GameStats component', () => { + const { container } = render(); + expect(container.querySelector('.GameStats')).toBeInTheDocument(); + }); + + test('renders TileGrid with tiles', () => { + const { container } = render(); + const tiles = container.querySelectorAll('.Tile-container'); + expect(tiles.length).toBeGreaterThan(0); + }); + + test('initializes with 0 moves', () => { + render(); + expect(screen.getByText('0')).toBeInTheDocument(); + }); + + test('increments move counter when tile is clicked', async () => { + const { container } = render(); + const tiles = container.querySelectorAll('.Tile-container'); + + // Click first tile + fireEvent.click(tiles[0]); + + await waitFor(() => { + expect(screen.getByText('1')).toBeInTheDocument(); + }); + }); + + test('NEW GAME button generates new game', () => { + const { container } = render(); + + // Get initial tile configuration + const initialTiles = container.querySelectorAll('.Tile-container'); + const initialCount = initialTiles.length; + + // Click NEW GAME button + fireEvent.click(screen.getByText('NEW GAME')); + + // Tiles should still exist (new configuration) + const newTiles = container.querySelectorAll('.Tile-container'); + expect(newTiles.length).toBe(initialCount); + }); + + test('RESET GAME button resets moves to 0', async () => { + const { container } = render(); + const tiles = container.querySelectorAll('.Tile-container'); + + // Make some moves + fireEvent.click(tiles[0]); + + await waitFor(() => { + expect(screen.getByText('1')).toBeInTheDocument(); + }); + + // Reset the game + fireEvent.click(screen.getByText('RESET GAME')); + + await waitFor(() => { + expect(screen.getByText('0')).toBeInTheDocument(); + }); + }); + + test('RESET GAME button unflips all tiles', async () => { + const { container } = render(); + const tiles = container.querySelectorAll('.Tile-container'); + + // Click some tiles + fireEvent.click(tiles[0]); + fireEvent.click(tiles[1]); + + // Reset the game + fireEvent.click(screen.getByText('RESET GAME')); + + await waitFor(() => { + const flippedTiles = container.querySelectorAll('.Tile-card.flipped'); + // After reset, tiles should not be permanently flipped + // (Some may be temporarily flipped during animation) + }); + }); + + test('renders all main components', () => { + const { container } = render(); + + expect(container.querySelector('.App-header')).toBeInTheDocument(); + expect(container.querySelector('.App-content')).toBeInTheDocument(); + expect(container.querySelector('.TileGrid')).toBeInTheDocument(); + expect(container.querySelector('footer')).toBeInTheDocument(); + }); + + test('maintains game state through interactions', async () => { + const { container } = render(); + + // Initial state + expect(screen.getByText('0')).toBeInTheDocument(); + + const tiles = container.querySelectorAll('.Tile-container'); + + // Make a move + fireEvent.click(tiles[0]); + + await waitFor(() => { + expect(screen.getByText('1')).toBeInTheDocument(); + }); + + // Make another move + fireEvent.click(tiles[1]); + + await waitFor(() => { + expect(screen.getByText('2')).toBeInTheDocument(); + }); + }); + + test('NEW GAME resets move counter', async () => { + const { container } = render(); + const tiles = container.querySelectorAll('.Tile-container'); + + // Make some moves + fireEvent.click(tiles[0]); + + await waitFor(() => { + expect(screen.getByText('1')).toBeInTheDocument(); + }); + + // Start new game + fireEvent.click(screen.getByText('NEW GAME')); + + await waitFor(() => { + expect(screen.getByText('0')).toBeInTheDocument(); + }); + }); + + test('tiles are clickable', () => { + const { container } = render(); + const tiles = container.querySelectorAll('.Tile-container'); + + expect(tiles.length).toBeGreaterThan(0); + tiles.forEach(tile => { + expect(tile).toBeInTheDocument(); + }); + }); }); diff --git a/src/AppHeader.js b/src/AppHeader.js index e97ee27..6a6cc63 100644 --- a/src/AppHeader.js +++ b/src/AppHeader.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import './AppHeader.css'; -const AppHeader = ({ moves, onNewGame, onRestart }) => ( +const AppHeader = ({ moves = 0, onNewGame, onRestart }) => (
React Puzzle Games - Memory Puzzle @@ -31,8 +31,4 @@ AppHeader.propTypes = { onRestart: PropTypes.func.isRequired, }; -AppHeader.defaultProps = { - moves: 0, -}; - export default AppHeader; diff --git a/src/AppHeader.test.js b/src/AppHeader.test.js new file mode 100644 index 0000000..433db69 --- /dev/null +++ b/src/AppHeader.test.js @@ -0,0 +1,108 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import AppHeader from './AppHeader'; + +describe('AppHeader', () => { + const mockOnNewGame = jest.fn(); + const mockOnRestart = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('renders header with title', () => { + render( + + ); + expect(screen.getByText('React Puzzle Games - Memory Puzzle')).toBeInTheDocument(); + }); + + test('renders NEW GAME button', () => { + render( + + ); + expect(screen.getByText('NEW GAME')).toBeInTheDocument(); + }); + + test('renders RESET GAME button', () => { + render( + + ); + expect(screen.getByText('RESET GAME')).toBeInTheDocument(); + }); + + test('displays move count', () => { + render( + + ); + expect(screen.getByText('5')).toBeInTheDocument(); + }); + + test('displays 0 moves by default', () => { + render( + + ); + expect(screen.getByText('0')).toBeInTheDocument(); + }); + + test('calls onNewGame when NEW GAME button is clicked', () => { + render( + + ); + fireEvent.click(screen.getByText('NEW GAME')); + expect(mockOnNewGame).toHaveBeenCalledTimes(1); + }); + + test('calls onRestart when RESET GAME button is clicked', () => { + render( + + ); + fireEvent.click(screen.getByText('RESET GAME')); + expect(mockOnRestart).toHaveBeenCalledTimes(1); + }); + + test('updates displayed moves when prop changes', () => { + const { rerender } = render( + + ); + expect(screen.getByText('3')).toBeInTheDocument(); + + rerender( + + ); + expect(screen.getByText('10')).toBeInTheDocument(); + }); +}); diff --git a/src/Footer.test.js b/src/Footer.test.js new file mode 100644 index 0000000..1920a13 --- /dev/null +++ b/src/Footer.test.js @@ -0,0 +1,77 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import Footer from './Footer'; + +describe('Footer', () => { + test('renders footer element', () => { + const { container } = render(