Update react-scripts & fix eslint
This commit is contained in:
parent
14d15abd94
commit
43c4eba0fa
|
|
@ -39,22 +39,22 @@ rules:
|
||||||
import/first: error
|
import/first: error
|
||||||
import/no-unused-modules: error
|
import/no-unused-modules: error
|
||||||
|
|
||||||
unicorn/no-abusive-eslint-disable: error
|
unicorn/no-abusive-eslint-disable: off
|
||||||
unicorn/no-array-instanceof: error
|
unicorn/no-array-instanceof: error
|
||||||
unicorn/no-unreadable-array-destructuring: error
|
unicorn/no-unreadable-array-destructuring: error
|
||||||
unicorn/no-zero-fractions: error
|
unicorn/no-zero-fractions: error
|
||||||
|
|
||||||
react/jsx-key: error
|
react/jsx-key: error
|
||||||
react/jsx-pascal-case: error
|
react/jsx-pascal-case: error
|
||||||
react/destructuring-assignment: warn
|
react/destructuring-assignment: off
|
||||||
react/function-component-definition: [error, {namedComponents: arrow-function, unnamedComponents: arrow-function}]
|
react/function-component-definition: off
|
||||||
react/no-array-index-key: error
|
react/no-array-index-key: error
|
||||||
react/no-deprecated: error
|
react/no-deprecated: off
|
||||||
react/no-string-refs: error
|
react/no-string-refs: error
|
||||||
react/no-this-in-sfc: error
|
react/no-this-in-sfc: error
|
||||||
react/no-typos: error
|
react/no-typos: error
|
||||||
react/no-unknown-property: error
|
react/no-unknown-property: error
|
||||||
react/prefer-stateless-function: error
|
react/prefer-stateless-function: off
|
||||||
react/prop-types: off
|
react/prop-types: off
|
||||||
|
|
||||||
jest/expect-expect: off
|
jest/expect-expect: off
|
||||||
|
|
@ -65,18 +65,21 @@ rules:
|
||||||
"@typescript-eslint/await-thenable": error
|
"@typescript-eslint/await-thenable": error
|
||||||
"@typescript-eslint/no-unused-vars": error
|
"@typescript-eslint/no-unused-vars": error
|
||||||
"@typescript-eslint/no-use-before-define": off
|
"@typescript-eslint/no-use-before-define": off
|
||||||
|
"@typescript-eslint/no-unsafe-call": off
|
||||||
"@typescript-eslint/consistent-type-assertions": [error, {assertionStyle: as}]
|
"@typescript-eslint/consistent-type-assertions": [error, {assertionStyle: as}]
|
||||||
|
|
||||||
"@typescript-eslint/no-extra-non-null-assertion": error
|
"@typescript-eslint/no-extra-non-null-assertion": error
|
||||||
"@typescript-eslint/no-inferrable-types": error
|
"@typescript-eslint/no-inferrable-types": error
|
||||||
"@typescript-eslint/no-this-alias": error
|
"@typescript-eslint/no-this-alias": error
|
||||||
"@typescript-eslint/no-throw-literal": error
|
"@typescript-eslint/no-throw-literal": error
|
||||||
|
"@typescript-eslint/no-non-null-assertion": off
|
||||||
"@typescript-eslint/prefer-nullish-coalescing": error
|
"@typescript-eslint/prefer-nullish-coalescing": error
|
||||||
"@typescript-eslint/prefer-optional-chain": error
|
"@typescript-eslint/prefer-optional-chain": error
|
||||||
"@typescript-eslint/prefer-readonly": error
|
"@typescript-eslint/prefer-readonly": off
|
||||||
"@typescript-eslint/unbound-method": error
|
"@typescript-eslint/unbound-method": error
|
||||||
"@typescript-eslint/no-empty-function": off
|
"@typescript-eslint/no-empty-function": off
|
||||||
"@typescript-eslint/explicit-module-boundary-types": off
|
"@typescript-eslint/explicit-module-boundary-types": off
|
||||||
|
"@typescript-eslint/ban-ts-comment": off
|
||||||
"@typescript-eslint/no-floating-promises": off
|
"@typescript-eslint/no-floating-promises": off
|
||||||
"@typescript-eslint/no-unsafe-member-access": off
|
"@typescript-eslint/no-unsafe-member-access": off
|
||||||
"@typescript-eslint/no-unsafe-return": off
|
"@typescript-eslint/no-unsafe-return": off
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test --env=node",
|
"test": "react-scripts test --env=node",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"lint": "eslint \"src/*.{ts,tsx}\"",
|
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
||||||
"format": "prettier \"src/**/*.{ts,tsx}\" --write",
|
"format": "prettier \"src/**/*.{ts,tsx}\" --write",
|
||||||
"testformat": "prettier \"src/**/*.{ts,tsx}\" --list-different"
|
"testformat": "prettier \"src/**/*.{ts,tsx}\" --list-different"
|
||||||
},
|
},
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
"get-port": "^5.1.1",
|
"get-port": "^5.1.1",
|
||||||
"prettier": "^2.1.1",
|
"prettier": "^2.1.1",
|
||||||
"puppeteer": "^5.3.0",
|
"puppeteer": "^5.3.0",
|
||||||
"react-scripts": "^3.4.3",
|
"react-scripts": "^4.0.3",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"tree-kill": "^1.2.0",
|
"tree-kill": "^1.2.0",
|
||||||
"typescript": "4.0.2",
|
"typescript": "4.0.2",
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,16 @@ export class AppStore extends BaseStore<IApplication> {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected requestItems = (): Promise<IApplication[]> => {
|
protected requestItems = (): Promise<IApplication[]> =>
|
||||||
return axios
|
axios
|
||||||
.get<IApplication[]>(`${config.get('url')}application`)
|
.get<IApplication[]>(`${config.get('url')}application`)
|
||||||
.then((response) => response.data);
|
.then((response) => response.data);
|
||||||
};
|
|
||||||
|
|
||||||
protected requestDelete = (id: number): Promise<void> => {
|
protected requestDelete = (id: number): Promise<void> =>
|
||||||
return axios.delete(`${config.get('url')}application/${id}`).then(() => {
|
axios.delete(`${config.get('url')}application/${id}`).then(() => {
|
||||||
this.onDelete();
|
this.onDelete();
|
||||||
return this.snack('Application deleted');
|
return this.snack('Application deleted');
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
public uploadImage = async (id: number, file: Blob): Promise<void> => {
|
public uploadImage = async (id: number, file: Blob): Promise<void> => {
|
||||||
|
|
|
||||||
|
|
@ -72,21 +72,19 @@ class Applications extends Component<Stores<'appStore'>> {
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{apps.map((app: IApplication) => {
|
{apps.map((app: IApplication) => (
|
||||||
return (
|
<Row
|
||||||
<Row
|
key={app.id}
|
||||||
key={app.id}
|
description={app.description}
|
||||||
description={app.description}
|
image={app.image}
|
||||||
image={app.image}
|
name={app.name}
|
||||||
name={app.name}
|
value={app.token}
|
||||||
value={app.token}
|
fUpload={() => this.uploadImage(app.id)}
|
||||||
fUpload={() => this.uploadImage(app.id)}
|
fDelete={() => (this.deleteId = app.id)}
|
||||||
fDelete={() => (this.deleteId = app.id)}
|
fEdit={() => (this.updateId = app.id)}
|
||||||
fEdit={() => (this.updateId = app.id)}
|
noDelete={app.internal}
|
||||||
noDelete={app.internal}
|
/>
|
||||||
/>
|
))}
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
<input
|
<input
|
||||||
|
|
@ -133,7 +131,7 @@ class Applications extends Component<Stores<'appStore'>> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private onUploadImage = (e: ChangeEvent<HTMLInputElement>) => {
|
private onUploadImage = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = e.target.files && e.target.files[0];
|
const file = e.target.files?.[0];
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,12 @@ interface IState {
|
||||||
export default class UpdateDialog extends Component<IProps, IState> {
|
export default class UpdateDialog extends Component<IProps, IState> {
|
||||||
public state = {name: '', description: ''};
|
public state = {name: '', description: ''};
|
||||||
|
|
||||||
public componentWillMount() {
|
constructor(props: IProps) {
|
||||||
this.setState({name: this.props.initialName, description: this.props.initialDescription});
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
name: props.initialName,
|
||||||
|
description: props.initialDescription,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,8 @@ export class ClientStore extends BaseStore<IClient> {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected requestItems = (): Promise<IClient[]> => {
|
protected requestItems = (): Promise<IClient[]> =>
|
||||||
return axios.get<IClient[]>(`${config.get('url')}client`).then((response) => response.data);
|
axios.get<IClient[]>(`${config.get('url')}client`).then((response) => response.data);
|
||||||
};
|
|
||||||
|
|
||||||
protected requestDelete(id: number): Promise<void> {
|
protected requestDelete(id: number): Promise<void> {
|
||||||
return axios
|
return axios
|
||||||
|
|
|
||||||
|
|
@ -64,17 +64,15 @@ class Clients extends Component<Stores<'clientStore'>> {
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{clients.map((client: IClient) => {
|
{clients.map((client: IClient) => (
|
||||||
return (
|
<Row
|
||||||
<Row
|
key={client.id}
|
||||||
key={client.id}
|
name={client.name}
|
||||||
name={client.name}
|
value={client.token}
|
||||||
value={client.token}
|
fEdit={() => (this.updateId = client.id)}
|
||||||
fEdit={() => (this.updateId = client.id)}
|
fDelete={() => (this.deleteId = client.id)}
|
||||||
fDelete={() => (this.deleteId = client.id)}
|
/>
|
||||||
/>
|
))}
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,11 @@ interface IState {
|
||||||
export default class UpdateDialog extends Component<IProps, IState> {
|
export default class UpdateDialog extends Component<IProps, IState> {
|
||||||
public state = {name: ''};
|
public state = {name: ''};
|
||||||
|
|
||||||
public componentWillMount() {
|
constructor(props: IProps) {
|
||||||
this.setState({name: this.props.initialName});
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
name: props.initialName,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,8 @@ export abstract class BaseStore<T extends HasID> implements IClearable {
|
||||||
return item;
|
return item;
|
||||||
};
|
};
|
||||||
|
|
||||||
public getByIDOrUndefined = (id: number): T | undefined => {
|
public getByIDOrUndefined = (id: number): T | undefined =>
|
||||||
return this.items.find((hasId: HasID) => hasId.id === id);
|
this.items.find((hasId: HasID) => hasId.id === id);
|
||||||
};
|
|
||||||
|
|
||||||
public getItems = (): T[] => this.items;
|
public getItems = (): T[] => this.items;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,22 +8,20 @@ interface ConnectionErrorBannerProps {
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConnectionErrorBanner = ({height, retry, message}: ConnectionErrorBannerProps) => {
|
export const ConnectionErrorBanner = ({height, retry, message}: ConnectionErrorBannerProps) => (
|
||||||
return (
|
<div
|
||||||
<div
|
style={{
|
||||||
style={{
|
backgroundColor: '#e74c3c',
|
||||||
backgroundColor: '#e74c3c',
|
height,
|
||||||
height,
|
width: '100%',
|
||||||
width: '100%',
|
zIndex: 1300,
|
||||||
zIndex: 1300,
|
position: 'relative',
|
||||||
position: 'relative',
|
}}>
|
||||||
}}>
|
<Typography align="center" variant="h6" style={{lineHeight: `${height}px`}}>
|
||||||
<Typography align="center" variant="h6" style={{lineHeight: `${height}px`}}>
|
{message}{' '}
|
||||||
{message}{' '}
|
<Button variant="outlined" onClick={retry}>
|
||||||
<Button variant="outlined" onClick={retry}>
|
Retry
|
||||||
Retry
|
</Button>
|
||||||
</Button>
|
</Typography>
|
||||||
</Typography>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,13 @@ const styles = () => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IProps extends WithStyles<'paper'> {
|
interface IProps extends WithStyles<'paper'> {
|
||||||
style?: object;
|
style?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container: React.SFC<IProps> = ({classes, children, style}) => {
|
const Container: React.FC<IProps> = ({classes, children, style}) => (
|
||||||
return (
|
<Paper elevation={6} className={classes.paper} style={style}>
|
||||||
<Paper elevation={6} className={classes.paper} style={style}>
|
{children}
|
||||||
{children}
|
</Paper>
|
||||||
</Paper>
|
);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withStyles(styles)(Container);
|
export default withStyles(styles)(Container);
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ import IconButton from '@material-ui/core/IconButton';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import Visibility from '@material-ui/icons/Visibility';
|
import Visibility from '@material-ui/icons/Visibility';
|
||||||
import VisibilityOff from '@material-ui/icons/VisibilityOff';
|
import VisibilityOff from '@material-ui/icons/VisibilityOff';
|
||||||
import React, {Component} from 'react';
|
import React, {Component, CSSProperties} from 'react';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
value: string;
|
value: string;
|
||||||
style?: object;
|
style?: CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ const ResponsiveButton: React.FC<{
|
||||||
id?: string;
|
id?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
}> = ({width, icon, children, label, ...rest}) => {
|
}> = ({width, icon, label, ...rest}) => {
|
||||||
if (width === 'xs' || width === 'sm') {
|
if (width === 'xs' || width === 'sm') {
|
||||||
return <IconButton {...rest}>{icon}</IconButton>;
|
return <IconButton {...rest}>{icon}</IconButton>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,8 @@ const themeMap: Record<ThemeKey, Theme> = {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const isThemeKey = (value: string | null): value is ThemeKey => {
|
const isThemeKey = (value: string | null): value is ThemeKey =>
|
||||||
return value === 'light' || value === 'dark';
|
value === 'light' || value === 'dark';
|
||||||
};
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class Layout extends React.Component<
|
class Layout extends React.Component<
|
||||||
|
|
|
||||||
|
|
@ -52,19 +52,17 @@ class Navigation extends Component<
|
||||||
const userApps =
|
const userApps =
|
||||||
apps.length === 0
|
apps.length === 0
|
||||||
? null
|
? null
|
||||||
: apps.map((app) => {
|
: apps.map((app) => (
|
||||||
return (
|
<Link
|
||||||
<Link
|
onClick={() => setNavOpen(false)}
|
||||||
onClick={() => setNavOpen(false)}
|
className={`${classes.link} item`}
|
||||||
className={`${classes.link} item`}
|
to={'/messages/' + app.id}
|
||||||
to={'/messages/' + app.id}
|
key={app.id}>
|
||||||
key={app.id}>
|
<ListItem button>
|
||||||
<ListItem button>
|
<ListItemText primary={app.name} />
|
||||||
<ListItemText primary={app.name} />
|
</ListItem>
|
||||||
</ListItem>
|
</Link>
|
||||||
</Link>
|
));
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const placeholderItems = [
|
const placeholderItems = [
|
||||||
<ListItem button disabled key={-1}>
|
<ListItem button disabled key={-1}>
|
||||||
|
|
@ -108,24 +106,22 @@ class Navigation extends Component<
|
||||||
|
|
||||||
const ResponsiveDrawer: React.FC<
|
const ResponsiveDrawer: React.FC<
|
||||||
DrawerProps & {navOpen: boolean; setNavOpen: (open: boolean) => void}
|
DrawerProps & {navOpen: boolean; setNavOpen: (open: boolean) => void}
|
||||||
> = ({navOpen, setNavOpen, children, ...rest}) => {
|
> = ({navOpen, setNavOpen, children, ...rest}) => (
|
||||||
return (
|
<>
|
||||||
<>
|
<Hidden smUp implementation="css">
|
||||||
<Hidden smUp implementation="css">
|
<Drawer variant="temporary" open={navOpen} {...rest}>
|
||||||
<Drawer variant="temporary" open={navOpen} {...rest}>
|
<IconButton onClick={() => setNavOpen(false)}>
|
||||||
<IconButton onClick={() => setNavOpen(false)}>
|
<CloseIcon />
|
||||||
<CloseIcon />
|
</IconButton>
|
||||||
</IconButton>
|
{children}
|
||||||
{children}
|
</Drawer>
|
||||||
</Drawer>
|
</Hidden>
|
||||||
</Hidden>
|
<Hidden xsDown implementation="css">
|
||||||
<Hidden xsDown implementation="css">
|
<Drawer variant="permanent" {...rest}>
|
||||||
<Drawer variant="permanent" {...rest}>
|
{children}
|
||||||
{children}
|
</Drawer>
|
||||||
</Drawer>
|
</Hidden>
|
||||||
</Hidden>
|
</>
|
||||||
</>
|
);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withStyles(styles, {withTheme: true})(inject('appStore')(Navigation));
|
export default withStyles(styles, {withTheme: true})(inject('appStore')(Navigation));
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import {observable} from 'mobx';
|
||||||
import ReactInfinite from 'react-infinite';
|
import ReactInfinite from 'react-infinite';
|
||||||
import {IMessage} from '../types';
|
import {IMessage} from '../types';
|
||||||
|
|
||||||
interface IProps extends RouteComponentProps<{id: string}> {}
|
type IProps = RouteComponentProps<{id: string}>;
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
appId: number;
|
appId: number;
|
||||||
|
|
@ -121,24 +121,22 @@ class Messages extends Component<IProps & Stores<'messagesStore' | 'appStore'>,
|
||||||
private deleteMessage = (message: IMessage) => () =>
|
private deleteMessage = (message: IMessage) => () =>
|
||||||
this.props.messagesStore.removeSingle(message);
|
this.props.messagesStore.removeSingle(message);
|
||||||
|
|
||||||
private renderMessage = (message: IMessage) => {
|
private renderMessage = (message: IMessage) => (
|
||||||
return (
|
<Message
|
||||||
<Message
|
key={message.id}
|
||||||
key={message.id}
|
height={(height: number) => {
|
||||||
height={(height: number) => {
|
if (!this.heights[message.id]) {
|
||||||
if (!this.heights[message.id]) {
|
this.heights[message.id] = height;
|
||||||
this.heights[message.id] = height;
|
}
|
||||||
}
|
}}
|
||||||
}}
|
fDelete={this.deleteMessage(message)}
|
||||||
fDelete={this.deleteMessage(message)}
|
title={message.title}
|
||||||
title={message.title}
|
date={message.date}
|
||||||
date={message.date}
|
content={message.message}
|
||||||
content={message.message}
|
image={message.image}
|
||||||
image={message.image}
|
extras={message.extras}
|
||||||
extras={message.extras}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
private checkIfLoadMore() {
|
private checkIfLoadMore() {
|
||||||
const {appId} = this.state;
|
const {appId} = this.state;
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export class MessagesStore {
|
||||||
);
|
);
|
||||||
|
|
||||||
state.messages.replace([...state.messages, ...pagedResult.messages]);
|
state.messages.replace([...state.messages, ...pagedResult.messages]);
|
||||||
state.nextSince = pagedResult.paging.since || 0;
|
state.nextSince = pagedResult.paging.since ?? 0;
|
||||||
state.hasMore = 'next' in pagedResult.paging;
|
state.hasMore = 'next' in pagedResult.paging;
|
||||||
state.loaded = true;
|
state.loaded = true;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
@ -139,12 +139,10 @@ export class MessagesStore {
|
||||||
.getItems()
|
.getItems()
|
||||||
.reduce((all, app) => ({...all, [app.id]: app.image}), {});
|
.reduce((all, app) => ({...all, [app.id]: app.image}), {});
|
||||||
|
|
||||||
return this.stateOf(appId, false).messages.map((message: IMessage) => {
|
return this.stateOf(appId, false).messages.map((message: IMessage) => ({
|
||||||
return {
|
...message,
|
||||||
...message,
|
image: appToImage[message.appid] || null,
|
||||||
image: appToImage[message.appid] || null,
|
}));
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public get = createTransformer(this.getUnCached);
|
public get = createTransformer(this.getUnCached);
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export class WebSocketStore {
|
||||||
setTimeout(() => this.listen(callback), 30000);
|
setTimeout(() => this.listen(callback), 30000);
|
||||||
})
|
})
|
||||||
.catch((error: AxiosError) => {
|
.catch((error: AxiosError) => {
|
||||||
if (error && error.response && error.response.status === 401) {
|
if (error?.response?.status === 401) {
|
||||||
this.snack('Could not authenticate with client token, logging out.');
|
this.snack('Could not authenticate with client token, logging out.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -47,5 +47,5 @@ export class WebSocketStore {
|
||||||
this.ws = ws;
|
this.ws = ws;
|
||||||
};
|
};
|
||||||
|
|
||||||
public close = () => this.ws && this.ws.close(1000, 'WebSocketStore#close');
|
public close = () => this.ws?.close(1000, 'WebSocketStore#close');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import Container from '../common/Container';
|
||||||
import {inject, Stores} from '../inject';
|
import {inject, Stores} from '../inject';
|
||||||
import {IPlugin} from '../types';
|
import {IPlugin} from '../types';
|
||||||
|
|
||||||
interface IProps extends RouteComponentProps<{id: string}> {}
|
type IProps = RouteComponentProps<{id: string}>;
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
displayText: string | null;
|
displayText: string | null;
|
||||||
|
|
@ -122,7 +122,7 @@ interface IPanelWrapperProps {
|
||||||
icon?: React.ComponentType;
|
icon?: React.ComponentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PanelWrapper: React.SFC<IPanelWrapperProps> = ({
|
const PanelWrapper: React.FC<IPanelWrapperProps> = ({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
refresh,
|
refresh,
|
||||||
|
|
@ -175,7 +175,7 @@ class ConfigurerPanel extends Component<IConfigurerPanelProps, {unsavedChanges:
|
||||||
theme: 'material',
|
theme: 'material',
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
}}
|
}}
|
||||||
onChange={(instance, data, value) => {
|
onChange={(_, _1, value) => {
|
||||||
let newConf: string | null = value;
|
let newConf: string | null = value;
|
||||||
if (value === this.props.initialConfig) {
|
if (value === this.props.initialConfig) {
|
||||||
newConf = null;
|
newConf = null;
|
||||||
|
|
@ -195,6 +195,7 @@ class ConfigurerPanel extends Component<IConfigurerPanelProps, {unsavedChanges:
|
||||||
className="config-save"
|
className="config-save"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const newConfig = this.state.unsavedChanges;
|
const newConfig = this.state.unsavedChanges;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
this.props.save(newConfig!).then(() => {
|
this.props.save(newConfig!).then(() => {
|
||||||
this.setState({unsavedChanges: null});
|
this.setState({unsavedChanges: null});
|
||||||
});
|
});
|
||||||
|
|
@ -210,7 +211,7 @@ interface IDisplayerPanelProps {
|
||||||
pluginInfo: IPlugin;
|
pluginInfo: IPlugin;
|
||||||
displayText: string;
|
displayText: string;
|
||||||
}
|
}
|
||||||
const DisplayerPanel: React.SFC<IDisplayerPanelProps> = ({pluginInfo, displayText}) => (
|
const DisplayerPanel: React.FC<IDisplayerPanelProps> = ({displayText}) => (
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
<ReactMarkDown source={displayText} />
|
<ReactMarkDown source={displayText} />
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
|
||||||
|
|
@ -12,23 +12,16 @@ export class PluginStore extends BaseStore<IPlugin> {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public requestConfig = (id: number): Promise<string> => {
|
public requestConfig = (id: number): Promise<string> =>
|
||||||
return axios
|
axios.get(`${config.get('url')}plugin/${id}/config`).then((response) => response.data);
|
||||||
.get(`${config.get('url')}plugin/${id}/config`)
|
|
||||||
.then((response) => response.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
public requestDisplay = (id: number): Promise<string> => {
|
public requestDisplay = (id: number): Promise<string> =>
|
||||||
return axios
|
axios.get(`${config.get('url')}plugin/${id}/display`).then((response) => response.data);
|
||||||
.get(`${config.get('url')}plugin/${id}/display`)
|
|
||||||
.then((response) => response.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
protected requestItems = (): Promise<IPlugin[]> => {
|
protected requestItems = (): Promise<IPlugin[]> =>
|
||||||
return axios.get<IPlugin[]>(`${config.get('url')}plugin`).then((response) => response.data);
|
axios.get<IPlugin[]>(`${config.get('url')}plugin`).then((response) => response.data);
|
||||||
};
|
|
||||||
|
|
||||||
protected requestDelete = (id: number): Promise<void> => {
|
protected requestDelete = (): Promise<void> => {
|
||||||
this.snack('Cannot delete plugin');
|
this.snack('Cannot delete plugin');
|
||||||
throw new Error('Cannot delete plugin');
|
throw new Error('Cannot delete plugin');
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -39,23 +39,21 @@ class Plugins extends Component<Stores<'pluginStore'>> {
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{plugins.map((plugin: IPlugin) => {
|
{plugins.map((plugin: IPlugin) => (
|
||||||
return (
|
<Row
|
||||||
<Row
|
key={plugin.token}
|
||||||
key={plugin.token}
|
id={plugin.id}
|
||||||
id={plugin.id}
|
token={plugin.token}
|
||||||
token={plugin.token}
|
name={plugin.name}
|
||||||
name={plugin.name}
|
enabled={plugin.enabled}
|
||||||
enabled={plugin.enabled}
|
fToggleStatus={() =>
|
||||||
fToggleStatus={() =>
|
this.props.pluginStore.changeEnabledState(
|
||||||
this.props.pluginStore.changeEnabledState(
|
plugin.id,
|
||||||
plugin.id,
|
!plugin.enabled
|
||||||
!plugin.enabled
|
)
|
||||||
)
|
}
|
||||||
}
|
/>
|
||||||
/>
|
))}
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
|
||||||
|
|
@ -26,44 +26,42 @@ const hiddenToken = '•••••••••••••••';
|
||||||
const $table = selector.table('#app-table');
|
const $table = selector.table('#app-table');
|
||||||
const $dialog = selector.form('#app-dialog');
|
const $dialog = selector.form('#app-dialog');
|
||||||
|
|
||||||
const hasApp = (name: string, description: string, row: number): (() => Promise<void>) => {
|
const hasApp = (
|
||||||
return async () => {
|
name: string,
|
||||||
expect(await innerText(page, $table.cell(row, Col.Name))).toBe(name);
|
description: string,
|
||||||
expect(await innerText(page, $table.cell(row, Col.Token))).toBe(hiddenToken);
|
row: number
|
||||||
expect(await innerText(page, $table.cell(row, Col.Description))).toBe(description);
|
): (() => Promise<void>) => async () => {
|
||||||
};
|
expect(await innerText(page, $table.cell(row, Col.Name))).toBe(name);
|
||||||
|
expect(await innerText(page, $table.cell(row, Col.Token))).toBe(hiddenToken);
|
||||||
|
expect(await innerText(page, $table.cell(row, Col.Description))).toBe(description);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateApp = (
|
const updateApp = (
|
||||||
id: number,
|
id: number,
|
||||||
data: {name?: string; description?: string}
|
data: {name?: string; description?: string}
|
||||||
): (() => Promise<void>) => {
|
): (() => Promise<void>) => async () => {
|
||||||
return async () => {
|
await page.click($table.cell(id, Col.EditUpdate, '.edit'));
|
||||||
await page.click($table.cell(id, Col.EditUpdate, '.edit'));
|
await page.waitForSelector($dialog.selector());
|
||||||
await page.waitForSelector($dialog.selector());
|
if (data.name) {
|
||||||
if (data.name) {
|
const nameSelector = $dialog.input('.name');
|
||||||
const nameSelector = $dialog.input('.name');
|
await clearField(page, nameSelector);
|
||||||
await clearField(page, nameSelector);
|
await page.type(nameSelector, data.name);
|
||||||
await page.type(nameSelector, data.name);
|
}
|
||||||
}
|
if (data.description) {
|
||||||
if (data.description) {
|
const descSelector = $dialog.textarea('.description');
|
||||||
const descSelector = $dialog.textarea('.description');
|
await clearField(page, descSelector);
|
||||||
await clearField(page, descSelector);
|
await page.type(descSelector, data.description);
|
||||||
await page.type(descSelector, data.description);
|
}
|
||||||
}
|
await page.click($dialog.button('.update'));
|
||||||
await page.click($dialog.button('.update'));
|
await waitToDisappear(page, $dialog.selector());
|
||||||
await waitToDisappear(page, $dialog.selector());
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createApp = (name: string, description: string): (() => Promise<void>) => {
|
const createApp = (name: string, description: string): (() => Promise<void>) => async () => {
|
||||||
return async () => {
|
await page.click('#create-app');
|
||||||
await page.click('#create-app');
|
await page.waitForSelector($dialog.selector());
|
||||||
await page.waitForSelector($dialog.selector());
|
await page.type($dialog.input('.name'), name);
|
||||||
await page.type($dialog.input('.name'), name);
|
await page.type($dialog.textarea('.description'), description);
|
||||||
await page.type($dialog.textarea('.description'), description);
|
await page.click($dialog.button('.create'));
|
||||||
await page.click($dialog.button('.create'));
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Application', () => {
|
describe('Application', () => {
|
||||||
|
|
|
||||||
|
|
@ -21,24 +21,20 @@ enum Col {
|
||||||
Delete = 4,
|
Delete = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasClient = (name: string, row: number): (() => Promise<void>) => {
|
const hasClient = (name: string, row: number): (() => Promise<void>) => async () => {
|
||||||
return async () => {
|
expect(await innerText(page, $table.cell(row, Col.Name))).toBe(name);
|
||||||
expect(await innerText(page, $table.cell(row, Col.Name))).toBe(name);
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateClient = (id: number, data: {name?: string}): (() => Promise<void>) => {
|
const updateClient = (id: number, data: {name?: string}): (() => Promise<void>) => async () => {
|
||||||
return async () => {
|
await page.click($table.cell(id, Col.Edit, '.edit'));
|
||||||
await page.click($table.cell(id, Col.Edit, '.edit'));
|
await page.waitForSelector($dialog.selector());
|
||||||
await page.waitForSelector($dialog.selector());
|
if (data.name) {
|
||||||
if (data.name) {
|
const nameSelector = $dialog.input('.name');
|
||||||
const nameSelector = $dialog.input('.name');
|
await clearField(page, nameSelector);
|
||||||
await clearField(page, nameSelector);
|
await page.type(nameSelector, data.name);
|
||||||
await page.type(nameSelector, data.name);
|
}
|
||||||
}
|
await page.click($dialog.button('.update'));
|
||||||
await page.click($dialog.button('.update'));
|
await waitToDisappear(page, $dialog.selector());
|
||||||
await waitToDisappear(page, $dialog.selector());
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const $table = selector.table('#client-table');
|
const $table = selector.table('#client-table');
|
||||||
|
|
@ -57,13 +53,11 @@ describe('Client', () => {
|
||||||
expect(await count(page, $table.rows())).toBe(1);
|
expect(await count(page, $table.rows())).toBe(1);
|
||||||
});
|
});
|
||||||
describe('create clients', () => {
|
describe('create clients', () => {
|
||||||
const createClient = (name: string): (() => Promise<void>) => {
|
const createClient = (name: string): (() => Promise<void>) => async () => {
|
||||||
return async () => {
|
await page.click('#create-client');
|
||||||
await page.click('#create-client');
|
await page.waitForSelector($dialog.selector());
|
||||||
await page.waitForSelector($dialog.selector());
|
await page.type($dialog.input('.name'), name);
|
||||||
await page.type($dialog.input('.name'), name);
|
await page.click($dialog.button('.create'));
|
||||||
await page.click($dialog.button('.create'));
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
it('phone', createClient('phone'));
|
it('phone', createClient('phone'));
|
||||||
it('desktop app', createClient('desktop app'));
|
it('desktop app', createClient('desktop app'));
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,10 @@ describe('Messages', () => {
|
||||||
it('has url', async () => {
|
it('has url', async () => {
|
||||||
expect(page.url()).toContain('/');
|
expect(page.url()).toContain('/');
|
||||||
});
|
});
|
||||||
const createApp = (name: string) => {
|
const createApp = (name: string) =>
|
||||||
return axios
|
axios
|
||||||
.post<IApplication>(`${gotify.url}/application`, {name}, axiosAuth)
|
.post<IApplication>(`${gotify.url}/application`, {name}, axiosAuth)
|
||||||
.then((resp) => resp.data.token);
|
.then((resp) => resp.data.token);
|
||||||
};
|
|
||||||
it('shows navigation', async () => {
|
it('shows navigation', async () => {
|
||||||
await page.waitForSelector(naviId);
|
await page.waitForSelector(naviId);
|
||||||
});
|
});
|
||||||
|
|
@ -121,11 +120,10 @@ describe('Messages', () => {
|
||||||
const backup2 = m('Backup done', 'Windows Server Backup finished (6.2GB).');
|
const backup2 = m('Backup done', 'Windows Server Backup finished (6.2GB).');
|
||||||
const backup3 = m('Backup done', 'Gotify Backup finished (0.1MB).');
|
const backup3 = m('Backup done', 'Gotify Backup finished (0.1MB).');
|
||||||
|
|
||||||
const createMessage = (msg: Partial<IMessage>, token: string) => {
|
const createMessage = (msg: Partial<IMessage>, token: string) =>
|
||||||
return axios.post<IMessage>(`${gotify.url}/message`, msg, {
|
axios.post<IMessage>(`${gotify.url}/message`, msg, {
|
||||||
headers: {'X-Gotify-Key': token},
|
headers: {'X-Gotify-Key': token},
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const expectMessages = async (toCheck: {
|
const expectMessages = async (toCheck: {
|
||||||
all: Msg[];
|
all: Msg[];
|
||||||
|
|
|
||||||
|
|
@ -49,13 +49,10 @@ const toggleEnabled = async (id: number) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const pluginInfo = async (className: string) => {
|
const pluginInfo = async (className: string) =>
|
||||||
return await innerText(page, `.plugin-info .${className} > span`);
|
await innerText(page, `.plugin-info .${className} > span`);
|
||||||
};
|
|
||||||
|
|
||||||
const getDisplayer = async () => {
|
const getDisplayer = async () => await innerText(page, '.displayer');
|
||||||
return await innerText(page, '.displayer');
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasReceivedMessage = async (title: RegExp, content: RegExp) => {
|
const hasReceivedMessage = async (title: RegExp, content: RegExp) => {
|
||||||
await page.click('#message-navigation a');
|
await page.click('#message-navigation a');
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,18 @@
|
||||||
export const heading = () => {
|
export const heading = () => `main h4`;
|
||||||
return `main h4`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const table = (tableSelector: string) => {
|
export const table = (tableSelector: string) => ({
|
||||||
return {
|
selector: () => tableSelector,
|
||||||
selector: () => tableSelector,
|
rows: () => `${tableSelector} tbody tr`,
|
||||||
rows: () => `${tableSelector} tbody tr`,
|
row: (index: number) => `${tableSelector} tbody tr:nth-child(${index})`,
|
||||||
row: (index: number) => `${tableSelector} tbody tr:nth-child(${index})`,
|
cell: (index: number, col: number, suffix = '') =>
|
||||||
cell: (index: number, col: number, suffix = '') =>
|
`${tableSelector} tbody tr:nth-child(${index}) td:nth-child(${col}) ${suffix}`,
|
||||||
`${tableSelector} tbody tr:nth-child(${index}) td:nth-child(${col}) ${suffix}`,
|
});
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const form = (dialogSelector: string) => {
|
export const form = (dialogSelector: string) => ({
|
||||||
return {
|
selector: () => dialogSelector,
|
||||||
selector: () => dialogSelector,
|
input: (selector: string) => `${dialogSelector} ${selector} input`,
|
||||||
input: (selector: string) => `${dialogSelector} ${selector} input`,
|
textarea: (selector: string) => `${dialogSelector} ${selector} textarea`,
|
||||||
textarea: (selector: string) => `${dialogSelector} ${selector} textarea`,
|
button: (selector: string) => `${dialogSelector} button${selector}`,
|
||||||
button: (selector: string) => `${dialogSelector} button${selector}`,
|
});
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const $confirmDialog = form('.confirm-dialog');
|
export const $confirmDialog = form('.confirm-dialog');
|
||||||
|
|
|
||||||
|
|
@ -82,8 +82,8 @@ const testFilePath = (): string => {
|
||||||
return path.join(testBuildPath, filename);
|
return path.join(testBuildPath, filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
const waitForGotify = (url: string): Promise<void> => {
|
const waitForGotify = (url: string): Promise<void> =>
|
||||||
return new Promise((resolve, err) => {
|
new Promise((resolve, err) => {
|
||||||
wait({resources: [url], timeout: 40000}, (error: string) => {
|
wait({resources: [url], timeout: 40000}, (error: string) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
@ -93,7 +93,6 @@ const waitForGotify = (url: string): Promise<void> => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const buildGoPlugin = (filename: string, pluginPath: string): Promise<void> => {
|
const buildGoPlugin = (filename: string, pluginPath: string): Promise<void> => {
|
||||||
process.stdout.write(`### Building Plugin ${pluginPath}\n`);
|
process.stdout.write(`### Building Plugin ${pluginPath}\n`);
|
||||||
|
|
|
||||||
|
|
@ -39,28 +39,28 @@ describe('User', () => {
|
||||||
name: string,
|
name: string,
|
||||||
password: string,
|
password: string,
|
||||||
isAdmin: boolean
|
isAdmin: boolean
|
||||||
): (() => Promise<void>) => {
|
): (() => Promise<void>) => async () => {
|
||||||
return async () => {
|
await page.click('#create-user');
|
||||||
await page.click('#create-user');
|
await page.waitForSelector($dialog.selector());
|
||||||
await page.waitForSelector($dialog.selector());
|
await page.type($dialog.input('.name'), name);
|
||||||
await page.type($dialog.input('.name'), name);
|
await page.type($dialog.input('.password'), password);
|
||||||
await page.type($dialog.input('.password'), password);
|
if (isAdmin) {
|
||||||
if (isAdmin) {
|
await page.click($dialog.input('.admin-rights'));
|
||||||
await page.click($dialog.input('.admin-rights'));
|
}
|
||||||
}
|
await page.click($dialog.button('.save-create'));
|
||||||
await page.click($dialog.button('.save-create'));
|
await waitToDisappear(page, $dialog.selector());
|
||||||
await waitToDisappear(page, $dialog.selector());
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
it('nicories', createUser('nicories', '123', false));
|
it('nicories', createUser('nicories', '123', false));
|
||||||
it('jmattheis', createUser('jmattheis', 'noice', true));
|
it('jmattheis', createUser('jmattheis', 'noice', true));
|
||||||
it('dude', createUser('dude', '1', false));
|
it('dude', createUser('dude', '1', false));
|
||||||
});
|
});
|
||||||
const hasUser = (name: string, isAdmin: boolean, row: number): (() => Promise<void>) => {
|
const hasUser = (
|
||||||
return async () => {
|
name: string,
|
||||||
expect(await innerText(page, $table.cell(row, Col.Name))).toBe(name);
|
isAdmin: boolean,
|
||||||
expect(await innerText(page, $table.cell(row, Col.Admin))).toBe(isAdmin ? 'Yes' : 'No');
|
row: number
|
||||||
};
|
): (() => Promise<void>) => async () => {
|
||||||
|
expect(await innerText(page, $table.cell(row, Col.Name))).toBe(name);
|
||||||
|
expect(await innerText(page, $table.cell(row, Col.Admin))).toBe(isAdmin ? 'Yes' : 'No');
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('has created users', () => {
|
describe('has created users', () => {
|
||||||
|
|
@ -131,10 +131,10 @@ describe('User', () => {
|
||||||
it('does logout', async () => await auth.logout(page));
|
it('does logout', async () => await auth.logout(page));
|
||||||
it('can login with new password (admin)', async () =>
|
it('can login with new password (admin)', async () =>
|
||||||
await auth.login(page, 'admin', 'changed'));
|
await auth.login(page, 'admin', 'changed'));
|
||||||
it('does logout', async () => await auth.logout(page));
|
it('does logout admin', async () => await auth.logout(page));
|
||||||
|
|
||||||
it('can login with nicolas', async () => await auth.login(page, 'nicolas', '123'));
|
it('can login with nicolas', async () => await auth.login(page, 'nicolas', '123'));
|
||||||
it('does logout', async () => await auth.logout(page));
|
it('does logout nicolas', async () => await auth.logout(page));
|
||||||
it('can login with jmattheis', async () => await auth.login(page, 'jmattheis', 'unicorn'));
|
it('can login with jmattheis', async () => await auth.login(page, 'jmattheis', 'unicorn'));
|
||||||
it('does logout', async () => await auth.logout(page));
|
it('does logout jmattheis', async () => await auth.logout(page));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ export const innerText = async (page: ElementHandle | Page, selector: string): P
|
||||||
const element = await page.$(selector);
|
const element = await page.$(selector);
|
||||||
const handle = await element!.getProperty('innerText');
|
const handle = await element!.getProperty('innerText');
|
||||||
const value = await handle.jsonValue();
|
const value = await handle.jsonValue();
|
||||||
return (value as object).toString().trim();
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return (value as any).toString().trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clickByText = async (page: Page, selector: string, text: string): Promise<void> => {
|
export const clickByText = async (page: Page, selector: string, text: string): Promise<void> => {
|
||||||
|
|
@ -21,42 +22,32 @@ export const clickByText = async (page: Page, selector: string, text: string): P
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const count = async (page: Page, selector: string): Promise<number> => {
|
export const count = async (page: Page, selector: string): Promise<number> =>
|
||||||
return page.$$(selector).then((elements) => elements.length);
|
page.$$(selector).then((elements) => elements.length);
|
||||||
};
|
|
||||||
|
|
||||||
export const waitToDisappear = async (page: Page, selector: string): Promise<JSHandle> => {
|
export const waitToDisappear = async (page: Page, selector: string): Promise<JSHandle> =>
|
||||||
return page.waitForFunction(
|
page.waitForFunction((_selector: string) => !document.querySelector(_selector), {}, selector);
|
||||||
(_selector: string) => !document.querySelector(_selector),
|
|
||||||
{},
|
|
||||||
selector
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const waitForCount = async (
|
export const waitForCount = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
selector: string,
|
selector: string,
|
||||||
amount: number
|
amount: number
|
||||||
): Promise<JSHandle> => {
|
): Promise<JSHandle> =>
|
||||||
return page.waitForFunction(
|
page.waitForFunction(
|
||||||
(_selector: string, _amount: number) =>
|
(_selector: string, _amount: number) =>
|
||||||
document.querySelectorAll(_selector).length === _amount,
|
document.querySelectorAll(_selector).length === _amount,
|
||||||
{},
|
{},
|
||||||
selector,
|
selector,
|
||||||
amount
|
amount
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
export const waitForExists = async (page: Page, selector: string, text: string): Promise<void> => {
|
export const waitForExists = async (page: Page, selector: string, text: string): Promise<void> => {
|
||||||
text = text.toLowerCase();
|
text = text.toLowerCase();
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(
|
||||||
(_selector: string, _text: string) => {
|
(_selector: string, _text: string) =>
|
||||||
return (
|
Array.from(document.querySelectorAll(_selector)).filter(
|
||||||
Array.from(document.querySelectorAll(_selector)).filter(
|
(element) => element.textContent!.toLowerCase().trim() === _text
|
||||||
(element) => element.textContent!.toLowerCase().trim() === _text
|
).length > 0,
|
||||||
).length > 0
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{},
|
{},
|
||||||
selector,
|
selector,
|
||||||
text
|
text
|
||||||
|
|
@ -67,7 +58,6 @@ export const clearField = async (element: ElementHandle | Page, selector: string
|
||||||
const elementHandle = await element.$(selector);
|
const elementHandle = await element.$(selector);
|
||||||
if (!elementHandle) {
|
if (!elementHandle) {
|
||||||
fail();
|
fail();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
await elementHandle.click();
|
await elementHandle.click();
|
||||||
await elementHandle.focus();
|
await elementHandle.focus();
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,5 @@ declare module 'react-timeago' {
|
||||||
date: string;
|
date: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TimeAgo extends React.Component<ITimeAgoProps, any> {}
|
export default class TimeAgo extends React.Component<ITimeAgoProps, unknown> {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ interface IState {
|
||||||
|
|
||||||
export default class AddEditDialog extends Component<IProps, IState> {
|
export default class AddEditDialog extends Component<IProps, IState> {
|
||||||
public state = {
|
public state = {
|
||||||
name: '',
|
name: this.props.name ?? '',
|
||||||
pass: '',
|
pass: '',
|
||||||
admin: false,
|
admin: this.props.admin ?? false,
|
||||||
};
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
|
@ -107,11 +107,6 @@ export default class AddEditDialog extends Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillMount() {
|
|
||||||
const {name, admin} = this.props;
|
|
||||||
this.setState({...this.state, name: name || '', admin: admin || false});
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleChange(propertyName: string, event: ChangeEvent<HTMLInputElement>) {
|
private handleChange(propertyName: string, event: ChangeEvent<HTMLInputElement>) {
|
||||||
const state = this.state;
|
const state = this.state;
|
||||||
state[propertyName] = event.target.value;
|
state[propertyName] = event.target.value;
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,8 @@ export class UserStore extends BaseStore<IUser> {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected requestItems = (): Promise<IUser[]> => {
|
protected requestItems = (): Promise<IUser[]> =>
|
||||||
return axios.get<IUser[]>(`${config.get('url')}user`).then((response) => response.data);
|
axios.get<IUser[]>(`${config.get('url')}user`).then((response) => response.data);
|
||||||
};
|
|
||||||
|
|
||||||
protected requestDelete(id: number): Promise<void> {
|
protected requestDelete(id: number): Promise<void> {
|
||||||
return axios
|
return axios
|
||||||
|
|
|
||||||
|
|
@ -90,17 +90,15 @@ class Users extends Component<WithStyles<'wrapper'> & Stores<'userStore'>> {
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{users.map((user: IUser) => {
|
{users.map((user: IUser) => (
|
||||||
return (
|
<UserRow
|
||||||
<UserRow
|
key={user.id}
|
||||||
key={user.id}
|
name={user.name}
|
||||||
name={user.name}
|
admin={user.admin}
|
||||||
admin={user.admin}
|
fDelete={() => (this.deleteId = user.id)}
|
||||||
fDelete={() => (this.deleteId = user.id)}
|
fEdit={() => (this.editId = user.id)}
|
||||||
fEdit={() => (this.editId = user.id)}
|
/>
|
||||||
/>
|
))}
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"keyofStringsOnly": true
|
"keyofStringsOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
|
|
|
||||||
6794
ui/yarn.lock
6794
ui/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue