Merge branch 'SettingsPage' into 'master'

Settings page

See merge request lukas/openmediacenter!6
This commit is contained in:
2020-07-17 21:14:56 +00:00
18 changed files with 740 additions and 104 deletions

View File

@ -1,7 +1,6 @@
import {shallow, mount} from "enzyme";
import {mount, shallow} from "enzyme";
import React from "react";
import CategoryPage from "./CategoryPage";
import VideoContainer from "../../elements/VideoContainer/VideoContainer";
function prepareFetchApi(response) {
const mockJsonPromise = Promise.resolve(response);
@ -111,4 +110,21 @@ describe('<CategoryPage/>', function () {
expect(func).toBeCalledTimes(1);
});
it('test sidebar tag clicks', function () {
const func = jest.fn();
const wrapper = mount(<CategoryPage category="fullhd"/>);
wrapper.instance().loadTag = func;
console.log(wrapper.debug());
expect(func).toBeCalledTimes(0);
wrapper.find("SideBar").find("Tag").forEach(e => {
e.simulate("click");
})
expect(func).toBeCalledTimes(4);
});
});

View File

@ -0,0 +1,131 @@
import React from "react";
import {Button, Col, Form} from "react-bootstrap";
import style from "./GeneralSettings.module.css"
class GeneralSettings extends React.Component {
constructor(props) {
super(props);
this.state = {
passwordsupport: false,
tmdbsupport: null,
videopath: "",
tvshowpath: "",
mediacentername: "",
password: ""
};
}
componentDidMount() {
const updateRequest = new FormData();
updateRequest.append('action', 'loadGeneralSettings');
fetch('/api/Settings.php', {method: 'POST', body: updateRequest})
.then((response) => response.json()
.then((result) => {
console.log(result);
this.setState({
videopath: result.video_path,
tvshowpath: result.episode_path,
mediacentername: result.mediacenter_name,
password: result.password,
passwordsupport: result.passwordEnabled,
tmdbsupport: result.TMDB_grabbing
});
}));
}
render() {
return (
<>
<div className={style.GeneralForm}>
<Form data-testid='mainformsettings' onSubmit={(e) => {
e.preventDefault();
this.saveSettings();
}}>
<Form.Row>
<Form.Group as={Col} data-testid="videpathform">
<Form.Label>Video Path</Form.Label>
<Form.Control type="text" placeholder="/var/www/html/video" value={this.state.videopath}
onChange={(ee) => this.setState({videopath: ee.target.value})}/>
</Form.Group>
<Form.Group as={Col} data-testid="tvshowpath">
<Form.Label>TV Show Path</Form.Label>
<Form.Control type='text' placeholder="/var/www/html/tvshow"
value={this.state.tvshowpath}
onChange={(e) => this.setState({tvshowpath: e.target.value})}/>
</Form.Group>
</Form.Row>
<Form.Check
type="switch"
id="custom-switch"
data-testid='passwordswitch'
label="Enable Password support"
checked={this.state.passwordsupport}
onChange={() => {
this.setState({passwordsupport: !this.state.passwordsupport})
}}
/>
<Form.Check
type="switch"
id="custom-switch-2"
data-testid='tmdb-switch'
label="Enable TMDB video grabbing support"
checked={this.state.tmdbsupport}
onChange={() => {
this.setState({tmdbsupport: !this.state.tmdbsupport})
}}
/>
{this.state.passwordsupport ?
<Form.Group data-testid="passwordfield">
<Form.Label>Password</Form.Label>
<Form.Control type="password" placeholder="**********" value={this.state.password}
onChange={(e) => this.setState({password: e.target.value})}/>
</Form.Group> : null
}
<Form.Group className={style.mediacenternameform} data-testid="nameform">
<Form.Label>The name of the Mediacenter</Form.Label>
<Form.Control type="text" placeholder="Mediacentername" value={this.state.mediacentername}
onChange={(e) => this.setState({mediacentername: e.target.value})}/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</div>
</>
);
}
saveSettings() {
const updateRequest = new FormData();
updateRequest.append('action', 'saveGeneralSettings');
updateRequest.append('password', this.state.passwordsupport ? this.state.password : "-1");
updateRequest.append('videopath', this.state.videopath);
updateRequest.append('tvshowpath', this.state.tvshowpath);
updateRequest.append('mediacentername', this.state.mediacentername);
updateRequest.append("tmdbsupport", this.state.tmdbsupport);
fetch('/api/Settings.php', {method: 'POST', body: updateRequest})
.then((response) => response.json()
.then((result) => {
if (result.success) {
console.log("successfully saved settings");
// todo 2020-07-10: popup success
} else {
console.log("failed to save settings");
// todo 2020-07-10: popup error
}
}));
}
}
export default GeneralSettings;

View File

@ -0,0 +1,8 @@
.GeneralForm {
width: 60%;
}
.mediacenternameform {
margin-top: 25px;
width: 40%;
}

View File

@ -0,0 +1,110 @@
import {shallow} from "enzyme";
import React from "react";
import GeneralSettings from "./GeneralSettings";
function prepareFetchApi(response) {
const mockJsonPromise = Promise.resolve(response);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
return (jest.fn().mockImplementation(() => mockFetchPromise));
}
describe('<GeneralSettings/>', function () {
it('renders without crashing ', function () {
const wrapper = shallow(<GeneralSettings/>);
wrapper.unmount();
});
it('test password hide/show switchbutton', function () {
const wrapper = shallow(<GeneralSettings/>);
expect(wrapper.find("[data-testid='passwordfield']")).toHaveLength(0);
wrapper.find("FormCheck").findWhere(it => it.props().label === "Enable Password support").simulate("change");
expect(wrapper.find("[data-testid='passwordfield']")).toHaveLength(1);
});
it('test savesettings', done => {
const wrapper = shallow(<GeneralSettings/>);
global.fetch = prepareFetchApi({success: true});
expect(global.fetch).toBeCalledTimes(0);
const fakeEvent = {preventDefault: () => console.log('preventDefault')};
wrapper.find("[data-testid='mainformsettings']").simulate("submit", fakeEvent);
expect(global.fetch).toBeCalledTimes(1);
process.nextTick(() => {
// todo 2020-07-13: test popup of error success here
global.fetch.mockClear();
done();
});
});
it('test failing savesettings', done => {
const wrapper = shallow(<GeneralSettings/>);
global.fetch = prepareFetchApi({success: false});
expect(global.fetch).toBeCalledTimes(0);
const fakeEvent = {preventDefault: () => console.log('preventDefault')};
wrapper.find("[data-testid='mainformsettings']").simulate("submit", fakeEvent);
expect(global.fetch).toBeCalledTimes(1);
process.nextTick(() => {
// todo 2020-07-13: test error popup here!
global.fetch.mockClear();
done();
});
});
it('test videopath change event', function () {
const wrapper = shallow(<GeneralSettings/>);
expect(wrapper.state().videopath).not.toBe("test");
const event = {target: {name: "pollName", value: "test"}};
wrapper.find("[data-testid='videpathform']").find("FormControl").simulate("change", event);
expect(wrapper.state().videopath).toBe("test");
});
it('test tvshowpath change event', function () {
const wrapper = shallow(<GeneralSettings/>);
const event = {target: {name: "pollName", value: "test"}};
expect(wrapper.state().tvshowpath).not.toBe("test");
wrapper.find("[data-testid='tvshowpath']").find("FormControl").simulate("change", event);
expect(wrapper.state().tvshowpath).toBe("test");
});
it('test mediacentername-form change event', function () {
const wrapper = shallow(<GeneralSettings/>);
const event = {target: {name: "pollName", value: "test"}};
expect(wrapper.state().mediacentername).not.toBe("test");
wrapper.find("[data-testid='nameform']").find("FormControl").simulate("change", event);
expect(wrapper.state().mediacentername).toBe("test");
});
it('test password-form change event', function () {
const wrapper = shallow(<GeneralSettings/>);
wrapper.setState({passwordsupport: true});
const event = {target: {name: "pollName", value: "test"}};
expect(wrapper.state().password).not.toBe("test");
wrapper.find("[data-testid='passwordfield']").find("FormControl").simulate("change", event);
expect(wrapper.state().password).toBe("test");
});
it('test tmdbsupport change event', function () {
const wrapper = shallow(<GeneralSettings/>);
wrapper.setState({tmdbsupport: true});
expect(wrapper.state().tmdbsupport).toBe(true);
wrapper.find("[data-testid='tmdb-switch']").simulate("change");
expect(wrapper.state().tmdbsupport).toBe(false);
});
});

View File

@ -0,0 +1,89 @@
import React from "react";
import style from "./MovieSettings.module.css"
class MovieSettings extends React.Component {
constructor(props) {
super(props);
this.state = {
text: [],
startbtnDisabled: false
};
}
componentDidMount() {
if (this.myinterval) {
clearInterval(this.myinterval);
}
this.myinterval = setInterval(this.updateStatus, 1000);
}
componentWillUnmount() {
clearInterval(this.myinterval);
}
render() {
return (
<>
<button disabled={this.state.startbtnDisabled} className='reindexbtn btn btn-success' onClick={() => {
this.startReindex()
}}>Reindex Movies
</button>
<div className={style.indextextarea}>{this.state.text.map(m => (
<div className='textarea-element'>{m}</div>
))}</div>
</>
);
}
startReindex() {
// clear output text before start
this.setState({text: []});
this.setState({startbtnDisabled: true});
console.log("starting");
const updateRequest = new FormData();
// fetch all videos available
fetch('/api/extractvideopreviews.php', {method: 'POST', body: updateRequest})
.then((response) => response.json()
.then((result) => {
// todo 2020-07-4: some kind of start event
console.log("returned");
}))
.catch(() => {
console.log("no connection to backend");
});
if (this.myinterval) {
clearInterval(this.myinterval);
}
this.myinterval = setInterval(this.updateStatus, 1000);
}
updateStatus = () => {
const updateRequest = new FormData();
fetch('/api/extractionData.php', {method: 'POST', body: updateRequest})
.then((response) => response.json()
.then((result) => {
if (result.contentAvailable === true) {
console.log(result);
// todo 2020-07-4: scroll to bottom of div here
this.setState({
// insert a string for each line
text: [...result.message.split("\n"),
...this.state.text]
});
} else {
// clear refresh interval if no content available
clearInterval(this.myinterval);
this.setState({startbtnDisabled: false});
}
}))
.catch(() => {
console.log("no connection to backend");
});
};
}
export default MovieSettings;

View File

@ -0,0 +1,13 @@
.indextextarea {
margin-top: 15px;
padding: 10px;
overflow-y: scroll;
overflow-x: auto;
min-height: 100px;
max-height: 300px;
width: 50%;
background-color: #c2c2c2;
border-radius: 5px;
}

View File

@ -0,0 +1,62 @@
import {shallow} from "enzyme";
import React from "react";
import MovieSettings from "./MovieSettings";
function prepareFetchApi(response) {
const mockJsonPromise = Promise.resolve(response);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
return (jest.fn().mockImplementation(() => mockFetchPromise));
}
describe('<MovieSettings/>', function () {
it('renders without crashing ', function () {
const wrapper = shallow(<MovieSettings/>);
wrapper.unmount();
});
it('received text renders into dom', function () {
const wrapper = shallow(<MovieSettings/>);
wrapper.setState({
text: [
"firstline",
"secline"
]
});
expect(wrapper.find(".indextextarea").find(".textarea-element")).toHaveLength(2);
});
it('test simulate reindex', function () {
global.fetch = prepareFetchApi({});
const wrapper = shallow(<MovieSettings/>);
wrapper.find(".reindexbtn").simulate("click");
// initial send of reindex request to server
expect(global.fetch).toBeCalledTimes(1);
});
it('content available received and in state', done => {
global.fetch = prepareFetchApi({
contentAvailable: true,
message: "firstline\nsecondline"
});
const wrapper = shallow(<MovieSettings/>);
wrapper.instance().updateStatus();
process.nextTick(() => {
expect(wrapper.state()).toMatchObject({
text: [
"firstline",
"secondline"
]
});
global.fetch.mockClear();
done();
});
});
});

View File

@ -1,82 +1,52 @@
import React from "react";
import PageTitle from "../../elements/PageTitle/PageTitle";
import MovieSettings from "./MovieSettings";
import GeneralSettings from "./GeneralSettings";
import style from "./SettingsPage.module.css"
class SettingsPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
text: []
currentpage: "general"
};
}
updateStatus = () => {
const updateRequest = new FormData();
fetch('/api/extractionData.php', {method: 'POST', body: updateRequest})
.then((response) => response.json()
.then((result) => {
if (result.contentAvailable === true) {
console.log(result);
this.setState({
text: [...result.message.split("\n"),
...this.state.text]
});
} else {
clearInterval(this.myinterval);
}
}))
.catch(() => {
console.log("no connection to backend");
});
};
componentDidMount() {
if (this.myinterval) {
clearInterval(this.myinterval);
getContent() {
switch (this.state.currentpage) {
case "general":
return <GeneralSettings/>;
case "movies":
return <MovieSettings/>;
case "tv":
return <span/>; // todo this page
default:
return "unknown button clicked";
}
this.myinterval = setInterval(this.updateStatus, 1000);
}
componentWillUnmount() {
clearInterval(this.myinterval);
}
render() {
return (
<div>
<PageTitle
title='Settings Page'
subtitle='todo'/>
<button className='reindexbtn btn btn-success' onClick={() => {
this.startReindex()
}}>Reindex Movies
</button>
<div className='indextextarea'>{this.state.text.map(m => (
<div className='textarea-element'>{m}</div>
))}</div>
<div className={style.SettingsSidebar}>
<div className={style.SettingsSidebarTitle}>Settings</div>
<div onClick={() => this.setState({currentpage: "general"})}
className={style.SettingSidebarElement}>General
</div>
<div onClick={() => this.setState({currentpage: "movies"})}
className={style.SettingSidebarElement}>Movies
</div>
<div onClick={() => this.setState({currentpage: "tv"})}
className={style.SettingSidebarElement}>TV Shows
</div>
</div>
<div className={style.SettingsContent}>
{this.getContent()}
</div>
</div>
);
}
startReindex() {
console.log("starting");
const updateRequest = new FormData();
// fetch all videos available
fetch('/api/extractvideopreviews.php', {method: 'POST', body: updateRequest})
.then((response) => response.json()
.then((result) => {
console.log("returned");
}))
.catch(() => {
console.log("no connection to backend");
});
if (this.myinterval) {
clearInterval(this.myinterval);
}
this.myinterval = setInterval(this.updateStatus, 1000);
console.log("sent");
}
}
export default SettingsPage;

View File

@ -0,0 +1,40 @@
.SettingsSidebar {
padding-top: 20px;
float: left;
width: 10%;
background-color: #d3dcef;
min-height: calc(100vh - 56px);
min-width: 110px;
}
.SettingsSidebarTitle {
text-align: center;
font-weight: bold;
text-transform: uppercase;
font-size: larger;
margin-bottom: 25px;
}
.SettingsContent {
float: left;
width: 80%;
padding-left: 30px;
padding-top: 30px;
}
.SettingSidebarElement {
margin: 10px 5px 5px;
padding: 5px;
background-color: #a8b2de;
text-align: center;
font-weight: bold;
border-radius: 7px;
}
.SettingSidebarElement:hover {
font-weight: bolder;
background-color: #7d8dd4;
box-shadow: #7d8dd4 0 0 0 5px;
transition: all 300ms;
cursor: pointer;
}

View File

@ -16,26 +16,33 @@ describe('<RandomPage/>', function () {
wrapper.unmount();
});
it('received text renders into dom', function () {
it('simulate topic clicka', function () {
const wrapper = shallow(<SettingsPage/>);
wrapper.setState({
text: [
"firstline",
"secline"
]
});
simulateSideBarClick("General", wrapper);
expect(wrapper.state().currentpage).toBe("general");
expect(wrapper.find(".SettingsContent").find("GeneralSettings")).toHaveLength(1);
expect(wrapper.find(".indextextarea").find(".textarea-element")).toHaveLength(2);
simulateSideBarClick("Movies", wrapper);
expect(wrapper.state().currentpage).toBe("movies");
expect(wrapper.find(".SettingsContent").find("MovieSettings")).toHaveLength(1);
simulateSideBarClick("TV Shows", wrapper);
expect(wrapper.state().currentpage).toBe("tv");
expect(wrapper.find(".SettingsContent").find("span")).toHaveLength(1);
});
it('test simulate reindex', function () {
global.fetch = prepareFetchApi({});
function simulateSideBarClick(name, wrapper) {
wrapper.find(".SettingSidebarElement").findWhere(it =>
it.text() === name &&
it.type() === "div").simulate("click");
}
it('simulate unknown topic', function () {
const wrapper = shallow(<SettingsPage/>);
wrapper.setState({currentpage: "unknown"});
wrapper.find(".reindexbtn").simulate("click");
expect(wrapper.find(".SettingsContent").text()).toBe("unknown button clicked");
// initial send of reindex request to server
expect(global.fetch).toBeCalledTimes(1);
});
});