Commit 6c6d8ee4 authored by Bogdan Bodnar's avatar Bogdan Bodnar

Merge branch '3-testing' into 'dev'

Resolve "Testing"

Closes #3

See merge request !1
parents 64206ec4 5b414ee6
......@@ -67,6 +67,7 @@ pip-log.txt
pip-delete-this-directory.txt
## Unit test / coverage reports
coverage/
htmlcov/
.tox/
.coverage
......
......@@ -3,15 +3,19 @@
# This is free software, licensed under the GNU General Public License v3.
# See /LICENSE for more information.
.PHONY: all install run run-js clean
.PHONY: all install run run-js test test-web clean
SHELL=/bin/bash
PYTHON=python3.6
PYTHON=python3.7
FLASK=flask
VENV_NAME?=venv
VENV_BIN=$(shell pwd)/${VENV_NAME}/bin
VENV_ACTIVATE=. ${VENV_BIN}/activate
JS_DIR=./js
export FLASK_APP=reforis
export FLASK_APP=reforis:create_app('dev')
export FLASK_ENV=development
all:
......@@ -41,11 +45,22 @@ install-js: js/package.json
run:
${FLASK} run --host="0.0.0.0" --port=81
run-js:
watch-js:
cd ${JS_DIR};\
npx watchify ./src/app.js -o ../reforis/static/js/app.min.js \
-t [ babelify --presets [ @babel/preset-env @babel/preset-react ] --plugins [ @babel/plugin-proposal-class-properties ] ]
test-venv: $(VENV_NAME)/bin/activate
$(VENV_NAME)/bin/activate: setup.py
test -d $(VENV_NAME) || virtualenv -p python3.7 $(VENV_NAME)
${VENV_BIN}/${PYTHON} -m pip install -e .[devel]
touch $(VENV_NAME)/bin/activate
test: test-js test-web
test-js:
cd js; npm test
test-web: test-venv
${VENV_BIN}/${PYTHON} -m pytest -vv tests
create-messages:
pybabel extract -F babel.cfg -o ./reforis/translations/messages.pot .
......
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
// https://jestjs.io/docs/en/configuration.html
module.exports = {
clearMocks: true,
collectCoverageFrom: ['src/**/*.{js,jsx}'],
coverageDirectory: 'coverage',
setupFiles: ['<rootDir>/src/testUtils/setupGlobals.js'],
testPathIgnorePatterns: ['/node_modules/','/__fixtures__/'],
verbose: false,
setupFilesAfterEnv: ['react-testing-library/cleanup-after-each',]
};
This diff is collapsed.
{
"name": "foris_wifi",
"version": "1.0.0",
"description": "",
"name": "reforis_js",
"author": "CZ.NIC, z.s.p.o.",
"repository": {
"type": "git",
"url": "https://gitlab.labs.nic.cz/turris/reforis.git"
},
"license": "GPL-3.0",
"version": "0.0.1",
"description": "Turris routers web configuration interface.",
"main": "./src/app.js",
"dependencies": {
"gettext.js": "^0.7.0",
"immutability-helper": "^3.0.0",
"po2json": "^1.0.0-alpha",
"recompose": "^0.30.0",
"watchify": "^3.11.1"
"prop-types": "^15.7.2",
"react": "^16.9.0-alpha.0",
"react-dom": "^16.9.0-alpha.0",
"react-uid": "^2.2.0",
"recompose": "^0.30.0"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/plugin-proposal-class-properties": "^7.3.0",
"@babel/plugin-transform-runtime": "^7.4.3",
"@babel/preset-env": "^7.3.1",
"@babel/preset-react": "^7.0.0",
"babel-jest": "^24.7.1",
"babel-plugin-react-transform": "^3.0.0",
"babelify": "^10.0.0",
"gettext.js": "^0.7.0",
"immutability-helper": "^3.0.0",
"react": "^16.8.6",
"react-dom": "^16.7.0"
"jest": "^24.7.1",
"react-testing-library": "^6.1.2",
"watchify": "^3.11.1"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"browserify": {
"transform": [
[
"babelify",
{
"presets": [
"@babel/preset-env"
]
}
]
]
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage --colors"
}
}
......@@ -6,14 +6,17 @@
*/
import React from 'react';
import {render} from 'react-dom';
import WiFi from './wifi/Wifi';
import WiFi from './wifi/WiFi';
import WAN from './wan/WAN';
import LAN from './lan/LAN';
import NotificationsDropdown from './notifications/NotificationsDropdown';
import NotificationsCenter from './notifications/NotificationsCenter';
import ConnectionTest from './connectionTest/ConnectionTest';
import webSockets from './webSockets';
const ws = new webSockets();
window.addEventListener('load', () => {
const apps = [
......@@ -28,6 +31,6 @@ window.addEventListener('load', () => {
for (let app of apps) {
const appElm = document.getElementById(app.id);
if (!appElm) continue;
render(<app.component/>, appElm);
render(<app.component ws={ws}/>, appElm);
}
}, false);
......@@ -6,12 +6,22 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
const OFFSET = 8;
const SIZE = 3;
const SIZE_CLASS = ' offset-lg-' + OFFSET + ' col-lg-' + SIZE;
const SIZE_CLASS_SM = ' col-sm-12';
Button.propTypes = {
className: PropTypes.string,
loading: PropTypes.bool,
children: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
]).isRequired
};
export default function Button({className, loading, children, ...props}) {
className = className ? 'btn ' + className : 'btn btn-primary ';
className += SIZE_CLASS + SIZE_CLASS_SM;
......@@ -22,4 +32,4 @@ export default function Button({className, loading, children, ...props}) {
return <button className={className} {...props}>
{span} {span ? ' ' : null} {children}
</button>;
}
}
\ No newline at end of file
......@@ -6,23 +6,29 @@
*/
import React from 'react';
import {LABEL_SIZE,FIELD_SIZE} from './constants';
import PropTypes from 'prop-types';
import {useUID} from 'react-uid';
export default function CheckBox({name, id, label, onChange, checked, helpText, ...props}) {
import {LABEL_SIZE, FIELD_SIZE} from './constants';
CheckBox.propTypes = {
label: PropTypes.string.isRequired,
helpText: PropTypes.string,
};
export default function CheckBox({label, helpText, ...props}) {
const uid = useUID();
return <div className='form-group row'>
<div className={'form-label col-sm-' + LABEL_SIZE}>
<label className='form-label' htmlFor={id}>{label}</label>
<label className='form-label' htmlFor={uid}>{label}</label>
</div>
<div className={'form-check col-sm-' + FIELD_SIZE}>
<input
type='checkbox'
name={name}
id={id}
onChange={onChange}
checked={checked}
id={uid}
{...props}
/>
<small className="form-text text-muted">{helpText}</small>
</div>
</div>;
}
}
\ No newline at end of file
......@@ -6,22 +6,20 @@
*/
import React from 'react';
import {useUID} from 'react-uid';
import {LABEL_SIZE, FIELD_SIZE} from './constants';
const Input = type => ({name, id, label, onChange, value, placeholder, helpText, error, ...props}) => {
const Input = type => ({label, helpText, error, ...props}) => {
const uid = useUID();
return <div className='form-group row'>
<label className={'form-control-label col-sm-' + LABEL_SIZE} htmlFor={id}>{label}</label>
<label className={'form-control-label col-sm-' + LABEL_SIZE} htmlFor={uid}>{label}</label>
<div className={'col-sm-' + FIELD_SIZE}>
<input
className={'form-control ' + (!error ? '' : 'is-invalid')}
type={type}
name={name}
id={id}
onChange={onChange}
value={value}
placeholder={placeholder}
id={uid}
{...props}
/>
<div className='invalid-feedback'>{error}</div>
......
......@@ -8,6 +8,18 @@
import React from 'react';
import Input from './Input';
import PropTypes from 'prop-types';
const NumberInput = Input('number');
NumberInput.propTypes = {
label: PropTypes.string.isRequired,
error: PropTypes.string,
helpText: PropTypes.string,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
};
export default NumberInput;
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from 'react';
import {LABEL_SIZE, FIELD_SIZE} from './constants';
export default function Password({name, id, label, onChange, value, helpText, error, ...props}) {
return <div className='form-group row'>
<label className={'form-control-label col-sm-' + LABEL_SIZE} htmlFor={id}>{label}</label>
<div className={'col-sm-' + FIELD_SIZE}>
<input
className={'form-control ' + (!error ? '' : 'is-invalid')}
type='password'
name={name}
id={id}
onChange={onChange}
value={value}
{...props}
/>
<div className='invalid-feedback'>{error}</div>
<small className='form-text text-muted'>{helpText}</small>
</div>
</div>;
}
\ No newline at end of file
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from 'react';
import PropTypes from 'prop-types';
import Input from './Input';
const PasswordInput = Input('password');
PasswordInput.propTypes = {
label: PropTypes.string.isRequired,
error: PropTypes.string,
helpText: PropTypes.string,
};
export default PasswordInput;
......@@ -6,25 +6,40 @@
*/
import React from 'react';
import propTypes from 'prop-types';
import {useUID} from 'react-uid';
import {LABEL_SIZE, FIELD_SIZE} from './constants';
export default function RadioSet({name, id, label, choices, value, onChange, helpText, ...props}) {
RadioSet.propTypes = {
name: propTypes.string.isRequired,
label: propTypes.string.isRequired,
choices: propTypes.arrayOf(propTypes.shape({
label: propTypes.string.isRequired,
value: propTypes.oneOfType([propTypes.string, propTypes.number]).isRequired,
})).isRequired,
value: propTypes.string,
helpText: propTypes.string,
};
export default function RadioSet({name, label, choices, value, helpText, ...props}) {
const uid = useUID();
const radios = choices.map((choice, key) => {
return <Radio
id={name ? `${name}-${key}` : null}
id={`${name}-${key}`}
key={key}
name={name}
label={choice.label}
value={choice.value}
onChange={onChange}
checked={choice.value === value}
{...props}
/>;
});
return <div id={id} className='form-group row'>
return <div id={uid} className='form-group row'>
<div className={'form-label col-sm-' + LABEL_SIZE}>
<label className='form-label' htmlFor={id}>{label}</label>
<label className='form-label' htmlFor={uid}>{label}</label>
</div>
<div className={'col-sm-' + FIELD_SIZE}>
{radios}
......@@ -33,16 +48,17 @@ export default function RadioSet({name, id, label, choices, value, onChange, hel
</div>;
}
function Radio({name, id, label, value, onChange, checked = false, ...props}) {
Radio.propTypes = {
label: propTypes.string.isRequired,
id: propTypes.string.isRequired,
};
function Radio({label, id, ...props}) {
return <div className='form-check form-check-inline'>
<input
id={id}
className='form-check-input'
type='radio'
name={name}
id={id}
value={value}
onChange={onChange}
checked={checked}
{...props}
/>
<label className='form-check-label' htmlFor={id}>{label}</label>
......
......@@ -6,26 +6,35 @@
*/
import React from 'react';
import propTypes from 'prop-types';
import {useUID} from 'react-uid';
import {LABEL_SIZE, FIELD_SIZE} from './constants';
export default function Select({name, id, label, choices, value, onChange, disabled, helpText, ...props}) {
Select.propTypes = {
label: propTypes.string.isRequired,
choices: propTypes.object.isRequired,
value: propTypes.oneOfType([
propTypes.string,
propTypes.number,
]).isRequired,
helpText: propTypes.string,
};
export default function Select({label, choices, helpText, ...props}) {
const uid = useUID();
let options = [];
for (let key in choices)
if (choices.hasOwnProperty(key))
options.push(<option key={key} value={key}>{choices[key]}</option>);
const options = Object.keys(choices).map(
key => <option key={key} value={key}>{choices[key]}</option>
);
return <div className='form-group row'>
<label className={'form-control-label col-sm-' + LABEL_SIZE} htmlFor={id}>{label}</label>
<label className={'form-control-label col-sm-' + LABEL_SIZE} htmlFor={uid}>{label}</label>
<div className={'col-sm-' + FIELD_SIZE}>
<select
disabled={disabled}
className='form-control'
id={id}
name={name}
value={value}
onChange={onChange}
id={uid}
{...props}
>
{options}
......
......@@ -6,7 +6,17 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import Input from './Input';
const TextInput = Input('text');
TextInput.propTypes = {
label: PropTypes.string.isRequired,
error: PropTypes.string,
helpText: PropTypes.string,
};
export default TextInput;
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from 'react';
import {render} from 'react-testing-library'
import Button from '../Button'
describe('<Button />', () => {
it('Render button correctly', () => {
const {container} = render(<Button>Test Button</Button>);
expect(container.firstChild).toMatchSnapshot()
});
it('Render button with custom classes', () => {
const {container} = render(<Button className="one two three">Test Button</Button>)
expect(container.firstChild).toMatchSnapshot()
});
it('Render button with spinner', () => {
const {container} = render(<Button loading={true}>Test Button</Button>)
expect(container.firstChild).toMatchSnapshot()
});
});
\ No newline at end of file
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from 'react';
import {render} from 'react-testing-library'
import Checkbox from '../Checkbox'
describe('<Checkbox/>', () => {
it('Render checkbox', () => {
const {container} = render(
<Checkbox
label="Test label"
checked
helpText="Some help text"
onChange={()=>{}}
/>
);
expect(container.firstChild).toMatchSnapshot();
});
it('Render uncheked checkbox', () => {
const {container} = render(
<Checkbox
label="Test label"
helpText="Some help text"
/>
);
expect(container.firstChild).toMatchSnapshot();
});
});
\ No newline at end of file
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from 'react';
import {render} from 'react-testing-library'
import NumberInput from '../PasswordInput';
describe('<PasswordInput/>', () => {
it('Render number input', () => {
const {container} = render(
<NumberInput
label="Test label"
helpText="Some help text"
value={1123}
onChange={() => {
}}
/>
);
expect(container.firstChild).toMatchSnapshot();
});
});
\ No newline at end of file
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from 'react';
import {render} from 'react-testing-library'
import PasswordInput from '../PasswordInput';
describe('<PasswordInput/>', () => {
it('Render password input', () => {
const {container} = render(
<PasswordInput
label="Test label"
helpText="Some help text"
value="Some password"
onChange={() => {
}}
/>
);
expect(container.firstChild).toMatchSnapshot();
});
});
\ No newline at end of file
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from 'react';
import {render} from 'react-testing-library'
import RadioSet from '../RadioSet';
const TEST_CHOICES = [
{label: 'label', value: 'value'},
{label: 'another label', value: 'another value'},
{label: 'another one label', value: 'another on value'}
];
describe('<RadioSet/>', () => {
it('Render radio set', () => {
const {container} = render(
<RadioSet
name={'test_name'}
label='Radios set label'
value='value'
choices={TEST_CHOICES}
helpText={'Some help text'}
onChange={() => {
}}
/>
);
expect(container.firstChild).toMatchSnapshot();
});
});
\ No newline at end of file
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from 'react';
import {render} from 'react-testing-library';
import Select from '../Select';
const TEST_CHOICES = {
key1: 'value1',
key2: 'value2',
key3: 'value3',
};
describe('<Select/>', () => {
it('Render select', () => {
const {container} = render(
<Select
label='Test label'
value='value'
choices={TEST_CHOICES}
helpText={'Help text'}
onChange={() => {
}}
/>
);
expect(container.firstChild).toMatchSnapshot();
});
});
\ No newline at end of file