├── .babelrc ├── .editorconfig ├── .eslintrc ├── .firebaserc ├── .gitignore ├── .nvmrc ├── .prettierrc ├── .storybook ├── Storyshots.test.js ├── __snapshots__ │ └── Storyshots.test.js.snap ├── addons.js ├── config.js ├── preview-head.html └── webpack.config.js ├── .travis.yml ├── LICENSE ├── README.md ├── __tests__ ├── Layout.test.js ├── actions │ └── actions.test.js ├── components │ ├── Button.test.js │ ├── ButtonContainer.test.js │ ├── Code.test.js │ ├── CodePreTag.test.jsx │ ├── Header.test.js │ ├── Pattern.test.js │ ├── PatternsList.test.js │ ├── Percentage.test.js │ ├── ProgressBar.test.js │ ├── Result.test.js │ ├── SVG.test.js │ ├── Title.test.js │ ├── Toggle.test.js │ └── __snapshots__ │ │ ├── Button.test.js.snap │ │ ├── Code.test.js.snap │ │ ├── CodePreTag.test.jsx.snap │ │ ├── Header.test.js.snap │ │ ├── Pattern.test.js.snap │ │ ├── PatternsList.test.js.snap │ │ ├── Percentage.test.js.snap │ │ ├── ProgressBar.test.js.snap │ │ ├── Result.test.js.snap │ │ ├── SVG.test.js.snap │ │ ├── Title.test.js.snap │ │ └── Toggle.test.js.snap ├── config │ ├── configure-enzyme.js │ └── register-context.js ├── helpers │ └── index.js ├── localStorage.js ├── middleware │ ├── submit.test.js │ └── toggle.test.js ├── pages │ ├── About.test.js │ ├── Game.test.js │ ├── Patterns.test.js │ └── __snapshots__ │ │ ├── About.test.js.snap │ │ ├── Game.test.js.snap │ │ └── Patterns.test.js.snap ├── randomFromRange.test.js ├── reducers │ └── reducers.test.js ├── selectors │ └── selectors.test.js ├── store │ └── store.test.js └── styles │ ├── __snapshots__ │ └── global.test.js.snap │ └── global.test.js ├── cypress.json ├── cypress └── integration │ └── about.spec.js ├── firebase.json ├── index.html ├── index.js ├── package.json ├── src ├── Layout.jsx ├── actions │ ├── restart.js │ ├── start.js │ ├── submitAnswer.js │ └── toggle.js ├── components │ ├── Button.jsx │ ├── ButtonContainer.jsx │ ├── Code.jsx │ ├── CodePreTag.jsx │ ├── Footer.jsx │ ├── Header.jsx │ ├── Pattern.jsx │ ├── PatternsList.jsx │ ├── Percentage.jsx │ ├── ProgressBar.jsx │ ├── Result.jsx │ ├── ScrollToTop.js │ ├── Svg.jsx │ ├── Title.jsx │ └── Toggle.jsx ├── helpers │ ├── localStorage.js │ ├── randomFromRange.js │ └── shuffleArray.js ├── middleware │ ├── index.js │ ├── submit.js │ └── toggle.js ├── pages │ ├── About.jsx │ ├── Game.jsx │ └── Patterns.jsx ├── reducers │ └── index.js ├── selectors │ └── index.js ├── static │ ├── constants │ │ ├── actions.js │ │ ├── colors.js │ │ └── themes.js │ ├── icons │ │ └── index.js │ └── patterns │ │ ├── behavioral_chainOfResponsibility.js │ │ ├── behavioral_command.js │ │ ├── behavioral_interpreter.js │ │ ├── behavioral_iterator.js │ │ ├── behavioral_mediator.js │ │ ├── behavioral_memento.js │ │ ├── behavioral_observer.js │ │ ├── behavioral_state.js │ │ ├── behavioral_strategy.js │ │ ├── behavioral_template.js │ │ ├── behavioral_visitor.js │ │ ├── creational_abstractFactory.js │ │ ├── creational_builder.js │ │ ├── creational_factory.js │ │ ├── creational_prototype.js │ │ ├── creational_singleton.js │ │ ├── index.js │ │ ├── structural_adapter.js │ │ ├── structural_bridge.js │ │ ├── structural_composite.js │ │ ├── structural_decorator.js │ │ ├── structural_facade.js │ │ ├── structural_flyweight.js │ │ └── structural_proxy.js ├── store │ └── index.js └── styles │ ├── global.js │ ├── hljs │ ├── hljs.dark.js │ └── hljs.light.js │ └── themes │ ├── theme.common.js │ ├── theme.dark.js │ └── theme.light.js ├── static └── screenshot.png ├── stories ├── Button.stories.js ├── Percentage.stories.js ├── ProgressBar.stories.js ├── Provider.js ├── Title.stories.js └── Toggle.stories.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": ["@babel/plugin-proposal-class-properties"], 4 | "env": { 5 | "test": { 6 | "plugins": ["require-context-hook"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "react", 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "jest": true, 8 | "es6": true 9 | }, 10 | "settings": { 11 | "react": { 12 | "version": "detect" 13 | } 14 | }, 15 | "rules": { 16 | "indent": ["error", 2], 17 | "space-before-function-paren": ["off"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "design-patterns-game-test" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | /cypress/videos 10 | /cypress/screenshots 11 | /cypress/fixtures 12 | /cypress/plugins 13 | /cypress/support 14 | /cypress/integration/examples 15 | 16 | # production 17 | /build 18 | 19 | # analyzer 20 | /stats 21 | 22 | # misc 23 | .DS_Store 24 | .env.local 25 | .env.development.local 26 | .env.test.local 27 | .env.production.local 28 | 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v11.10.1 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "bracketSpacing": true, 6 | "printWidth": 100 7 | } 8 | -------------------------------------------------------------------------------- /.storybook/Storyshots.test.js: -------------------------------------------------------------------------------- 1 | import initStoryshots from '@storybook/addon-storyshots'; 2 | import 'jest-styled-components'; 3 | 4 | initStoryshots(); 5 | -------------------------------------------------------------------------------- /.storybook/__snapshots__/Storyshots.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Storyshots Button default 1`] = `null`; 4 | 5 | exports[`Storyshots Percentage between 40 and 70 1`] = `null`; 6 | 7 | exports[`Storyshots Percentage over 70 1`] = `null`; 8 | 9 | exports[`Storyshots Percentage under 40 1`] = `null`; 10 | 11 | exports[`Storyshots ProgressBar all questions correct 1`] = `null`; 12 | 13 | exports[`Storyshots ProgressBar all questions wrong 1`] = `null`; 14 | 15 | exports[`Storyshots ProgressBar unanswered questions 1`] = `null`; 16 | 17 | exports[`Storyshots Title default 1`] = `null`; 18 | 19 | exports[`Storyshots Toggle js 1`] = `null`; 20 | 21 | exports[`Storyshots Toggle mode 1`] = `null`; 22 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-actions/register'; 2 | import '@storybook/addon-knobs/register'; 3 | import '@storybook/addon-links/register'; 4 | import 'storybook-addon-styled-component-theme/dist/src/register'; 5 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure, addDecorator } from '@storybook/react'; 2 | import { withThemesProvider } from 'storybook-addon-styled-component-theme'; 3 | import { themeCommon } from '../src/styles/themes/theme.common'; 4 | import { themeLight } from '../src/styles/themes/theme.light'; 5 | import { themeDark } from '../src/styles/themes/theme.dark'; 6 | 7 | const light = { ...themeCommon, ...themeLight }; 8 | const dark = { ...themeCommon, ...themeDark }; 9 | 10 | const themes = [light, dark]; 11 | addDecorator(withThemesProvider(themes)); 12 | 13 | const req = require.context('../stories', true, /.stories.js$/); 14 | 15 | function loadStories() { 16 | req.keys().forEach(filename => req(filename)); 17 | } 18 | 19 | configure(loadStories, module); 20 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | // you can use this file to add your custom webpack plugins, loaders and anything you like. 2 | // This is just the basic way to add additional webpack configurations. 3 | // For more information refer the docs: https://storybook.js.org/configurations/custom-webpack-config 4 | 5 | // IMPORTANT 6 | // When you add this file, we won't add the default configurations which is similar 7 | // to "React Create App". This only has babel loader to load JavaScript. 8 | 9 | module.exports = { 10 | plugins: [ 11 | // your custom plugins 12 | ], 13 | module: { 14 | rules: [ 15 | // add your custom rules. 16 | ], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10.16.0 4 | 5 | addons: 6 | apt: 7 | packages: 8 | - libgconf-2-4 9 | 10 | branches: 11 | only: 12 | - master 13 | - /^greenkeeper.*$/ 14 | 15 | script: 16 | - yarn 17 | - yarn build 18 | - yarn test 19 | - yarn test:coverage 20 | - yarn start:ci 21 | - yarn cypress:run 22 | 23 | after_success: 24 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 25 | 26 | deploy: 27 | provider: firebase 28 | token: 29 | secure: $FIREBASE_TOKEN 30 | skip_cleanup: true 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Design Patterns Game 2 | 3 | [![Build Status](https://travis-ci.org/zoltantothcom/Design-Patterns-JavaScript.svg?branch=master)](https://travis-ci.org/zoltantothcom/Design-Patterns-JavaScript) [![Coverage Status](https://coveralls.io/repos/github/zoltantothcom/Design-Patterns-JavaScript/badge.svg?branch=master)](https://coveralls.io/github/zoltantothcom/Design-Patterns-JavaScript?branch=master) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/5f4e97b771504e23b0b414d2cbc69506)](https://www.codacy.com/app/zoltantothcom/Design-Patterns-JavaScript) [![Greenkeeper badge](https://badges.greenkeeper.io/zoltantothcom/Design-Patterns-JavaScript.svg)](https://greenkeeper.io/) 4 | 5 | Design Patterns - a quick game to test your familiarity with the 'Gang of Four' design patterns implemented in JS. 6 | 7 | ### :zap: [PLAY HERE](https://designpatternsgame.com/) 8 | 9 | ![Design Patterns - game results screenshot](/static/screenshot.png?sanitize=true&raw=true) 10 | 11 | - [About](#about) 12 | - [How To Run Locally](#how-to-run-locally) 13 | - [Running the Tests](#running-the-tests) 14 | - [Inspiration and Credits](#inspiration-and-credits) 15 | - [License](#license) 16 | 17 | ## About 18 | 19 | This is a small game to get you familiar with all the 23 Gang of Four design patterns implemented in JavaScript. 20 | 21 | ### Creational Patterns 22 | 23 | - Abstract factory 24 | - Builder 25 | - Factory method 26 | - Prototype 27 | - Singleton 28 | 29 | ### Structural Patterns 30 | 31 | - Adapter 32 | - Bridge 33 | - Composite 34 | - Decorator 35 | - Facade 36 | - Flyweight 37 | - Proxy 38 | 39 | ### Behavioral Patterns 40 | 41 | - Chain of responsibility 42 | - Command 43 | - Interpreter 44 | - Iterator 45 | - Mediator 46 | - Memento 47 | - Observer 48 | - State 49 | - Strategy 50 | - Template 51 | - Visitor 52 | 53 | ## How To Run Locally 54 | 55 | 1. Clone this repo 56 | 57 | ``` 58 | git clone git@github.com:zoltantothcom/Design-Patterns-JavaScript.git 59 | ``` 60 | 61 | 2. Switch to project folder 62 | 63 | ``` 64 | cd Design-Patterns-JavaScript 65 | ``` 66 | 67 | 3. Install all the dependencies 68 | 69 | ``` 70 | yarn 71 | ``` 72 | 73 | 4. Time to start the engine 74 | 75 | ``` 76 | yarn start 77 | ``` 78 | 79 | 5. Voila! The project is running on [http://localhost:8080](http://localhost:8080) 80 | 81 | ## Running the Tests 82 | 83 | - To run the tests 84 | 85 | ``` 86 | yarn test 87 | ``` 88 | 89 | - To see the detailed test coverage 90 | 91 | ``` 92 | yarn test:coverage 93 | ``` 94 | 95 | ## Inspiration and Credits 96 | 97 | - Inspired by the very well-known [JavaScript Guessing Game](https://javascript-game.firebaseapp.com/) 98 | 99 | - Code samples impudently taken from the awesome compilation of [Design Patterns JS](https://github.com/fbeline/Design-Patterns-JS) 100 | 101 | If you want a deeper dive into the subject feel free to check out [Learning JavaScript Design Patterns](https://addyosmani.com/resources/essentialjsdesignpatterns/book/) by _Addy Osmani_. 102 | 103 | ## License 104 | 105 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. 106 | 107 | See [Unlicense](http://unlicense.org) for full details. 108 | -------------------------------------------------------------------------------- /__tests__/Layout.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import 'jest-styled-components'; 3 | import { mount } from 'enzyme'; 4 | import { Provider } from 'react-redux'; 5 | import configureMockStore from 'redux-mock-store'; 6 | import { ThemeProvider } from 'styled-components'; 7 | import { themeLight } from '../src/styles/themes/theme.light'; 8 | import { themeDark } from '../src/styles/themes/theme.dark'; 9 | import { BrowserRouter } from 'react-router-dom'; 10 | import Layout from '../src/Layout'; 11 | 12 | const mockStore = configureMockStore(); 13 | const patterns = [ 14 | { 15 | uuid: 'abc123', 16 | name: 'Prototype', 17 | type: 'creational', 18 | codeES5: 'Code ES5 - Prototype', 19 | codeES6: 'Code ES6 - Prototype', 20 | answered: false, 21 | answerId: null, 22 | correct: null 23 | }, 24 | 25 | { 26 | uuid: 'abc234', 27 | name: 'SIngleton', 28 | type: 'creational', 29 | codeES5: 'Code ES5 - Singleton', 30 | codeES6: 'Code ES6 - Singleton', 31 | answered: false, 32 | answerId: null, 33 | correct: null 34 | } 35 | ]; 36 | 37 | describe('Layout', () => { 38 | describe('Header - LIGHT mode', () => { 39 | let tree; 40 | const store = mockStore({ 41 | patterns: patterns, 42 | progress: { 43 | answers: [], 44 | remaining: [patterns[1]], 45 | current: patterns[0] 46 | }, 47 | intro: false, 48 | mode: 'light', 49 | js: 'es5' 50 | }); 51 | 52 | beforeEach(() => { 53 | tree = mount( 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | ); 62 | }); 63 | 64 | it('has the correct title', () => { 65 | expect(tree.find('header h1').text()).toMatch('Design Patterns'); 66 | }); 67 | 68 | it('renders 2 links', () => { 69 | expect(tree.find('header a')).toHaveLength(2); 70 | }); 71 | 72 | it('renders 1 span', () => { 73 | expect(tree.find('header span')).toHaveLength(1); 74 | }); 75 | 76 | it('renders 2 toggle buttons', () => { 77 | expect(tree.find('Header button')).toHaveLength(2); 78 | }); 79 | }); 80 | 81 | describe('Header - DARK mode', () => { 82 | let tree; 83 | const store = mockStore({ 84 | patterns: patterns, 85 | progress: { 86 | answers: [], 87 | remaining: [patterns[1]], 88 | current: patterns[0] 89 | }, 90 | intro: false, 91 | mode: 'dark', 92 | js: 'es5' 93 | }); 94 | 95 | beforeEach(() => { 96 | tree = mount( 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | ); 105 | }); 106 | 107 | it('has the correct title', () => { 108 | expect(tree.find('header h1').text()).toMatch('Design Patterns'); 109 | }); 110 | 111 | it('renders 2 links', () => { 112 | expect(tree.find('header a')).toHaveLength(2); 113 | }); 114 | 115 | it('renders 1 span', () => { 116 | expect(tree.find('header span')).toHaveLength(1); 117 | }); 118 | 119 | it('renders 2 toggle buttons', () => { 120 | expect(tree.find('Header button')).toHaveLength(2); 121 | }); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /__tests__/actions/actions.test.js: -------------------------------------------------------------------------------- 1 | import { start } from '../../src/actions/start'; 2 | import { restart } from '../../src/actions/restart'; 3 | import { submitAnswer } from '../../src/actions/submitAnswer'; 4 | import { toggle, toggleJS, toggleMode } from '../../src/actions/toggle'; 5 | import { 6 | SUBMIT, 7 | TOGGLE, 8 | TOGGLE_JS, 9 | TOGGLE_MODE, 10 | START, 11 | RESTART 12 | } from '../../src/static/constants/actions'; 13 | 14 | describe('Action Creators', () => { 15 | it('should dispatch START action', () => { 16 | expect(start('start')).toEqual({ 17 | type: START, 18 | payload: 'start' 19 | }); 20 | }); 21 | it('should dispatch RESTART action', () => { 22 | expect(restart('restart')).toEqual({ 23 | type: RESTART, 24 | payload: 'restart' 25 | }); 26 | }); 27 | 28 | it('should dispatch SUBMIT action', () => { 29 | expect(submitAnswer('submit')).toEqual({ 30 | type: SUBMIT, 31 | payload: 'submit' 32 | }); 33 | }); 34 | 35 | it('should dispatch TOGGLE action', () => { 36 | expect(toggle('toggle')).toEqual({ 37 | type: TOGGLE, 38 | payload: 'toggle' 39 | }); 40 | }); 41 | 42 | it('should dispatch TOGGLE_JS action', () => { 43 | expect(toggleJS('toggle js')).toEqual({ 44 | type: TOGGLE_JS, 45 | payload: 'toggle js' 46 | }); 47 | }); 48 | 49 | it('should dispatch TOGGLE_MODE action', () => { 50 | expect(toggleMode('toggle mode')).toEqual({ 51 | type: TOGGLE_MODE, 52 | payload: 'toggle mode' 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /__tests__/components/Button.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { shallow } from 'enzyme'; 4 | import 'jest-styled-components'; 5 | import Button from '../../src/components/Button'; 6 | import { renderWithLightTheme } from '../helpers'; 7 | import { renderWithDarkTheme } from '../helpers'; 8 | 9 | describe('