├── .DS_Store ├── .storybook ├── generated-stories-entry.js ├── stories.pattern ├── CHANGELOG.md ├── .DS_Store ├── utils │ ├── index.js │ └── getMediaType.js ├── addons │ ├── index.ts │ └── withCustomStyles.ts ├── styles │ ├── storybook.scss__ │ └── reset.css___ ├── constants │ └── index.js ├── preview.js ├── middleware.js ├── preview-head.html └── main.js ├── src ├── controller │ ├── websockets │ │ └── index.ts │ ├── saga │ │ ├── index.js │ │ └── saga.js │ ├── actions │ │ └── index.js │ └── reducers │ │ └── index.js ├── interfaces │ ├── IController.d.ts │ └── IStore.d.ts ├── .DS_Store ├── assets │ ├── 1.png │ ├── 2.png │ └── .DS_Store ├── favicon.ico ├── __tests__ │ ├── mocks │ │ └── body.js │ ├── body.test.tsx │ └── __snapshots__ │ │ └── body.test.tsx.snap ├── store │ ├── history.js │ ├── initialState.js │ ├── helpers │ │ ├── reduxLogger.js │ │ └── storeHMR.ts │ ├── middleware │ │ ├── rootSaga.js │ │ └── rootReducer.js │ └── store.js ├── components │ ├── index.js │ ├── Body │ │ ├── index.d.ts │ │ ├── index.scss.d.ts │ │ ├── index.story.tsx │ │ ├── index.scss │ │ └── index.tsx │ ├── Footer.js │ └── Header.js ├── styles │ ├── button.scss │ ├── button.scss.d.ts │ ├── index.scss.d.ts │ └── index.scss ├── constants │ └── index.js ├── manifest.json ├── layout │ └── index.js ├── provider │ └── index.tsx ├── containers │ └── body.ts ├── index.html ├── serviceWorker.js └── index.js ├── cypress ├── constants │ └── login.ts ├── template │ ├── constants │ │ └── index.ts │ ├── view │ │ ├── selectors │ │ │ └── lifeBar.ts │ │ └── behavior │ │ │ └── index.ts │ ├── interfaces │ │ ├── IMocks.d.ts │ │ └── IModules.d.ts │ ├── api │ │ └── index.ts │ ├── mocks │ │ └── index.ts │ ├── triggers │ │ └── index.ts │ ├── README.md │ ├── modules │ │ └── index.ts │ ├── controller │ │ └── index.ts │ └── scenarios │ │ └── basicFight.ts ├── .DS_Store ├── videos │ └── .DS_Store ├── apps │ └── test_example │ │ ├── interfaces │ │ ├── IController.d.ts │ │ ├── IUtils.d.ts │ │ ├── IScenarios.d.ts │ │ ├── IModules.d.ts │ │ └── IMocks.d.ts │ │ ├── view │ │ ├── selectors │ │ │ ├── stealthBar.ts │ │ │ ├── index.ts │ │ │ ├── common.ts │ │ │ ├── statusbar.ts │ │ │ ├── model.ts │ │ │ ├── facilities.ts │ │ │ ├── modal.ts │ │ │ ├── header.ts │ │ │ ├── effects.ts │ │ │ ├── armour.ts │ │ │ └── weapons.ts │ │ ├── behavior │ │ │ ├── index.ts │ │ │ ├── stealthbar.ts │ │ │ ├── statusbar.ts │ │ │ ├── model.ts │ │ │ ├── modal.ts │ │ │ ├── facilities.ts │ │ │ ├── header.ts │ │ │ └── effects.ts │ │ └── constants │ │ │ └── index.ts │ │ ├── modules │ │ ├── index.ts │ │ ├── redirectToLog.ts │ │ ├── runFight.ts │ │ ├── finishFight.ts │ │ └── shouldAttack.ts │ │ ├── utils │ │ ├── getDataMock.ts │ │ └── getModalTitle.ts │ │ ├── scenarios │ │ ├── index.ts │ │ ├── rivalsUI.ts │ │ ├── winFight.ts │ │ ├── escapedFight.ts │ │ ├── weaponsUI.ts │ │ ├── stalematedFight.ts │ │ ├── defenderUI.ts │ │ └── attackerUI.ts │ │ ├── mocks │ │ ├── view.ts │ │ └── index.ts │ │ ├── api │ │ └── index.ts │ │ ├── CHANGELOG.md │ │ ├── triggers │ │ └── index.ts │ │ ├── constants │ │ └── index.ts │ │ └── controller │ │ └── index.ts ├── mocks │ ├── profile.json │ └── example.json ├── fixtures │ └── profile.json ├── interfaces │ ├── IHelpers.d.ts │ └── IUtils.d.ts ├── screenshots │ └── examples │ │ └── misc.spec.ts │ │ └── my-image.png ├── utils │ ├── fixFetch.ts │ ├── getToken.ts │ ├── getElemWidth.ts │ ├── getSaveURL.ts │ ├── unquote.ts │ ├── getLoop.ts │ ├── complete2FA.ts │ ├── findCSSSheetValue.ts │ ├── matchPayload.ts │ ├── getCSSStylesheet.ts │ ├── getResponseData.ts │ ├── getRequestData.ts │ ├── getSyncInterval.ts │ ├── getCoords.ts │ ├── getStyles.ts │ ├── checkStyles.ts │ └── checkPosition.ts ├── helpers │ ├── visit.ts │ ├── cookies.ts │ └── login.ts ├── tsconfig.json ├── index.d.ts ├── services │ └── initiateE2ETest.ts ├── e2e │ ├── login.spec.ts │ └── test.spec.ts ├── webpack.config.js ├── examples │ ├── window.spec.ts │ ├── waiting.spec.ts │ ├── location.spec.ts │ ├── aliasing.spec.ts │ ├── navigation.spec.ts │ ├── viewport.spec.ts │ ├── local_storage.spec.ts │ ├── cookies.spec.ts │ ├── spies_stubs_clocks.spec.ts │ ├── connectors.spec.ts │ ├── misc.spec.ts │ ├── querying.spec.ts │ ├── files.spec.ts │ ├── traversal.spec.ts │ └── utilities.spec.ts ├── support │ ├── index.ts │ └── commands.ts ├── plugins │ └── index.ts └── README.md ├── LOGO_COCONAT.jpg ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── custom.md └── workflows │ ├── testing.yml │ ├── security.yml │ └── linting.yml ├── postcss.config.js ├── tslint.json ├── prettier.config.js ├── PULL_REQUEST_TEMPLATE.md ├── .prettierignore ├── .gitignore ├── globals.js ├── .stylelintignore ├── .eslintignore ├── .babelrc ├── lerna.json ├── cypress.json ├── .travis.yml ├── setupTests.js ├── SECURITY.md ├── jest.config.js ├── tsconfig.json ├── server ├── proxy.ts ├── ssl │ ├── localhost.crt │ └── localhost.key ├── compiler.ts └── server.ts ├── .editorconfig ├── LICENSE ├── typings.d.ts ├── .stylelintrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md └── CONTRIBUTING.md /.DS_Store: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.storybook/generated-stories-entry.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/controller/websockets/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.storybook/stories.pattern: -------------------------------------------------------------------------------- 1 | ../**/*.story.* 2 | -------------------------------------------------------------------------------- /cypress/constants/login.ts: -------------------------------------------------------------------------------- 1 | export const BASE_ROOT = 'www.google.com' 2 | -------------------------------------------------------------------------------- /src/interfaces/IController.d.ts: -------------------------------------------------------------------------------- 1 | // TODO: make controller logic typed 2 | -------------------------------------------------------------------------------- /.storybook/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Storybook 2 | 3 | ## 0.0.1 4 | * Init release. 5 | -------------------------------------------------------------------------------- /cypress/template/constants/index.ts: -------------------------------------------------------------------------------- 1 | // export const RIVAL_ID = '1435333' 2 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiosBoy/coconat/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /LOGO_COCONAT.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiosBoy/coconat/HEAD/LOGO_COCONAT.jpg -------------------------------------------------------------------------------- /src/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiosBoy/coconat/HEAD/src/assets/1.png -------------------------------------------------------------------------------- /src/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiosBoy/coconat/HEAD/src/assets/2.png -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiosBoy/coconat/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /cypress/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiosBoy/coconat/HEAD/cypress/.DS_Store -------------------------------------------------------------------------------- /.storybook/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiosBoy/coconat/HEAD/.storybook/.DS_Store -------------------------------------------------------------------------------- /src/assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiosBoy/coconat/HEAD/src/assets/.DS_Store -------------------------------------------------------------------------------- /cypress/videos/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiosBoy/coconat/HEAD/cypress/videos/.DS_Store -------------------------------------------------------------------------------- /.storybook/utils/index.js: -------------------------------------------------------------------------------- 1 | import getMediaType from './getMediaType' 2 | 3 | export { getMediaType } 4 | -------------------------------------------------------------------------------- /cypress/apps/test_example/interfaces/IController.d.ts: -------------------------------------------------------------------------------- 1 | export type TPersonTypes = 'attacker' | 'defender' 2 | -------------------------------------------------------------------------------- /cypress/mocks/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 8739, 3 | "name": "Jane", 4 | "email": "jane@example.com" 5 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [] 4 | patreon: sviat_dev 5 | -------------------------------------------------------------------------------- /cypress/fixtures/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 8739, 3 | "name": "Jane", 4 | "email": "jane@example.com" 5 | } -------------------------------------------------------------------------------- /cypress/interfaces/IHelpers.d.ts: -------------------------------------------------------------------------------- 1 | export interface ILogin { 2 | email?: string 3 | password?: string 4 | } 5 | -------------------------------------------------------------------------------- /.storybook/addons/index.ts: -------------------------------------------------------------------------------- 1 | import withCustomStyles from './withCustomStyles' 2 | 3 | export { withCustomStyles } 4 | -------------------------------------------------------------------------------- /cypress/template/view/selectors/lifeBar.ts: -------------------------------------------------------------------------------- 1 | // export const defenderLifeBar = () => cy.get('#player-progress_toshykazu') 2 | -------------------------------------------------------------------------------- /src/__tests__/mocks/body.js: -------------------------------------------------------------------------------- 1 | export default { 2 | imageToShow: 1, 3 | switchImage: (imgNumber) => imgNumber 4 | } 5 | -------------------------------------------------------------------------------- /src/interfaces/IStore.d.ts: -------------------------------------------------------------------------------- 1 | // TODO: make store logic typed 2 | export interface IAsyncReducersStore { 3 | asyncReducers: object 4 | } 5 | -------------------------------------------------------------------------------- /cypress/template/interfaces/IMocks.d.ts: -------------------------------------------------------------------------------- 1 | // export interface IStartFightData { 2 | // step: 'startFight' 3 | // user2ID: '1435333' 4 | // } 5 | -------------------------------------------------------------------------------- /src/store/history.js: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history' 2 | 3 | const history = createBrowserHistory() 4 | 5 | export default history 6 | -------------------------------------------------------------------------------- /cypress/screenshots/examples/misc.spec.ts/my-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiosBoy/coconat/HEAD/cypress/screenshots/examples/misc.spec.ts/my-image.png -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Header from './Header' 2 | import Body from './Body' 3 | import Footer from './Footer' 4 | 5 | export { Header, Body, Footer } 6 | -------------------------------------------------------------------------------- /.storybook/styles/storybook.scss__: -------------------------------------------------------------------------------- 1 | body { 2 | box-sizing: border-box; 3 | margin-top: 15px; 4 | padding-right: 15px; 5 | padding-left: 15px; 6 | } 7 | -------------------------------------------------------------------------------- /src/store/initialState.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | app: { 3 | appName: 'CoConatBuider', 4 | imageToShow: 1 5 | } 6 | } 7 | 8 | export default initialState 9 | -------------------------------------------------------------------------------- /src/styles/button.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | margin: 0; 3 | padding: 0; 4 | border: none; 5 | background: transparent; 6 | outline: none; 7 | cursor: pointer; 8 | } 9 | -------------------------------------------------------------------------------- /cypress/template/api/index.ts: -------------------------------------------------------------------------------- 1 | const subscribeAPI = () => { 2 | // cy.route('POST', '/loader.php?sid=attackData&mode=json').as('action'); 3 | } 4 | 5 | export default subscribeAPI 6 | -------------------------------------------------------------------------------- /cypress/apps/test_example/interfaces/IUtils.d.ts: -------------------------------------------------------------------------------- 1 | import { TModalTypes } from './IModules' 2 | 3 | export interface IGetModalTitle { 4 | type: TModalTypes 5 | removeName: boolean 6 | } 7 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-import': {}, 4 | 'postcss-cssnext': {}, 5 | 'postcss-preset-env': {}, 6 | 'cssnano': {} 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | 5 | --- 6 | 7 | Describe this issue template's purpose here. 8 | -------------------------------------------------------------------------------- /cypress/template/mocks/index.ts: -------------------------------------------------------------------------------- 1 | // const startFightData = { 2 | // step: 'startFight', 3 | // user2ID: '1435333' 4 | // } 5 | 6 | // export { 7 | // startFightData 8 | // } 9 | 10 | -------------------------------------------------------------------------------- /cypress/utils/fixFetch.ts: -------------------------------------------------------------------------------- 1 | const fixFetch = () => { 2 | return ({ 3 | onBeforeLoad(win: any) { 4 | delete win.fetch 5 | } 6 | }) 7 | } 8 | 9 | export default fixFetch 10 | -------------------------------------------------------------------------------- /cypress/mocks/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint-react"], 4 | "jsRules": { 5 | "jsx-no-multiline-js": true 6 | }, 7 | "rulesDirectory": [], 8 | "rules": {} 9 | } 10 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/selectors/stealthBar.ts: -------------------------------------------------------------------------------- 1 | export const stealthBar = () => cy.get('#attacker [class^="stealthBarWrap"]') 2 | export const stealthBarIcon = () => cy.get('#attacker [class^="iconStealth"]') 3 | -------------------------------------------------------------------------------- /cypress/utils/getToken.ts: -------------------------------------------------------------------------------- 1 | const totp = require('totp-generator') 2 | 3 | const getToken = () => { 4 | const token = totp('YNHALH32YJ7HSZLM') 5 | 6 | return token 7 | } 8 | 9 | export default getToken 10 | -------------------------------------------------------------------------------- /cypress/utils/getElemWidth.ts: -------------------------------------------------------------------------------- 1 | const getElemWidth = (elem: any) => { 2 | const { width } = window.getComputedStyle(elem) 3 | 4 | return Number(width.substr(0, width.length - 2)) 5 | } 6 | 7 | export default getElemWidth 8 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | useTabs: false, 3 | printWidth: 120, 4 | tabWidth: 2, 5 | singleQuote: true, 6 | trailingComma: 'none', 7 | jsxBracketSameLine: false, 8 | semi: false 9 | }; 10 | -------------------------------------------------------------------------------- /.storybook/constants/index.js: -------------------------------------------------------------------------------- 1 | export const BACKGROUNDS = { 2 | backgrounds: [ 3 | { name: 'ccc', value: '#ccc', default: true }, 4 | { name: 'white', value: '#fff' }, 5 | { name: 'black', value: '#111' } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /cypress/utils/getSaveURL.ts: -------------------------------------------------------------------------------- 1 | import { BASIC_AUTH, BASE_ROOT } from '../constants/login' 2 | 3 | const getSaveUrl = (url: string) => { 4 | return `https://${BASIC_AUTH}@${BASE_ROOT}${url}` 5 | } 6 | 7 | export default getSaveUrl 8 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | By submitting this pull request I confirm I've read and complied with the below requirements 🖖 3 | Please read it multiple times. I spent a lot of time on these guidelines and most people miss a lot. 4 | 5 | -------------------------------------------------------------------------------- /cypress/helpers/visit.ts: -------------------------------------------------------------------------------- 1 | import fixFetch from '../utils/fixFetch' 2 | import getSaveURL from '../utils/getSaveURL' 3 | 4 | const visit = (url: string) => { 5 | cy.visit(getSaveURL(url), fixFetch()) 6 | } 7 | 8 | export default visit 9 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | export const SWITCH_IMAGE = 'SWITCH_IMAGE' 2 | // consts for Saga asyc actions, probably you do not need this below 3 | export const SOME_SAGA = 'SOME_SAGA' 4 | export const SOME_ASYNC_ACTION = 'SOME_ASYNC_ACTION' 5 | -------------------------------------------------------------------------------- /src/styles/button.scss.d.ts: -------------------------------------------------------------------------------- 1 | // This file is automatically generated. 2 | // Please do not change this file! 3 | interface CssExports { 4 | 'button': string; 5 | } 6 | export const cssExports: CssExports; 7 | export default cssExports; 8 | -------------------------------------------------------------------------------- /src/components/Body/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is automatically generated. 2 | // Please do not change this file! 3 | interface CssExports { 4 | 'animWrap': string; 5 | } 6 | export const cssExports: CssExports; 7 | export default cssExports; 8 | -------------------------------------------------------------------------------- /cypress/utils/unquote.ts: -------------------------------------------------------------------------------- 1 | const unquote = (str: string) => { 2 | const matchPattern = new RegExp('(^")|("$)', 'g') 3 | const normalizedString = str.replace(matchPattern, '') 4 | 5 | return normalizedString 6 | } 7 | 8 | export default unquote 9 | -------------------------------------------------------------------------------- /src/controller/saga/index.js: -------------------------------------------------------------------------------- 1 | import { takeLatest } from 'redux-saga/effects' 2 | import saga from './saga' 3 | import { SOME_SAGA } from '../../constants' 4 | 5 | export default function* watchSagas() { 6 | yield takeLatest(SOME_SAGA, saga) 7 | } 8 | -------------------------------------------------------------------------------- /cypress/template/triggers/index.ts: -------------------------------------------------------------------------------- 1 | // import { common } from '../view/selectors' 2 | 3 | // export const start = () => common.start.click() 4 | // export const leave = () => common.leave.focus() 5 | // export const escape = () => common.escape.click() 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.yml 2 | *.css 3 | *.scss 4 | *.d.ts 5 | *.md 6 | *.json 7 | *.yml 8 | .prettierignore 9 | .eslintignore 10 | .stylelintignore 11 | .eslintrc.js 12 | .gitignore 13 | *.yml 14 | *.html 15 | .env 16 | *.json 17 | *.lock 18 | *.ejs 19 | -------------------------------------------------------------------------------- /src/store/helpers/reduxLogger.js: -------------------------------------------------------------------------------- 1 | // Console logger for Redux Action dispatch fires 2 | import { createLogger } from 'redux-logger' 3 | 4 | const logger = createLogger({ 5 | collapsed: true, 6 | timestamp: false, 7 | diff: true 8 | }) 9 | 10 | export default logger 11 | -------------------------------------------------------------------------------- /cypress/utils/getLoop.ts: -------------------------------------------------------------------------------- 1 | import { IGetLoop } from '../interfaces/IUtils' 2 | 3 | const getLoop = ({ callback, count = 30 }: IGetLoop) => { 4 | const loopArr = Array.from(Array(count).keys()) 5 | 6 | return cy.wrap(loopArr).each(callback) 7 | } 8 | 9 | export default getLoop 10 | -------------------------------------------------------------------------------- /cypress/utils/complete2FA.ts: -------------------------------------------------------------------------------- 1 | import getToken from './getToken' 2 | 3 | const complete2FA = () => { 4 | cy.get('#verify-code-input').then(() => cy.get('#verify-code-input').type(getToken())) 5 | cy.get('button[type="submit"][class="btn"]').eq(1).click() 6 | } 7 | 8 | export default complete2FA 9 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import styles from '../styles/index.scss' 3 | 4 | const Footer = memo(() => { 5 | return
App Footer
6 | }) 7 | 8 | Footer.displayName = 'Footer' 9 | 10 | export default Footer 11 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import styles from '../styles/index.scss' 3 | 4 | const Header = memo(() => { 5 | return
App Headers
6 | }) 7 | 8 | Header.displayName = 'Header' 9 | 10 | export default Header 11 | -------------------------------------------------------------------------------- /.storybook/addons/withCustomStyles.ts: -------------------------------------------------------------------------------- 1 | // Adds an ability to set custom styles for the body node. 2 | const withCustomStyles = (customStyles: string) => (story: Function) => { 3 | document.body.setAttribute('style', customStyles) 4 | 5 | return story() 6 | } 7 | 8 | export default withCustomStyles 9 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "../node_modules", 5 | "types": ["node", "cypress", "../node_modules/cypress"] 6 | }, 7 | "files": ["./index.d.ts"], 8 | "include": ["**/*.*"], 9 | "exclude": ["dist"] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | // put here all file that you need to execlude from pushing on git platform. 2 | 3 | *.log 4 | node_modules 5 | dist 6 | coverage 7 | .idea 8 | package-lock.json 9 | yarn-error.log 10 | .vscode 11 | .tmp 12 | .src 13 | lerna-debug.log 14 | .travis.yml 15 | storybook-static 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /cypress/utils/findCSSSheetValue.ts: -------------------------------------------------------------------------------- 1 | import unquote from './unquote' 2 | 3 | const findCSSSheetValue = (elementCSSData: CSSStyleDeclaration, propKey: string) => { 4 | const value = elementCSSData.getPropertyValue(propKey) 5 | 6 | return unquote(value) 7 | } 8 | 9 | export default findCSSSheetValue 10 | -------------------------------------------------------------------------------- /cypress/apps/test_example/modules/index.ts: -------------------------------------------------------------------------------- 1 | import runFight from './runFight' 2 | import shouldAttack from './shouldAttack' 3 | import finishFight from './finishFight' 4 | import redirectToLog from './redirectToLog' 5 | 6 | export { 7 | runFight, 8 | shouldAttack, 9 | finishFight, 10 | redirectToLog 11 | } 12 | -------------------------------------------------------------------------------- /cypress/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare namespace Cypress { 4 | // eslint-disable-next-line 5 | interface Chainable { 6 | getByDataTest(tag: string): Chainable 7 | styles(checkData: string | string[], pseudo?: 'after' | 'before'): Chainable 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /cypress/template/README.md: -------------------------------------------------------------------------------- 1 | # Cypress Template - best way to start your E2E App development 2 | 3 | ## Please, follow the architecture designed in this folder 4 | 5 | ## This is optimized way for creating and expanding your App E2E testing 6 | 7 | * For any question contact 3p-sviat in Slack or mail, please. 8 | 9 | Thanks! 10 | -------------------------------------------------------------------------------- /cypress/apps/test_example/modules/redirectToLog.ts: -------------------------------------------------------------------------------- 1 | import { continues } from '../triggers' 2 | 3 | const redirectToLog = () => { 4 | // finish fight via continue button 5 | continues() 6 | 7 | cy.url().should('include', 'loader.php?sid=attackLog') 8 | cy.contains('Attack log') 9 | } 10 | 11 | export default redirectToLog 12 | -------------------------------------------------------------------------------- /cypress/services/initiateE2ETest.ts: -------------------------------------------------------------------------------- 1 | import visit from '../helpers/visit' 2 | 3 | const initiateE2ETest = ({ runAPI, url }) => { 4 | // starting server for API proxy 5 | cy.server() 6 | 7 | // subscribing on AJAX request 8 | runAPI() 9 | 10 | // visiting App page 11 | visit(url) 12 | } 13 | 14 | export default initiateE2ETest 15 | -------------------------------------------------------------------------------- /globals.js: -------------------------------------------------------------------------------- 1 | // global variables for webpack and app hot-reload. Make them work more easly. 2 | global.__TEST__ = process.env.NODE_ENV === 'test'; 3 | global.__DEV__ = process.env.NODE_ENV === 'development'; 4 | global.__PROD__ = process.env.NODE_ENV === 'production'; 5 | global.__NODE_ENV__ = process.env.NODE_ENV; 6 | global.__PORT__ = process.env.PORT; 7 | -------------------------------------------------------------------------------- /src/controller/actions/index.js: -------------------------------------------------------------------------------- 1 | import { SWITCH_IMAGE, SOME_ASYNC_ACTION } from '../../constants' 2 | 3 | const switchImage = (imageID) => ({ 4 | type: SWITCH_IMAGE, 5 | imageID 6 | }) 7 | 8 | const someAsyncAction = (payload) => ({ 9 | type: SOME_ASYNC_ACTION, 10 | payload 11 | }) 12 | 13 | export { switchImage, someAsyncAction } 14 | -------------------------------------------------------------------------------- /cypress/template/interfaces/IModules.d.ts: -------------------------------------------------------------------------------- 1 | // export type TWeaponsType = 'mainWeapon' | 'secondaryWeapon' | 'meleeWeapon' | 'tempWeapon' | 'fistsWeapon' 2 | 3 | // export interface IShouldAttack { 4 | // weaponType: TWeaponsType 5 | // attackCount: number 6 | // } 7 | 8 | // export interface IRunFight { 9 | // withFocus?: boolean 10 | // } 11 | -------------------------------------------------------------------------------- /src/components/Body/index.scss.d.ts: -------------------------------------------------------------------------------- 1 | // This file is automatically generated. 2 | // Please do not change this file! 3 | interface CssExports { 4 | 'animWrap': string; 5 | 'black': string; 6 | 'red': string; 7 | 'textLabel': string; 8 | 'transparent': string; 9 | } 10 | export const cssExports: CssExports; 11 | export default cssExports; 12 | -------------------------------------------------------------------------------- /cypress/utils/matchPayload.ts: -------------------------------------------------------------------------------- 1 | import getRequestData, { TFormData } from './getRequestData' 2 | 3 | const matchPayloadData = ({ bodyData, mockData }: { bodyData: TFormData; mockData: object }) => { 4 | const requestData = getRequestData(bodyData as TFormData) 5 | 6 | expect(requestData).eql(mockData) 7 | } 8 | 9 | export default matchPayloadData 10 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/selectors/index.ts: -------------------------------------------------------------------------------- 1 | import * as common from './common' 2 | import * as weapons from './weapons' 3 | import * as effects from './effects' 4 | import * as stealthBar from './stealthBar' 5 | import * as header from './header' 6 | 7 | export { 8 | common, 9 | weapons, 10 | effects, 11 | stealthBar, 12 | header 13 | } 14 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | coverage/** 2 | node_modules/** 3 | dist/** 4 | src/index.html 5 | *.cssmodule.scss.d.ts 6 | *.cssmodule.d.ts 7 | *.css 8 | *.yml 9 | *.d.ts 10 | .prettierignore 11 | .stylelintignore 12 | .eslintignore 13 | .eslintrc.js 14 | .gitignore 15 | *.md 16 | *.html 17 | .env 18 | *.json 19 | *.lock 20 | *.js 21 | *.ts 22 | *.jsx 23 | *.tsx 24 | *.ejs 25 | -------------------------------------------------------------------------------- /cypress/apps/test_example/utils/getDataMock.ts: -------------------------------------------------------------------------------- 1 | const getDataMock = ({ dataMock, noUser2ID }: { dataMock: object; noUser2ID?: boolean }) => { 2 | if (!Cypress.env('DEFENDER_ID') || noUser2ID) { 3 | return dataMock 4 | } 5 | 6 | return { 7 | ...dataMock, 8 | user2ID: Cypress.env('DEFENDER_ID') 9 | } 10 | } 11 | 12 | export default getDataMock 13 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import centered from '@storybook/addon-centered/react' 2 | import { withKnobs } from '@storybook/addon-knobs' 3 | 4 | import { BACKGROUNDS } from './constants' 5 | 6 | export const parameters = { 7 | backgrounds: { 8 | default: 'grey', 9 | values: BACKGROUNDS 10 | } 11 | } 12 | 13 | export const decorators = [withKnobs, centered] 14 | -------------------------------------------------------------------------------- /src/styles/index.scss.d.ts: -------------------------------------------------------------------------------- 1 | // This file is automatically generated. 2 | // Please do not change this file! 3 | interface CssExports { 4 | 'appFooter': string; 5 | 'appHeader': string; 6 | 'appWrapper': string; 7 | 'body': string; 8 | 'bodyImg': string; 9 | 'extra': string; 10 | } 11 | export const cssExports: CssExports; 12 | export default cssExports; 13 | -------------------------------------------------------------------------------- /cypress/apps/test_example/interfaces/IScenarios.d.ts: -------------------------------------------------------------------------------- 1 | import { IModal } from './IModules' 2 | 3 | export interface IModalScenarios { 4 | continues: string 5 | mug: (name: string) => void 6 | hosp: string 7 | leave: string 8 | escape: string 9 | stalemated: string 10 | } 11 | 12 | export interface IWinFight extends IModal { 13 | withFocus?: boolean 14 | } 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/** 2 | node_modules/** 3 | dist/** 4 | src/index.html 5 | *.cssmodule.scss.d.ts 6 | *.cssmodule.d.ts 7 | *.cssmodule 8 | *.scss 9 | *.css 10 | *.yml 11 | *.d.ts 12 | typings.d.ts 13 | tsconfig.json 14 | .prettierignore 15 | .eslintignore 16 | .stylelintignore 17 | .eslintrc.js 18 | .gitignore 19 | *.md 20 | *.html 21 | .env 22 | *.json 23 | *.lock 24 | *.ejs 25 | -------------------------------------------------------------------------------- /cypress/apps/test_example/scenarios/index.ts: -------------------------------------------------------------------------------- 1 | import stalematedFight from './stalematedFight' 2 | import escapedFight from './escapedFight' 3 | import winFight from './winFight' 4 | import rivalsUI from './rivalsUI' 5 | import attackerUI from './attackerUI' 6 | import defenderUI from './defenderUI' 7 | 8 | export { rivalsUI, attackerUI, defenderUI, winFight, stalematedFight, escapedFight } 9 | -------------------------------------------------------------------------------- /cypress/utils/getCSSStylesheet.ts: -------------------------------------------------------------------------------- 1 | import { TPseudoCSSTypes } from '../interfaces/IUtils' 2 | 3 | const getCSSStylesheet = (el: HTMLElement, pseudo?: TPseudoCSSTypes) => { 4 | const { defaultView: cssStylesheet } = el[0].ownerDocument 5 | const CSSSheet = cssStylesheet.getComputedStyle(el[0], pseudo) 6 | 7 | return CSSSheet 8 | } 9 | 10 | export default getCSSStylesheet 11 | -------------------------------------------------------------------------------- /cypress/utils/getResponseData.ts: -------------------------------------------------------------------------------- 1 | const getResponseData = async (payload: Blob) => { 2 | let json = null 3 | 4 | try { 5 | const rawData = await payload.text() 6 | 7 | json = await JSON.parse(rawData) 8 | } catch (e) { 9 | console.error('Something wrong with ResponseData: ', json) 10 | } 11 | 12 | return json 13 | } 14 | 15 | export default getResponseData 16 | -------------------------------------------------------------------------------- /cypress/apps/test_example/mocks/view.ts: -------------------------------------------------------------------------------- 1 | import { IModalScenarios } from '../interfaces/IScenarios' 2 | 3 | export const modalScenarios: IModalScenarios = { 4 | continues: 'You defeated', 5 | mug: name => `You mugged${name} but their wallet was empty`, 6 | hosp: 'You hospitalized', 7 | leave: 'You defeated', 8 | escape: 'You escaped', 9 | stalemated: 'You stalemated' 10 | } 11 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "CoConat", 3 | "name": "CoConat Builder", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /cypress/e2e/login.spec.ts: -------------------------------------------------------------------------------- 1 | import setCookies from '../helpers/cookies' 2 | import makeLogin from '../helpers/login' 3 | 4 | context('Login', () => { 5 | it('should make a 2FA login', () => { 6 | setCookies({ type: 'basic' }) 7 | makeLogin({}).full() 8 | }) 9 | it('should make a basic login', () => { 10 | setCookies({ type: 'basic' }) 11 | makeLogin({}).basic() 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/env", 4 | "@babel/react", 5 | "@babel/typescript" 6 | ], 7 | "plugins": [ 8 | "@babel/plugin-transform-object-assign", 9 | "@babel/plugin-proposal-class-properties", 10 | "@babel/plugin-syntax-dynamic-import", 11 | ["@babel/plugin-transform-runtime", 12 | { 13 | "regenerator": true 14 | } 15 | ] 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3.8.4", 3 | "npmClient": "yarn", 4 | "command": { 5 | "publish": { 6 | "ignoreChanges": [ 7 | "ignored-file", 8 | "*.md" 9 | ] 10 | }, 11 | "bootstrap": { 12 | "npmClientArgs": [ 13 | "--no-package-lock" 14 | ] 15 | } 16 | }, 17 | "packages": [ 18 | "packages/*" 19 | ], 20 | "lerna": "3.8.4" 21 | } 22 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectId": "euwho3", 3 | "baseUrl": "https://google.com", 4 | "viewportWidth": 1200, 5 | "viewportHeight": 1100, 6 | "fixturesFolder": "cypress/mocks", 7 | "integrationFolder": "cypress/e2e", 8 | "screenshotsFolder": "cypress/screenshots", 9 | "videosFolder": "cypress/videos", 10 | "pluginsFile": "cypress/plugins/index.ts", 11 | "supportFile": "cypress/support/index.ts" 12 | } 13 | -------------------------------------------------------------------------------- /.storybook/utils/getMediaType.js: -------------------------------------------------------------------------------- 1 | const getMediaType = (callback) => { 2 | const clientWidth = document.body && document.body.getBoundingClientRect().width 3 | 4 | if (clientWidth <= 600) { 5 | callback('mobile') 6 | 7 | return 8 | } 9 | 10 | if (clientWidth <= 1000) { 11 | callback('tablet') 12 | 13 | return 14 | } 15 | 16 | callback('desktop') 17 | } 18 | 19 | export default getMediaType 20 | -------------------------------------------------------------------------------- /cypress/utils/getRequestData.ts: -------------------------------------------------------------------------------- 1 | export type TFormData = object[] 2 | 3 | const getRequestData = (formData: TFormData) => { 4 | const requestData = {} 5 | 6 | try { 7 | formData.forEach((value, key) => { 8 | requestData[key] = value 9 | }) 10 | } catch (e) { 11 | console.error('Something wrong with FormData: ', formData) 12 | } 13 | 14 | return requestData 15 | } 16 | 17 | export default getRequestData 18 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/selectors/common.ts: -------------------------------------------------------------------------------- 1 | export const start = () => cy.get('button:contains("Start fight")') 2 | export const escape = () => cy.get('button:contains("ESCAPE")') 3 | export const leave = () => cy.get('button:contains("leave")') 4 | export const mug = () => cy.get('button:contains("mug")') 5 | export const hosp = () => cy.get('button:contains("hosp")') 6 | export const continues = () => cy.get('button:contains("CONTINUE")') 7 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/selectors/statusbar.ts: -------------------------------------------------------------------------------- 1 | import { TPersonTypes } from '../../interfaces/IController' 2 | 3 | export const statusBarWrap = (userType: TPersonTypes) => cy.get(`#${userType} [class^="statusBarWrap"]`) 4 | export const statusBarIcon = (userType: TPersonTypes) => statusBarWrap(userType).find('[class^="weaponIcon"]') 5 | export const statusBarDesc = (userType: TPersonTypes) => statusBarWrap(userType).find('[class^="result"]') 6 | -------------------------------------------------------------------------------- /cypress/template/view/behavior/index.ts: -------------------------------------------------------------------------------- 1 | // import { defenderLifeBar } from '../selectors/lifeBar' 2 | 3 | // export const checkDefenderEffects = ({ wrap = 1, count }: { wrap?: number, count: number }) => { 4 | // defenderEffectsWrap().should('have.length', wrap) 5 | // defenderEffectsBottom().should('have.length', count) 6 | // defenderEffectsTop().should('have.length', count) 7 | // defenderEffectsIcon().should('have.length', count) 8 | // } 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | before_install: 4 | - curl -o- -L https://yarnpkg.com/install.sh | bash 5 | - export PATH=$HOME/.yarn/bin:$PATH 6 | jobs: 7 | include: 8 | - stage: "Tests" 9 | name: "Unit Test" 10 | script: yarn unitTest 11 | - stage: "Compilation stage" 12 | name: "Compilation Test" 13 | script: yarn compile 14 | node_js: 15 | - "10" 16 | -------------------------------------------------------------------------------- /.storybook/middleware.js: -------------------------------------------------------------------------------- 1 | // const express = require('express') 2 | // const path = require('path') 3 | // const proxy = require('../server/proxy.ts') 4 | 5 | module.exports = (router) => { 6 | // custom storybook server middleware for static folder serving 7 | // router.use('/public', express.static(path.join(__dirname, '../src/assets'))) 8 | // custom storybook proxy for handling static files from real DEV server 9 | // router.use('/', proxy) 10 | } 11 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /cypress/apps/test_example/api/index.ts: -------------------------------------------------------------------------------- 1 | import { RIVAL_ID } from '../constants' 2 | 3 | const subscribeAPI = () => { 4 | const rivalID = Cypress.env('DEFENDER_ID') || RIVAL_ID 5 | 6 | cy.route('POST', '/loader.php?sid=attackData&mode=json').as('action') 7 | cy.route('GET', `/loader.php?sid=attackData&mode=json&user2ID=${rivalID}`).as('init') 8 | cy.route('GET', `/loader.php?sid=attackData&mode=json&step=poll&user2ID=${rivalID}`).as('poll') 9 | } 10 | 11 | export default subscribeAPI 12 | -------------------------------------------------------------------------------- /cypress/apps/test_example/scenarios/rivalsUI.ts: -------------------------------------------------------------------------------- 1 | import initiateE2ETest from '../../../services/initiateE2ETest' 2 | import subscribeAPI from '../api' 3 | 4 | import attackerUI from './attackerUI' 5 | import defenderUI from './defenderUI' 6 | 7 | const rivalsUI = () => { 8 | initiateE2ETest({ 9 | runAPI: subscribeAPI, 10 | url: `/loader.php?sid=attack&user2ID=${Cypress.env('DEFENDER_ID')}` 11 | }) 12 | 13 | attackerUI() 14 | defenderUI() 15 | } 16 | 17 | export default rivalsUI 18 | -------------------------------------------------------------------------------- /cypress/utils/getSyncInterval.ts: -------------------------------------------------------------------------------- 1 | import { IGetSyncInterval } from '../interfaces/IUtils' 2 | 3 | const getSyncInterval = ({ callback, iterCount = 30, delay = 1000 }: IGetSyncInterval) => { 4 | cy.clock() 5 | 6 | const iterationCountArray = Array.from(Array(iterCount).keys()) 7 | 8 | return ({ isClear = false }) => iterationCountArray.forEach(() => { 9 | if (isClear) { 10 | return 11 | } 12 | 13 | cy.tick(delay).then(callback) 14 | }) 15 | } 16 | 17 | export default getSyncInterval 18 | -------------------------------------------------------------------------------- /cypress/apps/test_example/utils/getModalTitle.ts: -------------------------------------------------------------------------------- 1 | import { modalScenarios } from '../mocks/view' 2 | import { IGetModalTitle } from '../interfaces/IUtils' 3 | 4 | const getModalTitle = ({ type, removeName }: IGetModalTitle) => { 5 | const useDefenderName = !removeName ? ` ${Cypress.env('DEFENDER_NAME')}` : '' 6 | const modalTitle = modalScenarios[type] 7 | 8 | return typeof modalTitle === 'function' ? modalTitle(useDefenderName) : `${modalTitle}${useDefenderName}` 9 | } 10 | 11 | export default getModalTitle 12 | -------------------------------------------------------------------------------- /src/layout/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route, withRouter } from 'react-router-dom' 3 | 4 | import { Header, Footer } from '../components' 5 | 6 | import Body from '../containers/body' 7 | import styles from '../styles/index.scss' 8 | 9 | const CoreLayout = () => { 10 | return ( 11 |
12 |
13 | 14 |
15 |
16 | ) 17 | } 18 | 19 | export default withRouter(CoreLayout) 20 | -------------------------------------------------------------------------------- /cypress/apps/test_example/scenarios/winFight.ts: -------------------------------------------------------------------------------- 1 | import runFight from '../modules/runFight' 2 | import { shouldAttack, finishFight } from '../modules' 3 | import { IWinFight } from '../interfaces/IScenarios' 4 | 5 | const winFight = ({ type, withOptions, withFocus }: IWinFight) => { 6 | // staring fight 7 | runFight({ withFocus }) 8 | 9 | // fight until rival will be beat 10 | shouldAttack({}) 11 | 12 | // finish fight via continue 13 | finishFight({ type, withOptions }) 14 | } 15 | 16 | export default winFight 17 | -------------------------------------------------------------------------------- /src/store/helpers/storeHMR.ts: -------------------------------------------------------------------------------- 1 | import { Store } from 'redux' 2 | import makeRootReducer from '../middleware/rootReducer' 3 | 4 | import { IAsyncReducersStore } from '../../interfaces/IStore' 5 | 6 | const activateStoreHMR = (store: Store & IAsyncReducersStore) => { 7 | // Hot Module Replacement for Controller/Store 8 | if (module.hot) { 9 | module.hot.accept('../middleware/rootReducer', () => { 10 | store.replaceReducer(makeRootReducer(store.asyncReducers)) 11 | }) 12 | } 13 | } 14 | 15 | export default activateStoreHMR 16 | -------------------------------------------------------------------------------- /src/__tests__/body.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | 4 | import initialState from './mocks/body' 5 | 6 | import Body from '../components/Body' 7 | 8 | describe('', () => { 9 | it('should render basic Body Component with an image', () => { 10 | const Component = mount() 11 | 12 | expect(Component.find('.button').length).toBe(1) 13 | expect(Component.find('.bodyImg').length).toBe(1) 14 | 15 | expect(Component).toMatchSnapshot() 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /src/provider/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Provider } from 'react-redux' 3 | import { ConnectedRouter } from 'connected-react-router' 4 | 5 | import CoreLayout from '../layout' 6 | 7 | interface AppRouting { 8 | store: any 9 | history: any 10 | } 11 | 12 | const AppRouting = ({ store, history }: AppRouting) => { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | export default AppRouting 23 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/selectors/model.ts: -------------------------------------------------------------------------------- 1 | import { TPersonTypes } from '../../interfaces/IController' 2 | 3 | 4 | export const playersWrap = () => cy.get('[class^="players"]') 5 | 6 | export const modelWrap = (userType: TPersonTypes) => cy.get(`#${userType} [class^="modelWrap"]`) 7 | export const modelLayers = (type: TPersonTypes) => modelWrap(type).find('[class^="modelLayers"]') 8 | export const modelContainer = (type: TPersonTypes) => modelLayers(type).find('[class^="model"]') 9 | export const modelImage = (type: TPersonTypes) => modelContainer(type).find('img') 10 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/behavior/index.ts: -------------------------------------------------------------------------------- 1 | import * as model from './model' 2 | import * as armour from './armour' 3 | import * as statusbar from './statusbar' 4 | import * as stealthbar from './stealthbar' 5 | import * as weapons from './weapons' 6 | import * as facilities from './facilities' 7 | import * as modal from './modal' 8 | import * as header from './header' 9 | import * as effects from './effects' 10 | 11 | export { 12 | model, 13 | armour, 14 | statusbar, 15 | stealthbar, 16 | weapons, 17 | facilities, 18 | modal, 19 | header, 20 | effects 21 | } 22 | -------------------------------------------------------------------------------- /cypress/apps/test_example/scenarios/escapedFight.ts: -------------------------------------------------------------------------------- 1 | import runFight from '../modules/runFight' 2 | import { onAttack, onEscape } from '../controller' 3 | 4 | import { FISTS_WEAPON } from '../constants' 5 | import { finishFight } from '../modules' 6 | 7 | const leaveFight = () => { 8 | // staring fight 9 | runFight({}) 10 | 11 | // attack the rival 12 | onAttack(FISTS_WEAPON) 13 | 14 | // escape from the fight 15 | onEscape() 16 | 17 | // finish fight via continue 18 | finishFight({ type: 'escape', withoutDefenderName: true }) 19 | } 20 | 21 | export default leaveFight 22 | -------------------------------------------------------------------------------- /cypress/template/modules/index.ts: -------------------------------------------------------------------------------- 1 | // import initiateE2ETest from '../../../services/initiateE2ETest' 2 | 3 | // const runFight = ({ withFocus = false }: { withFocus?: boolean }) => { 4 | // initiateE2ETest({ 5 | // runAPI: subscribeAPI, 6 | // url: `/loader.php?sid=attack&user2ID=${RIVAL_ID}` 7 | // }) 8 | 9 | // // initial load data 10 | // onInit() 11 | 12 | // // focus should fire 13 | // withFocus && onFocus() 14 | 15 | // // poll should running 16 | // onPoll() 17 | 18 | // // staring fight 19 | // onStart() 20 | // } 21 | 22 | // export default runFight 23 | -------------------------------------------------------------------------------- /src/controller/saga/saga.js: -------------------------------------------------------------------------------- 1 | import { put } from 'redux-saga/effects' 2 | import { someAsyncAction } from '../actions' 3 | 4 | export function* saga() { 5 | try { 6 | const payload = yield fetch('https://www.github.com') 7 | 8 | // throw an error if no payload received 9 | if (!payload) { 10 | throw new Error('Error in payload!') 11 | } 12 | 13 | // some payload from the responce received 14 | yield put(someAsyncAction(payload)) 15 | } catch (error) { 16 | throw new Error('Some error in sagas occured!') 17 | } 18 | } 19 | 20 | export default saga 21 | -------------------------------------------------------------------------------- /cypress/apps/test_example/scenarios/weaponsUI.ts: -------------------------------------------------------------------------------- 1 | import { checkWeaponBoxes } from '../view/behavior/weapons' 2 | 3 | import initiateE2ETest from '../../../services/initiateE2ETest' 4 | import subscribeAPI from '../api' 5 | 6 | import { TPersonTypes } from '../interfaces/IController' 7 | 8 | const weaponsUI = ({ userType }: { userType: TPersonTypes }) => { 9 | initiateE2ETest({ 10 | runAPI: subscribeAPI, 11 | url: `/loader.php?sid=attack&user2ID=${Cypress.env('DEFENDER_ID')}` 12 | }) 13 | 14 | // checking weapons boxes 15 | checkWeaponBoxes({ userType }) 16 | } 17 | 18 | export default weaponsUI 19 | -------------------------------------------------------------------------------- /cypress/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | module: { 5 | rules: [ 6 | { 7 | test: /\.(js|jsx|ts|tsx)?$/, 8 | loader: 'ts-loader', 9 | options: { 10 | compilerOptions: { 11 | target: 'ES2019' 12 | } 13 | }, 14 | exclude: [/node_modules/] 15 | } 16 | ] 17 | }, 18 | entry: './src/index.ts', 19 | resolve: { 20 | extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'] 21 | }, 22 | output: { 23 | filename: 'bundle.js', 24 | path: path.resolve(__dirname, 'dist') 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /setupTests.js: -------------------------------------------------------------------------------- 1 | // TODO: Remove these polyfills once the below issue is solved. 2 | // It present here to allow Jest work with the last React environment. 3 | // https://github.com/facebookincubator/create-react-app/issues/3199#issuecomment-332842582 4 | global.requestAnimationFrame = cb => { 5 | setTimeout(cb, 0); 6 | }; 7 | 8 | global.matchMedia = window.matchMedia || function() { 9 | return { 10 | matches: false, 11 | addListener: () => {}, 12 | removeListener: () => {} 13 | }; 14 | }; 15 | 16 | import Enzyme from 'enzyme'; 17 | import Adapter from 'enzyme-adapter-react-16'; 18 | 19 | Enzyme.configure({ adapter: new Adapter() }); 20 | -------------------------------------------------------------------------------- /src/store/middleware/rootSaga.js: -------------------------------------------------------------------------------- 1 | import createSagaMiddleware from 'redux-saga' 2 | import { all } from 'redux-saga/effects' 3 | import createSagaMiddlewareHelpers from 'redux-saga-watch-actions/lib/middleware' 4 | 5 | import watchSagas from '../../controller/saga' 6 | 7 | const sagaMiddleware = createSagaMiddleware() 8 | const runSaga = (saga) => sagaMiddleware.run(saga) 9 | 10 | const { injectSaga, cancelTask } = createSagaMiddlewareHelpers(sagaMiddleware) // <-- bind to sagaMiddleware.run 11 | 12 | function* rootSaga() { 13 | yield all([watchSagas()]) 14 | } 15 | 16 | export { injectSaga, cancelTask, rootSaga, runSaga } 17 | 18 | export default sagaMiddleware 19 | -------------------------------------------------------------------------------- /cypress/examples/window.spec.ts: -------------------------------------------------------------------------------- 1 | context('Window', () => { 2 | beforeEach(() => { 3 | cy.visit('https://example.cypress.io/commands/window') 4 | }) 5 | 6 | it('cy.window() - get the global window object', () => { 7 | // https://on.cypress.io/window 8 | cy.window().should('have.property', 'top') 9 | }) 10 | 11 | it('cy.document() - get the document object', () => { 12 | // https://on.cypress.io/document 13 | cy.document().should('have.property', 'charset').and('eq', 'UTF-8') 14 | }) 15 | 16 | it('cy.title() - get the title', () => { 17 | // https://on.cypress.io/title 18 | cy.title().should('include', 'Kitchen Sink') 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /cypress/utils/getCoords.ts: -------------------------------------------------------------------------------- 1 | const getElemCoords = ({ topCoords, leftCoords }: any): any => { 2 | const { body, documentElement: docElem } = document || {} 3 | 4 | const scrollTop = window?.pageYOffset || docElem?.scrollTop || body?.scrollTop 5 | const scrollLeft = window?.pageXOffset || docElem?.scrollLeft || body?.scrollLeft 6 | 7 | const clientTop = docElem?.clientTop || body?.clientTop || 0 8 | const clientLeft = docElem?.clientLeft || body?.clientLeft || 0 9 | 10 | const top = topCoords + scrollTop - clientTop 11 | const left = leftCoords + scrollLeft - clientLeft 12 | 13 | return { 14 | top, 15 | left 16 | } 17 | } 18 | 19 | export default getElemCoords 20 | -------------------------------------------------------------------------------- /cypress/apps/test_example/interfaces/IModules.d.ts: -------------------------------------------------------------------------------- 1 | export type TWeaponsType = 'mainWeapon' | 'secondaryWeapon' | 'meleeWeapon' | 'tempWeapon' | 'fistsWeapon' 2 | 3 | export interface IShouldAttack { 4 | weaponType?: TWeaponsType 5 | attackCount?: number 6 | } 7 | 8 | export interface IRunFight { 9 | withFocus?: boolean 10 | rivalID?: number 11 | } 12 | 13 | export type TModalTypes = 'stalemated' | 'escape' | 'leave' | 'mug' | 'hosp' | 'continues' 14 | 15 | export interface IModal { 16 | type?: TModalTypes 17 | withOptions?: boolean 18 | } 19 | 20 | export interface IFinishFight { 21 | type?: TModalTypes 22 | withOptions?: boolean 23 | withoutDefenderName?: boolean 24 | } 25 | -------------------------------------------------------------------------------- /cypress/apps/test_example/interfaces/IMocks.d.ts: -------------------------------------------------------------------------------- 1 | export interface IStartFightData { 2 | step: 'startFight' 3 | user2ID: '1435333' 4 | } 5 | 6 | export interface ILeaveFightData { 7 | step: 'finish' 8 | fightResult: 'leave' 9 | } 10 | 11 | export interface IEscapeFightData { 12 | step: 'runAway' 13 | } 14 | 15 | export interface IAttackData { 16 | step: 'attack' 17 | user2ID: '1435333' 18 | } 19 | 20 | export interface IAttackWithID extends IAttackData { 21 | user1EquipedItemID: string 22 | } 23 | 24 | export interface IMugData { 25 | step: 'finish' 26 | fightResult: 'mug' 27 | } 28 | 29 | export interface IHospData { 30 | step: 'finish' 31 | fightResult: 'hosp' 32 | } 33 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /cypress/template/controller/index.ts: -------------------------------------------------------------------------------- 1 | // import getResponseData from '../../../utils/getResponseData'; 2 | // import getRequestData, { TFormData } from '../../../utils/getRequestData' 3 | 4 | // export const onInit = () => { 5 | // cy.wait('@init').then( 6 | // () => cy.log('**INIT IS FIRED**.') 7 | // ); 8 | // } 9 | 10 | // export const onStart = () => { 11 | // triggers.start().wait('@action') 12 | // .then(response => { 13 | // const { body } = response.request; 14 | // const requestData = getRequestData(body as TFormData); 15 | 16 | // expect(requestData).to.deep.equal(startFightData) 17 | // cy.log('**ATTACK STARTED. REQUEST PAYLOAD IS OK!**') 18 | // }); 19 | // } 20 | -------------------------------------------------------------------------------- /src/components/Body/index.story.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { select, boolean, text } from '@storybook/addon-knobs' 3 | 4 | import Body, { IBody } from './' 5 | 6 | export const Default = () => { 7 | const config: IBody = { 8 | backgroundColor: select('Select background color', ['red', 'black', 'transparent'], 'transparent'), 9 | withLabel: boolean('Activate Label:', false), 10 | labelText: text('Set Label text:', ''), 11 | imageToShow: select('Set current image btw 1 or 2:', [1, 2], 1), 12 | switchImage: (imgNumber: number) => console.log('CALLBACK FIRED', imgNumber) 13 | } 14 | 15 | return 16 | } 17 | 18 | export default { 19 | title: 'Button' 20 | } 21 | -------------------------------------------------------------------------------- /src/containers/body.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { Body } from '../components' 3 | import { switchImage } from '../controller/actions' 4 | 5 | interface ISwitchImage { 6 | type: string 7 | imageID: number 8 | } 9 | 10 | interface IStore { 11 | app: { 12 | imageToShow: number 13 | } 14 | dispatch: (someFunc: ISwitchImage) => void 15 | } 16 | 17 | const mapStateToProps = (state: IStore) => ({ 18 | imageToShow: state.app.imageToShow 19 | }) 20 | 21 | const mapDispatchToProps = (dispatch: IStore['dispatch']) => ({ 22 | switchImage: (imageID: ISwitchImage['imageID']) => dispatch(switchImage(imageID)) 23 | }) 24 | 25 | export default connect(mapStateToProps, mapDispatchToProps)(Body) 26 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | * { 2 | color: white; 3 | font-family: 'Roboto', sans-serif; 4 | } 5 | 6 | .appWrapper { 7 | display: flex; 8 | flex-direction: column; 9 | align-items: center; 10 | justify-content: center; 11 | width: 100%; 12 | height: 100%; 13 | } 14 | 15 | .extra { 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | width: 300px; 20 | height: 30px; 21 | background: linear-gradient(to right, #1110 0%, #f9db2d 50%, #1110 100%); 22 | } 23 | 24 | .appHeader { 25 | composes: extra; 26 | } 27 | 28 | .body { 29 | display: block; 30 | } 31 | 32 | .bodyImg { 33 | margin: 40px; 34 | width: 50vh; 35 | } 36 | 37 | .appFooter { 38 | composes: extra; 39 | } 40 | -------------------------------------------------------------------------------- /cypress/utils/getStyles.ts: -------------------------------------------------------------------------------- 1 | import getCSSStylesheet from './getCSSStylesheet' 2 | import findCSSSheetValue from './findCSSSheetValue' 3 | 4 | import { IGetStyles, IExpectations } from '../interfaces/IUtils' 5 | 6 | const getStyles = ({ el, properties, pseudo }: IGetStyles) => { 7 | const elementCSSData = getCSSStylesheet(el, pseudo) 8 | 9 | if (Array.isArray(properties)) { 10 | const propsObject: IExpectations = {}; 11 | 12 | (properties as string[]).forEach((propKey: string) => { 13 | propsObject[propKey] = findCSSSheetValue(elementCSSData, propKey) 14 | }) 15 | 16 | return propsObject 17 | } 18 | 19 | return findCSSSheetValue(elementCSSData, (properties as string)) 20 | } 21 | 22 | export default getStyles 23 | -------------------------------------------------------------------------------- /cypress/apps/test_example/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Attack - E2E scenarios 2 | 3 | 4 | ## 2.0.0 5 | * Fully covered Attacker & defender behaviors UI tests. 6 | * Improved selector structure. 7 | 8 | ## 1.7.0 9 | * Added Effects Behavior UI test suits. 10 | 11 | ## 1.6.0 12 | * Added StatusBar Behavior UI test suits. 13 | 14 | ## 1.5.0 15 | * Added Attacker Model and Armour Behavior UI test suits. 16 | 17 | ## 1.4.0 18 | * Added Attacker Overlay Behavior UI test suits. 19 | 20 | ## 1.3.0 21 | * Added Weapons Behavior UI test suits. 22 | * Added IndicatorFacilities Behavior UI test suits. 23 | * Added StealthBar Behavior UI test suits. 24 | 25 | ## 1.2.0 26 | * Added Header Behavior UI test suits. 27 | 28 | ## 1.1.0 29 | * Added 3 NPC fight tests. 30 | 31 | ## 1.0.0 32 | * Added first 3 basic scenarios. 33 | -------------------------------------------------------------------------------- /cypress/apps/test_example/modules/runFight.ts: -------------------------------------------------------------------------------- 1 | import initiateE2ETest from '../../../services/initiateE2ETest' 2 | 3 | import subscribeAPI from '../api' 4 | import { onInit, onFocus, onPoll, onStart } from '../controller' 5 | 6 | import { RIVAL_ID } from '../constants' 7 | import { IRunFight } from '../interfaces/IModules' 8 | 9 | const runFight = ({ withFocus = false, rivalID = null }: IRunFight) => { 10 | initiateE2ETest({ 11 | runAPI: subscribeAPI, 12 | url: `/loader.php?sid=attack&user2ID=${rivalID || Cypress.env('DEFENDER_ID') || RIVAL_ID}` 13 | }) 14 | 15 | // initial load data 16 | onInit() 17 | 18 | // focus should fire 19 | withFocus && onFocus() 20 | 21 | // poll should running 22 | onPoll() 23 | 24 | // staring fight 25 | onStart() 26 | } 27 | 28 | export default runFight 29 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const LIFE_BAR_BCG = 'rgba(0, 0, 0, 0) linear-gradient(rgb(107, 132, 218), rgb(68, 74, 208)) repeat scroll 0% 0% / auto padding-box border-box' 2 | 3 | export const COLORS = { 4 | green: '#66a80f', 5 | grey: 'rgb(136, 136, 136)', 6 | red: '#fff' 7 | } 8 | 9 | export const WEAPON_BOX_DIMENSIONS = { 10 | regular: { 11 | width: '159px', 12 | height: '96px' 13 | }, 14 | slim: { 15 | width: '159px', 16 | height: '67px' 17 | }, 18 | paired: { 19 | width: '79.5px', 20 | height: '67px' 21 | } 22 | } 23 | 24 | export const MODAL_ATTACKER_BCG = 'none' 25 | export const MODAL_DEFENDER_BCG = 'repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5) 1px, rgba(0, 0, 0, 0) 1.1px, rgba(0, 0, 0, 0) 3.5px), none' 26 | -------------------------------------------------------------------------------- /cypress/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | // import 'cypress' 18 | // eslint-disable-next-line 19 | import './commands' 20 | // Alternatively you can use CommonJS syntax: 21 | // require('./commands') 22 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CoConat Builder 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cacheDirectory: '/.tmp/jest', 3 | coverageDirectory: './.tmp/coverage', 4 | moduleNameMapper: { 5 | '^.+\\.(css|scss|cssmodule)$': 'identity-obj-proxy' 6 | }, 7 | modulePaths: [''], 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], 9 | globals: { 10 | NODE_ENV: 'test' 11 | }, 12 | verbose: true, 13 | testRegex: '(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js|jsx)$', 14 | testPathIgnorePatterns: ['/node_modules/', '/__tests__/mocks/.*', '/cypress/'], 15 | coveragePathIgnorePatterns: ['typings.d.ts'], 16 | transformIgnorePatterns: ['.*(node_modules).*$'], 17 | transform: { 18 | '^.+\\.jsx?$': 'babel-jest', 19 | '^.+\\.tsx?$': 'ts-jest' 20 | }, 21 | setupFiles: ['/setupTests.js'], 22 | snapshotSerializers: ['enzyme-to-json/serializer'] 23 | }; 24 | -------------------------------------------------------------------------------- /src/components/Body/index.scss: -------------------------------------------------------------------------------- 1 | .animWrap { 2 | position: relative; 3 | } 4 | 5 | .textLabel { 6 | color: #111; 7 | } 8 | 9 | .red { 10 | background-color: red; 11 | } 12 | 13 | .black { 14 | background-color: black; 15 | } 16 | 17 | .transparent { 18 | background-color: transparent; 19 | } 20 | 21 | :global { 22 | .mainImage-enter { 23 | opacity: 0; 24 | position: absolute; 25 | transition: all 500ms linear; 26 | transform: translateX(-10%); 27 | } 28 | 29 | .mainImage-enter-active { 30 | opacity: 1; 31 | transform: translateX(0%); 32 | } 33 | 34 | .mainImage-exit { 35 | transition: all 500ms linear; 36 | transform: translateX(0%); 37 | opacity: 1; 38 | } 39 | 40 | .mainImage-exit-active { 41 | transition: all 500ms linear; 42 | transform: translateX(10%); 43 | opacity: 0; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noUnusedLocals": true, 4 | "noUnusedParameters": true, 5 | "allowSyntheticDefaultImports": true, 6 | "esModuleInterop": true, 7 | "allowJs": true, 8 | "checkJs": false, 9 | "module": "esnext", 10 | "target": "es5", 11 | "jsx": "react", 12 | "moduleResolution": "node", 13 | "lib": ["es2020", "es2019", "es2018", "es2016", "es2015", "es6", "es2017", "dom"], 14 | "outDir": "./dist" 15 | }, 16 | "typeAcquisition": { 17 | "enable": true 18 | }, 19 | "typeRoots": [ 20 | "./typings.d.ts", 21 | "./node_modules/@types" 22 | ], 23 | "include": [ 24 | ".src/.ts", 25 | "src/**/*", 26 | "./typings.d.ts" 27 | ], 28 | "exclude": [ 29 | "node_modules", 30 | "**/*.test.ts", 31 | "cypress", 32 | "server", 33 | "dist" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/selectors/facilities.ts: -------------------------------------------------------------------------------- 1 | import { TPersonTypes } from '../../interfaces/IController' 2 | import { IFacilities } from '../../interfaces/IView' 3 | 4 | export const facilitiesWrap = (type: TPersonTypes) => cy.get(`#${type} [class^="indicatorsContainer"]`) 5 | 6 | export const facilityContainer = ({ userType, facility }: IFacilities) => facilitiesWrap(userType).find(`[id^="${userType}_${facility}"]`) 7 | export const facilityArrow = ({ userType, facility }: IFacilities) => facilitiesWrap(userType).find(`[id^="${userType}_${facility}"] [class^="arrow"]`) 8 | export const facilityValue = ({ userType, facility }: IFacilities) => facilitiesWrap(userType).find(`[id^="${userType}_${facility}"] [class^="value"]`) 9 | export const facilityLabel = ({ userType, facility }: IFacilities) => facilitiesWrap(userType).find(`[id^="${userType}_${facility}"] [class^="title"]`) 10 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/behavior/stealthbar.ts: -------------------------------------------------------------------------------- 1 | import checkPosition from '../../../../utils/checkPosition' 2 | 3 | import { stealthBar, stealthBarIcon } from '../selectors/stealthBar' 4 | 5 | import { ICheckStealthBar } from '../../interfaces/IView' 6 | import checkStyles from '../../../../utils/checkStyles' 7 | 8 | export const checkStealthBar = (config: ICheckStealthBar) => { 9 | cy.log('**Check StealthBar: attacker**') 10 | 11 | const { height = '385px' } = config 12 | 13 | stealthBar().should('have.length', 1) 14 | stealthBarIcon().should('have.length', 1) 15 | 16 | checkStyles({ 17 | elemNode: stealthBar(), 18 | expectations: { 19 | width: '2px', 20 | height: height, 21 | } 22 | }) 23 | 24 | // this is only displayed on attacker model we don't need 25 | checkPosition({ nodeElement: stealthBar, leftOffset: 123, topOffset: 174 }) 26 | checkPosition({ nodeElement: stealthBarIcon, leftOffset: 117, topOffset: 564 }) 27 | } 28 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/selectors/modal.ts: -------------------------------------------------------------------------------- 1 | import { TPersonTypes } from '../../interfaces/IController' 2 | import { ATTACKER } from '../../constants' 3 | import { TModalColors } from '../../interfaces/IView' 4 | 5 | export const modalWrap = (userType: TPersonTypes) => { 6 | const isAttacker = userType === ATTACKER 7 | const transBG = isAttacker ? '[class*="transBg"]' : '' 8 | 9 | return cy.get(`#${userType} [class^="modal"]${transBG}`) 10 | } 11 | 12 | export const modalContainer = (userType: TPersonTypes) => modalWrap(userType).find('[class^="dialog_"]') 13 | export const modalBox = (userType: TPersonTypes, type: TModalColors) => modalContainer(userType).find(`[class^="colored"]${type ? `[class*="${type}]` : ''}`) 14 | export const modalTitle = (userType: TPersonTypes, type: TModalColors) => modalBox(userType, type).find('[class^="title"]') 15 | export const modalButtonsWrap = (userType: TPersonTypes, type: TModalColors) => modalBox(userType, type).find('[class^="dialogButtons"]') 16 | -------------------------------------------------------------------------------- /cypress/apps/test_example/triggers/index.ts: -------------------------------------------------------------------------------- 1 | import { common, weapons } from '../view/selectors' 2 | import { ATTACKER } from '../constants' 3 | 4 | // native triggers 5 | export const windowFocus = () => cy.window().trigger('focus') 6 | 7 | // common triggers 8 | export const start = () => common.start().click() 9 | export const leave = () => common.leave().click() 10 | export const mug = () => common.mug().click() 11 | export const hosp = () => common.hosp().click() 12 | export const escape = () => common.escape().click() 13 | export const continues = () => common.continues().click() 14 | 15 | // attacker weapon triggers 16 | export const mainWeapon = () => weapons.mainWeapon(ATTACKER).click() 17 | export const secondaryWeapon = () => weapons.secondaryWeapon(ATTACKER).click() 18 | export const meleeWeapon = () => weapons.meleeWeapon(ATTACKER).click() 19 | export const tempWeapon = () => weapons.tempWeapon(ATTACKER).click() 20 | export const fistsWeapon = () => weapons.fistsWeapon(ATTACKER).click() 21 | -------------------------------------------------------------------------------- /src/store/middleware/rootReducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { connectRouter } from 'connected-react-router' 3 | import { createResponsiveStateReducer } from 'redux-responsive' 4 | 5 | import app from '../../controller/reducers' 6 | import history from '../history' 7 | 8 | // root Redux reducer 9 | const makeRootReducer = (asyncReducers) => { 10 | return combineReducers({ 11 | ...asyncReducers, 12 | app, 13 | router: connectRouter(history), 14 | browser: createResponsiveStateReducer({ 15 | mobile: 600, 16 | tablet: 1000, 17 | desktop: 5000 18 | }) 19 | }) 20 | } 21 | 22 | // Splite-Chunks environment, probably you would always need this 23 | export const injectReducer = (store, { key, reducer }) => { 24 | if (Object.hasOwnProperty.call(store.asyncReducers, key)) return 25 | store.asyncReducers[key] = reducer 26 | 27 | store.replaceReducer(makeRootReducer(store.asyncReducers)) 28 | } 29 | 30 | export default makeRootReducer 31 | -------------------------------------------------------------------------------- /server/proxy.ts: -------------------------------------------------------------------------------- 1 | // const { createProxyMiddleware } = require('http-proxy-middleware') 2 | 3 | // const proxyFiles = [ 4 | // '/js/**', 5 | // '/css/**', 6 | // '/images/**', 7 | // '/fonts/**', 8 | // '**/favicon.ico', 9 | // '**/manifest.json', 10 | // '**.php' // able to load real pages 11 | // ] 12 | 13 | // const commonProxyMiddleware = createProxyMiddleware( 14 | // proxyFiles, 15 | // { 16 | // target: 'YOUR_WEB_APP_URL', 17 | // changeOrigin: true, 18 | // // auth: '', 19 | // ws: true, 20 | // secure: true, 21 | // cookieDomainRewrite: 'localhost', 22 | // headers: { 23 | // origin: 'YOUR_WEB_APP_URL', 24 | // Connection: 'keep-alive', 25 | // 'Access-Control-Allow-Origin': '*' 26 | // }, 27 | // onProxyRes: _proxyRes => { 28 | // // modify your responses in way you like 29 | // } 30 | // } 31 | // ) 32 | 33 | // unwrap commonProxyMiddleware once you need to activate it 34 | module.exports = [/* commonProxyMiddleware */] 35 | -------------------------------------------------------------------------------- /src/controller/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { LOCATION_CHANGE } from 'connected-react-router' 2 | import { SWITCH_IMAGE, SOME_ASYNC_ACTION } from '../../constants' 3 | import initialState from '../../store/initialState' 4 | 5 | // ------------------------------------ 6 | // Action Handlers 7 | // ------------------------------------ 8 | const ACTION_HANDLERS = { 9 | [SWITCH_IMAGE]: (state, action) => ({ 10 | ...state, 11 | imageToShow: action.imageID 12 | }), 13 | [SOME_ASYNC_ACTION]: (state, action) => ({ 14 | ...state, 15 | ...action.payload 16 | }), 17 | [LOCATION_CHANGE]: (state, action) => ({ 18 | ...state, 19 | locationChange: action.payload.location.pathname 20 | }) 21 | } 22 | 23 | // ------------------------------------ 24 | // Reducer 25 | // ------------------------------------ 26 | const reducer = (state = initialState, action) => { 27 | const handler = ACTION_HANDLERS[action.type] 28 | 29 | return handler ? handler(state, action) : state 30 | } 31 | 32 | export default reducer 33 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/selectors/header.ts: -------------------------------------------------------------------------------- 1 | import { TPersonTypes } from '../../interfaces/IController' 2 | 3 | export const headerWrap = (userType: TPersonTypes) => cy.get(`#${userType} [class^="boxTitle"][class*="header"]`) 4 | 5 | export const userName = (userType: TPersonTypes) => headerWrap(userType).find('[class^="userName"]') 6 | export const hitsIcon = (userType: TPersonTypes) => headerWrap(userType).find('[class^="iconHits"]') 7 | export const hitsCount = (userType: TPersonTypes) => headerWrap(userType).find('span[id^="player-hits"]') 8 | export const damageIcon = (userType: TPersonTypes) => headerWrap(userType).find('[class^="iconDamage"]') 9 | export const damageCount = (userType: TPersonTypes) => headerWrap(userType).find('span[id^="player-damage"]') 10 | export const healthIcon = (userType: TPersonTypes) => headerWrap(userType).find('[class^="iconHealth"]') 11 | export const healthCount = (userType: TPersonTypes) => headerWrap(userType).find('span[id^="player-health"]') 12 | export const lifeBar = (userType: TPersonTypes) => headerWrap(userType).find('[class^="progress"]') 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | # A special property that should be specified at the top of the file outside of 4 | # any sections. Set to true to stop .editor config file search on current file 5 | root = true 6 | 7 | [*] 8 | # File character encoding 9 | # Possible values - latin1, utf-8, utf-16be, utf-16le 10 | charset = utf-8 11 | 12 | # Line ending file format 13 | # Possible values - lf, crlf, cr 14 | end_of_line = lf 15 | 16 | # Indentation style 17 | # Possible values - tab, space 18 | indent_style = space 19 | 20 | # Indentation size in single-spaced characters 21 | # Space Possible values - 1, 2, 4, 8 22 | indent_size = 2 23 | 24 | # Max line length 25 | # Possible values - integer, false 26 | max_line_length = 120 27 | 28 | # String quote type 29 | # Possible values - single, double 30 | quote_type = single 31 | 32 | # Denotes whether to trim whitespace at the end of lines 33 | # Possible values - true, false 34 | trim_trailing_whitespace = true 35 | 36 | # Denotes whether file should end with a newline 37 | # Possible values - true, false 38 | insert_final_newline = true 39 | -------------------------------------------------------------------------------- /cypress/helpers/cookies.ts: -------------------------------------------------------------------------------- 1 | const setCookies = ({ type }: { type?: 'basic' | 'full' }) => { 2 | cy.setCookie('rfc_id', '5e4958c36ae92') 3 | cy.setCookie('rfc_v', '5e4958c36ae92') 4 | 5 | if (type === 'full') { 6 | cy.setCookie('MUIDB', '226533C53ED0665A285B3DB73F1B67B8') 7 | cy.setCookie('MUID', '226533C53ED0665A285B3DB73F1B67B8') 8 | cy.setCookie('_uetsid', '_uetd8273680-7fd3-26ca-86e3-f3332e3317ee') 9 | cy.setCookie('uid', '2114355') 10 | cy.setCookie('at95b2278b6336bf6c96c827532981cc09', 'ca1d8c8426dca99624b79f4306d55860') 11 | cy.setCookie('MR2', '0') 12 | cy.setCookie('PHPSESSID', '902c198318f82ada9ffc5e373aee347a') 13 | cy.setCookie('__cfduid', 'd665319e0675d6939dfea07ca6dabdb601587613695') 14 | cy.setCookie('sso_wiki_user', 'svyat770') 15 | cy.setCookie('_gid', 'GA1.2.1616290244.1587615425') 16 | cy.setCookie('_gcl_au', '1.1.439557684.1587615425') 17 | cy.setCookie('MR', '0') 18 | cy.setCookie('secret', '5ea11001e6afb2.07976791') 19 | cy.setCookie('_ga', 'GA1.2.235348367.1587615425') 20 | } 21 | } 22 | 23 | export default setCookies 24 | -------------------------------------------------------------------------------- /cypress/template/scenarios/basicFight.ts: -------------------------------------------------------------------------------- 1 | // import { runFight, shouldAttack } from '../modules' 2 | // import { leave } from '../controller' 3 | // import { checkStealthBar, checkDefenderLifeBar, checkDefenderEffects } from '../view/behavior' 4 | // import { MAIN_WEAPON, TEMP_WEAPON, SECONDARY_WEAPON, FISTS_WEAPON } from '../constants' 5 | 6 | // const basicFight = () => { 7 | // // start fight 8 | // runFight({ withFocus: false }) 9 | 10 | // checkStealthBar() 11 | // checkDefenderLifeBar('463px') 12 | // checkDefenderEffects({ wrap: 0, count: 0 }) 13 | 14 | // // attack with temp 15 | // onAttack(MAIN_WEAPON) 16 | 17 | // // check temp effect 18 | // onAttack(TEMP_WEAPON) 19 | // checkDefenderEffects({ count: 1 }) 20 | 21 | // // attack with second gun 22 | // onAttack(SECONDARY_WEAPON) 23 | 24 | // // regular attack 25 | // shouldAttack({ weaponType: FISTS_WEAPON, attackCount: 30 }) 26 | 27 | // // finish fight via leave defender on the street 28 | // onLeave() 29 | // checkDefenderLifeBar('0px') 30 | // } 31 | 32 | // export default basicFight 33 | -------------------------------------------------------------------------------- /cypress/apps/test_example/constants/index.ts: -------------------------------------------------------------------------------- 1 | import { TWeaponsType } from '../interfaces/IModules' 2 | import { TPersonTypes } from '../interfaces/IController' 3 | import { TGenderTypes, TGenderShortTypes } from '../interfaces/IView' 4 | 5 | export const RIVAL_ID = '1435333' 6 | export const RIVAL_NAME = 'toshykazu' 7 | 8 | export const MAIN_WEAPON: TWeaponsType = 'mainWeapon' 9 | export const SECONDARY_WEAPON: TWeaponsType = 'secondaryWeapon' 10 | export const MELEE_WEAPON: TWeaponsType = 'meleeWeapon' 11 | export const TEMP_WEAPON: TWeaponsType = 'tempWeapon' 12 | export const FISTS_WEAPON: TWeaponsType = 'fistsWeapon' 13 | 14 | export const ATTACKER: TPersonTypes = 'attacker' 15 | export const DEFENDER: TPersonTypes = 'defender' 16 | 17 | export const MALE: TGenderTypes = 'male' 18 | export const FEMALE: TGenderTypes = 'female' 19 | export const MALE_SHORT: TGenderShortTypes = 'm' 20 | export const FEMALE_SHORT: TGenderShortTypes = 'f' 21 | 22 | export const SHORT_GENDERS = { 23 | m: MALE_SHORT, 24 | f: FEMALE_SHORT 25 | } 26 | 27 | export const FULL_GENDERS = { 28 | m: MALE, 29 | f: FEMALE 30 | } 31 | -------------------------------------------------------------------------------- /cypress/apps/test_example/modules/finishFight.ts: -------------------------------------------------------------------------------- 1 | import redirectToLog from './redirectToLog' 2 | 3 | import { checkModalOverlay } from '../view/behavior/modal' 4 | 5 | import { IFinishFight } from '../interfaces/IModules' 6 | import { DEFENDER } from '../constants' 7 | import { onWin } from '../controller' 8 | 9 | const finishFight = ({ type, withOptions, withoutDefenderName }: IFinishFight) => { 10 | // finish fight via continue 11 | checkModalOverlay({ 12 | userType: DEFENDER, 13 | type, 14 | withOptions, 15 | actionButton: !withOptions ? 'continue' : '' 16 | }) 17 | 18 | // in case we have option before finish the fight 19 | if (withOptions) { 20 | onWin({ type }) 21 | .then(() => { 22 | checkModalOverlay({ 23 | userType: DEFENDER, 24 | type, 25 | withName: !withoutDefenderName, 26 | actionButton: 'continue' 27 | }) 28 | 29 | // redirect to the log page 30 | redirectToLog() 31 | }) 32 | 33 | return 34 | } 35 | 36 | // redirect to the log page 37 | redirectToLog() 38 | } 39 | 40 | export default finishFight 41 | -------------------------------------------------------------------------------- /server/ssl/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC5TCCAc2gAwIBAgIJANt+kw3e+AwZMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0yMDAyMjQwNDE1MjNaFw0yMDAzMjUwNDE1MjNaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 5 | ggEBALxwpDv6L/kSJcSZWRotYegchu1+3ANJEIeAwK0bPocf240P9D71t/xjJ+KU 6 | DO4yRwdDMmYw3q/BFqub2/ZkuE3suPVjQr42L+qELQnUlCaZgEM3sqgSiTlMN3Jp 7 | skCpvkbZspaL3RFyKL8y+bNVWXXhtjzd11O2QdnWDnQcthjxcIjbml39f4P654NG 8 | 1VM/ofVihHgIu6xSZUBM9EZ7pW6D0Vv1sx7q0XqgrdFTQgTiLADj8FSHxD7dh9hN 9 | zwnpaD1xfH9wVSWEM0sCAy5vIPi1VD63KPznIlRz8HKL2sLI6vccMSrY7vU1oCSr 10 | bms+xU+qF34RSrslzOCqqtnUZSUCAwEAAaM6MDgwFAYDVR0RBA0wC4IJbG9jYWxo 11 | b3N0MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B 12 | AQsFAAOCAQEAGLgw6Hk5E03KL290Slc1AoKe/cjFvaG01oKXzVlUugbY6uJr1FlZ 13 | S8jRRjtCEBkG+Bsf3dO7UfdF5rSEhMVlnNfM+m2PiTZVNKkx6lMRultzVD83GRf6 14 | qz+9GtwsLbYUyhUc4g0ft/gewvuL5DgYpffqPuNB1xvqooxNsUmfPfxZLS16TOBH 15 | hq7auEcLjt7rmWzFqThG/jrN4UrjAwdgSTpxIVTO68WdcUyouUtDRqpBQd56cBUz 16 | kXliP53auIza3P4jwJIAAk2r8lUJnoN1AVRJ4RC+EkBODBT5xgvuQpsfwY0bBTzw 17 | b4/Y+pyejwJRv1iAeAagOsPZpziHI/IK7w== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sviat Kuzhelev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cypress/examples/waiting.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable cypress/no-unnecessary-waiting */ 2 | context('Waiting', () => { 3 | beforeEach(() => { 4 | cy.visit('https://example.cypress.io/commands/waiting') 5 | }) 6 | // BE CAREFUL of adding unnecessary wait times. 7 | // https://on.cypress.io/best-practices#Unnecessary-Waiting 8 | 9 | // https://on.cypress.io/wait 10 | it('cy.wait() - wait for a specific amount of time', () => { 11 | cy.get('.wait-input1').type('Wait 1000ms after typing') 12 | cy.wait(1000) 13 | cy.get('.wait-input2').type('Wait 1000ms after typing') 14 | cy.wait(1000) 15 | cy.get('.wait-input3').type('Wait 1000ms after typing') 16 | cy.wait(1000) 17 | }) 18 | 19 | it('cy.wait() - wait for a specific route', () => { 20 | cy.server() 21 | 22 | // Listen to GET to comments/1 23 | cy.route('GET', 'comments/*').as('getComment') 24 | 25 | // we have code that gets a comment when 26 | // the button is clicked in scripts.js 27 | cy.get('.network-btn').click() 28 | 29 | // wait for GET comments/1 30 | cy.wait('@getComment').its('status').should('eq', 200) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /cypress/apps/test_example/scenarios/stalematedFight.ts: -------------------------------------------------------------------------------- 1 | import { onAttack } from '../controller' 2 | import { runFight, shouldAttack, finishFight } from '../modules' 3 | import { checkEffects } from '../view/behavior/effects' 4 | import { MAIN_WEAPON, TEMP_WEAPON, SECONDARY_WEAPON, FISTS_WEAPON, DEFENDER } from '../constants' 5 | 6 | const stalematedFight = () => { 7 | // start fight 8 | runFight({ rivalID: Cypress.env('DEFENDER_ID'), withFocus: false }) 9 | 10 | checkEffects({ userType: DEFENDER, wrapCount: 0, effectsList: [] }) 11 | 12 | // attack with temp 13 | onAttack(MAIN_WEAPON) 14 | 15 | // check temp effect 16 | onAttack(TEMP_WEAPON) 17 | checkEffects({ 18 | userType: DEFENDER, 19 | wrapCount: 1, 20 | effectsList: [ 21 | { 22 | name: 'smoked', 23 | type: 'red' 24 | } 25 | ] 26 | }) 27 | 28 | // attack with second gun 29 | onAttack(SECONDARY_WEAPON) 30 | 31 | // regular attack 32 | shouldAttack({ weaponType: FISTS_WEAPON, attackCount: 30 }) 33 | 34 | // finish fight by continue 35 | finishFight({ type: 'stalemated', withoutDefenderName: true }) 36 | } 37 | 38 | export default stalematedFight 39 | -------------------------------------------------------------------------------- /cypress/examples/location.spec.ts: -------------------------------------------------------------------------------- 1 | context('Location', () => { 2 | beforeEach(() => { 3 | cy.visit('https://example.cypress.io/commands/location') 4 | }) 5 | 6 | it('cy.hash() - get the current URL hash', () => { 7 | // https://on.cypress.io/hash 8 | cy.hash().should('be.empty') 9 | }) 10 | 11 | it('cy.location() - get window.location', () => { 12 | // https://on.cypress.io/location 13 | cy.location().should((location) => { 14 | expect(location.hash).equal('') 15 | expect(location.href).equal('https://example.cypress.io/commands/location') 16 | expect(location.host).equal('example.cypress.io') 17 | expect(location.hostname).equal('example.cypress.io') 18 | expect(location.origin).equal('https://example.cypress.io') 19 | expect(location.pathname).equal('/commands/location') 20 | expect(location.port).equal('') 21 | expect(location.protocol).equal('https:') 22 | expect(location.search).equal('') 23 | }) 24 | }) 25 | 26 | it('cy.url() - get the current URL', () => { 27 | // https://on.cypress.io/url 28 | cy.url().should('eq', 'https://example.cypress.io/commands/location') 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // fetching required static data on load 2 | // self.addEventListener('install', e => { 3 | // console.log(e, 'SW has been installed!') 4 | // }) 5 | 6 | // self.addEventListener('fetch', function(event) { 7 | // console.log(event, 'SWHAS BEEN FETCHED!!!') 8 | // }) 9 | 10 | // notification 11 | self.addEventListener('push', function (event) { 12 | if (Notification.permission === 'default') { 13 | self.Notification.requestPermission().then((res) => console.log(res)) 14 | console.log('The permission request was dismissed.') 15 | return 16 | } 17 | 18 | if (Notification.permission === 'denied') { 19 | console.log(Notification.permission, "Permission wasn't granted. Allow a retry.") 20 | return 21 | } 22 | 23 | console.log('The permission request is granted!') 24 | 25 | try { 26 | event.waitUntil( 27 | self.registration.showNotification((event && event.data && event.data.text()) || 'Some Notification Here!') 28 | ) 29 | } catch (e) { 30 | throw new Error(`Error in SW: ${e}`) 31 | } 32 | }) 33 | 34 | self.addEventListener('sync', function (event) { 35 | console.log(event.tag, 'Sync is completed!!!') 36 | }) 37 | -------------------------------------------------------------------------------- /typings.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any; 3 | __REDUX_DEVTOOLS_EXTENSION__: any; 4 | } 5 | 6 | declare const __DEV__: string; 7 | declare const __PROD__: string; 8 | declare const __TEST__: string; 9 | 10 | declare module '*.json' { 11 | interface IClassNames { 12 | [className: string]: string 13 | } 14 | const classNames: IClassNames; 15 | export = classNames; 16 | } 17 | 18 | declare module '*.css' { 19 | interface IClassNames { 20 | [className: string]: string 21 | } 22 | const classNames: IClassNames; 23 | export = classNames; 24 | } 25 | 26 | declare module '*.scss' { 27 | interface IClassNames { 28 | [className: string]: string 29 | } 30 | const classNames: IClassNames; 31 | export = classNames; 32 | } 33 | 34 | declare module '*.cssmodule.scss' { 35 | interface IClassNames { 36 | [className: string]: string 37 | } 38 | const classNames: IClassNames; 39 | export = classNames; 40 | } 41 | 42 | declare module '*.svg' { 43 | interface IClassNames { 44 | [className: string]: string 45 | } 46 | const classNames: IClassNames; 47 | export = classNames; 48 | } 49 | -------------------------------------------------------------------------------- /cypress/helpers/login.ts: -------------------------------------------------------------------------------- 1 | import complete2FA from '../utils/complete2FA' 2 | 3 | import { ILogin } from '../interfaces/IHelpers' 4 | 5 | import { BASIC_AUTH, BASE_ROOT, EMAIL_2FA, PASSWORD_2FA, EMAIL, PASSWORD } from '../constants/login' 6 | 7 | const login = ({ email, password }: ILogin) => { 8 | cy.visit(`https://${BASIC_AUTH}@${BASE_ROOT}/login.php`) 9 | 10 | const basic = () => { 11 | cy.get('#player').type(email || EMAIL) 12 | cy.get('#password').type(password || PASSWORD) 13 | cy.get('.login').click() 14 | 15 | // checking if we're on the Home page 16 | cy.contains('You have logged on maryan060!') 17 | } 18 | 19 | const full = () => { 20 | // making basic login 21 | cy.get('#player').type(email || EMAIL_2FA) 22 | cy.get('#password').type(password || PASSWORD_2FA) 23 | cy.get('.login').click() 24 | 25 | // should be a redirect to authentication screen for 2FA 26 | // and complete 2FA challenge 27 | cy.url().should('match', /authenticate.php/) 28 | complete2FA() 29 | 30 | // checking if we're on the Home page 31 | cy.contains('Home') 32 | } 33 | 34 | return { 35 | basic, 36 | full 37 | } 38 | } 39 | 40 | export default login 41 | -------------------------------------------------------------------------------- /cypress/examples/aliasing.spec.ts: -------------------------------------------------------------------------------- 1 | context('Aliasing', () => { 2 | beforeEach(() => { 3 | cy.visit('https://example.cypress.io/commands/aliasing') 4 | }) 5 | 6 | it('.as() - alias a DOM element for later use', () => { 7 | // https://on.cypress.io/as 8 | 9 | // Alias a DOM element for use later 10 | // We don't have to traverse to the element 11 | // later in our code, we reference it with @ 12 | 13 | cy.get('.as-table').find('tbody>tr') 14 | .first().find('td').first() 15 | .find('button').as('firstBtn') 16 | 17 | // when we reference the alias, we place an 18 | // @ in front of its name 19 | cy.get('@firstBtn').click() 20 | 21 | cy.get('@firstBtn') 22 | .should('have.class', 'btn-success') 23 | .and('contain', 'Changed') 24 | }) 25 | 26 | it('.as() - alias a route for later use', () => { 27 | // Alias the route to wait for its response 28 | cy.server() 29 | cy.route('GET', 'comments/*').as('getComment') 30 | 31 | // we have code that gets a comment when 32 | // the button is clicked in scripts.js 33 | cy.get('.network-btn').click() 34 | 35 | // https://on.cypress.io/wait 36 | cy.wait('@getComment').its('status').should('eq', 200) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /cypress/apps/test_example/modules/shouldAttack.ts: -------------------------------------------------------------------------------- 1 | import { onAttack } from '../controller' 2 | import * as dataMocks from '../mocks' 3 | import getDataMock from '../utils/getDataMock' 4 | 5 | import getSyncInterval from '../../../utils/getSyncInterval' 6 | import { IShouldAttack } from '../interfaces/IModules' 7 | 8 | const shouldAttack = ({ weaponType = 'fistsWeapon', attackCount = 30 }: IShouldAttack) => { 9 | let FIGHT_PROGRESS = null 10 | 11 | const isFightOver = () => FIGHT_PROGRESS === 'end' 12 | const weaponMockData = getDataMock({ dataMock: dataMocks[`${weaponType}Data`] }) 13 | 14 | const commit = () => { 15 | if (isFightOver()) { 16 | return 17 | } 18 | 19 | onAttack(weaponType).then(({ requestData, responseData }) => { 20 | FIGHT_PROGRESS = responseData.DB.attackStatus 21 | 22 | const weaponIDCheck = `**ACTION FIRED. WEAPON ID:** ${requestData.user1EquipedItemID}.` 23 | const payloadCheck = `**PAYLOAD:** \n ${JSON.stringify(requestData)}` 24 | 25 | cy.log(`${weaponIDCheck} ${payloadCheck}`).then(() => expect(requestData).eql(weaponMockData)) 26 | }) 27 | } 28 | 29 | const attackWithInterval = getSyncInterval({ callback: commit, iterCount: attackCount }) 30 | 31 | attackWithInterval({ isClear: isFightOver() }) 32 | } 33 | 34 | export default shouldAttack 35 | -------------------------------------------------------------------------------- /server/compiler.ts: -------------------------------------------------------------------------------- 1 | // import global vars for a whole app 2 | require('../globals'); 3 | 4 | const debug = require('debug')('app:build:webpack-compiler'); 5 | const webpack = require('webpack'); 6 | const webpackConfig = require('../webpack.config.js'); 7 | 8 | // ----------------------------- 9 | // READING WEBPACK CONFIGURATION 10 | // ----------------------------- 11 | function webpackCompiler() { 12 | return new Promise((resolve, reject) => { 13 | const compiler = webpack(webpackConfig); 14 | 15 | compiler.run((err, stats) => { 16 | if (err) { 17 | debug('Webpack compiler encountered a fatal error.', err); 18 | 19 | return reject(err); 20 | } 21 | 22 | const jsonStats = stats.toJson(); 23 | 24 | debug('Webpack compilation is completed.'); 25 | 26 | resolve(jsonStats); 27 | }); 28 | }); 29 | } 30 | 31 | // ----------------------------- 32 | // STARTING APP COMPILATION PROCESS 33 | // ----------------------------- 34 | const compile = () => { 35 | debug('Starting compiler.'); 36 | 37 | return Promise.resolve() 38 | .then(() => webpackCompiler()) 39 | .then(() => { 40 | debug('Compilation completed successfully.'); 41 | }) 42 | .catch(err => { 43 | debug('Compiler encountered an error.', err); 44 | 45 | process.exit(1); 46 | }); 47 | }; 48 | 49 | compile(); 50 | -------------------------------------------------------------------------------- /cypress/interfaces/IUtils.d.ts: -------------------------------------------------------------------------------- 1 | export interface IGetSyncInterval { 2 | callback: () => void 3 | iterCount?: number 4 | delay?: number 5 | } 6 | 7 | export interface IWithoutParent { 8 | nodeElement: any 9 | topOffset: number 10 | leftOffset: number 11 | compareValue?: boolean 12 | } 13 | 14 | export interface IWithParentPosition { 15 | parentElement: any 16 | nodeElement: any 17 | topOffset: number 18 | leftOffset: number 19 | compareValue?: boolean 20 | } 21 | 22 | export interface IGetLoop { 23 | callback: () => void 24 | count?: number 25 | } 26 | 27 | export type TPseudoCSSTypes = 'before' | 'after' 28 | 29 | export interface ICheckElemPos { 30 | nodeElement: any 31 | withParent?: boolean 32 | compareValue?: boolean 33 | parentElement?: any 34 | topOffset?: number 35 | leftOffset?: number 36 | } 37 | 38 | // export interface IPropertiesList { 39 | // styles: string | string[] 40 | // pseudo: TPseudoCSSTypes 41 | // } 42 | 43 | export type TElementCSS = HTMLElement 44 | export type TPropertiesCSS = string | string[] 45 | export interface IExpectations { 46 | [x: string]: string 47 | } 48 | 49 | export interface IGetStyles { 50 | el: TElementCSS 51 | properties: TPropertiesCSS 52 | pseudo?: TPseudoCSSTypes 53 | } 54 | 55 | export interface ICheckStyles { 56 | elemNode: Cypress.Chainable> 57 | expectations: IExpectations 58 | pseudo?: TPseudoCSSTypes 59 | } 60 | -------------------------------------------------------------------------------- /cypress/plugins/index.ts: -------------------------------------------------------------------------------- 1 | // / 2 | const webpack = require('@cypress/webpack-preprocessor') 3 | 4 | // *********************************************************** 5 | // This example plugins/index.js can be used to load plugins 6 | // 7 | // You can change the location of this file or turn off loading 8 | // the plugins file with the 'pluginsFile' configuration option. 9 | // 10 | // You can read more here: 11 | // https://on.cypress.io/plugins-guide 12 | // *********************************************************** 13 | 14 | // This function is called when a project is opened or re-opened (e.g. due to 15 | // the project's config changing) 16 | 17 | // allow using custom webpack config along with TS support 18 | module.exports = (on: Function) => { 19 | const options = { 20 | webpackOptions: import('../webpack.config'), 21 | watchOptions: {} 22 | } 23 | 24 | on('file:preprocessor', webpack(options)) 25 | } 26 | 27 | // allows us to start Cypress Browser in the full-screen mode 28 | /** 29 | * @type {Cypress.PluginConfig} 30 | */ 31 | module.exports = (on: Function, _config: object) => { 32 | on('before:browser:launch', (browser = {}, launchOptions: any) => { 33 | // @ts-ignore 34 | if (browser.name === 'electron') { 35 | launchOptions.preferences.fullscreen = true 36 | } else { 37 | launchOptions.args.push('--start-fullscreen') 38 | } 39 | 40 | return launchOptions 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, compose, createStore } from 'redux' 2 | import { routerMiddleware } from 'connected-react-router' 3 | import initialState from './initialState' 4 | 5 | import history from './history' 6 | 7 | import logger from './helpers/reduxLogger' 8 | import activeStoreHMR from './helpers/storeHMR' 9 | 10 | import sagaMiddleware, { runSaga, rootSaga } from './middleware/rootSaga' 11 | import rootReducer from './middleware/rootReducer' 12 | 13 | // creating the root store config 14 | const rootStore = () => { 15 | const middleware = [] 16 | 17 | // Adding app routing 18 | middleware.push(routerMiddleware(history)) 19 | 20 | // Adding async Saga actions environment 21 | middleware.push(sagaMiddleware) 22 | 23 | // Adding console logger for Redux 24 | middleware.push(logger) 25 | 26 | const enhancers = [] 27 | 28 | // allow to use the redux browser plugin 29 | if (__DEV__ && window.__REDUX_DEVTOOLS_EXTENSION__) { 30 | enhancers.push(window.__REDUX_DEVTOOLS_EXTENSION__()) 31 | } 32 | 33 | // ====================================================== 34 | // Store Instantiation 35 | // ====================================================== 36 | const store = createStore(rootReducer(), initialState, compose(applyMiddleware(...middleware), ...enhancers)) 37 | 38 | store.asyncReducers = {} 39 | 40 | runSaga(rootSaga) 41 | activeStoreHMR(store) 42 | 43 | return store 44 | } 45 | 46 | export default rootStore() 47 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path') 3 | 4 | const globals = require('../globals') 5 | 6 | module.exports = { 7 | stories: ['../**/*.story.*'], 8 | addons: [ 9 | '@storybook/addon-actions', 10 | '@storybook/addon-knobs', 11 | '@storybook/addon-links', 12 | '@storybook/addon-viewport' 13 | ], 14 | webpackFinal: async (config) => { 15 | const customConfig = { 16 | ...config, 17 | plugins: [ 18 | ...config.plugins, 19 | new webpack.DefinePlugin(globals), 20 | ], 21 | module: { 22 | ...config.module, 23 | rules: [ 24 | ...config.module.rules, 25 | // SCSS 26 | { 27 | test: /.scss$/, 28 | use: [ 29 | 'style-loader', 30 | { 31 | loader: 'css-loader', 32 | options: { 33 | importLoaders: 1, 34 | modules: { 35 | localIdentName: "[name]__[local]___[hash:base64:5]" 36 | } 37 | } 38 | }, 39 | 'sass-loader' 40 | ], 41 | include: path.resolve(__dirname, '../') 42 | } 43 | ] 44 | }, 45 | externals: { 46 | ...config.externals 47 | }, 48 | resolve: { 49 | ...config.resolve, 50 | modules: [...config.resolve.modules, 'node_modules'], 51 | extensions: [...config.resolve.extensions, '.ts', '.tsx', '.js', '.jsx', '.json'] 52 | } 53 | } 54 | 55 | return customConfig 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cypress/apps/test_example/mocks/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IStartFightData, 3 | IAttackData, 4 | IAttackWithID, 5 | ILeaveFightData, 6 | IEscapeFightData, 7 | IMugData, 8 | IHospData 9 | } from '../interfaces/IMocks' 10 | 11 | const startFightData: IStartFightData = { 12 | step: 'startFight', 13 | user2ID: '1435333' 14 | } 15 | 16 | const leaveFightData: ILeaveFightData = { 17 | step: 'finish', 18 | fightResult: 'leave' 19 | } 20 | 21 | const mugFightData: IMugData = { 22 | step: 'finish', 23 | fightResult: 'mug' 24 | } 25 | 26 | const hospFightData: IHospData = { 27 | step: 'finish', 28 | fightResult: 'hosp' 29 | } 30 | 31 | const escapeFightData: IEscapeFightData = { 32 | step: 'runAway' 33 | } 34 | 35 | const attackData: IAttackData = { 36 | step: 'attack', 37 | user2ID: '1435333' 38 | } 39 | 40 | const mainWeaponData: IAttackWithID = { 41 | ...attackData, 42 | user1EquipedItemID: '1' 43 | } 44 | 45 | const secondaryWeaponData: IAttackWithID = { 46 | ...attackData, 47 | user1EquipedItemID: '2' 48 | } 49 | 50 | const meleeWeaponData: IAttackWithID = { 51 | ...attackData, 52 | user1EquipedItemID: '0' 53 | } 54 | 55 | const tempWeaponData: IAttackWithID = { 56 | ...attackData, 57 | user1EquipedItemID: '5' 58 | } 59 | 60 | const fistsWeaponData: IAttackWithID = { 61 | ...attackData, 62 | user1EquipedItemID: '999' 63 | } 64 | 65 | export { 66 | startFightData, 67 | leaveFightData, 68 | mugFightData, 69 | hospFightData, 70 | escapeFightData, 71 | mainWeaponData, 72 | secondaryWeaponData, 73 | meleeWeaponData, 74 | tempWeaponData, 75 | fistsWeaponData 76 | } 77 | -------------------------------------------------------------------------------- /cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | import getStyles from '../utils/getStyles' 2 | 3 | import { TElementCSS, TPropertiesCSS, TPseudoCSSTypes } from '../interfaces/IUtils' 4 | 5 | // *********************************************** 6 | // This example commands.js shows you how to 7 | // create various custom commands and overwrite 8 | // existing commands. 9 | // 10 | // For more comprehensive examples of custom 11 | // commands please read more here: 12 | // https://on.cypress.io/custom-commands 13 | // *********************************************** 14 | // 15 | // 16 | // -- This is a parent command -- 17 | // Cypress.Commands.add("login", (email, password) => { ... }) 18 | // 19 | // 20 | // -- This is a child command -- 21 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is a dual command -- 25 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 26 | // 27 | // 28 | // -- This will overwrite an existing command -- 29 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 30 | 31 | // Cypress.on('uncaught:exception', (err, runnable) => { 32 | // // returning false here prevents Cypress from 33 | // // failing the test 34 | // debugger 35 | // return false 36 | // }) 37 | 38 | // Cypress.on('fail', (err) => { 39 | // debugger 40 | // }) 41 | 42 | Cypress.Commands.add( 43 | 'styles', 44 | { 45 | prevSubject: 'element' 46 | }, 47 | (el: TElementCSS, checkData: TPropertiesCSS, pseudo: TPseudoCSSTypes) => { 48 | return getStyles({ el, properties: checkData, pseudo }) 49 | } 50 | ) 51 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/body.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` should render basic Body Component with an image 1`] = ` 4 | 8 |
11 | 56 |
57 | 58 | `; 59 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/selectors/effects.ts: -------------------------------------------------------------------------------- 1 | import { TPersonTypes } from '../../interfaces/IController' 2 | import { TEffectColors } from '../../interfaces/IView' 3 | 4 | export const effectsWrap = (userType: TPersonTypes) => cy.get(`#${userType} [class^="effectsWrap"]`) 5 | export const effectsIconsWrap = (userType: TPersonTypes) => cy.get(`#${userType} [class^="iconsContainer"]`) 6 | 7 | export const effectWrap = (userType: TPersonTypes, ID: number) => effectsWrap(userType).find('[class^="effectWrap"]').eq(ID) 8 | export const effectBottomLayer = (userType: TPersonTypes, effectName: string, ID: number) => effectWrap(userType, ID).find(`[class*="bottomLayer"][class*="${effectName}"]`) 9 | export const effectTopLayer = (userType: TPersonTypes, effectName: string, ID: number) => effectWrap(userType, ID).find(`[class*="topLayer"][class*="${effectName}"]`) 10 | 11 | export const effectsIconsScrollWrap = (userType: TPersonTypes) => effectsIconsWrap(userType).find('[class^="scrollIconsWrap"][class*="scrollRight"]') 12 | export const effectIconWrap = (userType: TPersonTypes, effectType: TEffectColors, ID: number) => effectsIconsScrollWrap(userType).find(`[class^="iconWrap"][class*="${effectType}"]`).eq(ID) 13 | export const effectIconContainer = (userType: TPersonTypes, effectType: TEffectColors, ID: number) => effectIconWrap(userType, effectType, ID).find('[class^="iconContainer"]') 14 | export const effectIcon = (userType: TPersonTypes, effectType: TEffectColors, ID: number) => effectIconContainer(userType, effectType, ID).find('[class^="svgIconWrap"][class*="effect"]') 15 | export const effectIconImage = (userType: TPersonTypes, effectType: TEffectColors, ID: number) => effectIcon(userType, effectType, ID).find('img') 16 | -------------------------------------------------------------------------------- /cypress/utils/checkStyles.ts: -------------------------------------------------------------------------------- 1 | import { ICheckStyles } from '../interfaces/IUtils' 2 | 3 | /** 4 | * 5 | * @example 6 | * // Basic invoke 7 | * checkStyles({ 8 | * elemNode, 9 | * pseudo: 'after', 10 | * expectations: { 11 | * width: '40px', 12 | * height: '25px' 13 | * } 14 | * }) 15 | * 16 | * @param config - object with core values 17 | * @param config.elemNode - elemNode to process on 18 | * @param config.pseudo - "after" or "before" values for deep checking 19 | * @param config.expectations - a list of key-value CSS pair to check 20 | * 21 | * @example 22 | * // Alternative ways to use, native: 23 | * elemNode.should('have.css', 'width', '418px') // native 24 | * elemNode.styles('height').should('eq', '425px') // our custom 25 | * 26 | */ 27 | const checkStyles = ({ elemNode, pseudo, expectations }: ICheckStyles) => { 28 | const styleKeysToCheck = Object.keys(expectations) 29 | const invokeStyles = elemNode.styles(styleKeysToCheck, pseudo) 30 | 31 | const checkValues = (cssData: object) => styleKeysToCheck.forEach((key: string) => { 32 | expect(cssData[key]).equal(expectations[key]) 33 | }) 34 | 35 | // use with decorator to log some events in Cypress 36 | // browser's window (this is the hack for its own bug) 37 | const checkWithLog = (callback: Function, data: object) => { 38 | const pseudoInfo = pseudo ? ` pseudo: "::${pseudo}"` : '' 39 | 40 | cy 41 | .log(`**[START] checking styles...${pseudoInfo}**`) 42 | .then(() => callback(data)) 43 | .log('**[END] checking styles done.**') 44 | } 45 | 46 | invokeStyles.then((resp: object) => checkWithLog(checkValues, resp)) 47 | } 48 | 49 | export default checkStyles 50 | -------------------------------------------------------------------------------- /server/server.ts: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const browserSync = require('browser-sync') 3 | const historyApiFallback = require('connect-history-api-fallback') 4 | const webpack = require('webpack') 5 | const webpackDevMiddleware = require('webpack-dev-middleware') 6 | const webpackHotMiddleware = require('webpack-hot-middleware') 7 | 8 | const proxyMiddleware = require('./proxy.ts') 9 | const webpackConfig = require('../webpack.config.js') 10 | 11 | const bundler = webpack(webpackConfig) 12 | 13 | // ======================================================== 14 | // WEBPACK MIDDLEWARE CONFIGURATION 15 | // ======================================================== 16 | const devMiddlewareOptions = { 17 | publicPath: webpackConfig.output.publicPath, 18 | headers: { 'Access-Control-Allow-Origin': '*' } 19 | } 20 | 21 | // ======================================================== 22 | // Server Configuration 23 | // ======================================================== 24 | const webpackMiddleware = [webpackDevMiddleware(bundler, devMiddlewareOptions)] 25 | 26 | if (__DEV__) { 27 | webpackMiddleware.push(webpackHotMiddleware(bundler)) 28 | } 29 | 30 | browserSync({ 31 | open: false, 32 | cors: false, 33 | https: { 34 | key: path.resolve(__dirname, './ssl/localhost.key'), 35 | cert: path.resolve(__dirname, './ssl/localhost.crt') 36 | }, 37 | ghostMode: { 38 | clicks: false, 39 | forms: false, 40 | scroll: true 41 | }, 42 | server: { 43 | baseDir: path.resolve(__dirname, '../src'), 44 | middleware: [historyApiFallback(), ...proxyMiddleware, ...webpackMiddleware] 45 | }, 46 | files: [ 47 | 'src/../*.tsx', 48 | 'src/../*.ts', 49 | 'src/../*.jsx', 50 | 'src/../*.js', 51 | 'src/../*.json', 52 | 'src/../*.scss', 53 | 'src/../*.html' 54 | ] 55 | }) 56 | -------------------------------------------------------------------------------- /server/ssl/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8cKQ7+i/5EiXE 3 | mVkaLWHoHIbtftwDSRCHgMCtGz6HH9uND/Q+9bf8YyfilAzuMkcHQzJmMN6vwRar 4 | m9v2ZLhN7Lj1Y0K+Ni/qhC0J1JQmmYBDN7KoEok5TDdyabJAqb5G2bKWi90Rcii/ 5 | MvmzVVl14bY83ddTtkHZ1g50HLYY8XCI25pd/X+D+ueDRtVTP6H1YoR4CLusUmVA 6 | TPRGe6Vug9Fb9bMe6tF6oK3RU0IE4iwA4/BUh8Q+3YfYTc8J6Wg9cXx/cFUlhDNL 7 | AgMubyD4tVQ+tyj85yJUc/Byi9rCyOr3HDEq2O71NaAkq25rPsVPqhd+EUq7Jczg 8 | qqrZ1GUlAgMBAAECggEBAJYBbRu5m7rAMYSBNibafZfSLa6dT/LllNzpJ3glue7C 9 | 83klfB8qZ53oKPX7ORfcuiIT4ejrejmakbtmRQGtR/HwQYRF0fmtFROwyFGNAaqA 10 | g1P+4J9eshIElBbmANnsxeWMoteo2wBqSfl6UL/rb12hofpt9l1TNrR76+GOgX70 11 | ReoaqR+vL7E1xqfIjy/dZe3LzWC+gCic3MVs9ADMj4nG/Rh0ONm1XWr2UeZSghf6 12 | yk3KXUO+D1vbq0hDwTdze9Gt5gaFfudt6+DTLGWiqnz1oRUz0UTZ66hCnzMzU7zY 13 | G5lwNp6B3kRsS9if/4SHFwRlD+7iwN77dk4VkMVEB3kCgYEA4xBG7kT9Q0HA6wrb 14 | CHsHiPL8tJzHsPLktEHpFs5wB+xLwObCmGb0JPXJ74EbljxZTOvoe7UjhmG2VHDj 15 | puWQS9j3B4/9FuiQNi41RvfJ/N9nYx0nP4TIdbJNhi6wiBR0lCllkZ8676cos+3P 16 | KWGVCMBG/1IEuBAQkrhQJb40ISsCgYEA1HRPDhbmMSCqPX2i3zlqMJEskg35SWvD 17 | xgshTxPrYluMih2hzPjN14NCk4BQcJAW8ED0sECcUFgbyo+ChxZSAlSXO6sO6q0z 18 | zZ5ZoTsJXJco1a/XlPJ5zihJkl3A45rASqpxSVrMstoTKcN8q42NI/48kpVNOCoL 19 | wBKlP/k5Su8CgYB9b0R/5AS03uIf9gNCEBT9hp4reGCoU8Yb/j0xqCEjf1np2pCt 20 | eFJvCIjhkkUXHYeHgtsPW+WgGuKKi0J3tGGeROFSgCykNx1TkPtFlSU0WIXYrwY7 21 | hLLosfM5qRlEU2iDYMsHQxtfwMwvebPzfEDEcNPHwBtzQwykwMUl1IkfuQKBgDzN 22 | XKKnoxGES7R1dFt2TxG/OoZCQYnlsY3IOawsAz2O/dW5TUkgG6kGA9O68UqFskro 23 | DB81HodP8AQ0tiJ7HNVV6EJHXmmDUEQUgYpHHxLEoAcn4Abcrkd1+DhPZJi+/TFh 24 | PjLELbkx+DIHpHJkfHREmolI+WSNg/dgXrLEXMU5AoGABUEMDSbbtKg6PTml2hEd 25 | c6lTVOs2lGVzdDpgM0KlwJ/gE57RJ/EZVY0TyeJKvohrQY76/awoNAl2tagh6IgA 26 | n8q53+Pk7NQ+FdygisjaCjCeQtdenDA9jiUlBEs1cwpBsygWYvkV/kM/E80mIr0c 27 | ovm85T+EuPr0E250LPBX8Ng= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /src/components/Body/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useCallback } from 'react' 2 | import { TransitionGroup, CSSTransition } from 'react-transition-group' 3 | import cn from 'classnames' 4 | 5 | import globalStyles from '../../styles/index.scss' 6 | import buttonsStyles from '../../styles/button.scss' 7 | import bodyStyles from './index.scss' 8 | 9 | const FIRST_IMAGE_ID = 1 10 | const SECOND_IMAGE_ID = 2 11 | 12 | export interface IBody { 13 | backgroundColor?: 'red' | 'black' | 'transparent' 14 | labelText?: string 15 | withLabel?: boolean 16 | imageToShow: number 17 | switchImage: (imgNumber: number) => void 18 | } 19 | 20 | const Body = memo((props: IBody) => { 21 | const { backgroundColor, imageToShow, switchImage, labelText, withLabel } = props 22 | 23 | const handlerClick = useCallback(() => { 24 | if (imageToShow === FIRST_IMAGE_ID) { 25 | return switchImage(SECOND_IMAGE_ID) 26 | } 27 | 28 | return switchImage(FIRST_IMAGE_ID) 29 | }, [imageToShow, switchImage]) 30 | 31 | const bodyClasses = cn({ 32 | [globalStyles.body]: true, 33 | [bodyStyles[backgroundColor]]: !!backgroundColor 34 | }) 35 | 36 | return ( 37 |
38 | 46 |
47 | ) 48 | }) 49 | 50 | Body.displayName = 'Body' 51 | 52 | export default Body 53 | -------------------------------------------------------------------------------- /cypress/e2e/test.spec.ts: -------------------------------------------------------------------------------- 1 | import makeLogin from '../helpers/login' 2 | import setCookies from '../helpers/cookies' 3 | 4 | import { rivalsUI, winFight, stalematedFight, escapedFight } from '../apps/attack/scenarios' 5 | 6 | context('Attack App', () => { 7 | beforeEach(() => { 8 | setCookies({ type: 'basic' }) 9 | makeLogin({ email: 'mar@gmail.com', password: 'per' }).basic() 10 | }) 11 | 12 | it('check rivals UI', () => { 13 | Cypress.env('DEFENDER_ID', '1414133') 14 | Cypress.env('DEFENDER_NAME', 'popandopulo') 15 | 16 | rivalsUI() 17 | }) 18 | 19 | // it('should run basic win fight', () => { 20 | // Cypress.env('DEFENDER_ID', '1414133') 21 | // Cypress.env('DEFENDER_NAME', 'popandopulo') 22 | 23 | // winFight({ withFocus: true }) 24 | // }) 25 | // it('should run fight with stalemated result', () => { 26 | // Cypress.env('DEFENDER_ID', '595784') 27 | // Cypress.env('DEFENDER_NAME', 'AAA') 28 | 29 | // stalematedFight() 30 | // }) 31 | // it('should escape after one commit', () => { 32 | // Cypress.env('DEFENDER_ID', '1435333') 33 | // Cypress.env('DEFENDER_NAME', 'toshykazu') 34 | 35 | // escapedFight() 36 | // }) 37 | // it('should win and leave on the street', () => { 38 | // Cypress.env('DEFENDER_ID', '1163708') 39 | // Cypress.env('DEFENDER_NAME', 'Heather') 40 | 41 | // winFight({ type: 'leave', withOptions: true }) 42 | // }) 43 | // it('should win and mug', () => { 44 | // Cypress.env('DEFENDER_ID', '1163708') 45 | // Cypress.env('DEFENDER_NAME', 'Heather') 46 | 47 | // winFight({ type: 'mug', withOptions: true }) 48 | // }) 49 | // it('should win and hospitalize', () => { 50 | // Cypress.env('DEFENDER_ID', '1163708') 51 | // Cypress.env('DEFENDER_NAME', 'Heather') 52 | 53 | // winFight({ type: 'hosp', withOptions: true }) 54 | // }) 55 | }) 56 | -------------------------------------------------------------------------------- /cypress/examples/navigation.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-arrow/prefer-arrow-functions */ 2 | context('Navigation', () => { 3 | beforeEach(() => { 4 | cy.visit('https://example.cypress.io') 5 | cy.get('.navbar-nav').contains('Commands').click() 6 | cy.get('.dropdown-menu').contains('Navigation').click() 7 | }) 8 | 9 | it('cy.go() - go back or forward in the browser\'s history', () => { 10 | // https://on.cypress.io/go 11 | 12 | cy.location('pathname').should('include', 'navigation') 13 | 14 | cy.go('back') 15 | cy.location('pathname').should('not.include', 'navigation') 16 | 17 | cy.go('forward') 18 | cy.location('pathname').should('include', 'navigation') 19 | 20 | // clicking back 21 | cy.go(-1) 22 | cy.location('pathname').should('not.include', 'navigation') 23 | 24 | // clicking forward 25 | cy.go(1) 26 | cy.location('pathname').should('include', 'navigation') 27 | }) 28 | 29 | it('cy.reload() - reload the page', () => { 30 | // https://on.cypress.io/reload 31 | cy.reload() 32 | 33 | // reload the page without using the cache 34 | cy.reload(true) 35 | }) 36 | 37 | it('cy.visit() - visit a remote url', () => { 38 | // https://on.cypress.io/visit 39 | 40 | // Visit any sub-domain of your current domain 41 | 42 | // Pass options to the visit 43 | cy.visit('https://example.cypress.io/commands/navigation', { 44 | timeout: 50000, // increase total time for the visit to resolve 45 | onBeforeLoad(contentWindow) { 46 | // contentWindow is the remote page's window object 47 | expect(typeof contentWindow === 'object').equal(true) 48 | }, 49 | onLoad(contentWindow) { 50 | // contentWindow is the remote page's window object 51 | expect(typeof contentWindow === 'object').equal(true) 52 | } 53 | }) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import RedBox from 'redbox-react' 4 | 5 | import store from './store/store' 6 | import history from './store/history' 7 | import AppProvider from './provider/index' 8 | 9 | const ENTRY_POINT = document.querySelector('#react-app-root') 10 | 11 | // creating starting endpoint for app. 12 | const render = () => { 13 | ReactDOM.render(, ENTRY_POINT) 14 | } 15 | 16 | // this will help us understand where the problem is located once app will fall. 17 | const renderError = (error) => { 18 | ReactDOM.render(, ENTRY_POINT) 19 | } 20 | 21 | // register serviceWorkers if available 22 | // we don't need them for now 23 | // if ('serviceWorker' in navigator) { 24 | // navigator.serviceWorker 25 | // .register('./serviceWorker.js') 26 | // .then(registration => { 27 | // console.log('Excellent, registered with scope: ', registration.scope); 28 | // }) 29 | // .catch(e => console.error('ERROR IN SERVICE WORKERS: ', e)); 30 | // } 31 | 32 | // This code is excluded from production bundle 33 | if (__DEV__) { 34 | // ======================================================== 35 | // DEVELOPMENT STAGE! HOT MODULE REPLACE ACTIVATION! 36 | // ======================================================== 37 | // const devRender = () => { 38 | // // works project-wide for now 39 | // // if (module.hot) { 40 | // // module.hot.accept('./provider/index', () => render()); 41 | // // } 42 | 43 | // render(); 44 | // }; 45 | 46 | // Wrap render in try/catch 47 | try { 48 | render() 49 | } catch (error) { 50 | console.error(error) 51 | renderError(error) 52 | } 53 | } else { 54 | // ======================================================== 55 | // PRODUCTION GO! 56 | // ======================================================== 57 | render() 58 | } 59 | -------------------------------------------------------------------------------- /.storybook/styles/reset.css___: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, 7 | body, 8 | div, 9 | span, 10 | applet, 11 | object, 12 | iframe, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | blockquote, 21 | pre, 22 | input, 23 | a, 24 | abbr, 25 | acronym, 26 | address, 27 | big, 28 | cite, 29 | code, 30 | del, 31 | dfn, 32 | em, 33 | img, 34 | ins, 35 | kbd, 36 | q, 37 | s, 38 | samp, 39 | small, 40 | strike, 41 | strong, 42 | sub, 43 | sup, 44 | tt, 45 | b, 46 | u, 47 | i, 48 | center, 49 | dl, 50 | dt, 51 | dd, 52 | ol, 53 | ul, 54 | li, 55 | fieldset, 56 | form, 57 | label, 58 | legend, 59 | tbody, 60 | tfoot, 61 | thead, 62 | tr, 63 | th, 64 | table, 65 | caption, 66 | article, 67 | aside, 68 | canvas, 69 | details, 70 | embed, 71 | figure, 72 | figcaption, 73 | footer, 74 | header, 75 | hgroup, 76 | menu, 77 | nav, 78 | output, 79 | ruby, 80 | section, 81 | summary, 82 | time, 83 | mark, 84 | audio, 85 | video, 86 | table:not([cellpadding]) td { 87 | margin: 0; 88 | padding: 0; 89 | border: 0; 90 | font: inherit; 91 | font-size: 100%; 92 | vertical-align: baseline; 93 | } 94 | 95 | /* HTML5 display-role reset for older browsers */ 96 | article, 97 | aside, 98 | details, 99 | figcaption, 100 | figure, 101 | footer, 102 | header, 103 | hgroup, 104 | menu, 105 | nav, 106 | section { 107 | display: block; 108 | } 109 | 110 | body { 111 | line-height: 1; 112 | } 113 | 114 | ol, 115 | ul { 116 | list-style: none; 117 | } 118 | 119 | blockquote, 120 | q { 121 | quotes: none; 122 | } 123 | 124 | blockquote::before, 125 | blockquote::after, 126 | q::before, 127 | q::after { 128 | content: ''; 129 | content: none; 130 | } 131 | 132 | table { 133 | border-collapse: collapse; 134 | border-spacing: 0; 135 | } 136 | 137 | :focus { 138 | outline: none; 139 | } 140 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/selectors/armour.ts: -------------------------------------------------------------------------------- 1 | import { TPersonTypes } from '../../interfaces/IController' 2 | 3 | export const armourWrap = (userType: TPersonTypes) => cy.get(`#${userType} [class^="armoursWrap"]`) 4 | 5 | export const armourContainer = (type: TPersonTypes) => armourWrap(type).find('[class^="ammour_container"]') 6 | 7 | export const armourImgWrap = (type: TPersonTypes, ID: number, node?: any) => { 8 | if (node) { 9 | return node().find('[class^="armour"]') 10 | } 11 | 12 | return armourContainer(type).eq(ID || 0).find('[class^="armour"]') 13 | } 14 | 15 | export const armourImg = (type: TPersonTypes, ID: number, node?: any, wrapID?: number) => { 16 | if (node) { 17 | return node().find('img') 18 | } 19 | 20 | return armourImgWrap(type, wrapID).eq(ID || 0).find('img') 21 | } 22 | 23 | export const armourMaskImgWrap = (type: TPersonTypes, ID: number, node?: any) => { 24 | if (node) { 25 | return node().find('[class^="mask"]') 26 | } 27 | 28 | return armourContainer(type).eq(ID || 0).find('[class^="mask"]') 29 | } 30 | 31 | export const armourMaskImg = (type: TPersonTypes, ID: number, node?: any, wrapID?: number) => { 32 | if (node) { 33 | return node().find('img[class^="itemImg"]') 34 | } 35 | 36 | return armourMaskImgWrap(type, wrapID).eq(ID || 0).find('[class^="itemImg"]') 37 | } 38 | 39 | export const armourBackgroundImgWrap = (type: TPersonTypes, ID: number, node?: any) => { 40 | if (node) { 41 | return node().find('[class^="background_"]') 42 | } 43 | 44 | return armourContainer(type).eq(ID || 0).find('[class^="background_"]') 45 | } 46 | 47 | export const armourBackgroundImg = (type: TPersonTypes, ID: number, node?: any, wrapID?: number) => { 48 | if (node) { 49 | return node().find('img[class^="itemImg"][class*="backgroundImage"]') 50 | } 51 | 52 | return armourBackgroundImgWrap(type, wrapID) 53 | .eq(ID || 0) 54 | .find('[class^="img[class^="itemImg"][class*="backgroundImage"]') 55 | } 56 | -------------------------------------------------------------------------------- /cypress/examples/viewport.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable cypress/no-unnecessary-waiting */ 2 | /* eslint-disable max-statements */ 3 | context('Viewport', () => { 4 | beforeEach(() => { 5 | cy.visit('https://example.cypress.io/commands/viewport') 6 | }) 7 | 8 | it('cy.viewport() - set the viewport size and dimension', () => { 9 | // https://on.cypress.io/viewport 10 | 11 | cy.get('#navbar').should('be.visible') 12 | cy.viewport(320, 480) 13 | 14 | // the navbar should have collapse since our screen is smaller 15 | cy.get('#navbar').should('not.be.visible') 16 | cy.get('.navbar-toggle').should('be.visible').click() 17 | cy.get('.nav').find('a').should('be.visible') 18 | 19 | // lets see what our app looks like on a super large screen 20 | cy.viewport(2999, 2999) 21 | 22 | // cy.viewport() accepts a set of preset sizes 23 | // to easily set the screen to a device's width and height 24 | 25 | // We added a cy.wait() between each viewport change so you can see 26 | // the change otherwise it is a little too fast to see :) 27 | 28 | cy.viewport('macbook-15') 29 | cy.wait(200) 30 | cy.viewport('macbook-13') 31 | cy.wait(200) 32 | cy.viewport('macbook-11') 33 | cy.wait(200) 34 | cy.viewport('ipad-2') 35 | cy.wait(200) 36 | cy.viewport('ipad-mini') 37 | cy.wait(200) 38 | cy.viewport('iphone-6+') 39 | cy.wait(200) 40 | cy.viewport('iphone-6') 41 | cy.wait(200) 42 | cy.viewport('iphone-5') 43 | cy.wait(200) 44 | cy.viewport('iphone-4') 45 | cy.wait(200) 46 | cy.viewport('iphone-3') 47 | cy.wait(200) 48 | 49 | // cy.viewport() accepts an orientation for all presets 50 | // the default orientation is 'portrait' 51 | cy.viewport('ipad-2', 'portrait') 52 | cy.wait(200) 53 | cy.viewport('iphone-4', 'landscape') 54 | cy.wait(200) 55 | 56 | // The viewport will be reset back to the default dimensions 57 | // in between tests (the default can be set in cypress.json) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /cypress/examples/local_storage.spec.ts: -------------------------------------------------------------------------------- 1 | context('Local Storage', () => { 2 | beforeEach(() => { 3 | cy.visit('https://example.cypress.io/commands/local-storage') 4 | }) 5 | // Although local storage is automatically cleared 6 | // in between tests to maintain a clean state 7 | // sometimes we need to clear the local storage manually 8 | 9 | it('cy.clearLocalStorage() - clear all data in local storage', () => { 10 | // https://on.cypress.io/clearlocalstorage 11 | cy.get('.ls-btn').click().should(() => { 12 | expect(localStorage.getItem('prop1')).equal('red') 13 | expect(localStorage.getItem('prop2')).equal('blue') 14 | expect(localStorage.getItem('prop3')).equal('magenta') 15 | }) 16 | 17 | // clearLocalStorage() yields the localStorage object 18 | cy.clearLocalStorage().should((ls) => { 19 | expect(ls.getItem('prop1')).equal(null) 20 | expect(ls.getItem('prop2')).equal(null) 21 | expect(ls.getItem('prop3')).equal(null) 22 | }) 23 | 24 | // Clear key matching string in Local Storage 25 | cy.get('.ls-btn').click().should(() => { 26 | expect(localStorage.getItem('prop1')).equal('red') 27 | expect(localStorage.getItem('prop2')).equal('blue') 28 | expect(localStorage.getItem('prop3')).equal('magenta') 29 | }) 30 | 31 | cy.clearLocalStorage('prop1').should((ls) => { 32 | expect(ls.getItem('prop1')).equal(null) 33 | expect(ls.getItem('prop2')).equal('blue') 34 | expect(ls.getItem('prop3')).equal('magenta') 35 | }) 36 | 37 | // Clear keys matching regex in Local Storage 38 | cy.get('.ls-btn').click().should(() => { 39 | expect(localStorage.getItem('prop1')).equal('red') 40 | expect(localStorage.getItem('prop2')).equal('blue') 41 | expect(localStorage.getItem('prop3')).equal('magenta') 42 | }) 43 | 44 | cy.clearLocalStorage(/prop1|2/).should((ls) => { 45 | expect(ls.getItem('prop1')).equal(null) 46 | expect(ls.getItem('prop2')).equal(null) 47 | expect(ls.getItem('prop3')).equal('magenta') 48 | }) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /cypress/apps/test_example/scenarios/defenderUI.ts: -------------------------------------------------------------------------------- 1 | import { checkEffects } from '../view/behavior/effects' 2 | import { checkHeader } from '../view/behavior/header' 3 | import { checkFacilities } from '../view/behavior/facilities' 4 | import { checkModalOverlay } from '../view/behavior/modal' 5 | 6 | import initiateE2ETest from '../../../services/initiateE2ETest' 7 | import subscribeAPI from '../api' 8 | 9 | import { MALE, MALE_SHORT, DEFENDER } from '../constants' 10 | import { checkModel } from '../view/behavior/model' 11 | import { checkArmour } from '../view/behavior/armour' 12 | import { checkStatusBar } from '../view/behavior/statusbar' 13 | import { checkWeaponBoxes } from '../view/behavior/weapons' 14 | 15 | const defenderUI = (isInit?: boolean) => { 16 | isInit && initiateE2ETest({ 17 | runAPI: subscribeAPI, 18 | url: `/loader.php?sid=attack&user2ID=${Cypress.env('DEFENDER_ID')}` 19 | }) 20 | 21 | // checking overlay 22 | checkModalOverlay({ userType: DEFENDER, colorType: '' }) 23 | 24 | // checking header 25 | checkHeader({ type: DEFENDER, name: Cypress.env('DEFENDER_NAME'), health: '625 / 625' }) 26 | 27 | // checking model 28 | checkModel({ type: DEFENDER, gender: MALE }) 29 | 30 | // checking armour 31 | checkArmour({ 32 | type: DEFENDER, 33 | itemsList: [], 34 | shortGender: MALE_SHORT 35 | }) 36 | 37 | // checking effects 38 | checkEffects({ 39 | userType: DEFENDER, 40 | effectsList: [], 41 | wrapCount: 0 42 | }) 43 | 44 | // checking bottom indicator facilities block 45 | checkFacilities({ 46 | userType: DEFENDER, 47 | isDisabled: true 48 | }) 49 | 50 | // checking bottom statusBar 51 | checkWeaponBoxes({ 52 | userType: DEFENDER, 53 | weaponsData: { 54 | main: { 55 | disableBottomBlock: true 56 | }, 57 | secondary: { 58 | disableBottomBlock: true 59 | }, 60 | melee: { 61 | disableBottomBlock: true 62 | } 63 | } 64 | }) 65 | 66 | // checking bottom statusBar 67 | checkStatusBar({ userType: DEFENDER }) 68 | } 69 | 70 | export default defenderUI 71 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Unit + UI Testing 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | unit-ui-testing: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Staring Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | 28 | - name: Restoring Yarn cache 29 | uses: actions/cache@v2 30 | with: 31 | path: '**/node_modules' 32 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 33 | 34 | - name: Bootstraping packages 35 | if: steps.yarn-cache.outputs.cache-hit != 'true' 36 | run: yarn install 37 | 38 | - name: Testing Shared Utils 39 | if: always() 40 | run: yarn jest ./shared 41 | 42 | - name: Testing Storybook UI 43 | if: always() 44 | run: yarn storybook:build 45 | 46 | - name: Slack Notification 47 | uses: 8398a7/action-slack@v3.8.0 48 | if: failure() 49 | with: 50 | status: custom 51 | fields: workflow,job,commit,repo,ref,author,took 52 | custom_payload: | 53 | { 54 | username: 'React-Apps-CI', 55 | icon_emoji: ':react:', 56 | author_name: 'Unit + UI Integration Test', 57 | attachments: [{ 58 | color: '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning', 59 | text: `CI Task: ${process.env.AS_WORKFLOW}\ncommit: (${process.env.AS_COMMIT}) ${{ github.event_name }} ${{ job.status }}. Initiated by ${process.env.AS_AUTHOR} in ${process.env.AS_TOOK}`, 60 | }] 61 | } 62 | env: 63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 64 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 65 | MATRIX_CONTEXT: ${{ toJson(matrix) }} 66 | 67 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/behavior/statusbar.ts: -------------------------------------------------------------------------------- 1 | import checkPosition from '../../../../utils/checkPosition' 2 | 3 | import { statusBarWrap, statusBarIcon, statusBarDesc } from '../selectors/statusbar' 4 | 5 | import { ICheckStatusBar } from '../../interfaces/IView' 6 | import { TPersonTypes } from '../../interfaces/IController' 7 | import { STATUS_BAR_COORDS } from '../constants/coords' 8 | import checkStyles from '../../../../utils/checkStyles' 9 | 10 | export const checkStatusBarWrap = ({ userType }: { userType: TPersonTypes }) => { 11 | const wrap = () => statusBarWrap(userType) 12 | 13 | wrap().should('have.length', 1) 14 | checkStyles({ 15 | elemNode: wrap(), 16 | expectations: { 17 | bottom: '0px', 18 | width: '323px', 19 | 'z-index': '100000' 20 | } 21 | }) 22 | 23 | checkPosition({ nodeElement: wrap, ...STATUS_BAR_COORDS[userType].wrap }) 24 | } 25 | 26 | export const checkStatusBarIcon = ({ userType }: { userType: TPersonTypes }) => { 27 | const icon = () => statusBarIcon(userType) 28 | 29 | icon().should('have.length', 1) 30 | checkStyles({ 31 | elemNode: icon(), 32 | expectations: { 33 | height: '30px', 34 | 'flex-grow': '0', 35 | 'flex-shrink': '0', 36 | 'flex-basis': '57px', 37 | 'background-color': 'rgb(204, 204, 204)' 38 | } 39 | }) 40 | 41 | checkPosition({ nodeElement: icon, ...STATUS_BAR_COORDS[userType].icon }) 42 | } 43 | 44 | export const checkStatusBarDesc = ({ userType }: { userType: TPersonTypes }) => { 45 | const desc = () => statusBarDesc(userType) 46 | 47 | checkStyles({ 48 | elemNode: desc(), 49 | expectations: { 50 | color: 'rgb(255, 255, 255)', 51 | height: '30px', 52 | 'padding-left': '15px', 53 | 'text-shadow': 'rgba(0, 0, 0, 0.65) 1px 1px 2px', 54 | 'background-color': 'rgb(170, 170, 170)' 55 | } 56 | }) 57 | 58 | checkPosition({ nodeElement: desc, ...STATUS_BAR_COORDS[userType].desc }) 59 | } 60 | 61 | export const checkStatusBar = (config: ICheckStatusBar) => { 62 | const { userType } = config 63 | 64 | cy.log(`**[START] Check StatusBar: ${userType}**`) 65 | 66 | checkStatusBarWrap({ userType }) 67 | checkStatusBarIcon({ userType }) 68 | checkStatusBarDesc({ userType }) 69 | 70 | cy.log(`**[END] Check StatusBar: ${userType}**`) 71 | } 72 | -------------------------------------------------------------------------------- /cypress/examples/cookies.spec.ts: -------------------------------------------------------------------------------- 1 | context('Cookies', () => { 2 | beforeEach(() => { 3 | Cypress.Cookies.debug(true) 4 | 5 | cy.visit('https://example.cypress.io/commands/cookies') 6 | 7 | // clear cookies again after visiting to remove 8 | // any 3rd party cookies picked up such as cloudflare 9 | cy.clearCookies() 10 | }) 11 | 12 | it('cy.getCookie() - get a browser cookie', () => { 13 | // https://on.cypress.io/getcookie 14 | cy.get('#getCookie .set-a-cookie').click() 15 | 16 | // cy.getCookie() yields a cookie object 17 | cy.getCookie('token').should('have.property', 'value', '123ABC') 18 | }) 19 | 20 | it('cy.getCookies() - get browser cookies', () => { 21 | // https://on.cypress.io/getcookies 22 | cy.getCookies().should('be.empty') 23 | 24 | cy.get('#getCookies .set-a-cookie').click() 25 | 26 | // cy.getCookies() yields an array of cookies 27 | cy.getCookies().should('have.length', 1).should((cookies) => { 28 | // each cookie has these properties 29 | expect(cookies[0]).to.have.property('name', 'token') 30 | expect(cookies[0]).to.have.property('value', '123ABC') 31 | expect(cookies[0]).to.have.property('httpOnly', false) 32 | expect(cookies[0]).to.have.property('secure', false) 33 | expect(cookies[0]).to.have.property('domain') 34 | expect(cookies[0]).to.have.property('path') 35 | }) 36 | }) 37 | 38 | it('cy.setCookie() - set a browser cookie', () => { 39 | // https://on.cypress.io/setcookie 40 | cy.getCookies().should('be.empty') 41 | 42 | cy.setCookie('foo', 'bar') 43 | 44 | // cy.getCookie() yields a cookie object 45 | cy.getCookie('foo').should('have.property', 'value', 'bar') 46 | }) 47 | 48 | it('cy.clearCookie() - clear a browser cookie', () => { 49 | // https://on.cypress.io/clearcookie 50 | cy.getCookie('token').should('be.null') 51 | 52 | cy.get('#clearCookie .set-a-cookie').click() 53 | 54 | cy.getCookie('token').should('have.property', 'value', '123ABC') 55 | 56 | // cy.clearCookies() yields null 57 | cy.clearCookie('token').should('be.null') 58 | 59 | cy.getCookie('token').should('be.null') 60 | }) 61 | 62 | it('cy.clearCookies() - clear browser cookies', () => { 63 | // https://on.cypress.io/clearcookies 64 | cy.getCookies().should('be.empty') 65 | 66 | cy.get('#clearCookies .set-a-cookie').click() 67 | 68 | cy.getCookies().should('have.length', 1) 69 | 70 | // cy.clearCookies() yields null 71 | cy.clearCookies() 72 | 73 | cy.getCookies().should('be.empty') 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/behavior/model.ts: -------------------------------------------------------------------------------- 1 | import checkPosition from '../../../../utils/checkPosition' 2 | 3 | import { modelWrap, modelLayers, modelContainer, modelImage } from '../selectors/model' 4 | 5 | import { ICheckModel, TGenderTypes } from '../../interfaces/IView' 6 | import { TPersonTypes } from '../../interfaces/IController' 7 | import { MODEL_COORDS } from '../constants/coords' 8 | import checkStyles from '../../../../utils/checkStyles' 9 | 10 | export const checkModelWrap = ({ userType }: { userType: TPersonTypes }) => { 11 | const wrap = () => modelWrap(userType) 12 | 13 | wrap().should('have.length', 1) 14 | checkStyles({ 15 | elemNode: wrap(), 16 | expectations: { 17 | width: '324px', 18 | height: '455px', 19 | 'background-image': 'url("https://google.com/test.jpg")', 20 | 'border-top-color': 'rgb(255, 255, 255)', 21 | 'border-right-color': 'rgb(251, 251, 251)', 22 | 'border-bottom-color': 'rgb(237, 237, 237)', 23 | 'border-left-color': 'rgb(242, 242, 242)' 24 | } 25 | }) 26 | 27 | checkPosition({ nodeElement: wrap, ...MODEL_COORDS[userType].wrap }) 28 | } 29 | 30 | export const checkModelLayers = ({ userType }: { userType: TPersonTypes }) => { 31 | const layers = () => modelLayers(userType) 32 | 33 | layers().should('have.length', 1) 34 | checkStyles({ 35 | elemNode: layers(), 36 | expectations: { 37 | width: '240px', 38 | top: '0px', 39 | 'padding-top': '20px', 40 | 'z-index': '1', 41 | position: 'absolute' 42 | } 43 | }) 44 | 45 | checkPosition({ nodeElement: layers, ...MODEL_COORDS[userType].layers }) 46 | } 47 | 48 | export const checkModelContainer = ({ userType }: { userType: TPersonTypes }) => { 49 | const container = () => modelContainer(userType) 50 | 51 | container().should('have.length', 1) 52 | checkStyles({ 53 | elemNode: container(), 54 | expectations: { 55 | 'z-index': '2', 56 | position: 'absolute' 57 | } 58 | }) 59 | 60 | checkPosition({ nodeElement: container, ...MODEL_COORDS[userType].container }) 61 | } 62 | 63 | export const checkModelImage = ({ userType, gender }: { userType: TPersonTypes; gender: TGenderTypes }) => { 64 | const image = () => modelImage(userType) 65 | 66 | image().should('have.length', 1) 67 | image().styles('user-select').should('eq', 'none') 68 | image().should('have.attr', 'src', `/images/v2/attack/models/${gender}_model.png`) 69 | 70 | checkPosition({ nodeElement: image, ...MODEL_COORDS[userType].image }) 71 | } 72 | 73 | export const checkModel = (config: ICheckModel) => { 74 | const { type, gender } = config 75 | 76 | cy.log(`**[START] Check Model: ${type}**`) 77 | 78 | checkModelWrap({ userType: type }) 79 | checkModelLayers({ userType: type }) 80 | checkModelContainer({ userType: type }) 81 | checkModelImage({ userType: type, gender }) 82 | 83 | cy.log(`**[END] Check Model: ${type}**`) 84 | } 85 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/behavior/modal.ts: -------------------------------------------------------------------------------- 1 | import { modalWrap, modalTitle, modalContainer, modalBox, modalButtonsWrap } from '../selectors/modal' 2 | import { start, leave, mug, hosp, continues } from '../selectors/common' 3 | 4 | import getModalTitle from '../../utils/getModalTitle' 5 | import checkPosition from '../../../../utils/checkPosition' 6 | 7 | import { ATTACKER } from '../../constants' 8 | import { MODAL_ATTACKER_BCG, MODAL_DEFENDER_BCG } from '../constants' 9 | import { MODAL_COORDS } from '../constants/coords' 10 | 11 | import { IModalOverlay, ICheckModalBox, TActionButtons } from '../../interfaces/IView' 12 | import { TPersonTypes } from '../../interfaces/IController' 13 | 14 | export const checkWrap = (userType: TPersonTypes, isDisabled: boolean) => { 15 | const modalWrapNode = () => modalWrap(userType) 16 | 17 | if (isDisabled) { 18 | modalWrapNode().should('have.length', 0) 19 | 20 | return 21 | } 22 | 23 | const { wrap: wrapCoords } = MODAL_COORDS[userType] 24 | const modalBcg = userType === ATTACKER ? MODAL_ATTACKER_BCG : MODAL_DEFENDER_BCG 25 | 26 | modalWrapNode().should('have.length', 1) 27 | modalWrapNode().styles('background-image').should('eq', modalBcg) 28 | 29 | checkPosition({ nodeElement: modalWrapNode, ...wrapCoords }) 30 | } 31 | 32 | export const checkOptions = () => { 33 | leave().should('have.length', 1) 34 | mug().should('have.length', 1) 35 | hosp().should('have.length', 1) 36 | } 37 | 38 | export const checkActions = (type: TActionButtons) => { 39 | const actionButtons = { 40 | continue: continues, 41 | start: start 42 | } 43 | 44 | const buttonToAction = actionButtons[type] 45 | 46 | if (!buttonToAction) { 47 | return 48 | } 49 | 50 | buttonToAction().should('have.length', 1) 51 | } 52 | 53 | export const checkModalBox = (config: ICheckModalBox) => { 54 | const { userType, type, colorType, withName, withOptions, actionButton } = config 55 | 56 | modalContainer(userType).should('have.length', 1) 57 | modalBox(userType, colorType).should('have.length', 1) 58 | modalTitle(userType, colorType).should('have.length', 1) 59 | modalButtonsWrap(userType, colorType).should('have.length', 1) 60 | 61 | if (withOptions) { 62 | checkOptions() 63 | 64 | return 65 | } 66 | 67 | type && modalTitle(userType, colorType).should('have.text', getModalTitle({ type, removeName: !withName })) 68 | 69 | checkActions(actionButton) 70 | } 71 | 72 | export const checkModalOverlay = (config: IModalOverlay) => { 73 | const { isDisabled, userType, type, colorType, withName, withOptions, actionButton } = config 74 | 75 | cy.log(`**[START] Check Overlay: ${userType}**`) 76 | 77 | checkWrap(userType, isDisabled) 78 | 79 | if (userType === ATTACKER || isDisabled) { 80 | return 81 | } 82 | 83 | checkModalBox({ userType, type, colorType, withName, withOptions, actionButton }) 84 | 85 | cy.log(`**[END] Check Overlay: ${userType}**`) 86 | } 87 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/behavior/facilities.ts: -------------------------------------------------------------------------------- 1 | import { facilitiesWrap, facilityContainer, facilityArrow, facilityValue, facilityLabel } from '../selectors/facilities' 2 | 3 | import { TPersonTypes } from '../../interfaces/IController' 4 | import { ICheckFacilities, ICheckFacility, TFacilities } from '../../interfaces/IView' 5 | import { COLORS } from '../constants' 6 | import checkStyles from '../../../../utils/checkStyles' 7 | 8 | const getNodes = (userType: TPersonTypes, label?: TFacilities) => ({ 9 | facilitiesContainer: () => facilitiesWrap(userType), 10 | containerNode: () => facilityContainer({ userType, facility: label }), 11 | arrowNode: () => facilityArrow({ userType, facility: label }), 12 | valueNode: () => facilityValue({ userType, facility: label }), 13 | labelNode: () => facilityLabel({ userType, facility: label }) 14 | }) 15 | 16 | export const checkFacilitiesWrap = (userType: TPersonTypes, count = 1) => { 17 | const { facilitiesContainer } = getNodes(userType) 18 | 19 | facilitiesContainer().should('have.length', count) 20 | facilitiesContainer().styles('height').should('eq', '32px') 21 | 22 | // cy.wrap(null).should(() => { 23 | // container.should('have.css', 'bottom', '31px') 24 | // }) 25 | 26 | // disabled because of inconvenient while testing coords 27 | // checkPosition({ nodeElement: facilitiesContainer, leftOffset: 113, topOffset: 587 }) 28 | } 29 | 30 | export const checkFacility = ({ userType, facilityProps }: ICheckFacility) => { 31 | const { isArrow, color, count, value, label } = facilityProps 32 | 33 | const textColor = COLORS[color] 34 | const tempCount = count ?? 1 35 | 36 | const { containerNode, arrowNode, valueNode, labelNode } = getNodes(userType, label) 37 | 38 | containerNode().should('have.length', tempCount) 39 | arrowNode().should('have.length', tempCount) 40 | valueNode().should('have.length', tempCount) 41 | labelNode().should('have.length', tempCount) 42 | 43 | valueNode().should('have.text', value) 44 | labelNode().should('have.text', label) 45 | 46 | valueNode().styles('color').should('eq', textColor) 47 | labelNode().styles('color').should('eq', textColor) 48 | 49 | checkStyles({ 50 | elemNode: containerNode(), 51 | expectations: { 52 | top: '0px', 53 | left: '0px' 54 | } 55 | }) 56 | } 57 | 58 | export const checkFacilities = (config: ICheckFacilities) => { 59 | const { isDisabled, userType, wrapCount = 1, strength, speed, dexterity, defence } = config 60 | 61 | cy.log(`**[START] Check Facilities: ${userType}**`) 62 | 63 | if (isDisabled) { 64 | return 65 | } 66 | 67 | checkFacilitiesWrap(userType, wrapCount) 68 | 69 | checkFacility({ userType, facilityProps: strength }) 70 | 71 | checkFacility({ userType, facilityProps: speed }) 72 | 73 | checkFacility({ userType, facilityProps: dexterity }) 74 | 75 | checkFacility({ userType, facilityProps: defence }) 76 | 77 | cy.log(`**[END] Check Facilities: ${userType}**`) 78 | } 79 | -------------------------------------------------------------------------------- /cypress/examples/spies_stubs_clocks.spec.ts: -------------------------------------------------------------------------------- 1 | context('Spies, Stubs, and Clock', () => { 2 | it('cy.spy() - wrap a method in a spy', () => { 3 | // https://on.cypress.io/spy 4 | cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') 5 | 6 | const obj = { 7 | foo() {} 8 | } 9 | 10 | // eslint-disable-next-line cypress/no-assigning-return-values 11 | const spy = cy.spy(obj, 'foo').as('anyArgs') 12 | 13 | obj.foo() 14 | 15 | // eslint-disable-next-line no-unused-expressions 16 | expect(spy).to.be.called 17 | }) 18 | 19 | it('cy.spy() retries until assertions pass', () => { 20 | cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') 21 | 22 | const obj = { 23 | /** 24 | * Prints the argument passed 25 | * @param x {any} 26 | */ 27 | foo(x) { 28 | console.log('obj.foo called with', x) 29 | } 30 | } 31 | 32 | cy.spy(obj, 'foo').as('foo') 33 | 34 | setTimeout(() => { 35 | obj.foo('first') 36 | }, 500) 37 | 38 | setTimeout(() => { 39 | obj.foo('second') 40 | }, 2500) 41 | 42 | cy.get('@foo').should('have.been.calledTwice') 43 | }) 44 | 45 | it('cy.stub() - create a stub and/or replace a function with stub', () => { 46 | // https://on.cypress.io/stub 47 | cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') 48 | 49 | const obj = { 50 | /** 51 | * prints both arguments to the console 52 | * @param a {string} 53 | * @param b {string} 54 | */ 55 | foo(a, b) { 56 | console.log('a', a, 'b', b) 57 | } 58 | } 59 | 60 | // eslint-disable-next-line cypress/no-assigning-return-values 61 | const stub = cy.stub(obj, 'foo').as('foo') 62 | 63 | obj.foo('foo', 'bar') 64 | 65 | // eslint-disable-next-line no-unused-expressions 66 | expect(stub).to.be.called 67 | }) 68 | 69 | it('cy.clock() - control time in the browser', () => { 70 | // https://on.cypress.io/clock 71 | 72 | // create the date in UTC so its always the same 73 | // no matter what local timezone the browser is running in 74 | const now = new Date(Date.UTC(2017, 2, 14)).getTime() 75 | 76 | cy.clock(now) 77 | cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') 78 | cy.get('#clock-div').click() 79 | .should('have.text', '1489449600') 80 | }) 81 | 82 | it('cy.tick() - move time in the browser', () => { 83 | // https://on.cypress.io/tick 84 | 85 | // create the date in UTC so its always the same 86 | // no matter what local timezone the browser is running in 87 | const now = new Date(Date.UTC(2017, 2, 14)).getTime() 88 | 89 | cy.clock(now) 90 | cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') 91 | cy.get('#tick-div').click() 92 | .should('have.text', '1489449600') 93 | cy.tick(10000) // 10 seconds passed 94 | cy.get('#tick-div').click() 95 | .should('have.text', '1489449610') 96 | }) 97 | }) 98 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "plugins": [ 4 | "stylelint-no-unsupported-browser-features", 5 | "stylelint-order" 6 | ], 7 | "ignoreFiles": ["**/*.js","**/*.jsx","**/*.ts","**/*.tsx","**/*.html"], 8 | "rules": { 9 | "declaration-block-no-redundant-longhand-properties": [ 10 | true, 11 | { 12 | "ignoreShorthands": ["flex-flow"] 13 | } 14 | ], 15 | "function-comma-space-after": "always", 16 | "declaration-block-no-duplicate-properties": null, 17 | "comment-empty-line-before": [ 18 | "always" 19 | ], 20 | "at-rule-no-unknown": [true, { 21 | "ignoreAtRules": ["function", "if", "each", "include", "mixin"] 22 | }], 23 | "at-rule-empty-line-before": [ 24 | "always", 25 | { 26 | "except": [ 27 | "after-same-name" 28 | ] 29 | } 30 | ], 31 | "number-leading-zero": "never", 32 | "indentation": [ 33 | 2, { 34 | "indentInsideParens": "once-at-root-twice-in-block", 35 | "severity": "error" 36 | } 37 | ], 38 | "plugin/no-unsupported-browser-features": [ 39 | true, 40 | { 41 | "severity": "warning", 42 | "ignore": [ 43 | "css-boxshadow", 44 | "pointer-events", 45 | "border-radius", 46 | "border-image", 47 | "css-gradients", 48 | "css-textshadow", 49 | "transforms2d", 50 | "css-animation", 51 | "css-transitions", 52 | "css-transform", 53 | "transition", 54 | "transform", 55 | "css-image-set", 56 | "flexbox", 57 | "viewport-units", 58 | "calc", 59 | "intrinsic-width" 60 | ] 61 | } 62 | ], 63 | "selector-pseudo-class-no-unknown": [ 64 | true, 65 | { 66 | "ignorePseudoClasses": ["global", "local"] 67 | } 68 | ], 69 | "selector-pseudo-class-case": null, 70 | "property-no-unknown": [ 71 | true, 72 | { 73 | "ignoreProperties": ["composes", "r"] 74 | } 75 | ], 76 | "unit-no-unknown": [ 77 | true, 78 | { 79 | "ignoreUnits": ["x"] 80 | } 81 | ], 82 | "order/order": [ 83 | [ 84 | "custom-properties", 85 | "at-variables", 86 | "declarations", 87 | "at-rules", 88 | "rules", 89 | "less-mixins" 90 | ], 91 | { 92 | "severity": "warning" 93 | } 94 | ] 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /cypress/apps/test_example/scenarios/attackerUI.ts: -------------------------------------------------------------------------------- 1 | import { checkEffects } from '../view/behavior/effects' 2 | import { checkHeader } from '../view/behavior/header' 3 | import { checkFacilities } from '../view/behavior/facilities' 4 | import { checkModalOverlay } from '../view/behavior/modal' 5 | import { checkStealthBar } from '../view/behavior/stealthbar' 6 | 7 | import initiateE2ETest from '../../../services/initiateE2ETest' 8 | import subscribeAPI from '../api' 9 | 10 | import { ATTACKER, MALE, MALE_SHORT } from '../constants' 11 | import { checkModel } from '../view/behavior/model' 12 | import { checkArmour } from '../view/behavior/armour' 13 | import { checkStatusBar } from '../view/behavior/statusbar' 14 | import { checkWeaponBoxes } from '../view/behavior/weapons' 15 | 16 | const attackerUI = (isInit?: boolean) => { 17 | isInit && initiateE2ETest({ 18 | runAPI: subscribeAPI, 19 | url: `/loader.php?sid=attack&user2ID=${Cypress.env('DEFENDER_ID')}` 20 | }) 21 | 22 | // checking attacker overlay 23 | checkModalOverlay({ userType: ATTACKER }) 24 | 25 | // checking header 26 | checkHeader({ type: ATTACKER, name: 'maryan060', health: '250 / 250' }) 27 | 28 | // checking stealthBar 29 | checkStealthBar({ height: '385px' }) 30 | 31 | // checking model 32 | checkModel({ type: ATTACKER, gender: MALE }) 33 | 34 | // checking armour 35 | checkArmour({ 36 | type: ATTACKER, 37 | itemsList: [ 38 | { itemID: 842 }, 39 | { itemID: 841, maskID: 'head-top' }, 40 | { itemID: 843, maskID: 'duster', isBackground: true } 41 | ], 42 | shortGender: MALE_SHORT 43 | }) 44 | 45 | // checking effects 46 | checkEffects({ 47 | userType: ATTACKER, 48 | effectsList: [], 49 | wrapCount: 0 50 | }) 51 | 52 | // checking bottom indicator facilities block 53 | checkFacilities({ 54 | userType: ATTACKER, 55 | strength: { 56 | isArrow: false, 57 | color: 'grey', 58 | count: 1, 59 | value: '+ 0%', 60 | label: 'strength' 61 | }, 62 | speed: { 63 | isArrow: false, 64 | color: 'grey', 65 | value: '+ 0%', 66 | label: 'speed' 67 | }, 68 | dexterity: { 69 | isArrow: false, 70 | color: 'grey', 71 | value: '+ 0%', 72 | label: 'dexterity' 73 | }, 74 | defence: { 75 | isArrow: false, 76 | color: 'grey', 77 | value: '+ 0%', 78 | label: 'defence' 79 | } 80 | }) 81 | 82 | // checking bottom statusBar 83 | checkWeaponBoxes({ 84 | userType: ATTACKER, 85 | weaponsData: { 86 | main: { 87 | damage: '66.22', 88 | patrons: '30/30 (2)', 89 | accuracy: '44.42' 90 | }, 91 | secondary: { 92 | damage: '45.08', 93 | patrons: '15/15 (2)', 94 | accuracy: '59.30' 95 | }, 96 | melee: { 97 | damage: '60.62', 98 | accuracy: '45.87' 99 | } 100 | } 101 | }) 102 | 103 | // checking bottom statusBar 104 | checkStatusBar({ userType: ATTACKER }) 105 | } 106 | 107 | export default attackerUI 108 | -------------------------------------------------------------------------------- /cypress/examples/connectors.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-nested-callbacks */ 2 | context('Connectors', () => { 3 | beforeEach(() => { 4 | cy.visit('https://example.cypress.io/commands/connectors') 5 | }) 6 | 7 | it('.each() - iterate over an array of elements', () => { 8 | // https://on.cypress.io/each 9 | cy.get('.connectors-each-ul>li') 10 | .each(($el, index, $list) => { 11 | console.log($el, index, $list) 12 | }) 13 | }) 14 | 15 | it('.its() - get properties on the current subject', () => { 16 | // https://on.cypress.io/its 17 | cy.get('.connectors-its-ul>li') 18 | // calls the 'length' property yielding that value 19 | .its('length') 20 | .should('be.gt', 2) 21 | }) 22 | 23 | it('.invoke() - invoke a function on the current subject', () => { 24 | // our div is hidden in our script.js 25 | // $('.connectors-div').hide() 26 | 27 | // https://on.cypress.io/invoke 28 | cy.get('.connectors-div').should('be.hidden') 29 | // call the jquery method 'show' on the 'div.container' 30 | .invoke('show') 31 | .should('be.visible') 32 | }) 33 | 34 | it('.spread() - spread an array as individual args to callback function', () => { 35 | // https://on.cypress.io/spread 36 | const arr = ['foo', 'bar', 'baz'] 37 | 38 | cy.wrap(arr).spread((foo, bar, baz) => { 39 | expect(foo).equal('foo') 40 | expect(bar).equal('bar') 41 | expect(baz).equal('baz') 42 | }) 43 | }) 44 | 45 | describe('.then()', () => { 46 | it('invokes a callback function with the current subject', () => { 47 | // https://on.cypress.io/then 48 | cy.get('.connectors-list > li') 49 | .then(($lis) => { 50 | expect($lis).to.have.length(3) 51 | expect($lis.eq(0)).to.contain('Walk the dog') 52 | expect($lis.eq(1)).to.contain('Feed the cat') 53 | expect($lis.eq(2)).to.contain('Write JavaScript') 54 | }) 55 | }) 56 | 57 | it('yields the returned value to the next command', () => { 58 | cy.wrap(1) 59 | .then((num) => { 60 | expect(num).equal(1) 61 | 62 | return 2 63 | }) 64 | .then((num) => { 65 | expect(num).equal(2) 66 | }) 67 | }) 68 | 69 | it('yields the original subject without return', () => { 70 | cy.wrap(1) 71 | .then((num) => { 72 | expect(num).equal(1) 73 | // note that nothing is returned from this callback 74 | }) 75 | .then((num) => { 76 | // this callback receives the original unchanged value 1 77 | expect(num).equal(1) 78 | }) 79 | }) 80 | 81 | it('yields the value yielded by the last Cypress command inside', () => { 82 | cy.wrap(1) 83 | .then((num) => { 84 | expect(num).equal(1) 85 | // note how we run a Cypress command 86 | // the result yielded by this Cypress command 87 | // will be passed to the second ".then" 88 | cy.wrap(2) 89 | }) 90 | .then((num) => { 91 | // this callback receives the value yielded by "cy.wrap(2)" 92 | expect(num).equal(2) 93 | }) 94 | }) 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /cypress/examples/misc.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-arrow-callback */ 2 | /* eslint-disable func-names */ 3 | /* eslint-disable prefer-arrow/prefer-arrow-functions */ 4 | context('Misc', () => { 5 | beforeEach(() => { 6 | cy.visit('https://example.cypress.io/commands/misc') 7 | }) 8 | 9 | it('.end() - end the command chain', () => { 10 | // https://on.cypress.io/end 11 | 12 | // cy.end is useful when you want to end a chain of commands 13 | // and force Cypress to re-query from the root element 14 | cy.get('.misc-table').within(() => { 15 | // ends the current chain and yields null 16 | cy.contains('Cheryl').click().end() 17 | 18 | // queries the entire table again 19 | cy.contains('Charles').click() 20 | }) 21 | }) 22 | 23 | it('cy.exec() - execute a system command', () => { 24 | // execute a system command. 25 | // so you can take actions necessary for 26 | // your test outside the scope of Cypress. 27 | // https://on.cypress.io/exec 28 | 29 | // we can use Cypress.platform string to 30 | // select appropriate command 31 | // https://on.cypress/io/platform 32 | cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`) 33 | 34 | // on CircleCI Windows build machines we have a failure to run bash shell 35 | // https://github.com/cypress-io/cypress/issues/5169 36 | // so skip some of the tests by passing flag "--env circle=true" 37 | const isCircleOnWindows = Cypress.platform === 'win32' && Cypress.env('circle') 38 | 39 | if (isCircleOnWindows) { 40 | return 41 | } 42 | 43 | cy.exec('echo Jane Lane') 44 | .its('stdout').should('contain', 'Jane Lane') 45 | 46 | if (Cypress.platform === 'win32') { 47 | cy.exec('print cypress.json') 48 | .its('stderr').should('be.empty') 49 | } else { 50 | cy.exec('cat cypress.json') 51 | .its('stderr').should('be.empty') 52 | 53 | cy.exec('pwd') 54 | .its('code').should('eq', 0) 55 | } 56 | }) 57 | 58 | it('cy.focused() - get the DOM element that has focus', () => { 59 | // https://on.cypress.io/focused 60 | cy.get('.misc-form').find('#name').click() 61 | cy.focused().should('have.id', 'name') 62 | 63 | cy.get('.misc-form').find('#description').click() 64 | cy.focused().should('have.id', 'description') 65 | }) 66 | 67 | context('Cypress.Screenshot', function () { 68 | it('cy.screenshot() - take a screenshot', () => { 69 | // https://on.cypress.io/screenshot 70 | cy.screenshot('my-image') 71 | }) 72 | 73 | it('Cypress.Screenshot.defaults() - change default config of screenshots', function () { 74 | Cypress.Screenshot.defaults({ 75 | blackout: ['.foo'], 76 | capture: 'viewport', 77 | clip: { x: 0, y: 0, width: 200, height: 200 }, 78 | scale: false, 79 | disableTimersAndAnimations: true, 80 | screenshotOnRunFailure: true, 81 | beforeScreenshot() {}, 82 | afterScreenshot() {} 83 | }) 84 | }) 85 | }) 86 | 87 | it('cy.wrap() - wrap an object', () => { 88 | // https://on.cypress.io/wrap 89 | cy.wrap({ foo: 'bar' }) 90 | .should('have.property', 'foo') 91 | .and('include', 'bar') 92 | }) 93 | }) 94 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Webpack4 - React16 Starter Kit 2 | 3 | 4 | # 3.0.0 5 | * Bumped up all outdated deps. 6 | * Removed TSLint in favor of ESLint for single source of truth. 7 | * Added proxy middleware starter right into the app. 8 | * Added SSL certificates and ability to work over HTTPS on localhost. 9 | * Improved SCSS assets serving in development mode by placing them right into bundles instead of hardcode injection. 10 | 11 | # 2.1.2 12 | * Fixed HMR work in development mode. 13 | 14 | # 2.1.1 15 | * Update UjlifyJS plugin correspond to its internal update. 16 | * Improved distinguish between prod and dev version running via Hot Module Replacement. 17 | 18 | # 2.0.1 19 | * Updated Webpack to 4.29.1 version. 20 | * Removed .travis.ci file from git watch. 21 | 22 | # 2.0.0 23 | * Partiarly moved app to TypeScript v.3.3.4. 24 | * Major Update of react dependencies (react react-dom react-redux react-router react-router-dom redux). 25 | * Added React Hooks Example inroduction in Boddy Component. 26 | * Added es2017 lib in tsconfig.json for better namespaces handling. 27 | * Added react-hooks linter to eslint configuration. 28 | * Added react-hooks linter to tslint configuration. 29 | * Added react-hooks/exhaustive-deps dependency. 30 | * Moved space-in-parens rule in tslint config into disabled state. 31 | * Removed yarn-error.lock file. 32 | 33 | # 1.9.1 34 | * Added Code of Conduct and Pull Request Templates. 35 | * Minor fixes. 36 | 37 | # 1.9.0 38 | * Updated react, redux, react-redux dependencies. 39 | 40 | # 1.8.3 41 | * Fixed chunks info in Development mode. 42 | 43 | # 1.8.2 44 | * Fixed Test scripts suite. 45 | 46 | # 1.8.1 47 | * Added travis-ci integration for automation build passing. 48 | * Fixed Jest testing environment. 49 | * Minor improvements. 50 | 51 | # 1.7.1 52 | * Added Jest dev and peer packages. 53 | * Fixed Jest testing environment. 54 | * Minor improvements. 55 | 56 | # 1.4.0 57 | * Added Tests example in the App. 58 | * Fixed animation in App Component. 59 | * Updated babel packages. 60 | 61 | # 1.2.0 62 | * Added ServiceWorkers in App. 63 | * Added Added AnimationTransitionGroup v2 package for animation handling with example in Body Component. 64 | 65 | # 1.1.0 66 | * Improved App Structure. 67 | * Added Redux connected store in App. 68 | 69 | # 1.0.0 70 | * First stabel release! 71 | * Added tslint ans eslint inside compilation babel processes builds. 72 | 73 | # 0.7.0 74 | * Added major improvements and optimizations inside Webpack Configuration file. 75 | 76 | # 0.6.0 77 | * Major update of dependencies and devDependencies. 78 | * Updated eslint config. 79 | * Updated tslint config. 80 | 81 | # 0.5.0 82 | * Integrated React-Routing for app routring. 83 | 84 | # 0.4.0 85 | * Integrated Redux state managment in the app. 86 | * Integrated Redux saga for async actions managment. 87 | 88 | # 0.3.2 89 | * Fixed TSLint checking on pre-commit stage. 90 | 91 | # 0.3.1 92 | * Fixed hot module replacement with unchecked app changes. 93 | 94 | # 0.3.0 95 | * Added polifylls for unsuportated babel methods. 96 | * Added TypeScript support. 97 | * Injected rocket-start React-Redux state controller framework 98 | 99 | # 0.2.0 100 | * Added more beatiful mocks (extended from create-react-app). 101 | 102 | # 0.1.0 103 | * App has been more clearly documented across whole app code. 104 | 105 | # 0.0.1 106 | * App has been created. 107 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at svyat770@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /cypress/utils/checkPosition.ts: -------------------------------------------------------------------------------- 1 | import getElemCoords from './getCoords' 2 | 3 | import { ICheckElemPos, IWithoutParent, IWithParentPosition } from '../interfaces/IUtils' 4 | 5 | const normalizeValue = (value: number) => Math.ceil(value) 6 | const isValue = (value: any) => ![undefined, null].includes(value) 7 | const COORDS_ARRURACY = [-1, 0, 1] 8 | 9 | const checkCoords = ({ compareValue, topShift, leftShift, leftOffset, topOffset }) => { 10 | const { left, top } = getElemCoords({ topCoords: topShift, leftCoords: leftShift }) 11 | 12 | if (isValue(leftOffset)) { 13 | if (!compareValue) { 14 | const coordsDiff = normalizeValue(left) - leftOffset 15 | const isDiffAllowed = COORDS_ARRURACY.includes(coordsDiff) 16 | 17 | expect(isDiffAllowed, `left coords check: ${leftOffset}`).to.be.true 18 | } else { 19 | expect(normalizeValue(left), 'left coords check').equal(leftOffset) 20 | } 21 | } 22 | 23 | if (isValue(topOffset)) { 24 | if (!compareValue) { 25 | const coordsDiff = normalizeValue(top) - topOffset 26 | const isDiffAllowed = COORDS_ARRURACY.includes(coordsDiff) 27 | 28 | expect(isDiffAllowed, `top coords check: ${topOffset}`).to.be.true 29 | } else { 30 | expect(normalizeValue(top), 'top coords check').equal(topOffset) 31 | } 32 | } 33 | } 34 | 35 | const globalCheck = ({ nodeElement, topOffset, leftOffset, compareValue }: IWithoutParent) => { 36 | nodeElement().then((target: HTMLElement) => { 37 | const { top: topShift, left: leftShift } = target[0].getBoundingClientRect() 38 | 39 | checkCoords({ compareValue, topShift, leftShift, leftOffset, topOffset }) 40 | }) 41 | } 42 | 43 | const localCheck = ({ parentElement, nodeElement, topOffset, leftOffset, compareValue }: IWithParentPosition) => { 44 | const getTargetNode = new Promise(resolve => { 45 | return nodeElement() 46 | .then(target => resolve(target)) 47 | }).then(res => res) 48 | 49 | const getParentNode = new Promise(resolve => { 50 | const parent = parentElement || nodeElement 51 | 52 | return parent() 53 | .then(target => resolve(!parentElement ? [target[0].parentNode] : target)) 54 | }).then(res => res) 55 | 56 | Promise.all([getTargetNode, getParentNode]).then(nodesArr => { 57 | const [targetNode, parentNode] = nodesArr 58 | 59 | const { top: childTop, left: childLeft } = (targetNode[0] as HTMLElement).getBoundingClientRect() 60 | const { top: topParent, left: leftParent } = (parentNode[0] as HTMLElement).getBoundingClientRect() 61 | 62 | const topShift = normalizeValue(childTop - topParent) 63 | const leftShift = normalizeValue(childLeft - leftParent) 64 | 65 | // expect(childTop - topParent).equal(topOffset) 66 | // expect(childLeft - leftParent).equal(leftOffset) 67 | 68 | // this is the hack in favor to solve the issue: https://github.com/cypress-io/cypress/issues/4742 69 | cy.wrap(null).should(() => { 70 | checkCoords({ compareValue, topShift, leftShift, leftOffset, topOffset }) 71 | }) 72 | }) 73 | } 74 | 75 | const checkPosition = (config: ICheckElemPos) => { 76 | const { 77 | withParent = false, 78 | compareValue = false, 79 | parentElement = null, 80 | nodeElement = null, 81 | topOffset, 82 | leftOffset 83 | } = config 84 | 85 | if (!nodeElement) { 86 | throw new Error('Some error with elements') 87 | } 88 | 89 | if (withParent) { 90 | localCheck({ parentElement, nodeElement, topOffset, leftOffset, compareValue }) 91 | 92 | return 93 | } 94 | 95 | globalCheck({ nodeElement, topOffset, leftOffset, compareValue }) 96 | } 97 | 98 | export default checkPosition 99 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/behavior/header.ts: -------------------------------------------------------------------------------- 1 | import checkPosition from '../../../../utils/checkPosition' 2 | 3 | import { 4 | userName, 5 | hitsIcon, 6 | hitsCount, 7 | damageIcon, 8 | damageCount, 9 | healthIcon, 10 | healthCount, 11 | lifeBar 12 | } from '../selectors/header' 13 | 14 | import { ICheckHeader, ICheckHits, ICheckDamage, ICheckHealth } from '../../interfaces/IView' 15 | import { TPersonTypes } from '../../interfaces/IController' 16 | 17 | import { HEADER_COORDS } from '../constants/coords' 18 | import checkStyles from '../../../../utils/checkStyles' 19 | 20 | export const checkName = ({ userType, userName: userNameData }: { userType: TPersonTypes; userName: string }) => { 21 | cy.log(`**Check Name: ${userType}**`) 22 | 23 | const nameNode = () => userName(userType) 24 | 25 | nameNode().should('have.length', 1) 26 | nameNode().should('have.text', userNameData) 27 | 28 | checkPosition({ nodeElement: nameNode, ...HEADER_COORDS[userType].userName }) 29 | } 30 | 31 | export const checkHits = ({ userType, hits }: ICheckHits) => { 32 | cy.log(`**Check Hits: ${userType}**`) 33 | 34 | const hitsIconNode = () => hitsIcon(userType) 35 | const hitsCountNode = () => hitsCount(userType) 36 | 37 | hitsIconNode().should('have.length', 1) 38 | hitsCountNode().should('have.text', hits) 39 | 40 | checkPosition({ nodeElement: hitsIconNode, ...HEADER_COORDS[userType].hitsIcon }) 41 | checkPosition({ nodeElement: hitsCountNode, ...HEADER_COORDS[userType].hitsCount }) 42 | } 43 | 44 | export const checkDamage = ({ userType, damage }: ICheckDamage) => { 45 | cy.log(`**Check Damage: ${userType}**`) 46 | 47 | const damageIconNode = () => damageIcon(userType) 48 | const damageCountNode = () => damageCount(userType) 49 | 50 | damageIconNode().should('have.length', 1) 51 | damageCountNode().should('have.text', damage) 52 | 53 | checkPosition({ nodeElement: damageIconNode, ...HEADER_COORDS[userType].damageIcon }) 54 | checkPosition({ nodeElement: damageCountNode, ...HEADER_COORDS[userType].damageCount }) 55 | } 56 | 57 | export const checkHealth = ({ userType, health }: ICheckHealth) => { 58 | cy.log(`**Check Health: ${userType}**`) 59 | 60 | const healthIconNode = () => healthIcon(userType) 61 | const healthCountNode = () => healthCount(userType) 62 | 63 | healthIconNode().should('have.length', 1) 64 | healthCountNode().should('have.text', health) 65 | 66 | checkPosition({ nodeElement: healthIconNode, ...HEADER_COORDS[userType].healthIcon }) 67 | checkPosition({ nodeElement: healthCountNode, ...HEADER_COORDS[userType].healthCount }) 68 | } 69 | 70 | export const checkLifeBar = ({ userType, width }: { userType: TPersonTypes; width: string }) => { 71 | cy.log(`**Check LifeBar: ${userType}**`) 72 | 73 | const lifeBarNode = () => lifeBar(userType) 74 | 75 | lifeBarNode().should('have.length', 1) 76 | checkStyles({ 77 | elemNode: lifeBarNode(), 78 | expectations: { 79 | width: width, 80 | 'background-image': 'linear-gradient(rgb(107, 132, 218), rgb(68, 74, 208))' 81 | } 82 | }) 83 | 84 | checkPosition({ nodeElement: lifeBarNode, ...HEADER_COORDS[userType].lifeBar }) 85 | } 86 | 87 | export const checkHeader = (config: ICheckHeader) => { 88 | const { type, name, hits = '0', damage = '0', health, lifeWidth = '463px' } = config 89 | 90 | cy.log(`**[START] Check Header: ${type}**`) 91 | 92 | checkName({ userType: type, userName: name }) 93 | checkHits({ userType: type, hits }) 94 | checkDamage({ userType: type, damage }) 95 | checkHealth({ userType: type, health }) 96 | checkLifeBar({ userType: type, width: lifeWidth }) 97 | 98 | cy.log(`**[END] Check Header: ${type}**`) 99 | } 100 | -------------------------------------------------------------------------------- /.github/workflows/security.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "Security" 7 | 8 | on: 9 | push: 10 | branches: [ master ] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [ master ] 14 | schedule: 15 | - cron: '0 15 * * 6' 16 | 17 | jobs: 18 | security: 19 | name: Analyze 20 | runs-on: macos-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['javascript', 'typescript'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | - run: git checkout HEAD^2 42 | if: ${{ github.event_name == 'pull_request' }} 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | 73 | - name: Slack Notification 74 | uses: 8398a7/action-slack@v3.8.0 75 | if: failure() 76 | with: 77 | status: custom 78 | fields: workflow,job,commit,repo,ref,author,took 79 | custom_payload: | 80 | { 81 | username: 'React-Apps-CI', 82 | icon_emoji: ':react:', 83 | author_name: 'Security Test', 84 | attachments: [{ 85 | color: '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning', 86 | text: `CI Task: ${process.env.AS_WORKFLOW}\ncommit: (${process.env.AS_COMMIT}) ${{ github.event_name }} ${{ job.status }}. Initiated by ${process.env.AS_AUTHOR} in ${process.env.AS_TOOK}`, 87 | }] 88 | } 89 | env: 90 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 91 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 92 | MATRIX_CONTEXT: ${{ toJson(matrix) }} 93 | 94 | -------------------------------------------------------------------------------- /cypress/apps/test_example/controller/index.ts: -------------------------------------------------------------------------------- 1 | import getResponseData from '../../../utils/getResponseData' 2 | import getRequestData, { TFormData } from '../../../utils/getRequestData' 3 | 4 | import * as triggers from '../triggers' 5 | import * as dataMocks from '../mocks' 6 | import getDataMock from '../utils/getDataMock' 7 | import { IStartFightData, IAttackWithID } from '../interfaces/IMocks' 8 | import { TModalTypes, TWeaponsType } from '../interfaces/IModules' 9 | 10 | const { start, windowFocus, escape } = triggers 11 | 12 | export const onInit = () => { 13 | cy.wait('@init').then( 14 | () => cy.log('**INIT IS FIRED**.') 15 | ) 16 | } 17 | 18 | export const onStart = () => { 19 | start().wait('@action') 20 | .then(response => { 21 | const { body } = response.request 22 | const requestData = getRequestData(body as TFormData) as IStartFightData 23 | 24 | cy.log(`**ATTACK STARTED. REQUEST PAYLOAD:** ${JSON.stringify(requestData)}`) 25 | .then(() => assert.deepEqual(requestData, getDataMock({ dataMock: dataMocks.startFightData }))) 26 | }) 27 | } 28 | 29 | export const onFocus = () => { 30 | windowFocus().wait('@init') 31 | .then( 32 | () => cy.log('**FOCUS IS FIRED**.') 33 | ) 34 | } 35 | 36 | export const onPoll = () => { 37 | cy.wait('@poll') 38 | .then( 39 | () => cy.log('**POLL IS STARTED**.') 40 | ) 41 | } 42 | 43 | export const onAttack = (buttonType: TWeaponsType) => { 44 | const weapon = triggers[buttonType] 45 | 46 | return weapon().wait('@action') 47 | .then(async ({ request, response }) => { 48 | const { body } = request 49 | const { body: payload } = response 50 | 51 | const requestData = getRequestData(body as TFormData) as IAttackWithID 52 | const responseData = await getResponseData(payload as Blob) 53 | 54 | // FIXME: need to be fixed once Cypress make some solution here https://github.com/cypress-io/cypress/issues/5365 55 | // cy.log(`**ACTION STARTED. WEAPON ID:** ${requestData.user1EquipedItemID}. 56 | // **PAYLOAD:** \n ${JSON.stringify(requestData)}`) 57 | 58 | return { 59 | requestData, 60 | responseData 61 | } 62 | }) 63 | } 64 | 65 | export const onEscape = () => { 66 | const mockData = getDataMock({ dataMock: dataMocks.escapeFightData, noUser2ID: true }) 67 | 68 | escape().wait('@action') 69 | .then(async ({ request }) => { 70 | const { body } = request 71 | const requestData = getRequestData(body as TFormData) 72 | 73 | cy.log(`**ATTACK FINISHED (ESCAPED)**. \n **PAYLOAD:** ${JSON.stringify(requestData)}`) 74 | .then(() => assert.deepEqual(requestData, mockData)) 75 | }) 76 | } 77 | 78 | export const onWin = ({ type }: { type: TModalTypes }) => { 79 | const win = triggers[type] 80 | const winMockData = getDataMock({ dataMock: dataMocks[`${type}FightData`], noUser2ID: true }) 81 | 82 | return win().wait('@action') 83 | .then(response => { 84 | const { body } = response.request 85 | const requestData = getRequestData(body as TFormData) 86 | 87 | cy.log(`**ATTACK FINISHED (${type})**. \n **PAYLOAD:** ${JSON.stringify(requestData)}`) 88 | .then(() => assert.deepEqual(requestData, winMockData)) 89 | }) 90 | } 91 | 92 | // export const onContinue = () => { 93 | // continues().wait('@continue') 94 | // .then(({ request }) => { 95 | // const { body } = request 96 | // const requestData = getRequestData(body as TFormData) 97 | 98 | // console.log(request, requestData, 'request') 99 | 100 | // cy.log(`**ATTACK FINISHED (CONTINUE)**. \n **PAYLOAD:** ${JSON.stringify(requestData)}`) 101 | // // .then(() => assert.deepEqual(requestData, dataMocks.continueData)) 102 | // }) 103 | // } 104 | -------------------------------------------------------------------------------- /cypress/examples/querying.spec.ts: -------------------------------------------------------------------------------- 1 | context('Querying', () => { 2 | beforeEach(() => { 3 | cy.visit('https://example.cypress.io/commands/querying') 4 | }) 5 | 6 | // The most commonly used query is 'cy.get()', you can 7 | // think of this like the '$' in jQuery 8 | 9 | it('cy.get() - query DOM elements', () => { 10 | // https://on.cypress.io/get 11 | 12 | cy.get('#query-btn').should('contain', 'Button') 13 | 14 | cy.get('.query-btn').should('contain', 'Button') 15 | 16 | cy.get('#querying .well>button:first').should('contain', 'Button') 17 | // ↲ 18 | // Use CSS selectors just like jQuery 19 | 20 | cy.get('[data-test-id="test-example"]').should('have.class', 'example') 21 | 22 | // 'cy.get()' yields jQuery object, you can get its attribute 23 | // by invoking `.attr()` method 24 | cy.get('[data-test-id="test-example"]') 25 | .invoke('attr', 'data-test-id') 26 | .should('equal', 'test-example') 27 | 28 | // or you can get element's CSS property 29 | cy.get('[data-test-id="test-example"]') 30 | .invoke('css', 'position') 31 | .should('equal', 'static') 32 | 33 | // or use assertions directly during 'cy.get()' 34 | // https://on.cypress.io/assertions 35 | cy.get('[data-test-id="test-example"]') 36 | .should('have.attr', 'data-test-id', 'test-example') 37 | .and('have.css', 'position', 'static') 38 | }) 39 | 40 | it('cy.contains() - query DOM elements with matching content', () => { 41 | // https://on.cypress.io/contains 42 | cy.get('.query-list') 43 | .contains('bananas') 44 | .should('have.class', 'third') 45 | 46 | // we can pass a regexp to `.contains()` 47 | cy.get('.query-list') 48 | .contains(/^b\w+/) 49 | .should('have.class', 'third') 50 | 51 | cy.get('.query-list') 52 | .contains('apples') 53 | .should('have.class', 'first') 54 | 55 | // passing a selector to contains will 56 | // yield the selector containing the text 57 | cy.get('#querying') 58 | .contains('ul', 'oranges') 59 | .should('have.class', 'query-list') 60 | 61 | cy.get('.query-button') 62 | .contains('Save Form') 63 | .should('have.class', 'btn') 64 | }) 65 | 66 | it('.within() - query DOM elements within a specific element', () => { 67 | // https://on.cypress.io/within 68 | cy.get('.query-form').within(() => { 69 | cy.get('input:first').should('have.attr', 'placeholder', 'Email') 70 | cy.get('input:last').should('have.attr', 'placeholder', 'Password') 71 | }) 72 | }) 73 | 74 | it('cy.root() - query the root DOM element', () => { 75 | // https://on.cypress.io/root 76 | 77 | // By default, root is the document 78 | cy.root().should('match', 'html') 79 | 80 | cy.get('.query-ul').within(() => { 81 | // In this within, the root is now the ul DOM element 82 | cy.root().should('have.class', 'query-ul') 83 | }) 84 | }) 85 | 86 | it('best practices - selecting elements', () => { 87 | // https://on.cypress.io/best-practices#Selecting-Elements 88 | cy.get('[data-cy=best-practices-selecting-elements]').within(() => { 89 | // Worst - too generic, no context 90 | cy.get('button').click() 91 | 92 | // Bad. Coupled to styling. Highly subject to change. 93 | cy.get('.btn.btn-large').click() 94 | 95 | // Average. Coupled to the `name` attribute which has HTML semantics. 96 | cy.get('[name=submission]').click() 97 | 98 | // Better. But still coupled to styling or JS event listeners. 99 | cy.get('#main').click() 100 | 101 | // Slightly better. Uses an ID but also ensures the element 102 | // has an ARIA role attribute 103 | cy.get('#main[role=button]').click() 104 | 105 | // Much better. But still coupled to text content that may change. 106 | cy.contains('Submit').click() 107 | 108 | // Best. Insulated from all changes. 109 | cy.get('[data-cy=submit]').click() 110 | }) 111 | }) 112 | }) 113 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/behavior/effects.ts: -------------------------------------------------------------------------------- 1 | import firstLetterUpper from '../../../../../shared/utils/firstLetterUpper' 2 | 3 | import { 4 | effectsWrap, 5 | effectWrap, 6 | effectBottomLayer, 7 | effectTopLayer, 8 | effectsIconsWrap, 9 | effectsIconsScrollWrap, 10 | effectIconContainer, 11 | effectIconWrap, 12 | effectIcon, 13 | effectIconImage 14 | } from '../selectors/effects' 15 | 16 | import { ICheckEffects, ICheckEffect, ICheckEffectIcon, ICheckEffectBackground } from '../../interfaces/IView' 17 | import { DEFENDER } from '../../constants' 18 | import { TPersonTypes } from '../../interfaces/IController' 19 | import checkStyles from '../../../../utils/checkStyles' 20 | 21 | export const checkEffectsWrap = (userType: TPersonTypes, count?: number) => { 22 | effectsWrap(userType).should('have.length', count) 23 | } 24 | 25 | export const checkEffectsIconsWrap = (userType: TPersonTypes, count?: number) => { 26 | effectsIconsWrap(userType).should('have.length', count) 27 | 28 | if (count === 0) { 29 | return 30 | } 31 | 32 | effectsIconsScrollWrap(userType).should('have.length', count) 33 | } 34 | 35 | export const checkEffectBackground = ({ userType, effectName, ID }: ICheckEffectBackground) => { 36 | effectWrap(userType, ID).should('have.length', 1) 37 | effectBottomLayer(userType, effectName, ID).should('have.length', 1) 38 | effectTopLayer(userType, effectName, ID).should('have.length', 1) 39 | 40 | checkStyles({ 41 | elemNode: effectBottomLayer(userType, effectName, ID), 42 | expectations: { 43 | width: '418px', 44 | height: '425px' 45 | } 46 | }) 47 | checkStyles({ 48 | elemNode: effectTopLayer(userType, effectName, ID), 49 | expectations: { 50 | width: '418px', 51 | height: '425px' 52 | } 53 | }) 54 | } 55 | 56 | export const checkEffectIcon = ({ userType, effectName, effectType, ID }: ICheckEffectIcon) => { 57 | effectIconContainer(userType, effectType, ID).should('have.length', 1) 58 | effectIconWrap(userType, effectType, ID).should('have.length', 1) 59 | effectIcon(userType, effectType, ID).should('have.length', 1) 60 | effectIconImage(userType, effectType, ID).should('have.length', 1) 61 | 62 | effectIconContainer(userType, effectType, ID).should( 63 | 'have.attr', 64 | 'id', 65 | firstLetterUpper({ value: effectName, isDisabledMultiUpper: true }) 66 | ) 67 | 68 | checkStyles({ 69 | elemNode: effectIconContainer(userType, effectType, ID), 70 | expectations: { 71 | width: '40px', 72 | height: '25px' 73 | } 74 | }) 75 | 76 | checkStyles({ 77 | elemNode: effectIcon(userType, effectType, ID), 78 | pseudo: 'after', 79 | expectations: { 80 | 'z-index': '-1', 81 | 'background-color': 'rgba(178, 45, 0, 0.3)', 82 | width: '60px', 83 | height: '7px', 84 | // top: '12.5px', // this is can be 0px or 12.5px for some reason (CSSStylesheet is mutable) 85 | left: '15px', 86 | position: 'absolute', 87 | transform: 'matrix(1, 0, 0, 1, 0, -3.5)' 88 | } 89 | }) 90 | 91 | effectIconImage(userType, effectType, ID).should( 92 | 'have.attr', 93 | 'src', 94 | `/images/v2/attack/effect_icons/${effectName}.svg` 95 | ) 96 | } 97 | 98 | export const checkEffect = ({ userType, effectName, effectType, ID }: ICheckEffect) => { 99 | checkEffectBackground({ userType, effectName, ID }) 100 | checkEffectIcon({ userType, effectName, effectType, ID }) 101 | } 102 | 103 | export const checkEffects = ({ userType = DEFENDER, effectsList, wrapCount = 1 }: ICheckEffects) => { 104 | cy.log(`**[START] Check Effects: ${userType}**`) 105 | 106 | checkEffectsWrap(userType, wrapCount) 107 | checkEffectsIconsWrap(userType, wrapCount) 108 | 109 | if (!effectsList || effectsList.length === 0) { 110 | return 111 | } 112 | 113 | effectsList.forEach(({ name, type }, index) => { 114 | checkEffect({ userType, ID: index, effectName: name, effectType: type }) 115 | }) 116 | 117 | cy.log(`**[END] Check Effects: ${userType}**`) 118 | } 119 | -------------------------------------------------------------------------------- /cypress/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Cypress - real-world App E2E testing 3 | 4 | A fully automated testing for pre-production app checking based on [Cypress.io](http://cypress.io/) environment. 5 | 6 | ### Why do we need this? 7 | 8 | Our E2E environment will allow us to completely run any component, any script’s behavior on the REAL page with REAL-like clicks/events on [www.google.com](http://google.com/). Whether - it’s a shifted element, wrong set color of the text, the button not pressed or the wrong redirect - we will find out with 100% probability until the release for sure! 9 | 10 | We literally avoid boring apps' unit tests in favor of fully controlled, automated testing right on the dev. 11 | 12 | ### How to use it 13 | 14 | The system environment is finally designed, running on the basis of `react-apps` repo. Architecturally, built on an MVC design pattern. Capable of easy expansion and independent testing for any app on the web! 15 | 16 | ### Current architecture: 17 | ``` 18 | ./cypress 19 | | 20 | | 21 | | --- /apps - all the tests should be described here 22 | | --- /constants - root cypress variables 23 | | --- /e2e - endpoint for tests running, included from ./apps 24 | | --- /examples - playaround tests for training with Cypress 25 | | --- /helpers - useful functions required through each test 26 | | --- /interfaces - core typization 27 | | --- /plugins - cypress system folder for its customization 28 | | --- /services - app-oriented, Cypress core functions. 29 | | --- /support - cypress system folder for describing custom methods 30 | | --- /template - starter kit for faster rolling up 31 | | --- /utils - reused code shared btw several sources 32 | | --- /videos - tests' records saved after running 33 | | --- /screenshots - system snapshots fired by developer on run 34 | | --- CHANGELOG.md - all the internal changed should be described here 35 | | --- index.d.ts - entrypoint for TS declarations 36 | | --- README.md - you're here 37 | | --- tsconfig.json - extend base config, but with own typization. 38 | | --- webpack.config.json - used for serving TS along with JS files. 39 | ``` 40 | 41 | Good, now we got a practical understanding about E2E Cypress architecture. So the next and most interest question is about how to build the particular app testing architecture? Well, it's quite easy. You can check any actual app inside `./cypress/apps/{app}/` . 42 | 43 | The other way is to grab the starter kit template from `./cypress/template/` and rolled it up among `./cypress/apps` folder. 44 | 45 | ### Particular app 46 | 47 | ``` 48 | ./cypress/apps/{your_awesome_app}/ 49 | | 50 | | 51 | | --- api - every app's endpoint for REST/WS should be described here. 52 | | --- constants - some variables shared btw the app. 53 | | --- controller - this is the core! Place all the business/async logic here. 54 | | --- mocks - request-data/UI-labels/etc should be described here. 55 | | --- modules - list all business behaviors, combined into reusable chunks. 56 | | --- scenarios - this is the place where all your tests should starts. 57 | | --- triggers - interactive page's elements which exposes request/action 58 | | --- utils - reused code shared btw the app tests 59 | | --- view 60 | | 61 | | --- behavior - checking UI on predictability here. 62 | | --- selectors - gathering elements together for shared reusing. 63 | 64 | ``` 65 | 66 | So as you see particular app architecture is very easy for understanding and expanding. You can store your code as independent files or main `index.ts` based on its complexity inside folders. 67 | 68 | Once you obviously distinguish some independent entities inside, then you should think about their separation indeed. 69 | 70 | ### Just FYI, some advices 71 | 72 | - Keep your `scenarios` clean from heavy, async or repetitive code. Use `controller` and `modules` for describing main app behavior and include them just as a reusable then. 73 | - Store all the UI-oriented logic, like buttons, containers, any UI checks) inside `view` folder. 74 | - Don't forgot to initiate `initiateE2ETest` with two arguments: API and URL. On each test. 75 | 76 | P.S. 77 | Feel free to ask for any other questions 3p-sviat in Slack. 78 | 79 | May the force be with you! 80 | -------------------------------------------------------------------------------- /cypress/examples/files.spec.ts: -------------------------------------------------------------------------------- 1 | // JSON fixture file can be loaded directly using 2 | // the built-in JavaScript bundler 3 | // @ts-ignore 4 | import requiredExample from '../../mocks/example.json' 5 | 6 | context('Files', () => { 7 | beforeEach(() => { 8 | cy.visit('https://example.cypress.io/commands/files') 9 | }) 10 | 11 | beforeEach(() => { 12 | // load example.json fixture file and store 13 | // in the test context object 14 | cy.fixture('example.json').as('example') 15 | }) 16 | 17 | it('cy.fixture() - load a fixture', () => { 18 | // https://on.cypress.io/fixture 19 | 20 | // Instead of writing a response inline you can 21 | // use a fixture file's content. 22 | 23 | cy.server() 24 | cy.fixture('example.json').as('comment') 25 | // when application makes an Ajax request matching "GET comments/*" 26 | // Cypress will intercept it and reply with object 27 | // from the "comment" alias 28 | cy.route('GET', 'comments/*', '@comment').as('getComment') 29 | 30 | // we have code that gets a comment when 31 | // the button is clicked in scripts.js 32 | cy.get('.fixture-btn').click() 33 | 34 | cy.wait('@getComment').its('responseBody') 35 | .should('have.property', 'name') 36 | .and('include', 'Using fixtures to represent data') 37 | 38 | // you can also just write the fixture in the route 39 | cy.route('GET', 'comments/*', 'fixture:example.json').as('getComment') 40 | 41 | // we have code that gets a comment when 42 | // the button is clicked in scripts.js 43 | cy.get('.fixture-btn').click() 44 | 45 | cy.wait('@getComment').its('responseBody') 46 | .should('have.property', 'name') 47 | .and('include', 'Using fixtures to represent data') 48 | 49 | // or write fx to represent fixture 50 | // by default it assumes it's .json 51 | cy.route('GET', 'comments/*', 'fx:example').as('getComment') 52 | 53 | // we have code that gets a comment when 54 | // the button is clicked in scripts.js 55 | cy.get('.fixture-btn').click() 56 | 57 | cy.wait('@getComment').its('responseBody') 58 | .should('have.property', 'name') 59 | .and('include', 'Using fixtures to represent data') 60 | }) 61 | 62 | it('cy.fixture() or require - load a fixture', function () { 63 | // we are inside the "function () { ... }" 64 | // callback and can use test context object "this" 65 | // "this.example" was loaded in "beforeEach" function callback 66 | expect(this.example, 'fixture in the test context') 67 | .to.deep.equal(requiredExample) 68 | 69 | // or use "cy.wrap" and "should('deep.equal', ...)" assertion 70 | // @ts-ignore 71 | cy.wrap(this.example, 'fixture vs require') 72 | .should('deep.equal', requiredExample) 73 | }) 74 | 75 | it('cy.readFile() - read file contents', () => { 76 | // https://on.cypress.io/readfile 77 | 78 | // You can read a file and yield its contents 79 | // The filePath is relative to your project's root. 80 | cy.readFile('cypress.json').then((json) => { 81 | expect(json).to.be.an('object') 82 | }) 83 | }) 84 | 85 | it('cy.writeFile() - write to a file', () => { 86 | // https://on.cypress.io/writefile 87 | 88 | // You can write to a file 89 | 90 | // Use a response from a request to automatically 91 | // generate a fixture file for use later 92 | cy.request('https://jsonplaceholder.cypress.io/users') 93 | .then((response) => { 94 | cy.writeFile('cypress/mocks/users.json', response.body) 95 | }) 96 | 97 | cy.fixture('users').should((users) => { 98 | // eslint-disable-next-line no-unused-expressions 99 | expect(users[0].name).to.exist 100 | }) 101 | 102 | // JavaScript arrays and objects are stringified 103 | // and formatted into text. 104 | cy.writeFile('cypress/fixtures/profile.json', { 105 | id: 8739, 106 | name: 'Jane', 107 | email: 'jane@example.com' 108 | }) 109 | 110 | cy.fixture('profile').should((profile) => { 111 | expect(profile.name).to.eq('Jane') 112 | }) 113 | }) 114 | }) 115 | -------------------------------------------------------------------------------- /cypress/examples/traversal.spec.ts: -------------------------------------------------------------------------------- 1 | context('Traversal', () => { 2 | beforeEach(() => { 3 | cy.visit('https://example.cypress.io/commands/traversal') 4 | }) 5 | 6 | it('.children() - get child DOM elements', () => { 7 | // https://on.cypress.io/children 8 | cy.get('.traversal-breadcrumb') 9 | .children('.active') 10 | .should('contain', 'Data') 11 | }) 12 | 13 | it('.closest() - get closest ancestor DOM element', () => { 14 | // https://on.cypress.io/closest 15 | cy.get('.traversal-badge') 16 | .closest('ul') 17 | .should('have.class', 'list-group') 18 | }) 19 | 20 | it('.eq() - get a DOM element at a specific index', () => { 21 | // https://on.cypress.io/eq 22 | cy.get('.traversal-list>li') 23 | .eq(1).should('contain', 'siamese') 24 | }) 25 | 26 | it('.filter() - get DOM elements that match the selector', () => { 27 | // https://on.cypress.io/filter 28 | cy.get('.traversal-nav>li') 29 | .filter('.active').should('contain', 'About') 30 | }) 31 | 32 | it('.find() - get descendant DOM elements of the selector', () => { 33 | // https://on.cypress.io/find 34 | cy.get('.traversal-pagination') 35 | .find('li').find('a') 36 | .should('have.length', 7) 37 | }) 38 | 39 | it('.first() - get first DOM element', () => { 40 | // https://on.cypress.io/first 41 | cy.get('.traversal-table td') 42 | .first().should('contain', '1') 43 | }) 44 | 45 | it('.last() - get last DOM element', () => { 46 | // https://on.cypress.io/last 47 | cy.get('.traversal-buttons .btn') 48 | .last().should('contain', 'Submit') 49 | }) 50 | 51 | it('.next() - get next sibling DOM element', () => { 52 | // https://on.cypress.io/next 53 | cy.get('.traversal-ul') 54 | .contains('apples').next().should('contain', 'oranges') 55 | }) 56 | 57 | it('.nextAll() - get all next sibling DOM elements', () => { 58 | // https://on.cypress.io/nextall 59 | cy.get('.traversal-next-all') 60 | .contains('oranges') 61 | .nextAll().should('have.length', 3) 62 | }) 63 | 64 | it('.nextUntil() - get next sibling DOM elements until next el', () => { 65 | // https://on.cypress.io/nextuntil 66 | cy.get('#veggies') 67 | .nextUntil('#nuts').should('have.length', 3) 68 | }) 69 | 70 | it('.not() - remove DOM elements from set of DOM elements', () => { 71 | // https://on.cypress.io/not 72 | cy.get('.traversal-disabled .btn') 73 | .not('[disabled]').should('not.contain', 'Disabled') 74 | }) 75 | 76 | it('.parent() - get parent DOM element from DOM elements', () => { 77 | // https://on.cypress.io/parent 78 | cy.get('.traversal-mark') 79 | .parent().should('contain', 'Morbi leo risus') 80 | }) 81 | 82 | it('.parents() - get parent DOM elements from DOM elements', () => { 83 | // https://on.cypress.io/parents 84 | cy.get('.traversal-cite') 85 | .parents().should('match', 'blockquote') 86 | }) 87 | 88 | it('.parentsUntil() - get parent DOM elements from DOM elements until el', () => { 89 | // https://on.cypress.io/parentsuntil 90 | cy.get('.clothes-nav') 91 | .find('.active') 92 | .parentsUntil('.clothes-nav') 93 | .should('have.length', 2) 94 | }) 95 | 96 | it('.prev() - get previous sibling DOM element', () => { 97 | // https://on.cypress.io/prev 98 | cy.get('.birds').find('.active') 99 | .prev().should('contain', 'Lorikeets') 100 | }) 101 | 102 | it('.prevAll() - get all previous sibling DOM elements', () => { 103 | // https://on.cypress.io/prevAll 104 | cy.get('.fruits-list').find('.third') 105 | .prevAll().should('have.length', 2) 106 | }) 107 | 108 | it('.prevUntil() - get all previous sibling DOM elements until el', () => { 109 | // https://on.cypress.io/prevUntil 110 | cy.get('.foods-list').find('#nuts') 111 | .prevUntil('#veggies').should('have.length', 3) 112 | }) 113 | 114 | it('.siblings() - get all sibling DOM elements', () => { 115 | // https://on.cypress.io/siblings 116 | cy.get('.traversal-pills .active') 117 | .siblings().should('have.length', 2) 118 | }) 119 | }) 120 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Linting 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | linting: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | with: 24 | ref: ${{ github.head_ref }} 25 | 26 | - name: Staring Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v1 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | 31 | - name: Restoring Yarn cache 32 | uses: actions/cache@v2 33 | with: 34 | path: '**/node_modules' 35 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 36 | 37 | - name: Bootstraping packages 38 | if: steps.yarn-cache.outputs.cache-hit != 'true' 39 | run: yarn install 40 | 41 | - name: Get file changes 42 | id: get_file_changes 43 | uses: trilom/file-changes-action@v1.2.3 44 | with: 45 | output: ' ' 46 | 47 | - name: Echo file changes 48 | id: hello 49 | run: | 50 | echo Added files: ${{ steps.get_file_changes.outputs.files_added }} 51 | echo Changed files: ${{ steps.get_file_changes.outputs.files_modified }} 52 | echo Removed files: ${{ steps.get_file_changes.outputs.files_removed }} 53 | 54 | - name: Prettier Checking 55 | if: ${{ always() && (steps.get_file_changes.outputs.files_added || steps.get_file_changes.outputs.files_modified) }} 56 | run: yarn prettier --config ./prettier.config.js --ignore-path ./.prettierignore ${{ steps.get_file_changes.outputs.files_added }} ${{ steps.get_file_changes.outputs.files_modified }} --fix 57 | 58 | - name: ESLint Checking 59 | if: ${{ always() && (steps.get_file_changes.outputs.files_added || steps.get_file_changes.outputs.files_modified) }} 60 | run: yarn eslint --config ./.eslintrc.js --ignore-path ./.eslintignore ${{ steps.get_file_changes.outputs.files_added }} ${{ steps.get_file_changes.outputs.files_modified }} --fix 61 | 62 | - name: TSLint Checking 63 | if: ${{ always() && (steps.get_file_changes.outputs.files_added || steps.get_file_changes.outputs.files_modified) }} 64 | run: yarn tslint --config ./tslint.json -e "**/*.(js|jsx|css|scss|html|md|json|yml)" ${{ steps.get_file_changes.outputs.files_added }} ${{ steps.get_file_changes.outputs.files_modified }} --fix 65 | 66 | - name: StyleLint Checking 67 | if: ${{ always() && (steps.get_file_changes.outputs.files_added || steps.get_file_changes.outputs.files_modified) }} 68 | run: yarn stylelint --config ./.stylelintrc --ignore-path ./.stylelintignore --allow-empty-input ${{ steps.get_file_changes.outputs.files_added }} ${{ steps.get_file_changes.outputs.files_modified }} --fix 69 | 70 | - name: Commit changes 71 | if: always() 72 | uses: stefanzweifel/git-auto-commit-action@v4.1.2 73 | with: 74 | commit_message: Apply formatting changes 75 | # branch: ${{ github.head_ref }} 76 | 77 | - name: Slack Notification 78 | uses: 8398a7/action-slack@v3.8.0 79 | if: failure() 80 | with: 81 | status: custom 82 | fields: workflow,job,commit,repo,ref,author,took 83 | custom_payload: | 84 | { 85 | username: 'React-Apps-CI', 86 | icon_emoji: ':react:', 87 | author_name: 'Linting Test', 88 | attachments: [{ 89 | color: '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning', 90 | text: `CI Task: ${process.env.AS_WORKFLOW}\ncommit: (${process.env.AS_COMMIT}) ${{ github.event_name }} ${{ job.status }}. Initiated by ${process.env.AS_AUTHOR} in ${process.env.AS_TOOK}`, 91 | }] 92 | } 93 | env: 94 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 95 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 96 | MATRIX_CONTEXT: ${{ toJson(matrix) }} 97 | 98 | -------------------------------------------------------------------------------- /cypress/apps/test_example/view/selectors/weapons.ts: -------------------------------------------------------------------------------- 1 | import { TPersonTypes } from '../../interfaces/IController' 2 | import { TWeaponTypes } from '../../interfaces/IView' 3 | 4 | export const mainWeapon = (userType: TPersonTypes) => cy.get(`#${userType} #weapon_main`) 5 | export const secondaryWeapon = (userType: TPersonTypes) => cy.get(`#${userType} #weapon_second`) 6 | export const meleeWeapon = (userType: TPersonTypes) => cy.get(`#${userType} #weapon_melee`) 7 | export const tempWeapon = (userType: TPersonTypes) => cy.get(`#${userType} #weapon_temp`) 8 | export const fistsWeapon = (userType: TPersonTypes) => cy.get(`#${userType} #weapon_fists`) 9 | export const bootsWeapon = (userType: TPersonTypes) => cy.get(`#${userType} #weapon_boots`) 10 | 11 | export const weaponTopWrap = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 12 | cy.get(`#${userType} #weapon_${weaponType} [class^="top_"]`) 13 | ) 14 | 15 | export const weaponImageWrap = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 16 | cy.get(`#${userType} #weapon_${weaponType} [class^="weaponImage"]`) 17 | ) 18 | 19 | export const weaponBottomWrap = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 20 | cy.get(`#${userType} #weapon_${weaponType} [class^="bottom_"]`) 21 | ) 22 | 23 | export const weaponTopWrapPropsLeft = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 24 | weaponTopWrap(userType, weaponType).find('[class^="props"]').eq(0) 25 | ) 26 | 27 | export const weaponTopWrapPropsLeftIconFirst = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 28 | weaponTopWrapPropsLeft(userType, weaponType).find('[class^="bonus"]').eq(0) 29 | ) 30 | 31 | export const weaponTopWrapPropsLeftIconLast = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 32 | weaponTopWrapPropsLeft(userType, weaponType).find('[class^="bonus"]').eq(1) 33 | ) 34 | 35 | export const weaponTopWrapLabel = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 36 | weaponTopWrap(userType, weaponType).find('[id^="label"]') 37 | ) 38 | 39 | export const weaponTopWrapPropsRight = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 40 | weaponTopWrap(userType, weaponType).find('[class^="props"]').eq(1) 41 | ) 42 | 43 | export const weaponTopWrapPropsRightIconFirst = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 44 | weaponTopWrapPropsRight(userType, weaponType).find('[class^="bonus"]').eq(0) 45 | ) 46 | 47 | export const weaponTopWrapPropsRightIconLast = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 48 | weaponTopWrapPropsRight(userType, weaponType).find('[class^="bonus"]').eq(1) 49 | ) 50 | 51 | export const weaponImage = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 52 | weaponImageWrap(userType, weaponType).find('img') 53 | ) 54 | 55 | export const weaponBottomWrapPropsLeft = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 56 | weaponBottomWrap(userType, weaponType).find('[class^="props"]').eq(0) 57 | ) 58 | 59 | export const weaponBottomWrapPropsLeftIcon = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 60 | weaponBottomWrapPropsLeft(userType, weaponType).find('[class^="bonus-attachment"]') 61 | ) 62 | 63 | export const weaponBottomWrapPropsLeftDamage = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 64 | weaponBottomWrapPropsLeft(userType, weaponType).find('[id^="damage-value"]') 65 | ) 66 | 67 | export const weaponBottomWrapLabelWrap = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 68 | weaponBottomWrap(userType, weaponType).find('[class^="bottomMarker"]') 69 | ) 70 | 71 | export const weaponBottomWrapLabel = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 72 | weaponBottomWrapLabelWrap(userType, weaponType).find('span') 73 | ) 74 | 75 | export const weaponBottomWrapLabelEternity = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 76 | weaponBottomWrapLabelWrap(userType, weaponType).find('span[class^="eternity"]') 77 | ) 78 | 79 | export const weaponBottomWrapPropsRight = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 80 | weaponBottomWrap(userType, weaponType).find('[class^="props"]').eq(1) 81 | ) 82 | 83 | export const weaponBottomWrapPropsRightIcon = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 84 | weaponBottomWrapPropsRight(userType, weaponType).find('i[class^="bonus-attachment"]') 85 | ) 86 | 87 | export const weaponBottomWrapPropsRightDamage = (userType: TPersonTypes, weaponType: TWeaponTypes) => ( 88 | weaponBottomWrapPropsRight(userType, weaponType).find('span[id^="accuracy"]') 89 | ) 90 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Code of Conduct 20 | 21 | ### Our Pledge 22 | 23 | In the interest of fostering an open and welcoming environment, we as 24 | contributors and maintainers pledge to making participation in our project and 25 | our community a harassment-free experience for everyone, regardless of age, body 26 | size, disability, ethnicity, gender identity and expression, level of experience, 27 | nationality, personal appearance, race, religion, or sexual identity and 28 | orientation. 29 | 30 | ### Our Standards 31 | 32 | Examples of behavior that contributes to creating a positive environment 33 | include: 34 | 35 | * Using welcoming and inclusive language 36 | * Being respectful of differing viewpoints and experiences 37 | * Gracefully accepting constructive criticism 38 | * Focusing on what is best for the community 39 | * Showing empathy towards other community members 40 | 41 | Examples of unacceptable behavior by participants include: 42 | 43 | * The use of sexualized language or imagery and unwelcome sexual attention or 44 | advances 45 | * Trolling, insulting/derogatory comments, and personal or political attacks 46 | * Public or private harassment 47 | * Publishing others' private information, such as a physical or electronic 48 | address, without explicit permission 49 | * Other conduct which could reasonably be considered inappropriate in a 50 | professional setting 51 | 52 | ### Our Responsibilities 53 | 54 | Project maintainers are responsible for clarifying the standards of acceptable 55 | behavior and are expected to take appropriate and fair corrective action in 56 | response to any instances of unacceptable behavior. 57 | 58 | Project maintainers have the right and responsibility to remove, edit, or 59 | reject comments, commits, code, wiki edits, issues, and other contributions 60 | that are not aligned to this Code of Conduct, or to ban temporarily or 61 | permanently any contributor for other behaviors that they deem inappropriate, 62 | threatening, offensive, or harmful. 63 | 64 | ### Scope 65 | 66 | This Code of Conduct applies both within project spaces and in public spaces 67 | when an individual is representing the project or its community. Examples of 68 | representing a project or community include using an official project e-mail 69 | address, posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. Representation of a project may be 71 | further defined and clarified by project maintainers. 72 | 73 | ### Enforcement 74 | 75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 76 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 77 | complaints will be reviewed and investigated and will result in a response that 78 | is deemed necessary and appropriate to the circumstances. The project team is 79 | obligated to maintain confidentiality with regard to the reporter of an incident. 80 | Further details of specific enforcement policies may be posted separately. 81 | 82 | Project maintainers who do not follow or enforce the Code of Conduct in good 83 | faith may face temporary or permanent repercussions as determined by other 84 | members of the project's leadership. 85 | 86 | ### Attribution 87 | 88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 89 | available at [http://contributor-covenant.org/version/1/4][version] 90 | 91 | [homepage]: http://contributor-covenant.org 92 | [version]: http://contributor-covenant.org/version/1/4/ 93 | -------------------------------------------------------------------------------- /cypress/examples/utilities.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-nested-callbacks */ 2 | context('Utilities', () => { 3 | beforeEach(() => { 4 | cy.visit('https://example.cypress.io/utilities') 5 | }) 6 | 7 | it('Cypress._ - call a lodash method', () => { 8 | // https://on.cypress.io/_ 9 | cy.request('https://jsonplaceholder.cypress.io/users') 10 | .then((response) => { 11 | const ids = Cypress._.chain(response.body).map('id').take(3).value() 12 | 13 | expect(ids).to.deep.eq([1, 2, 3]) 14 | }) 15 | }) 16 | 17 | it('Cypress.$ - call a jQuery method', () => { 18 | // https://on.cypress.io/$ 19 | const $li = Cypress.$('.utility-jquery li:first') 20 | 21 | cy.wrap($li) 22 | .should('not.have.class', 'active') 23 | .click() 24 | .should('have.class', 'active') 25 | }) 26 | 27 | it('Cypress.Blob - blob utilities and base64 string conversion', () => { 28 | // https://on.cypress.io/blob 29 | cy.get('.utility-blob').then(($div) => ( 30 | // https://github.com/nolanlawson/blob-util#imgSrcToDataURL 31 | // get the dataUrl string for the javascript-logo 32 | Cypress.Blob.imgSrcToDataURL('https://example.cypress.io/assets/img/javascript-logo.png', undefined, 'anonymous') 33 | .then((dataUrl) => { 34 | // create an element and set its src to the dataUrl 35 | const img = Cypress.$('', { src: dataUrl }) 36 | 37 | // need to explicitly return cy here since we are initially returning 38 | // the Cypress.Blob.imgSrcToDataURL promise to our test 39 | // append the image 40 | $div.append(img) 41 | 42 | cy.get('.utility-blob img').click().should('have.attr', 'src', dataUrl) 43 | }))) 44 | }) 45 | 46 | it('Cypress.minimatch - test out glob patterns against strings', () => { 47 | // https://on.cypress.io/minimatch 48 | let matching = Cypress.minimatch('/users/1/comments', '/users/*/comments', { 49 | matchBase: true 50 | }) 51 | 52 | expect(matching).equal(true) 53 | 54 | matching = Cypress.minimatch('/users/1/comments/2', '/users/*/comments', { 55 | matchBase: true 56 | }) 57 | expect(matching).equal(false) 58 | 59 | // ** matches against all downstream path segments 60 | matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/**', { 61 | matchBase: true 62 | }) 63 | expect(matching).equal(true) 64 | 65 | // whereas * matches only the next path segment 66 | 67 | matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/*', { 68 | matchBase: false 69 | }) 70 | expect(matching).equal(false) 71 | }) 72 | 73 | 74 | it('Cypress.moment() - format or parse dates using a moment method', () => { 75 | // https://on.cypress.io/moment 76 | const time = Cypress.moment('2014-04-25T19:38:53.196Z').utc().format('h:mm A') 77 | 78 | expect(time).to.be.a('string') 79 | 80 | cy.get('.utility-moment').contains('3:38 PM') 81 | .should('have.class', 'badge') 82 | 83 | // the time in the element should be between 3pm and 5pm 84 | const start = Cypress.moment('3:00 PM', 'LT') 85 | const end = Cypress.moment('5:00 PM', 'LT') 86 | 87 | cy.get('.utility-moment .badge') 88 | .should(($el) => { 89 | // parse American time like "3:38 PM" 90 | const m = Cypress.moment($el.text().trim(), 'LT') 91 | 92 | // eslint-disable-next-line no-unused-expressions 93 | expect(m.isBetween(start, end)).to.be.true 94 | }) 95 | }) 96 | 97 | 98 | it('Cypress.Promise - instantiate a bluebird promise', () => { 99 | // https://on.cypress.io/promise 100 | let waited = false 101 | 102 | /** 103 | * @return Bluebird 104 | */ 105 | // eslint-disable-next-line prefer-arrow/prefer-arrow-functions 106 | function waitOneSecond() { 107 | // return a promise that resolves after 1 second 108 | // @ts-ignore TS2351 (new Cypress.Promise) 109 | return new Cypress.Promise((resolve, _reject) => { 110 | setTimeout(() => { 111 | // set waited to true 112 | waited = true 113 | 114 | // resolve with 'foo' string 115 | resolve('foo') 116 | }, 1000) 117 | }) 118 | } 119 | 120 | cy.then(() => ( 121 | // return a promise to cy.then() that 122 | // is awaited until it resolves 123 | // @ts-ignore TS7006 124 | waitOneSecond().then((str) => { 125 | expect(str).equal('foo') 126 | expect(waited).equal(true) 127 | }))) 128 | }) 129 | }) 130 | --------------------------------------------------------------------------------