add jest testing

This commit is contained in:
Sergey Krylov 2022-07-08 08:43:52 +05:00
parent c73008fe03
commit 21df4f3f78
14 changed files with 8347 additions and 380 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env", "@babel/preset-typescript"]
}

View File

@ -1,7 +1,7 @@
module.exports = { module.exports = {
root: true, root: true,
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'], plugins: ['@typescript-eslint', "jest"],
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
@ -27,6 +27,7 @@ module.exports = {
"@typescript-eslint/restrict-template-expressions": "off", "@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/unbound-method": "off", "@typescript-eslint/unbound-method": "off",
"@typescript-eslint/quotes": "warn", "@typescript-eslint/quotes": "warn",
"@typescript-eslint/no-unused-expressions": "off",
"arrow-parens": "off", "arrow-parens": "off",
"class-methods-use-this": "off", "class-methods-use-this": "off",
"func-names": "off", "func-names": "off",
@ -38,11 +39,14 @@ module.exports = {
"no-continue": "off", "no-continue": "off",
"no-plusplus": "off", "no-plusplus": "off",
"object-curly-newline": "off", "object-curly-newline": "off",
"no-alert": "off",
"no-restricted-globals": "off",
}, },
env: { env: {
browser: true, browser: true,
node: true, node: true,
es6: true, es6: true,
jest: true,
}, },
ignorePatterns: ['.eslintrc.js', 'deploy-config.js'] ignorePatterns: ['.eslintrc.js', 'deploy-config.js'],
}; };

8544
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,21 +3,28 @@
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.js",
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@types/happypack": "^5.0.2", "@types/happypack": "^5.0.2",
"@types/html-webpack-plugin": "^3.2.6", "@types/html-webpack-plugin": "^3.2.6",
"@types/jest": "^28.1.4",
"@types/node": "^17.0.40", "@types/node": "^17.0.40",
"@types/sass-loader": "^8.0.3", "@types/sass-loader": "^8.0.3",
"@types/webpack": "^5.28.0", "@types/webpack": "^5.28.0",
"@typescript-eslint/eslint-plugin": "^5.27.1", "@typescript-eslint/eslint-plugin": "^5.27.1",
"@typescript-eslint/parser": "^5.27.1", "@typescript-eslint/parser": "^5.27.1",
"babel-jest": "^28.1.2",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.1", "css-loader": "^6.7.1",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"eslint": "^8.17.0", "eslint": "^8.17.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-airbnb-typescript": "^17.0.0",
"eslint-plugin-jest": "^26.5.3",
"eslint-webpack-plugin": "^3.1.1", "eslint-webpack-plugin": "^3.1.1",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"jest": "^28.1.2",
"jest-environment-jsdom": "^28.1.2",
"mini-css-extract-plugin": "^2.6.0", "mini-css-extract-plugin": "^2.6.0",
"sass": "^1.52.2", "sass": "^1.52.2",
"sass-loader": "^13.0.0", "sass-loader": "^13.0.0",
@ -38,7 +45,9 @@
"scripts": { "scripts": {
"start": "webpack serve --mode=development", "start": "webpack serve --mode=development",
"build": "webpack --mode=production", "build": "webpack --mode=production",
"deploy": "node deploy-config" "deploy": "node deploy-config",
"test": "npx jest",
"test-watch": "npx jest --watch"
}, },
"keywords": [], "keywords": [],
"author": "ksv741", "author": "ksv741",
@ -52,5 +61,10 @@
"module": "true", "module": "true",
"dependencies": { "dependencies": {
"normalize.css": "^8.0.1" "normalize.css": "^8.0.1"
},
"jest": {
"transform": {
"\\.[jt]sx?$": "babel-jest"
}
} }
} }

View File

@ -1,4 +1,4 @@
import { Store } from 'core/createStore'; import { Store } from 'core/store/createStore';
import { $ } from 'core/dom'; import { $ } from 'core/dom';
import { Emitter } from 'core/Emitter'; import { Emitter } from 'core/Emitter';
import { ExcelComponent } from 'core/ExcelComponent'; import { ExcelComponent } from 'core/ExcelComponent';

View File

@ -1,8 +1,8 @@
import { ActionType } from 'redux/types'; import { ActionType } from 'redux/types';
import { Store } from './createStore'; import { Store } from 'core/store/createStore';
import { Dom } from './dom'; import { Dom } from 'core/dom';
import { DomListener } from './DomListener'; import { DomListener } from 'core/DomListener';
import { Emitter } from './Emitter'; import { Emitter } from 'core/Emitter';
interface ExcelComponentClass { interface ExcelComponentClass {
toHTML: () => string; toHTML: () => string;

View File

@ -1,5 +1,5 @@
import { StateType } from 'redux/types'; import { StateType } from 'redux/types';
import { Store } from './createStore'; import { Store } from './store/createStore';
import { isEqual } from './utils'; import { isEqual } from './utils';
export class StoreSubscriber { export class StoreSubscriber {

View File

@ -1,6 +1,6 @@
import { initialStyleState } from '../constants'; import { initialStyleState } from '../constants';
type SelectorType = string | HTMLElement; export type SelectorType = string | HTMLElement;
export interface DomClass { export interface DomClass {
html(html?: string): string | DomClass; html(html?: string): string | DomClass;

View File

@ -0,0 +1,38 @@
/**
* @jest-environment jsdom
*/
import { Router } from './router';
import { Page } from '../Page';
class DashboardPage extends Page {
getRoot() {
const root = document.createElement('div');
root.innerHTML = 'dashboard';
return root;
}
}
class ExcelPage extends Page {}
describe('Router:', () => {
let router;
let $root;
beforeEach(() => {
$root = document.createElement('div');
router = new Router($root, {
excel: ExcelPage,
dashboard: DashboardPage,
});
});
test('should be defined', () => {
expect(router).toBeDefined();
});
test('should render Dasboard Page', () => {
router.changePageHandler();
expect($root.innerHTML).toBe('<div>dashboard</div>');
});
});

View File

@ -1,17 +1,19 @@
import { DashboardPage } from 'pages/DashboardPage'; import { DashboardPage } from 'pages/DashboardPage';
import { ExcelPage } from 'pages/ExcelPage'; import { ExcelPage } from 'pages/ExcelPage';
import { $, Dom } from 'core/dom'; import { $, Dom, SelectorType } from 'core/dom';
import { ActiveRoute } from './ActiveRoute'; import { ActiveRoute } from './ActiveRoute';
export class Router { type RoutesType = {
private $placeholder: Dom;
private routes: {
dashboard: DashboardPage dashboard: DashboardPage
excel: ExcelPage excel: ExcelPage
}; };
private page: DashboardPage | ExcelPage;
constructor(selector: string, routes: any) { export class Router {
private $placeholder: Dom;
private routes: RoutesType;
private page: DashboardPage | ExcelPage | null;
constructor(selector: SelectorType, routes: RoutesType) {
if (!selector) throw new Error('Selector not provided'); if (!selector) throw new Error('Selector not provided');
this.$placeholder = $(selector); this.$placeholder = $(selector);
@ -47,8 +49,8 @@ export class Router {
// @ts-ignore // @ts-ignore
this.page = new Page(ActiveRoute.param); this.page = new Page(ActiveRoute.param);
this.$placeholder.append(this.page.getRoot()); this.$placeholder.append(this.page?.getRoot());
this.page.afterRender(); this.page?.afterRender();
} }
destroy() { destroy() {

View File

@ -0,0 +1,77 @@
import { Store } from './createStore';
const initialState = {
count: 0,
};
const reducer = (state, action) => {
if (action.type === 'ADD') {
return { ...state, count: state.count + 1 };
}
return state;
};
describe('Create store', () => {
let store;
let handler;
beforeEach(() => {
store = new Store(reducer, initialState);
handler = jest.fn();
});
test('should return store object', () => {
expect(store).toBeDefined();
expect(store.dispatch).toBeDefined();
expect(store.subscribe).toBeDefined();
expect(store.getState).not.toBeUndefined();
});
test('should return object as a state', () => {
expect(store.getState()).toBeInstanceOf(Object);
});
test('should return default state', () => {
expect(store.getState()).toEqual(initialState);
});
test('should change state if actions exist', () => {
store.dispatch({ type: 'ADD' });
expect(store.getState().count).toBe(1);
});
test("should NOT change state if actions don't exist", () => {
store.dispatch({ type: 'NOT_EXISTING_TYPE' });
expect(store.getState().count).toBe(0);
});
test('should call subscriber', () => {
store.subscribe(handler);
store.dispatch({ type: 'ADD' });
expect(handler).toHaveBeenCalled();
expect(handler).toHaveBeenCalledWith(store.getState());
});
test('should NOT call sub if unsubscribe', () => {
const unsub = store.subscribe(handler);
unsub.unsubscribe();
store.dispatch({ type: 'ADD' });
expect(handler).not.toHaveBeenCalled();
});
test('should dispatch in async way', () => {
return new Promise(resolve => {
setTimeout(() => {
store.dispatch({ type: 'ADD' });
}, 500);
setTimeout(() => {
expect(store.getState().count).toBe(1);
resolve();
}, 1000);
});
});
});

View File

@ -1,4 +1,4 @@
import { ActionType, ReducerType, StateType, SubscribeType } from '../redux/types'; import { ActionType, ReducerType, StateType, SubscribeType } from 'redux/types';
export class Store { export class Store {
state: StateType; state: StateType;

View File

@ -3,7 +3,7 @@ import { Formula } from 'components/formula/Formula';
import { Header } from 'components/header/Header'; import { Header } from 'components/header/Header';
import { Table } from 'components/table/Table'; import { Table } from 'components/table/Table';
import { Toolbar } from 'components/toolbar/Toolbar'; import { Toolbar } from 'components/toolbar/Toolbar';
import { Store } from 'core/createStore'; import { Store } from 'core/store/createStore';
import { Page } from 'core/Page'; import { Page } from 'core/Page';
import { debounce, storage } from 'core/utils'; import { debounce, storage } from 'core/utils';
import { rootReducer } from 'redux/rootReducer'; import { rootReducer } from 'redux/rootReducer';

View File

@ -19,7 +19,8 @@
"compilerOptions": { "compilerOptions": {
"module": "CommonJS", "module": "CommonJS",
"strictPropertyInitialization": true, "strictPropertyInitialization": true,
"sourceMap": true "sourceMap": true,
"exclude": ["node_modules"]
} }
}, },
"exclude": ["node_modules"], "exclude": ["node_modules"],