5 Commits

10 changed files with 229 additions and 10 deletions

17
api/settings.php Normal file
View File

@ -0,0 +1,17 @@
<?php
require 'Database.php';
$conn = Database::getInstance()->getConnection();
if (isset($_POST['action'])) {
$action = $_POST['action'];
switch ($action) {
case "isPasswordNeeded":
echo '{"password": true}';
break;
case "checkPassword":
break;
}
}

View File

@ -7,11 +7,13 @@ import RandomPage from "./pages/RandomPage/RandomPage";
import 'bootstrap/dist/css/bootstrap.min.css';
import SettingsPage from "./pages/SettingsPage/SettingsPage";
import CategoryPage from "./pages/CategoryPage/CategoryPage";
import {Spinner} from "react-bootstrap";
import LoginPage from "./pages/LoginPage/LoginPage";
class App extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {page: "default"};
this.state = {page: "unverified"};
// bind this to the method for being able to call methods such as this.setstate
this.showVideo = this.showVideo.bind(this);
@ -42,12 +44,42 @@ class App extends React.Component {
} else if (this.state.page === "lastpage") {
// return back to last page
page = this.mypage;
} else if (this.state.page === "loginpage") {
// return back to last page
page = <LoginPage/>;
} else if (this.state.page === "unverified") {
// return back to last page
page =
<div className='loadSpinner'>
<Spinner style={{marginLeft: "40px", marginBottom: "20px"}} animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner>
<div>Content loading...</div>
</div>;
} else {
page = <div>unimplemented yet!</div>;
}
return (page);
}
componentDidMount() {
const updateRequest = new FormData();
updateRequest.append("action", "isPasswordNeeded");
fetch('/api/settings.php', {method: 'POST', body: updateRequest})
.then((response) => response.json()
.then((result) => {
if (result.password === false) {
this.setState({page: "default"});
} else {
this.setState({page: "loginpage"});
}
}))
.catch(() => {
console.log("no connection to backend");
});
}
render() {
return (
<div className="App">

View File

@ -17,4 +17,62 @@ describe('<App/>', function () {
const wrapper = shallow(<App/>);
expect(wrapper.find('nav').find('li')).toHaveLength(4);
});
it('simulate video view change ', function () {
const wrapper = shallow(<App/>);
wrapper.instance().showVideo(<div id='testit'></div>);
expect(wrapper.find("#testit")).toHaveLength(1);
});
it('test hide video again', function () {
const wrapper = shallow(<App/>);
wrapper.instance().showVideo(<div id='testit'></div>);
expect(wrapper.find("#testit")).toHaveLength(1);
wrapper.instance().hideVideo();
expect(wrapper.find("HomePage")).toHaveLength(1);
});
it('test fallback to last loaded page', function () {
const wrapper = shallow(<App/>);
wrapper.find(".nav-link").findWhere(t => t.text() === "Random Video" && t.type() === "div").simulate("click");
wrapper.instance().showVideo(<div id='testit'></div>);
expect(wrapper.find("#testit")).toHaveLength(1);
wrapper.instance().hideVideo();
expect(wrapper.find("RandomPage")).toHaveLength(1);
});
it('test home click', function () {
const wrapper = shallow(<App/>);
wrapper.setState({page: "wrongvalue"});
expect(wrapper.find("HomePage")).toHaveLength(0);
wrapper.find(".nav-link").findWhere(t => t.text() === "Home" && t.type() === "div").simulate("click");
expect(wrapper.find("HomePage")).toHaveLength(1);
});
it('test category click', function () {
const wrapper = shallow(<App/>);
expect(wrapper.find("CategoryPage")).toHaveLength(0);
wrapper.find(".nav-link").findWhere(t => t.text() === "Categories" && t.type() === "div").simulate("click");
expect(wrapper.find("CategoryPage")).toHaveLength(1);
});
it('test settings click', function () {
const wrapper = shallow(<App/>);
expect(wrapper.find("SettingsPage")).toHaveLength(0);
wrapper.find(".nav-link").findWhere(t => t.text() === "Settings" && t.type() === "div").simulate("click");
expect(wrapper.find("SettingsPage")).toHaveLength(1);
});
});

View File

@ -10,3 +10,8 @@
.nav-link:hover {
color: rgba(255, 255, 255, 1);
}
.loadSpinner {
margin-top: 200px;
margin-left: 50%;
}

View File

@ -39,7 +39,7 @@ describe('<CategoryPage/>', function () {
let message;
global.console.log = jest.fn((m) => {
message = m;
})
});
const wrapper = shallow(<CategoryPage/>);

View File

@ -8,7 +8,7 @@
width: 10%;
}
.searchform{
.searchform {
margin-top: 25px;
float: right;
}

View File

@ -124,16 +124,16 @@ class HomePage extends React.Component {
<div className='pageheader'>
<span className='pageheadertitle'>Home Page</span>
<span className='pageheadersubtitle'>{this.state.tag} Videos - {this.state.selectionnr}</span>
<form className="form-inline searchform" action="#">
<form className="form-inline searchform" onSubmit={(e) => {
e.preventDefault();
this.searchVideos(this.keyword);
}}>
<input data-testid='searchtextfield' className="form-control mr-sm-2"
type="text" placeholder="Search"
onChange={(e) => {
this.keyword = e.target.value
}}/>
<button data-testid='searchbtnsubmit' className="btn btn-success" type="submit" onClick={() => {
this.searchVideos(this.keyword)
}}>Search
</button>
<button data-testid='searchbtnsubmit' className="btn btn-success" type="submit">Search</button>
</form>
<hr/>
</div>

View File

@ -11,6 +11,11 @@ function prepareFetchApi(response) {
return (jest.fn().mockImplementation(() => mockFetchPromise));
}
function prepareFailingFetchApi() {
const mockFetchPromise = Promise.reject("myreason");
return (jest.fn().mockImplementation(() => mockFetchPromise));
}
describe('<HomePage/>', function () {
it('renders without crashing ', function () {
const wrapper = shallow(<HomePage/>);
@ -58,11 +63,11 @@ describe('<HomePage/>', function () {
});
it('test search field', done => {
global.fetch = prepareFetchApi([{},{}]);
global.fetch = prepareFetchApi([{}, {}]);
const wrapper = shallow(<HomePage/>);
wrapper.find('[data-testid="searchtextfield"]').simulate('change', { target: { value: 'testvalue' } });
wrapper.find('[data-testid="searchtextfield"]').simulate('change', {target: {value: 'testvalue'}});
wrapper.find('[data-testid="searchbtnsubmit"]').simulate("click");
process.nextTick(() => {
@ -73,4 +78,43 @@ describe('<HomePage/>', function () {
done();
});
});
it('test form submit', done => {
global.fetch = prepareFetchApi([{}, {}]);
const wrapper = shallow(<HomePage/>);
const fakeEvent = {preventDefault: () => console.log('preventDefault')};
wrapper.find(".searchform").simulate('submit', fakeEvent);
expect(wrapper.state().selectionnr).toBe(0);
process.nextTick(() => {
// state to be set correctly with response
expect(wrapper.state().selectionnr).toBe(2);
global.fetch.mockClear();
done();
});
});
it('test no backend connection behaviour', done => {
// this test assumes a console.log within every connection fail
global.fetch = prepareFailingFetchApi();
let count = 0;
global.console.log = jest.fn((m) => {
count++;
});
const wrapper = shallow(<HomePage/>);
process.nextTick(() => {
// state to be set correctly with response
expect(global.fetch).toBeCalledTimes(2);
global.fetch.mockClear();
done();
});
});
});

View File

@ -0,0 +1,60 @@
import React from "react";
import {Form} from "react-bootstrap";
class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.props = props;
}
render() {
return (
<>
<div className='pageheader'>
<span className='pageheadertitle'>Login Page</span>
<span className='pageheadersubtitle'>type correct password!</span>
<hr/>
</div>
<div style={{marginLeft: "35%", width: "400px", marginTop: "100px"}}>
<Form.Group>
<Form.Label>Password</Form.Label>
<Form.Control id='passfield' type="password" placeholder="Enter Password" onChange={(v) => {
this.password = v.target.value
}}/>
<Form.Text className="text-muted">
You can disable/enable this feature on settingspage.
</Form.Text>
<hr/>
<button className='btn btn-success' type='submit' onClick={() => this.checkPassword()}>Submit
</button>
</Form.Group>
</div>
</>
);
}
checkPassword() {
const updateRequest = new FormData();
updateRequest.append("action", "checkPassword");
updateRequest.append("password", this.state.password);
fetch('/api/settings.php', {method: 'POST', body: updateRequest})
.then((response) => response.json()
.then((result) => {
if (result.correct) {
// todo 2020-06-18: call a callback to return back to right page
} else {
// error popup
}
}))
.catch(() => {
console.log("no connection to backend");
});
}
}
export default LoginPage;

View File

@ -34,7 +34,10 @@ class SettingsPage extends React.Component {
if (this.myinterval) {
clearInterval(this.myinterval);
}
// todo 2020-06-18: maybe not start on mount
this.myinterval = setInterval(this.updateStatus, 1000);
// todo 2020-06-18: fetch path data here
}
componentWillUnmount() {