Major deps updates: router, redux, intl, react-transition-group, flow-type, testing deps

This commit is contained in:
SleepWalker 2019-06-30 16:32:50 +03:00
parent 6821bcfe40
commit 627d503d43
76 changed files with 2819 additions and 1703 deletions

View File

@ -1,5 +1,5 @@
// flow-typed signature: e68caa23426dedefced5662fb92b4638
// flow-typed version: d13a175635/react-intl_v2.x.x/flow_>=v0.57.x
// flow-typed signature: 3902298e28ed22d8cd8d49828801a760
// flow-typed version: eb50783110/react-intl_v2.x.x/flow_>=v0.63.x
/**
* Original implementation of this file by @marudor at https://github.com/marudor/flowInterfaces
@ -7,39 +7,39 @@
* https://github.com/marudor/flowInterfaces/issues/6
*/
// Mostly from https://github.com/yahoo/react-intl/wiki/API#react-intl-api
declare module "react-intl" {
import type { Element, ChildrenArray } from "react";
import type { Element, ChildrenArray } from "react";
type $npm$ReactIntl$LocaleData = {
declare type $npm$ReactIntl$LocaleData = {
locale: string,
[key: string]: any
};
};
type $npm$ReactIntl$MessageDescriptor = {
declare type $npm$ReactIntl$MessageDescriptor = {
id: string,
description?: string,
defaultMessage?: string
};
};
type $npm$ReactIntl$IntlConfig = {
declare type $npm$ReactIntl$IntlConfig = {
locale: string,
formats: Object,
messages: { [id: string]: string },
defaultLocale?: string,
defaultFormats?: Object
};
};
type $npm$ReactIntl$IntlProviderConfig = {
declare type $npm$ReactIntl$IntlProviderConfig = {
locale?: string,
formats?: Object,
messages?: { [id: string]: string },
defaultLocale?: string,
defaultFormats?: Object
};
};
type $npm$ReactIntl$IntlFormat = {
declare type $npm$ReactIntl$IntlFormat = {
formatDate: (value: any, options?: Object) => string,
formatTime: (value: any, options?: Object) => string,
formatRelative: (value: any, options?: Object) => string,
@ -53,12 +53,12 @@ type $npm$ReactIntl$IntlFormat = {
messageDescriptor: $npm$ReactIntl$MessageDescriptor,
values?: Object
) => string
};
};
type $npm$ReactIntl$IntlShape = $npm$ReactIntl$IntlConfig &
declare type $npm$ReactIntl$IntlShape = $npm$ReactIntl$IntlConfig &
$npm$ReactIntl$IntlFormat & { now: () => number };
type $npm$ReactIntl$DateTimeFormatOptions = {
declare type $npm$ReactIntl$DateTimeFormatOptions = {
localeMatcher?: "best fit" | "lookup",
formatMatcher?: "basic" | "best fit",
@ -74,14 +74,14 @@ type $npm$ReactIntl$DateTimeFormatOptions = {
minute?: "numeric" | "2-digit",
second?: "numeric" | "2-digit",
timeZoneName?: "short" | "long"
};
};
type $npm$ReactIntl$RelativeFormatOptions = {
declare type $npm$ReactIntl$RelativeFormatOptions = {
style?: "best fit" | "numeric",
units?: "second" | "minute" | "hour" | "day" | "month" | "year"
};
};
type $npm$ReactIntl$NumberFormatOptions = {
declare type $npm$ReactIntl$NumberFormatOptions = {
localeMatcher?: "best fit" | "lookup",
style?: "decimal" | "currency" | "percent",
@ -96,13 +96,13 @@ type $npm$ReactIntl$NumberFormatOptions = {
maximumFractionDigits?: number,
minimumSignificantDigits?: number,
maximumSignificantDigits?: number
};
};
type $npm$ReactIntl$PluralFormatOptions = {
declare type $npm$ReactIntl$PluralFormatOptions = {
style?: "cardinal" | "ordinal"
};
};
type $npm$ReactIntl$PluralCategoryString =
declare type $npm$ReactIntl$PluralCategoryString =
| "zero"
| "one"
| "two"
@ -110,9 +110,7 @@ type $npm$ReactIntl$PluralCategoryString =
| "many"
| "other";
type $npm$ReactIntl$DateParseable = number | string | Date;
declare module "react-intl" {
declare type $npm$ReactIntl$DateParseable = number | string | Date;
// PropType checker
declare function intlShape(
props: Object,
@ -123,7 +121,7 @@ declare module "react-intl" {
data: $npm$ReactIntl$LocaleData | Array<$npm$ReactIntl$LocaleData>
): void;
declare function defineMessages<
T: { [key: string]: $npm$ReactIntl$MessageDescriptor }
T: { [key: string]: $Exact<$npm$ReactIntl$MessageDescriptor> }
>(
messageDescriptors: T
): T;
@ -132,6 +130,10 @@ declare module "react-intl" {
intl: $npm$ReactIntl$IntlShape
}
declare type InjectIntlVoidProps = {
intl: $npm$ReactIntl$IntlShape | void
}
declare type ComponentWithDefaultProps<DefaultProps: {}, Props: {}> =
| React$ComponentType<Props>
| React$StatelessFunctionalComponent<Props>
@ -152,19 +154,12 @@ declare module "react-intl" {
IntlInjectedComponent<TOwnProps, TDefaultProps>
>;
declare function injectIntl<OriginalProps: InjectIntlProvidedProps, DefaultProps: {}>
(
component: ComponentWithDefaultProps<DefaultProps, OriginalProps>,
declare function injectIntl<P: {}, Component: React$ComponentType<P>>(
WrappedComponent: Component,
options?: InjectIntlOptions,
):
IntlInjectedComponentClass<$Diff<OriginalProps, InjectIntlProvidedProps>, DefaultProps>
declare function injectIntl<OriginalProps: InjectIntlProvidedProps>
(
component: React$ComponentType<OriginalProps>,
options?: InjectIntlOptions,
):
IntlInjectedComponentClass<$Diff<OriginalProps, InjectIntlProvidedProps>>;
): React$ComponentType<
$Diff<React$ElementConfig<Component>, InjectIntlVoidProps>
>;
declare function formatMessage(
messageDescriptor: $npm$ReactIntl$MessageDescriptor,
@ -202,7 +197,9 @@ declare module "react-intl" {
$npm$ReactIntl$MessageDescriptor & {
values?: Object,
tagName?: string,
children?: (...formattedMessage: Array<React$Node>) => React$Node
children?:
| ((...formattedMessage: Array<React$Node>) => React$Node)
| (string => React$Node)
}
> {}
declare class FormattedHTMLMessage extends React$Component<

View File

@ -1,114 +1,276 @@
// flow-typed signature: c5fac64666f9589a0c1b2de956dc7919
// flow-typed version: 81d6274128/react-redux_v5.x.x/flow_>=v0.53.x
// flow-typed signature: f06f00c3ad0cfedb90c0c6de04b219f3
// flow-typed version: 3a6d556e4b/react-redux_v5.x.x/flow_>=v0.89.x
// flow-typed signature: 8db7b853f57c51094bf0ab8b2650fd9c
// flow-typed version: ab8db5f14d/react-redux_v5.x.x/flow_>=v0.30.x
/**
The order of type arguments for connect() is as follows:
import type { Dispatch, Store } from "redux";
connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>()
declare module "react-redux" {
/*
In Flow v0.89 only the first two are mandatory to specify. Other 4 can be repaced with the new awesome type placeholder:
connect<Props, OwnProps, _, _, _, _>()
But beware, in case of weird type errors somewhere in random places
just type everything and get to a green field and only then try to
remove the definitions you see bogus.
Decrypting the abbreviations:
WC = Component being wrapped
S = State
A = Action
D = Dispatch
OP = OwnProps
SP = StateProps
DP = DispatchProps
MP = Merge props
RSP = Returned state props
RDP = Returned dispatch props
RMP = Returned merge props
CP = Props for returned component
Com = React Component
ST = Static properties of Com
EFO = Extra factory options (used only in connectAdvanced)
*/
*/
declare module "react-redux" {
// ------------------------------------------------------------
// Typings for connect()
// ------------------------------------------------------------
declare type MapStateToProps<S, OP: Object, SP: Object> = (
state: S,
ownProps: OP
) => SP | MapStateToProps<S, OP, SP>;
declare export type Options<S, OP, SP, MP> = {|
pure?: boolean,
withRef?: boolean,
areStatesEqual?: (next: S, prev: S) => boolean,
areOwnPropsEqual?: (next: OP, prev: OP) => boolean,
areStatePropsEqual?: (next: SP, prev: SP) => boolean,
areMergedPropsEqual?: (next: MP, prev: MP) => boolean,
storeKey?: string,
|};
declare type MapDispatchToProps<A, OP: Object, DP: Object> =
| ((dispatch: Dispatch<A>, ownProps: OP) => DP)
| DP;
declare type MapStateToProps<-S, -OP, +SP> =
| ((state: S, ownProps: OP) => SP)
// If you want to use the factory function but get a strange error
// like "function is not an object" then just type the factory function
// like this:
// const factory: (State, OwnProps) => (State, OwnProps) => StateProps
// and provide the StateProps type to the SP type parameter.
| ((state: S, ownProps: OP) => (state: S, ownProps: OP) => SP);
declare type MergeProps<SP, DP: Object, OP: Object, P: Object> = (
declare type Bind<D> = <A, R>((...A) => R) => (...A) => $Call<D, R>;
declare type MapDispatchToPropsFn<D, -OP, +DP> =
| ((dispatch: D, ownProps: OP) => DP)
// If you want to use the factory function but get a strange error
// like "function is not an object" then just type the factory function
// like this:
// const factory: (Dispatch, OwnProps) => (Dispatch, OwnProps) => DispatchProps
// and provide the DispatchProps type to the DP type parameter.
| ((dispatch: D, ownProps: OP) => (dispatch: D, ownProps: OP) => DP);
declare class ConnectedComponent<OP, +WC> extends React$Component<OP> {
static +WrappedComponent: WC;
getWrappedInstance(): React$ElementRef<WC>;
}
// The connection of the Wrapped Component and the Connected Component
// happens here in `MP: P`. It means that type wise MP belongs to P,
// so to say MP >= P.
declare type Connector<P, OP, MP: P> = <WC: React$ComponentType<P>>(
WC,
) => Class<ConnectedComponent<OP, WC>> & WC;
// No `mergeProps` argument
// Got error like inexact OwnProps is incompatible with exact object type?
// Just make the OP parameter for `connect()` an exact object.
declare type MergeOP<OP, D> = {| ...$Exact<OP>, dispatch: D |};
declare type MergeOPSP<OP, SP, D> = {| ...$Exact<OP>, ...SP, dispatch: D |};
declare type MergeOPDP<OP, DP> = {| ...$Exact<OP>, ...DP |};
declare type MergeOPSPDP<OP, SP, DP> = {| ...$Exact<OP>, ...SP, ...DP |};
declare export function connect<-P, -OP, -SP, -DP, -S, -D>(
mapStateToProps?: null | void,
mapDispatchToProps?: null | void,
mergeProps?: null | void,
options?: ?Options<S, OP, {||}, MergeOP<OP, D>>,
): Connector<P, OP, MergeOP<OP, D>>;
declare export function connect<-P, -OP, -SP, -DP, -S, -D>(
// If you get error here try adding return type to your mapStateToProps function
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps?: null | void,
mergeProps?: null | void,
options?: ?Options<S, OP, SP, MergeOPSP<OP, SP, D>>,
): Connector<P, OP, MergeOPSP<OP, SP, D>>;
// In this case DP is an object of functions which has been bound to dispatch
// by the given mapDispatchToProps function.
declare export function connect<-P, -OP, -SP, -DP, S, D>(
mapStateToProps: null | void,
mapDispatchToProps: MapDispatchToPropsFn<D, OP, DP>,
mergeProps?: null | void,
options?: ?Options<S, OP, {||}, MergeOPDP<OP, DP>>,
): Connector<P, OP, MergeOPDP<OP, DP>>;
// In this case DP is an object of action creators not yet bound to dispatch,
// this difference is not important in the vanila redux,
// but in case of usage with redux-thunk, the return type may differ.
declare export function connect<-P, -OP, -SP, -DP, S, D>(
mapStateToProps: null | void,
mapDispatchToProps: DP,
mergeProps?: null | void,
options?: ?Options<S, OP, {||}, MergeOPDP<OP, DP>>,
): Connector<P, OP, MergeOPDP<OP, $ObjMap<DP, Bind<D>>>>;
declare export function connect<-P, -OP, -SP, -DP, S, D>(
// If you get error here try adding return type to your mapStateToProps function
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: MapDispatchToPropsFn<D, OP, DP>,
mergeProps?: null | void,
options?: ?Options<S, OP, SP, {| ...OP, ...SP, ...DP |}>,
): Connector<P, OP, {| ...OP, ...SP, ...DP |}>;
declare export function connect<-P, -OP, -SP, -DP, S, D>(
// If you get error here try adding return type to your mapStateToProps function
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: DP,
mergeProps?: null | void,
options?: ?Options<S, OP, SP, MergeOPSPDP<OP, SP, DP>>,
): Connector<P, OP, MergeOPSPDP<OP, SP, $ObjMap<DP, Bind<D>>>>;
// With `mergeProps` argument
declare type MergeProps<+P, -OP, -SP, -DP> = (
stateProps: SP,
dispatchProps: DP,
ownProps: OP
ownProps: OP,
) => P;
declare type Context = { store: Store<*, *> };
declare export function connect<-P, -OP, -SP: {||}, -DP: {||}, S, D>(
mapStateToProps: null | void,
mapDispatchToProps: null | void,
// If you get error here try adding return type to you mapStateToProps function
mergeProps: MergeProps<P, OP, {||}, {| dispatch: D |}>,
options?: ?Options<S, OP, {||}, P>,
): Connector<P, OP, P>;
declare class ConnectedComponent<OP, P> extends React$Component<OP> {
static WrappedComponent: Class<React$Component<P>>,
getWrappedInstance(): React$Component<P>,
props: OP,
state: void
}
declare export function connect<-P, -OP, -SP, -DP: {||}, S, D>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: null | void,
// If you get error here try adding return type to you mapStateToProps function
mergeProps: MergeProps<P, OP, SP, {| dispatch: D |}>,
options?: ?Options<S, OP, SP, P>,
): Connector<P, OP, P>;
declare type ConnectedComponentClass<OP, P> = Class<
ConnectedComponent<OP, P>
>;
// In this case DP is an object of functions which has been bound to dispatch
// by the given mapDispatchToProps function.
declare export function connect<-P, -OP, -SP: {||}, -DP, S, D>(
mapStateToProps: null | void,
mapDispatchToProps: MapDispatchToPropsFn<D, OP, DP>,
mergeProps: MergeProps<P, OP, {||}, DP>,
options?: ?Options<S, OP, {||}, P>,
): Connector<P, OP, P>;
declare type Connector<OP, P> = (
component: React$ComponentType<P>
) => ConnectedComponentClass<OP, P>;
// In this case DP is an object of action creators not yet bound to dispatch,
// this difference is not important in the vanila redux,
// but in case of usage with redux-thunk, the return type may differ.
declare export function connect<-P, -OP, -SP: {||}, -DP, S, D>(
mapStateToProps: null | void,
mapDispatchToProps: DP,
mergeProps: MergeProps<P, OP, {||}, $ObjMap<DP, Bind<D>>>,
options?: ?Options<S, OP, {||}, P>,
): Connector<P, OP, P>;
declare class Provider<S, A> extends React$Component<{
store: Store<S, A>,
children?: any
// In this case DP is an object of functions which has been bound to dispatch
// by the given mapDispatchToProps function.
declare export function connect<-P, -OP, -SP, -DP, S, D>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: MapDispatchToPropsFn<D, OP, DP>,
mergeProps: MergeProps<P, OP, SP, DP>,
options?: ?Options<S, OP, SP, P>,
): Connector<P, OP, P>;
// In this case DP is an object of action creators not yet bound to dispatch,
// this difference is not important in the vanila redux,
// but in case of usage with redux-thunk, the return type may differ.
declare export function connect<-P, -OP, -SP, -DP, S, D>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: DP,
mergeProps: MergeProps<P, OP, SP, $ObjMap<DP, Bind<D>>>,
options?: ?Options<S, OP, SP, P>,
): Connector<P, OP, P>;
// ------------------------------------------------------------
// Typings for Provider
// ------------------------------------------------------------
declare export class Provider<Store> extends React$Component<{
store: Store,
children?: React$Node,
}> {}
declare function createProvider(
declare export function createProvider(
storeKey?: string,
subKey?: string
): Provider<*, *>;
subKey?: string,
): Class<Provider<*>>;
declare type ConnectOptions = {
pure?: boolean,
withRef?: boolean
// ------------------------------------------------------------
// Typings for connectAdvanced()
// ------------------------------------------------------------
declare type ConnectAdvancedOptions = {
getDisplayName?: (name: string) => string,
methodName?: string,
renderCountProp?: string,
shouldHandleStateChanges?: boolean,
storeKey?: string,
withRef?: boolean,
};
declare type Null = null | void;
declare type SelectorFactoryOptions<Com> = {
getDisplayName: (name: string) => string,
methodName: string,
renderCountProp: ?string,
shouldHandleStateChanges: boolean,
storeKey: string,
withRef: boolean,
displayName: string,
wrappedComponentName: string,
WrappedComponent: Com,
};
declare function connect<A, OP>(
...rest: Array<void> // <= workaround for https://github.com/facebook/flow/issues/2360
): Connector<OP, $Supertype<{ dispatch: Dispatch<A> } & OP>>;
declare type MapStateToPropsEx<S: Object, SP: Object, RSP: Object> = (
state: S,
props: SP,
) => RSP;
declare function connect<A, OP>(
mapStateToProps: Null,
mapDispatchToProps: Null,
mergeProps: Null,
options: ConnectOptions
): Connector<OP, $Supertype<{ dispatch: Dispatch<A> } & OP>>;
declare type SelectorFactory<
Com: React$ComponentType<*>,
Dispatch,
S: Object,
OP: Object,
EFO: Object,
CP: Object,
> = (
dispatch: Dispatch,
factoryOptions: SelectorFactoryOptions<Com> & EFO,
) => MapStateToPropsEx<S, OP, CP>;
declare function connect<S, A, OP, SP>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: Null,
mergeProps: Null,
options?: ConnectOptions
): Connector<OP, $Supertype<SP & { dispatch: Dispatch<A> } & OP>>;
declare export function connectAdvanced<
Com: React$ComponentType<*>,
D,
S: Object,
OP: Object,
CP: Object,
EFO: Object,
ST: { [_: $Keys<Com>]: any },
>(
selectorFactory: SelectorFactory<Com, D, S, OP, EFO, CP>,
connectAdvancedOptions: ?(ConnectAdvancedOptions & EFO),
): (component: Com) => React$ComponentType<OP> & $Shape<ST>;
declare function connect<A, OP, DP>(
mapStateToProps: Null,
mapDispatchToProps: MapDispatchToProps<A, OP, DP>,
mergeProps: Null,
options?: ConnectOptions
): Connector<OP, $Supertype<DP & OP>>;
declare function connect<S, A, OP, SP, DP>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: MapDispatchToProps<A, OP, DP>,
mergeProps: Null,
options?: ConnectOptions
): Connector<OP, $Supertype<SP & DP & OP>>;
declare function connect<S, A, OP, SP, DP, P>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: Null,
mergeProps: MergeProps<SP, DP, OP, P>,
options?: ConnectOptions
): Connector<OP, P>;
declare function connect<S, A, OP, SP, DP, P>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: MapDispatchToProps<A, OP, DP>,
mergeProps: MergeProps<SP, DP, OP, P>,
options?: ConnectOptions
): Connector<OP, P>;
declare export default {
Provider: typeof Provider,
createProvider: typeof createProvider,
connect: typeof connect,
connectAdvanced: typeof connectAdvanced,
};
}

View File

@ -0,0 +1,176 @@
// flow-typed signature: 9987f80c12a2cad7dfa2b08cc14d2edc
// flow-typed version: 2973a15489/react-router-dom_v5.x.x/flow_>=v0.98.x
declare module "react-router-dom" {
declare export var BrowserRouter: React$ComponentType<{|
basename?: string,
forceRefresh?: boolean,
getUserConfirmation?: GetUserConfirmation,
keyLength?: number,
children?: React$Node
|}>
declare export var HashRouter: React$ComponentType<{|
basename?: string,
getUserConfirmation?: GetUserConfirmation,
hashType?: "slash" | "noslash" | "hashbang",
children?: React$Node
|}>
declare export var Link: React$ComponentType<{
className?: string,
to: string | LocationShape,
replace?: boolean,
children?: React$Node
}>
declare export var NavLink: React$ComponentType<{
to: string | LocationShape,
activeClassName?: string,
className?: string,
activeStyle?: { +[string]: mixed },
style?: { +[string]: mixed },
isActive?: (match: Match, location: Location) => boolean,
children?: React$Node,
exact?: boolean,
strict?: boolean
}>
// NOTE: Below are duplicated from react-router. If updating these, please
// update the react-router and react-router-native types as well.
declare export type Location = {
pathname: string,
search: string,
hash: string,
state?: any,
key?: string
};
declare export type LocationShape = {
pathname?: string,
search?: string,
hash?: string,
state?: any
};
declare export type HistoryAction = "PUSH" | "REPLACE" | "POP";
declare export type RouterHistory = {
length: number,
location: Location,
action: HistoryAction,
listen(
callback: (location: Location, action: HistoryAction) => void
): () => void,
push(path: string | LocationShape, state?: any): void,
replace(path: string | LocationShape, state?: any): void,
go(n: number): void,
goBack(): void,
goForward(): void,
canGo?: (n: number) => boolean,
block(
callback: string | (location: Location, action: HistoryAction) => ?string
): () => void,
// createMemoryHistory
index?: number,
entries?: Array<Location>
};
declare export type Match = {
params: { [key: string]: ?string },
isExact: boolean,
path: string,
url: string
};
declare export type ContextRouter = {|
history: RouterHistory,
location: Location,
match: Match,
staticContext?: StaticRouterContext
|};
declare type ContextRouterVoid = {
history: RouterHistory | void,
location: Location | void,
match: Match | void,
staticContext?: StaticRouterContext | void
};
declare export type GetUserConfirmation = (
message: string,
callback: (confirmed: boolean) => void
) => void;
declare export type StaticRouterContext = {
url?: string
};
declare export var StaticRouter: React$ComponentType<{|
basename?: string,
location?: string | Location,
context: StaticRouterContext,
children?: React$Node
|}>
declare export var MemoryRouter: React$ComponentType<{|
initialEntries?: Array<LocationShape | string>,
initialIndex?: number,
getUserConfirmation?: GetUserConfirmation,
keyLength?: number,
children?: React$Node
|}>
declare export var Router: React$ComponentType<{|
history: RouterHistory,
children?: React$Node
|}>
declare export var Prompt: React$ComponentType<{|
message: string | ((location: Location) => string | boolean),
when?: boolean
|}>
declare export var Redirect: React$ComponentType<{|
to: string | LocationShape,
push?: boolean,
from?: string,
exact?: boolean,
strict?: boolean
|}>
declare export var Route: React$ComponentType<{|
component?: React$ComponentType<*>,
render?: (router: ContextRouter) => React$Node,
children?: React$ComponentType<ContextRouter> | React$Node,
path?: string | Array<string>,
exact?: boolean,
strict?: boolean,
location?: LocationShape,
sensitive?: boolean
|}>
declare export var Switch: React$ComponentType<{|
children?: React$Node,
location?: Location
|}>
declare export function withRouter<Props: {}, Component: React$ComponentType<Props>>(
WrappedComponent: Component
): React$ComponentType<$Diff<React$ElementConfig<Component>, ContextRouterVoid>>;
declare type MatchPathOptions = {
path?: string,
exact?: boolean,
sensitive?: boolean,
strict?: boolean
};
declare export function matchPath(
pathname: string,
options?: MatchPathOptions | string,
parent?: Match
): null | Match;
declare export function generatePath(pattern?: string, params?: { +[string]: mixed }): string;
}

View File

@ -1,139 +0,0 @@
// flow-typed signature: b45080e7e6a55f1c9092f07c205cd527
// flow-typed version: 3e35e41eb5/react-router_v4.x.x/flow_v0.53.x
// flow-typed signature: b701192ca557cf27adf1b295517299fd
// flow-typed version: b43dff3e0e/react-router_v4.x.x/flow_>=v0.53.x
import * as React from "react";
declare module "react-router" {
// NOTE: many of these are re-exported by react-router-dom and
// react-router-native, so when making changes, please be sure to update those
// as well.
declare export type Location = {
pathname: string,
search: string,
hash: string,
state?: any,
key?: string
};
declare export type LocationShape = {
pathname?: string,
search?: string,
hash?: string,
state?: any
};
declare export type HistoryAction = "PUSH" | "REPLACE" | "POP";
declare export type RouterHistory = {
length: number,
location: Location,
action: HistoryAction,
listen(
callback: (location: Location, action: HistoryAction) => void
): () => void,
push(path: string | LocationShape, state?: any): void,
replace(path: string | LocationShape, state?: any): void,
go(n: number): void,
goBack(): void,
goForward(): void,
canGo?: (n: number) => boolean,
block(
callback: (location: Location, action: HistoryAction) => boolean
): void,
// createMemoryHistory
index?: number,
entries?: Array<Location>
};
declare export type Match = {
params: { [key: string]: ?string },
isExact: boolean,
path: string,
url: string
};
declare export type ContextRouter = {
history: RouterHistory,
location: Location,
match: Match
};
declare export type GetUserConfirmation = (
message: string,
callback: (confirmed: boolean) => void
) => void;
declare type StaticRouterContext = {
url?: string
};
declare type StaticRouterProps = {
basename?: string,
location?: string | Location,
context: StaticRouterContext,
children?: React$Element<*>
};
declare export class StaticRouter extends React$Component<
StaticRouterProps
> {}
declare type MemoryRouterProps = {
initialEntries?: Array<LocationShape | string>,
initialIndex?: number,
getUserConfirmation?: GetUserConfirmation,
keyLength?: number,
children?: React$Element<*>
};
declare export class MemoryRouter extends React$Component<
MemoryRouterProps
> {}
declare type RouterProps = {
history: RouterHistory,
children?: React$Element<*>
};
declare export class Router extends React$Component<RouterProps> {}
declare type PromptProps = {
message: string | ((location: Location) => string | true),
when?: boolean
};
declare export class Prompt extends React$Component<PromptProps> {}
declare type RedirectProps = {
to: string | LocationShape,
push?: boolean
};
declare export class Redirect extends React$Component<RedirectProps> {}
declare type RouteProps = {
component?: React$ComponentType<*>,
render?: (router: ContextRouter) => React$Element<*>,
children?: (router: ContextRouter) => React$Element<*>,
path?: string,
exact?: boolean,
strict?: boolean
};
declare export class Route extends React$Component<RouteProps> {}
declare type SwithcProps = {
children?: Array<React$Element<*>>
};
declare export class Switch extends React$Component<SwithcProps> {}
declare export function withRouter<P>(
Component: React$ComponentType<ContextRouter & P>
): React$ComponentType<P>;
declare type MatchPathOptions = {
exact?: boolean,
strict?: boolean
};
declare export function matchPath(
pathname: string,
path: string,
options?: MatchPathOptions
): null | Match;
}

View File

@ -1,109 +0,0 @@
// flow-typed signature: 86993bd000012d3e1ef10d757d16952d
// flow-typed version: a165222d28/redux_v3.x.x/flow_>=v0.33.x
declare module 'redux' {
/*
S = State
A = Action
D = Dispatch
*/
declare type DispatchAPI<A> = (action: A) => A;
declare type Dispatch<A: { type: $Subtype<string> }> = DispatchAPI<A>;
declare type MiddlewareAPI<S, A, D = Dispatch<A>> = {
dispatch: D;
getState(): S;
};
declare type Store<S, A, D = Dispatch<A>> = {
// rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages)
dispatch: D;
getState(): S;
subscribe(listener: () => void): () => void;
replaceReducer(nextReducer: Reducer<S, A>): void
};
declare type Reducer<S, A> = (state: S, action: A) => S;
declare type CombinedReducer<S, A> = (state: $Shape<S> & {} | void, action: A) => S;
declare type Middleware<S, A, D = Dispatch<A>> =
(api: MiddlewareAPI<S, A, D>) =>
(next: D) => D;
declare type StoreCreator<S, A, D = Dispatch<A>> = {
(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
(reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
};
declare type StoreEnhancer<S, A, D = Dispatch<A>> = (next: StoreCreator<S, A, D>) => StoreCreator<S, A, D>;
declare function createStore<S, A, D>(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
declare function createStore<S, A, D>(reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
declare function applyMiddleware<S, A, D>(...middlewares: Array<Middleware<S, A, D>>): StoreEnhancer<S, A, D>;
declare type ActionCreator<A, B> = (...args: Array<B>) => A;
declare type ActionCreators<K, A> = { [key: K]: ActionCreator<A, any> };
declare function bindActionCreators<A, C: ActionCreator<A, any>, D: DispatchAPI<A>>(actionCreator: C, dispatch: D): C;
declare function bindActionCreators<A, K, C: ActionCreators<K, A>, D: DispatchAPI<A>>(actionCreators: C, dispatch: D): C;
declare function combineReducers<O: Object, A>(reducers: O): CombinedReducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;
declare function compose<A, B>(ab: (a: A) => B): (a: A) => B
declare function compose<A, B, C>(
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => C
declare function compose<A, B, C, D>(
cd: (c: C) => D,
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => D
declare function compose<A, B, C, D, E>(
de: (d: D) => E,
cd: (c: C) => D,
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => E
declare function compose<A, B, C, D, E, F>(
ef: (e: E) => F,
de: (d: D) => E,
cd: (c: C) => D,
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => F
declare function compose<A, B, C, D, E, F, G>(
fg: (f: F) => G,
ef: (e: E) => F,
de: (d: D) => E,
cd: (c: C) => D,
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => G
declare function compose<A, B, C, D, E, F, G, H>(
gh: (g: G) => H,
fg: (f: F) => G,
ef: (e: E) => F,
de: (d: D) => E,
cd: (c: C) => D,
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => H
declare function compose<A, B, C, D, E, F, G, H, I>(
hi: (h: H) => I,
gh: (g: G) => H,
fg: (f: F) => G,
ef: (e: E) => F,
de: (d: D) => E,
cd: (c: C) => D,
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => I
}

100
flow-typed/npm/redux_v4.x.x.js vendored Normal file
View File

@ -0,0 +1,100 @@
// flow-typed signature: a49a6c96fe8a8bb3330cce2028588f4c
// flow-typed version: de5b3a01c6/redux_v4.x.x/flow_>=v0.89.x
declare module 'redux' {
/*
S = State
A = Action
D = Dispatch
*/
declare export type Action<T> = {
type: T
}
declare export type DispatchAPI<A> = (action: A) => A;
declare export type Dispatch<A: { type: * }> = DispatchAPI<A>;
declare export type MiddlewareAPI<S, A, D = Dispatch<A>> = {
dispatch: D,
getState(): S,
};
declare export type Store<S, A, D = Dispatch<A>> = {
// rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages)
dispatch: D,
getState(): S,
subscribe(listener: () => void): () => void,
replaceReducer(nextReducer: Reducer<S, A>): void,
};
declare export type Reducer<S, A> = (state: S | void, action: A) => S;
declare export type CombinedReducer<S, A> = (
state: ($Shape<S> & {}) | void,
action: A
) => S;
declare export type Middleware<S, A, D = Dispatch<A>> = (
api: MiddlewareAPI<S, A, D>
) => (next: D) => D;
declare export type StoreCreator<S, A, D = Dispatch<A>> = {
(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>,
(
reducer: Reducer<S, A>,
preloadedState: S,
enhancer?: StoreEnhancer<S, A, D>
): Store<S, A, D>,
};
declare export type StoreEnhancer<S, A, D = Dispatch<A>> = (
next: StoreCreator<S, A, D>
) => StoreCreator<S, A, D>;
declare export function createStore<S, A, D>(
reducer: Reducer<S, A>,
enhancer?: StoreEnhancer<S, A, D>
): Store<S, A, D>;
declare export function createStore<S, A, D>(
reducer: Reducer<S, A>,
preloadedState?: S,
enhancer?: StoreEnhancer<S, A, D>
): Store<S, A, D>;
declare export function applyMiddleware<S, A, D>(
...middlewares: Array<Middleware<S, A, D>>
): StoreEnhancer<S, A, D>;
declare export type ActionCreator<A, B> = (...args: Array<B>) => A;
declare export type ActionCreators<K, A> = {
[key: K]: ActionCreator<A, any>,
};
declare export function bindActionCreators<
A,
C: ActionCreator<A, any>,
D: DispatchAPI<A>
>(
actionCreator: C,
dispatch: D
): C;
declare export function bindActionCreators<
A,
K,
C: ActionCreators<K, A>,
D: DispatchAPI<A>
>(
actionCreators: C,
dispatch: D
): C;
declare export function combineReducers<O: {}, A>(
reducers: O
): CombinedReducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;
declare export var compose: $Compose;
}

View File

@ -21,6 +21,7 @@
"scripts": {
"start": "yarn run clean && yarn run build:dll && NODE_PATH=./src webpack-dev-server --progress --colors",
"clean": "rm -rf dist/",
"e2e": "yarn --cwd ./tests-e2e test",
"test": "yarn run build:dll && NODE_PATH=./src karma start ./karma.conf.js",
"lint": "eslint ./src",
"flow": "flow",
@ -39,9 +40,7 @@
"copy-to-clipboard": "^3.0.8",
"debounce": "^1.0.0",
"flag-icon-css": "^2.8.0",
"intl": "^1.2.2",
"intl-format-cache": "^2.0.4",
"intl-messageformat": "^2.1.0",
"intl": "^1.2.5",
"promise.prototype.finally": "3.1.0",
"prop-types": "^15.6.2",
"raf": "^3.4.1",
@ -51,14 +50,14 @@
"react-helmet": "^5.0.0",
"react-intl": "^2.7.2",
"react-motion": "^0.5.0",
"react-redux": "^5.0.6",
"react-router-dom": "^4.3.1",
"react-redux": "^7.1.0",
"react-router-dom": "^5.0.1",
"react-textarea-autosize": "^6.0.0",
"react-transition-group": "^1.1.3",
"redux": "^3.0.4",
"react-transition-group": "^4.2.0",
"redux": "^4.0.1",
"redux-localstorage": "^0.4.1",
"redux-thunk": "^2.0.0",
"url-search-params-polyfill": "^5.0.0",
"url-search-params-polyfill": "^6.0.0",
"webfontloader": "^1.6.26",
"whatwg-fetch": "^3.0.0"
},
@ -90,22 +89,22 @@
"exports-loader": "^0.6.3",
"extract-text-webpack-plugin": "^1.0.0",
"file-loader": "^0.11.0",
"flow-bin": "~0.80.0",
"flow-bin": "~0.102.0",
"fontgen-loader": "^0.2.1",
"html-loader": "^0.4.3",
"html-webpack-plugin": "^2.0.0",
"imports-loader": "^0.7.0",
"jsdom": "^9.8.3",
"jsdom": "^15.1.1",
"json-loader": "^0.5.4",
"karma": "^1.1.0",
"karma-jsdom-launcher": "^6.0.0",
"karma": "^4.1.0",
"karma-jsdom-launcher": "^7.1.1",
"karma-mocha": "^1.0.0",
"karma-nyan-reporter": "^0.2.3",
"karma-sinon": "^1.0.4",
"karma-sourcemap-loader": "*",
"karma-webpack": "^2.0.0",
"loader-utils": "^1.0.0",
"mocha": "^3.0.2",
"mocha": "^6.1.4",
"node-sass": "^4.7.2",
"postcss-import": "^9.0.0",
"postcss-loader": "^1.2.0",
@ -115,10 +114,10 @@
"react-test-renderer": "^16.7.0",
"sass-loader": "^4.0.0",
"scripts": "file:./scripts",
"sinon": "^3.2.1",
"sinon": "^7.3.2",
"sitemap-webpack-plugin": "^0.5.1",
"style-loader": "^0.18.0",
"unexpected": "^10.33.2",
"unexpected": "^11.6.1",
"unexpected-sinon": "^10.5.1",
"url-loader": "^0.5.7",
"webpack": "^1.12.9",

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import { browserHistory } from 'services/history';
@ -445,8 +445,9 @@ describe('components/accounts/actions', () => {
return logoutStrangers()(dispatch, getState)
.then(() =>
expect(dispatch, 'was always called with',
expect.it('not to satisfy', activate(account)))
expect(dispatch, 'not to have calls satisfying',
[activate(account)]
)
);
});
@ -511,8 +512,8 @@ describe('components/accounts/actions', () => {
});
it('should not log out', () =>
expect(dispatch, 'was always called with',
expect.it('not to equal', {payload: foreignAccount})
expect(dispatch, 'not to have calls satisfying',
[{payload: foreignAccount}]
)
);
});

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import accounts from 'components/accounts/reducer';
import {

View File

@ -1,7 +1,7 @@
// @flow
import type { User } from 'components/user';
import type { AccountsState } from 'components/accounts';
import type { Node, Element } from 'react';
import type { Element } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
@ -24,6 +24,8 @@ const opacitySpringConfig = {stiffness: 300, damping: 20};
const transformSpringConfig = {stiffness: 500, damping: 50, precision: 0.5};
const changeContextSpringConfig = {stiffness: 500, damping: 20, precision: 0.5};
type PanelId = string;
/**
* Definition of relation between contexts and panels
*
@ -35,7 +37,7 @@ const changeContextSpringConfig = {stiffness: 500, damping: 20, precision: 0.5};
* - Panel index defines the direction of X transition of both panels
* (e.g. the panel with lower index will slide from left side, and with greater from right side)
*/
const contexts = [
const contexts: Array<PanelId[]> = [
['login', 'password', 'forgotPassword', 'mfa', 'recoverPassword'],
['register', 'activation', 'resendActivation'],
['acceptRules'],
@ -71,18 +73,27 @@ type AnimationProps = {|
|};
type AnimationContext = {
key: string,
key: PanelId,
style: AnimationProps,
data: {
Title: Node,
Title: Element<any>,
Body: Element<any>,
Footer: Node,
Links: Node,
Footer: Element<any>,
Links: Element<any>,
hasBackButton: bool,
}
};
type OwnProps = {|
Title: Element<any>,
Body: Element<any>,
Footer: Element<any>,
Links: Element<any>,
children?: Element<any>
|};
type Props = {
...OwnProps,
// context props
auth: {
error: string | {
@ -98,20 +109,12 @@ type Props = {
clearErrors: () => void,
resolve: () => void,
reject: () => void,
// local props
Title: Node,
Body: typeof Component,
Footer: Node,
Links: Node,
children: Node
};
type State = {
contextHeight: number,
panelId: string | void,
prevPanelId: string | void,
panelId: PanelId | void,
prevPanelId: PanelId | void,
isHeightDirty: bool,
forceHeight: 1 | 0,
direction: 'X' | 'Y',
@ -187,7 +190,7 @@ class PanelTransition extends Component<Props, State> {
return {
auth: this.props.auth,
user: this.props.user,
requestRedraw: () =>
requestRedraw: (): Promise<void> =>
new Promise((resolve) =>
this.setState(
{isHeightDirty: true},
@ -207,9 +210,9 @@ class PanelTransition extends Component<Props, State> {
};
}
componentWillReceiveProps(nextProps) {
const nextPanel = nextProps.Body && (nextProps.Body: any).type.panelId;
const prevPanel = this.props.Body && (this.props.Body: any).type.panelId;
componentWillReceiveProps(nextProps: Props) {
const nextPanel: PanelId = nextProps.Body && (nextProps.Body: any).type.panelId;
const prevPanel: PanelId = this.props.Body && (this.props.Body: any).type.panelId;
if (nextPanel !== prevPanel) {
const direction = this.getDirection(nextPanel, prevPanel);
@ -250,7 +253,7 @@ class PanelTransition extends Component<Props, State> {
}
const {panelId, hasGoBack}: {
panelId: string,
panelId: PanelId,
hasGoBack: bool,
} = (Body: any).type;
@ -336,10 +339,10 @@ class PanelTransition extends Component<Props, State> {
}
};
onFormInvalid = (errors) => this.props.setErrors(errors);
onFormInvalid = (errors: { [key: string]: ValidationError }) => this.props.setErrors(errors);
willEnter = (config) => this.getTransitionStyles(config);
willLeave = (config) => this.getTransitionStyles(config, {isLeave: true});
willEnter = (config: AnimationContext) => this.getTransitionStyles(config);
willLeave = (config: AnimationContext) => this.getTransitionStyles(config, {isLeave: true});
/**
* @param {object} config
@ -349,7 +352,7 @@ class PanelTransition extends Component<Props, State> {
*
* @return {object}
*/
getTransitionStyles({key}, options = {}): {|
getTransitionStyles({key}: AnimationContext, options: { isLeave?: bool } = {}): {|
transformSpring: number,
opacitySpring: number,
|} {
@ -380,7 +383,7 @@ class PanelTransition extends Component<Props, State> {
};
}
getDirection(next, prev): 'X' | 'Y' {
getDirection(next: PanelId, prev: PanelId): 'X' | 'Y' {
const context = contexts.find((context) => context.includes(prev));
if (!context) {
@ -390,7 +393,7 @@ class PanelTransition extends Component<Props, State> {
return context.includes(next) ? 'X' : 'Y';
}
onUpdateHeight = (height, key) => {
onUpdateHeight = (height: number, key: PanelId) => {
const heightKey = `formHeight${key}`;
this.setState({
@ -398,13 +401,13 @@ class PanelTransition extends Component<Props, State> {
});
};
onUpdateContextHeight = (height) => {
onUpdateContextHeight = (height: number) => {
this.setState({
contextHeight: height
});
};
onGoBack = (event) => {
onGoBack = (event: SyntheticEvent<HTMLElement>) => {
event.preventDefault();
authFlow.goBack();
@ -415,7 +418,7 @@ class PanelTransition extends Component<Props, State> {
*
* @param {number} length number of panels transitioned
*/
tryToAutoFocus(length) {
tryToAutoFocus(length: number) {
if (!this.body) {
return;
}
@ -588,7 +591,7 @@ class PanelTransition extends Component<Props, State> {
*
* @return {object}
*/
translate(value, direction = 'X', unit = '%') {
translate(value: number, direction: 'X' | 'Y' = 'X', unit: '%' | 'px' = '%') {
return {
WebkitTransform: `translate${direction}(${value}${unit})`,
transform: `translate${direction}(${value}${unit})`
@ -596,7 +599,7 @@ class PanelTransition extends Component<Props, State> {
}
}
export default connect((state) => {
export default connect<Props, OwnProps, _, _, _, _>((state) => {
const login = getLogin(state);
let user = {
...state.user

View File

@ -1,5 +1,5 @@
// @flow
import type { OauthData } from 'services/api/oauth';
import type { OauthData, Client, Scope } from 'services/api/oauth';
import type { OAuthResponse } from 'services/api/authentication';
import { browserHistory } from 'services/history';
import logger from 'services/logger';
@ -19,8 +19,14 @@ import signup from 'services/api/signup';
import dispatchBsod from 'components/ui/bsod/dispatchBsod';
import { create as createPopup } from 'components/ui/popup/actions';
import ContactForm from 'components/contact/ContactForm';
import { getCredentials } from './reducer';
type ValidationError = string | {
type: string,
payload: { [key: string]: any },
};
export { updateUser } from 'components/user/actions';
export {
authenticate,
@ -212,7 +218,7 @@ export function resendActivation({
}
export function contactUs() {
return createPopup(ContactForm);
return createPopup({ Popup: ContactForm });
}
export const SET_CREDENTIALS = 'auth:setCredentials';
@ -283,7 +289,7 @@ export function setAccountSwitcher(isOn: bool) {
}
export const ERROR = 'auth:error';
export function setErrors(errors: ?{[key: string]: string}) {
export function setErrors(errors: ?{[key: string]: ValidationError}) {
return {
type: ERROR,
payload: errors,
@ -413,11 +419,7 @@ export function setClient({
id,
name,
description
}: {
id: string,
name: string,
description: string
}) {
}: Client) {
return {
type: SET_CLIENT,
payload: {id, name, description}
@ -502,7 +504,7 @@ export function requirePermissionsAccept() {
}
export const SET_SCOPES = 'set_scopes';
export function setScopes(scopes: Array<string>) {
export function setScopes(scopes: Scope[]) {
if (!(scopes instanceof Array)) {
throw new Error('Scopes must be array');
}

View File

@ -1,5 +1,5 @@
import sinon from 'sinon';
import expect from 'unexpected';
import expect from 'test/unexpected';
import request from 'services/request';

View File

@ -19,10 +19,10 @@ type Credentials = {
rememberMe?: bool,
returnUrl?: string,
isRelogin?: bool,
isTotpRequired?: bool,
isTotpRequired?: bool
};
export default combineReducers({
export default combineReducers<_, { action: string, payload?: mixed }>({
credentials,
error,
isLoading,
@ -32,10 +32,7 @@ export default combineReducers({
scopes
});
function error(
state = null,
{type, payload = null, error = false}
) {
function error(state = null, { type, payload = null, error = false }) {
switch (type) {
case ERROR:
if (!error) {
@ -51,15 +48,16 @@ function error(
function credentials(
state = {},
{type, payload}: {
{
type,
payload
}: {
type: string,
payload: ?Credentials
}
) {
if (type === SET_CREDENTIALS) {
if (payload
&& typeof payload === 'object'
) {
if (payload && typeof payload === 'object') {
return {
...payload
};
@ -71,10 +69,7 @@ function credentials(
return state;
}
function isSwitcherEnabled(
state = true,
{type, payload = false}
) {
function isSwitcherEnabled(state = true, { type, payload = false }) {
switch (type) {
case SET_SWITCHER:
if (typeof payload !== 'boolean') {
@ -88,10 +83,7 @@ function isSwitcherEnabled(
}
}
function isLoading(
state = false,
{type, payload = null}
) {
function isLoading(state = false, { type, payload = null }) {
switch (type) {
case SET_LOADING_STATE:
return !!payload;
@ -101,10 +93,7 @@ function isLoading(
}
}
function client(
state = null,
{type, payload = {}}
) {
function client(state = null, { type, payload = {} }) {
switch (type) {
case SET_CLIENT:
return {
@ -118,10 +107,7 @@ function client(
}
}
function oauth(
state = null,
{type, payload = {}}
) {
function oauth(state = null, { type, payload = {} }) {
switch (type) {
case SET_OAUTH:
return {
@ -153,10 +139,7 @@ function oauth(
}
}
function scopes(
state = [],
{type, payload = []}
) {
function scopes(state = [], { type, payload = [] }) {
switch (type) {
case SET_SCOPES:
return payload;

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import auth from 'components/auth/reducer';
import {

View File

@ -1,9 +1,8 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import classNames from 'classnames';
import { FormattedMessage as Message } from 'react-intl';
import { Input, TextArea, Button, Form, FormModel, Dropdown } from 'components/ui/form';
import feedback from 'services/api/feedback';
import icons from 'components/ui/icons.scss';
@ -174,8 +173,6 @@ export class ContactForm extends Component {
};
}
import { connect } from 'react-redux';
export default connect((state) => ({
user: state.user
}))(ContactForm);

View File

@ -1,5 +1,5 @@
import React from 'react';
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import { shallow, mount } from 'enzyme';

View File

@ -1,16 +1,21 @@
// @flow
import type { ElementConfig } from 'react';
import React from 'react';
import { connect } from 'react-redux';
import { create as createPopup } from 'components/ui/popup/actions';
import ContactForm from './ContactForm';
type OwnProps = $Exact<ElementConfig<'a'>>;
type Props = {
...OwnProps,
createContactPopup: () => void,
};
function ContactLink({
createContactPopup,
...props
}: {
createContactPopup: () => void,
props: Object
}) {
}: Props) {
return (
<a
href="#"
@ -25,9 +30,9 @@ function ContactLink({
);
}
export default connect(
export default connect<Props, OwnProps, _, _, _, _>(
null,
{
createContactPopup: () => createPopup(ContactForm)
createContactPopup: () => createPopup({ Popup: ContactForm })
}
)(ContactLink);

View File

@ -97,7 +97,7 @@ export default class ApplicationForm extends Component<{
</div>
</div>
{FormComponent && (
{!!FormComponent && (
<Button
color={COLOR_GREEN}
block

View File

@ -63,7 +63,7 @@ export default class ApplicationItem extends Component<
[styles.appExpanded]: expand
})}
data-e2e="appItem"
data-e2e-app={app.clientId}
data-e2e-app-name={app.name}
>
<div className={styles.appItemTile} onClick={this.onTileToggle}>
<div className={styles.appTileTitle}>

View File

@ -1,10 +1,11 @@
// @flow
import type { OauthAppResponse } from 'services/api/oauth';
import type { Action } from './actions';
export type Apps = {
+available: Array<OauthAppResponse>,
};
export type Apps = {|
+available: OauthAppResponse[],
|};
const defaults: Apps = {
available: [],
@ -13,7 +14,7 @@ const defaults: Apps = {
export default function apps(
state: Apps = defaults,
action: Action
) {
): Apps {
switch (action.type) {
case 'apps:setAvailable':
return {
@ -23,7 +24,7 @@ export default function apps(
case 'apps:addApp': {
const { payload } = action;
const available: Array<OauthAppResponse> = [...state.available];
const available = [...state.available];
let index = available.findIndex((app) => app.clientId === payload.clientId);
if (index === -1) {

View File

@ -10,10 +10,16 @@ import { ContactLink } from 'components/contact';
import styles from './footerMenu.scss';
import messages from './footerMenu.intl.json';
class FooterMenu extends Component<{
type OwnProps = {|
createContactPopup: () => void,
|};
type Props = {
...OwnProps,
createLanguageSwitcherPopup: () => void,
}> {
};
class FooterMenu extends Component<Props> {
static displayName = 'FooterMenu';
render() {
@ -50,6 +56,6 @@ class FooterMenu extends Component<{
// mark this component, as not pure, because it is stateless,
// but should be re-rendered, if current lang was changed
export default connect(null, {
createLanguageSwitcherPopup: () => createPopup(LanguageSwitcher),
export default connect<Props, OwnProps, _, _, _, _>(null, {
createLanguageSwitcherPopup: () => createPopup({ Popup: LanguageSwitcher }),
}, null, {pure: false})(FooterMenu);

View File

@ -2,11 +2,13 @@
import React, { Component } from 'react';
import { FormattedMessage as Message, intlShape } from 'react-intl';
import classNames from 'classnames';
import { connect } from 'react-redux';
import { changeLang } from 'components/user/actions';
import LANGS from 'i18n/index.json';
import formStyles from 'components/ui/form/form.scss';
import popupStyles from 'components/ui/popup/popup.scss';
import icons from 'components/ui/icons.scss';
import styles from './languageSwitcher.scss';
import messages from './languageSwitcher.intl.json';
import LanguageList from './LanguageList';
@ -23,21 +25,27 @@ export type LocaleData = {
export type LocalesMap = {[code: string]: LocaleData};
class LanguageSwitcher extends Component<{
onClose: Function,
selectedLocale: string,
changeLang: (lang: string) => void,
type OwnProps = {|
onClose: () => void,
langs: LocalesMap,
emptyCaptions: Array<{
src: string,
caption: string,
}>,
}, {
|};
type Props = {
...OwnProps,
selectedLocale: string,
changeLang: (lang: string) => void,
};
class LanguageSwitcher extends Component<Props, {
filter: string,
filteredLangs: LocalesMap,
}> {
static contextTypes = {
intl: intlShape.isRequired,
intl: intlShape,
};
state = {
@ -108,7 +116,7 @@ class LanguageSwitcher extends Component<{
onChangeLang = this.changeLang.bind(this);
changeLang(lang) {
changeLang(lang: string) {
this.props.changeLang(lang);
setTimeout(this.props.onClose, 300);
@ -153,10 +161,7 @@ class LanguageSwitcher extends Component<{
}
}
import { connect } from 'react-redux';
import { changeLang } from 'components/user/actions';
export default connect((state) => ({
export default connect<Props, OwnProps, _, _, _, _>((state) => ({
selectedLocale: state.i18n.locale,
}), {
changeLang,

View File

@ -3,17 +3,27 @@ import React from 'react';
import classNames from 'classnames';
import { localeFlags } from 'components/i18n';
import LANGS from 'i18n/index.json';
import { connect } from 'react-redux';
import { create as createPopup } from 'components/ui/popup/actions';
import LanguageSwitcher from 'components/languageSwitcher';
import styles from './link.scss';
type OwnProps = {|
|};
type Props = {
...OwnProps,
userLang: string;
interfaceLocale: string;
showLanguageSwitcherPopup: Function;
};
function LanguageLink({
userLang,
interfaceLocale,
showLanguageSwitcherPopup,
}: {
userLang: string;
interfaceLocale: string;
showLanguageSwitcherPopup: Function;
}) {
}: Props) {
const localeDefinition = LANGS[userLang] || LANGS[interfaceLocale];
return (
@ -31,13 +41,9 @@ function LanguageLink({
);
}
import { connect } from 'react-redux';
import { create as createPopup } from 'components/ui/popup/actions';
import LanguageSwitcher from 'components/languageSwitcher';
export default connect((state) => ({
export default connect<Props, OwnProps, _, _, _, _>((state) => ({
userLang: state.user.lang,
interfaceLocale: state.i18n.locale,
}), {
showLanguageSwitcherPopup: () => createPopup(LanguageSwitcher),
showLanguageSwitcherPopup: () => createPopup({ Popup: LanguageSwitcher }),
})(LanguageLink);

View File

@ -13,10 +13,16 @@ import RulesPage from 'pages/rules/RulesPage';
import type { User } from 'components/user';
class Profile extends Component<{
type OwnProps = {|
|};
type Props = {
...OwnProps,
user: User;
interfaceLocale: string;
}> {
};
class Profile extends Component<Props> {
UUID: ?HTMLElement;
render() {
@ -150,10 +156,7 @@ class Profile extends Component<{
}
}
export default connect(({ user, i18n }): {
user: User;
interfaceLocale: string;
} => ({
export default connect<Props, OwnProps, _, _, _, _>(({ user, i18n }) => ({
user,
interfaceLocale: i18n.locale,
}))(Profile);

View File

@ -1,5 +1,5 @@
import React from 'react';
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import { shallow } from 'enzyme';

View File

@ -8,7 +8,7 @@ const ABORT_ERR = 20;
export default function BsodMiddleware(dispatchBsod: Function, logger: Logger) {
return {
catch<T: Resp<*> | InternalServerError | Error>(resp?: T): Promise<T> {
catch<T: Resp<any> | InternalServerError | Error>(resp?: T): Promise<T> {
const originalResponse: Object = (resp && resp.originalResponse) || {};
if (

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import BsodMiddleware from 'components/ui/bsod/BsodMiddleware';

View File

@ -16,7 +16,7 @@ export default class Captcha extends FormInputComponent<{
}, {
code: string,
}> {
el: ?HTMLDivElement;
elRef = React.createRef<HTMLDivElement>();
captchaId: CaptchaID;
static defaultProps = {
@ -26,7 +26,9 @@ export default class Captcha extends FormInputComponent<{
componentDidMount() {
setTimeout(() => {
this.el && captcha.render(this.el, {
const {current: el} = this.elRef;
el && captcha.render(el, {
skin: this.props.skin,
onSetCode: this.setCode
})
@ -48,7 +50,7 @@ export default class Captcha extends FormInputComponent<{
<ComponentLoader />
</div>
<div ref={this.setEl} className={classNames(
<div ref={this.elRef} className={classNames(
styles.captcha,
styles[`${skin}Captcha`]
)} />

View File

@ -19,6 +19,8 @@ export default class Checkbox extends FormInputComponent<{
skin: SKIN_DARK,
};
elRef = React.createRef<HTMLInputElement>();
render() {
const { color, skin } = this.props;
let { label } = this.props;
@ -30,7 +32,7 @@ export default class Checkbox extends FormInputComponent<{
return (
<div className={classNames(styles[`${color}MarkableRow`], styles[`${skin}MarkableRow`])}>
<label className={styles.markableContainer}>
<input ref={this.setEl}
<input ref={this.elRef}
className={styles.markableInput}
type="checkbox"
{...props}
@ -44,13 +46,13 @@ export default class Checkbox extends FormInputComponent<{
}
getValue() {
const { el } = this;
const { current: el } = this.elRef;
return el && el.checked ? 1 : 0;
}
focus() {
const { el } = this;
const { current: el } = this.elRef;
el && el.focus();
}

View File

@ -1,22 +1,20 @@
// @flow
import type { Node } from 'react';
import React, { Component } from 'react';
import classNames from 'classnames';
import logger from 'services/logger';
import type FormModel from './FormModel';
import styles from './form.scss';
import type FormModel from './FormModel';
type Props = {
type Props = {|
id: string,
isLoading: bool,
form?: FormModel,
onSubmit: Function,
onSubmit: (form: FormModel | FormData) => void | Promise<void>,
onInvalid: (errors: {[errorKey: string]: string}) => void,
children: *
};
children: Node
|};
type State = {
isTouched: bool,
isLoading: bool
@ -130,7 +128,7 @@ export default class Form extends Component<Props, State> {
);
}
} else {
const invalidEls = form.querySelectorAll(':invalid');
const invalidEls: NodeList<InputElement> = (form.querySelectorAll(':invalid'): any);
const errors = {};
invalidEls[0].focus(); // focus on first error

View File

@ -5,7 +5,7 @@ import { intlShape } from 'react-intl';
export default class FormComponent<P, S = void> extends Component<P, S> {
static contextTypes = {
intl: intlShape.isRequired
intl: intlShape,
};
/**

View File

@ -12,8 +12,6 @@ export default class FormInputComponent<P, S = void> extends FormComponent<P & {
}, S & {
error?: Error,
}> {
el: ?HTMLDivElement;
componentWillReceiveProps() {
if (this.state && this.state.error) {
Reflect.deleteProperty(this.state, 'error');
@ -22,10 +20,6 @@ export default class FormInputComponent<P, S = void> extends FormComponent<P & {
}
}
setEl = (el: ?HTMLDivElement) => {
this.el = el;
};
renderError() {
const error = this.state && this.state.error || this.props.error;

View File

@ -1,10 +1,12 @@
// @flow
import FormInputComponent from './FormInputComponent';
type LoadingListener = (isLoading: bool) => void;
export default class FormModel {
fields = {};
errors = {};
handlers = [];
handlers: LoadingListener[] = [];
renderErrors: bool;
_isLoading: bool;
@ -145,7 +147,7 @@ export default class FormModel {
*
* @return {object}
*/
serialize() {
serialize(): {[key: string]: any} {
return Object.keys(this.fields).reduce((acc, fieldId) => {
const field = this.fields[fieldId];
@ -164,7 +166,7 @@ export default class FormModel {
*
* @param {function} fn
*/
addLoadingListener(fn: Function) {
addLoadingListener(fn: LoadingListener) {
this.removeLoadingListener(fn);
this.handlers.push(fn);
}
@ -174,7 +176,7 @@ export default class FormModel {
*
* @param {function} fn
*/
removeLoadingListener(fn: Function) {
removeLoadingListener(fn: LoadingListener) {
this.handlers = this.handlers.filter((handler) => handler !== fn);
}

View File

@ -37,6 +37,8 @@ export default class Input extends FormInputComponent<{
wasCopied: false,
};
elRef = React.createRef<HTMLInputElement>();
render() {
const { color, skin, center } = this.props;
let { icon, label, copy } = this.props;
@ -89,7 +91,7 @@ export default class Input extends FormInputComponent<{
<div className={baseClass}>
{label}
<div className={styles.textFieldContainer}>
<input ref={this.setEl}
<input ref={this.elRef}
className={classNames(
styles[`${skin}TextField`],
styles[`${color}TextField`],
@ -108,11 +110,13 @@ export default class Input extends FormInputComponent<{
}
getValue() {
return this.el && this.el.value;
const { current: el } = this.elRef;
return el && el.value;
}
focus() {
const el = this.el;
const { current: el } = this.elRef;
if (!el) {
return;

View File

@ -1,6 +1,6 @@
import React from 'react';
import { mount } from 'enzyme';
import expect from 'unexpected';
import expect from 'test/unexpected';
import { IntlProvider } from 'react-intl';
import Input from './Input';

View File

@ -1,12 +1,15 @@
// @flow
import type { ElementProps } from 'react';
import type { ElementConfig } from 'react';
import React from 'react';
import { Link } from 'react-router-dom';
import Button from './Button';
export default function LinkButton(
props: ElementProps<typeof Button> & ElementProps<typeof Link>
props: {
...$Exact<ElementConfig<typeof Button>>,
...$Exact<ElementConfig<typeof Link>>
}
) {
const { to, ...restProps } = props;

View File

@ -19,6 +19,8 @@ export default class Radio extends FormInputComponent<{
skin: SKIN_DARK,
};
elRef = React.createRef<HTMLInputElement>();
render() {
const { color, skin } = this.props;
let { label } = this.props;
@ -30,7 +32,7 @@ export default class Radio extends FormInputComponent<{
return (
<div className={classNames(styles[`${color}MarkableRow`], styles[`${skin}MarkableRow`])}>
<label className={styles.markableContainer}>
<input ref={this.setEl}
<input ref={this.elRef}
className={styles.markableInput}
type="radio"
{...props}
@ -44,13 +46,13 @@ export default class Radio extends FormInputComponent<{
}
getValue() {
const { el } = this;
const { current: el } = this.elRef;
return el && el.checked ? 1 : 0;
}
focus() {
const { el } = this;
const { current: el } = this.elRef;
el && el.focus();
}

View File

@ -30,6 +30,8 @@ export default class TextArea extends FormInputComponent<{
skin: SKIN_DARK,
};
elRef = React.createRef<HTMLTextAreaElement>();
render() {
const { color, skin } = this.props;
let { label } = this.props;
@ -59,7 +61,7 @@ export default class TextArea extends FormInputComponent<{
<div className={styles.formRow}>
{label}
<div className={styles.textAreaContainer}>
<TextareaAutosize inputRef={this.setEl}
<TextareaAutosize inputRef={this.elRef}
className={classNames(
styles.textArea,
styles[`${skin}TextField`],
@ -74,11 +76,13 @@ export default class TextArea extends FormInputComponent<{
}
getValue() {
return this.el && this.el.value;
const { current: el } = this.elRef;
return el && el.value;
}
focus() {
const { el } = this;
const { current: el } = this.elRef;
if (!el) {
return;

View File

@ -1,19 +1,20 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { CSSTransitionGroup } from 'react-transition-group';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import { browserHistory } from 'services/history';
import { connect } from 'react-redux';
import { destroy } from 'components/ui/popup/actions';
import styles from './popup.scss';
export class PopupStack extends Component {
static displayName = 'PopupStack';
static propTypes = {
popups: PropTypes.arrayOf(PropTypes.shape({
popups: PropTypes.arrayOf(
PropTypes.shape({
type: PropTypes.func,
props: PropTypes.oneOfType([PropTypes.object, PropTypes.func])
})),
})
),
destroy: PropTypes.func.isRequired
};
@ -28,31 +29,34 @@ export class PopupStack extends Component {
}
render() {
const {popups} = this.props;
const { popups } = this.props;
return (
<CSSTransitionGroup
transitionName={{
<TransitionGroup>
{popups.map((popup, index) => {
const { Popup } = popup;
return (
<CSSTransition
key={index}
classNames={{
enter: styles.trEnter,
enterActive: styles.trEnterActive,
leave: styles.trLeave,
leaveActive: styles.trLeaveActive
exit: styles.trExit,
exitActive: styles.trExitActive
}}
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
timeout={500}
>
{popups.map((popup, index) => {
const {Popup} = popup;
return (
<div className={styles.overlay} key={index}
<div
className={styles.overlay}
onClick={this.onOverlayClick(popup)}
>
<Popup onClose={this.onClose(popup)} />
</div>
</CSSTransition>
);
})}
</CSSTransitionGroup>
</TransitionGroup>
);
}
@ -62,7 +66,10 @@ export class PopupStack extends Component {
onOverlayClick(popup) {
return (event) => {
if (event.target !== event.currentTarget || popup.disableOverlayClose) {
if (
event.target !== event.currentTarget
|| popup.disableOverlayClose
) {
return;
}
@ -81,7 +88,8 @@ export class PopupStack extends Component {
}
onKeyPress = (event) => {
if (event.which === 27) { // ESC key
if (event.which === 27) {
// ESC key
this.popStack();
}
};
@ -93,11 +101,11 @@ export class PopupStack extends Component {
};
}
import { connect } from 'react-redux';
import { destroy } from 'components/ui/popup/actions';
export default connect((state) => ({
export default connect(
(state) => ({
...state.popup
}), {
}),
{
destroy
})(PopupStack);
}
)(PopupStack);

View File

@ -1,5 +1,5 @@
import sinon from 'sinon';
import expect from 'unexpected';
import expect from 'test/unexpected';
import React from 'react';

View File

@ -1,5 +1,6 @@
// @flow
import type { ElementType } from 'react';
export const POPUP_CREATE = 'POPUP_CREATE';
/**
@ -9,16 +10,10 @@ export const POPUP_CREATE = 'POPUP_CREATE';
*
* @return {object}
*/
export function create(payload: ElementType | {
export function create(payload: {
Popup: ElementType,
disableOverlayClose?: bool,
disableOverlayClose?: bool
}) {
if (typeof payload === 'function') {
payload = {
Popup: payload
};
}
return {
type: POPUP_CREATE,
payload

View File

@ -141,7 +141,7 @@ $popupInitPosition: translateY(10%) rotateX(-8deg);
}
}
.trLeave {
.trExit {
opacity: 1;
overflow: hidden;
@ -163,7 +163,7 @@ $popupInitPosition: translateY(10%) rotateX(-8deg);
}
.trEnter,
.trLeave {
.trExit {
.close {
// do not show close during transition, because transform forces position: fixed
// to layout relative container, instead of body

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import reducer from 'components/ui/popup/reducer';
import {create, destroy} from 'components/ui/popup/actions';
@ -22,14 +22,6 @@ describe('popup/reducer', () => {
});
});
it('should support shortcut popup creation', () => {
const actual = reducer(undefined, create(FakeComponent));
expect(actual.popups[0], 'to equal', {
Popup: FakeComponent
});
});
it('should create multiple popups', () => {
let actual = reducer(undefined, create({
Popup: FakeComponent
@ -44,7 +36,7 @@ describe('popup/reducer', () => {
});
it('throws when no type provided', () => {
expect(() => reducer(undefined, create()), 'to throw', 'Popup is required');
expect(() => reducer(undefined, create({})), 'to throw', 'Popup is required');
});
});
@ -53,7 +45,7 @@ describe('popup/reducer', () => {
let popup;
beforeEach(() => {
state = reducer(state, create(FakeComponent));
state = reducer(state, create({ Popup: FakeComponent }));
popup = state.popups[0];
});

View File

@ -1,10 +1,11 @@
// @flow
import type { Location } from 'react-router-dom';
import React from 'react';
import { withRouter } from 'react-router-dom';
import { restoreScroll } from './scroll';
class ScrollIntoView extends React.Component<{
location: string,
class ScrollIntoView extends React.PureComponent<{
location: Location,
top?: bool, // do not touch any DOM and simply scroll to top on location change
}> {
componentDidMount() {

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import bearerHeaderMiddleware from 'components/user/middlewares/bearerHeaderMiddleware';

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import refreshTokenMiddleware from 'components/user/middlewares/refreshTokenMiddleware';

View File

@ -1,7 +1,6 @@
import React from 'react';
import sinon from 'sinon';
import expect from 'unexpected';
import expect from 'test/unexpected';
import { mount } from 'enzyme';
import authFlow from 'services/authFlow';
@ -18,15 +17,13 @@ describe('AuthFlowRouteContents', () => {
});
function Component() {
return (
<div />
);
return <div />;
}
it('should render component if route allowed', () => {
const request = {
path: '/path',
params: {foo: 1},
params: { foo: 1 },
query: new URLSearchParams()
};
@ -36,23 +33,25 @@ describe('AuthFlowRouteContents', () => {
query: request.query
},
match: {
params: request.params,
params: request.params
}
};
authFlow.handleRequest.callsArg(2);
const wrapper = mount(<AuthFlowRouteContents
const wrapper = mount(
<AuthFlowRouteContents
routerProps={routerProps}
component={Component}
/>);
/>
);
const component = wrapper.find(Component);
expect(authFlow.handleRequest, 'to have a call satisfying', [
request,
function() {},
function() {}
expect.it('to be a function'),
expect.it('to be a function')
]);
expect(component.exists(), 'to be true');

View File

@ -1,26 +1,35 @@
// @flow
import type { ComponentType, ElementConfig } from 'react';
import type { Account } from 'components/accounts';
import type { Location } from 'react-router-dom';
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { getActiveAccount } from 'components/accounts/reducer';
import type { ComponentType } from 'react';
import type { Account } from 'components/accounts';
const PrivateRoute = ({account, component: Component, ...rest}: {
type OwnProps = {|
...$Exact<ElementConfig<typeof Route>>,
component: ComponentType<any>,
|};
type Props = {
...OwnProps,
account: ?Account
}) => (
<Route {...rest} render={(props: {location: string}) => (
};
const PrivateRoute = ({ account, component: Component, ...rest }: Props) => (
<Route
{...rest}
render={(props: { location: Location }) =>
!account || !account.token ? (
<Redirect to="/login" />
) : (
<Component {...props}/>
<Component {...props} />
)
)}/>
}
/>
);
export default connect((state): {
account: ?Account,
} => ({
export default connect<Props, OwnProps, _, _, _, _>((state) => ({
account: getActiveAccount(state)
}))(PrivateRoute);

View File

@ -18,6 +18,8 @@ import ForgotPassword from 'components/auth/forgotPassword/ForgotPassword';
import RecoverPassword from 'components/auth/recoverPassword/RecoverPassword';
import Mfa from 'components/auth/mfa/Mfa';
import Finish from 'components/auth/finish/Finish';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import styles from './auth.scss';
@ -27,13 +29,19 @@ import styles from './auth.scss';
// so that it persist disregarding remounts
let isSidebarHiddenCache = false;
class AuthPage extends Component<{
type OwnProps = {|
|};
type Props = {
...OwnProps,
client: {
id: string,
name: string,
description: string
}
}, {
};
class AuthPage extends Component<Props, {
isSidebarHidden: bool
}> {
state = {
@ -91,20 +99,10 @@ function renderPanelTransition(factory) {
Body={<Body {...props} />}
Footer={<Footer />}
Links={<Links />}
{...props}
/>
);
}
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
export default withRouter(connect((state): {
client: {
id: string,
name: string,
description: string
}
} => ({
export default withRouter(connect<Props, OwnProps, _, _, _, _>((state) => ({
client: state.auth.client
}))(AuthPage));

View File

@ -1,5 +1,5 @@
// @flow
import type { Location, RouterHistory } from 'react-router';
import type { Location, RouterHistory } from 'react-router-dom';
import type { User } from 'components/user';
import type { OauthAppResponse } from 'services/api/oauth';
import React, { Component } from 'react';
@ -11,17 +11,21 @@ import {
} from 'components/dev/apps/actions';
import ApplicationsIndex from 'components/dev/apps/ApplicationsIndex';
interface Props {
type OwnProps = {|
location: Location;
history: RouterHistory;
|};
type Props = {
...OwnProps,
user: User;
apps: Array<OauthAppResponse>;
apps: OauthAppResponse[];
fetchAvailableApps: () => Promise<void>;
deleteApp: string => Promise<void>;
resetApp: (string, bool) => Promise<void>;
}
interface State {
type State = {
isLoading: bool;
forceUpdate: bool;
}
@ -36,7 +40,7 @@ class ApplicationsListPage extends Component<Props, State> {
!this.props.user.isGuest && this.loadApplicationsList();
}
componentDidUpdate({ user }) {
componentDidUpdate({ user }: Props) {
if (this.props.user !== user) {
// eslint-disable-next-line react/no-did-update-set-state
this.setState({ forceUpdate: true });
@ -80,10 +84,12 @@ class ApplicationsListPage extends Component<Props, State> {
};
}
export default connect((state) => ({
export default connect<Props, OwnProps, _, _, _, _>((state) => ({
user: state.user,
apps: state.apps.available,
}), {
}),
// $FlowFixMe: we need a better action typings for thunks
{
fetchAvailableApps,
resetApp,
deleteApp,

View File

@ -11,18 +11,21 @@ import PageNotFound from 'pages/404/PageNotFound';
import { getApp, fetchApp } from 'components/dev/apps/actions';
import ApplicationForm from 'components/dev/apps/applicationForm/ApplicationForm';
type MatchType = {
type OwnProps = {|
match: {
params: {
clientId: string,
},
},
};
|}
class UpdateApplicationPage extends Component<{
type Props = {
...OwnProps,
app: ?OauthAppResponse,
fetchApp: (string) => Promise<void>,
} & MatchType, {
}
class UpdateApplicationPage extends Component<Props, {
isNotFound: bool,
}> {
form: FormModel = new FormModel();
@ -104,7 +107,7 @@ class UpdateApplicationPage extends Component<{
goToMainPage = (hash?: string) => browserHistory.push(`/dev/applications${hash ? `#${hash}` : ''}`);
}
export default connect((state, props: MatchType) => ({
export default connect<Props, OwnProps, _, _, _, _>((state, props) => ({
app: getApp(state, props.match.params.clientId),
}), {
fetchApp,

View File

@ -1,15 +1,13 @@
// @flow
import type { RouterHistory, Match } from 'react-router';
import type { RouterHistory, Match } from 'react-router-dom';
import type FormModel from 'components/ui/form/FormModel';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import ChangeEmail from 'components/profile/changeEmail/ChangeEmail';
import { requestEmailChange, setNewEmail, confirmNewEmail } from 'services/api/accounts';
interface Props {
lang: string;
email: string;
type OwnProps = {|
history: RouterHistory;
match: {
...Match;
@ -18,6 +16,12 @@ interface Props {
code: string;
};
};
|};
type Props = {
...OwnProps,
lang: string;
email: string;
}
class ChangeEmailPage extends Component<Props> {
@ -53,11 +57,11 @@ class ChangeEmailPage extends Component<Props> {
);
}
onChangeStep = (step) => {
onChangeStep = (step: number) => {
this.props.history.push(`/profile/change-email/step${++step}`);
};
onSubmit = (step: number, form) => {
onSubmit = (step: number, form: FormModel) => {
return this.context.onSubmit({
form,
sendData: () => {
@ -102,10 +106,7 @@ function handleErrors(repeatUrl) {
};
}
import { connect } from 'react-redux';
export default connect((state) => ({
export default connect<Props, OwnProps, _, _, _, _>((state) => ({
email: state.user.email,
lang: state.user.lang
}), {
})(ChangeEmailPage);
}))(ChangeEmailPage);

View File

@ -7,7 +7,11 @@ import { changePassword } from 'services/api/accounts';
import { FormModel } from 'components/ui/form';
import ChangePassword from 'components/profile/changePassword/ChangePassword';
interface Props {
type OwnProps = {|
|};
type Props = {
...OwnProps,
updateUser: (fields: $Shape<User>) => void;
}
@ -45,6 +49,6 @@ class ChangePasswordPage extends Component<Props> {
import { connect } from 'react-redux';
import { updateUser } from 'components/user/actions';
export default connect(null, {
export default connect<Props, OwnProps, _, _, _, _>(null, {
updateUser,
})(ChangePasswordPage);

View File

@ -6,14 +6,15 @@ import { changeUsername } from 'services/api/accounts';
import { FormModel } from 'components/ui/form';
import ChangeUsername from 'components/profile/changeUsername/ChangeUsername';
interface Props {
type OwnProps = {|
|};
type Props = {
username: string;
updateUsername: (username: string) => void;
}
};
class ChangeUsernamePage extends Component<Props> {
static displayName = 'ChangeUsernamePage';
static contextTypes = {
userId: PropTypes.number.isRequired,
onSubmit: PropTypes.func.isRequired,
@ -42,7 +43,7 @@ class ChangeUsernamePage extends Component<Props> {
);
}
onUsernameChange = (username) => {
onUsernameChange = (username: string) => {
this.props.updateUsername(username);
};
@ -70,7 +71,7 @@ class ChangeUsernamePage extends Component<Props> {
import { connect } from 'react-redux';
import { updateUser } from 'components/user/actions';
export default connect((state) => ({
export default connect<Props, OwnProps, _, _, _, _>((state) => ({
username: state.user.username,
}), {
updateUsername: (username) => updateUser({username}),

View File

@ -1,5 +1,5 @@
// @flow
import type { RouterHistory, Match } from 'react-router';
import type { RouterHistory, Match } from 'react-router-dom';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
@ -9,8 +9,7 @@ import type { MfaStep } from 'components/profile/multiFactorAuth';
import type { FormModel } from 'components/ui/form';
import type { User } from 'components/user';
interface Props {
user: User;
type OwnProps = {|
history: RouterHistory;
match: {
...Match;
@ -18,6 +17,11 @@ interface Props {
step?: '1' | '2' | '3';
};
};
|};
type Props = {
...OwnProps,
user: User;
}
class MultiFactorAuthPage extends Component<Props> {
@ -86,4 +90,6 @@ class MultiFactorAuthPage extends Component<Props> {
};
}
export default connect(({user}): { user: User } => ({user}))(MultiFactorAuthPage);
export default connect<Props, OwnProps, _, _, _, _>(
({user}) => ({user})
)(MultiFactorAuthPage);

View File

@ -20,7 +20,11 @@ import styles from './profile.scss';
import type { FormModel } from 'components/ui/form';
interface Props {
type OwnProps = {|
|};
type Props = {
...OwnProps,
userId: number;
onSubmit: ({form: FormModel, sendData: () => Promise<*>}) => void;
fetchUserData: () => Promise<*>;
@ -65,7 +69,7 @@ class ProfilePage extends Component<Props> {
goToProfile = () => browserHistory.push('/');
}
export default connect((state) => ({
export default connect<Props, OwnProps, _, _, _, _>((state) => ({
userId: state.user.id,
}), {
fetchUserData,

View File

@ -4,7 +4,7 @@ import type { Account } from 'components/accounts/reducer';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { resetAuth } from 'components/auth/actions';
import { withRouter } from 'react-router';
import { withRouter } from 'react-router-dom';
import { FormattedMessage as Message } from 'react-intl';
import { Route, Link, Switch } from 'react-router-dom';
import Helmet from 'react-helmet';

View File

@ -1,7 +1,7 @@
import React from 'react';
import sinon from 'sinon';
import expect from 'unexpected';
import expect from 'test/unexpected';
import {shallow} from 'enzyme';
import RulesPage from './RulesPage';

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
describe('promise.prototype.finally', () => {
it('should be invoked after promise resolved', () =>

View File

@ -41,37 +41,37 @@ export function changePassword(id: number, {
});
}
export function acceptRules(id: number) {
export function acceptRules(id: number): Promise<{ success: bool }> {
return request.post(`/api/v1/accounts/${id}/rules`);
}
export function changeUsername(id: number, username: ?string, password: ?string) {
export function changeUsername(id: number, username: ?string, password: ?string): Promise<{ success: bool }> {
return request.post(`/api/v1/accounts/${id}/username`, {
username,
password,
});
}
export function changeLang(id: number, lang: string) {
export function changeLang(id: number, lang: string): Promise<{ success: bool }> {
return request.post(`/api/v1/accounts/${id}/language`, {
lang,
});
}
export function requestEmailChange(id: number, password: string) {
export function requestEmailChange(id: number, password: string): Promise<{ success: bool }> {
return request.post(`/api/v1/accounts/${id}/email-verification`, {
password,
});
}
export function setNewEmail(id: number, email: string, key: string) {
export function setNewEmail(id: number, email: string, key: string): Promise<{ success: bool }> {
return request.post(`/api/v1/accounts/${id}/new-email-verification`, {
email,
key,
});
}
export function confirmNewEmail(id: number, key: string) {
export function confirmNewEmail(id: number, key: string): Promise<{ success: bool }> {
return request.post(`/api/v1/accounts/${id}/email`, {
key,
});

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import request from 'services/request';

View File

@ -1,5 +1,5 @@
/* eslint-disable camelcase */
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import request from 'services/request';

View File

@ -4,6 +4,18 @@ import type { Resp } from 'services/request';
import type { ApplicationType } from 'components/dev/apps';
import request from 'services/request';
export type Scope =
| 'minecraft_server_session'
| 'offline_access'
| 'account_info'
| 'account_email';
export type Client = {|
id: string,
name: string,
description: string
|};
export type OauthAppResponse = {
clientId: string,
clientSecret: string,
@ -24,7 +36,7 @@ type OauthRequestData = {
redirect_uri: string,
response_type: string,
description: string,
scope: string,
scope: Scope,
prompt: string,
login_hint?: string,
state?: string,
@ -35,7 +47,7 @@ export type OauthData = {
redirectUrl: string,
responseType: string,
description: string,
scope: string,
scope: Scope,
prompt: 'none' | 'consent' | 'select_account',
loginHint?: string,
state?: string
@ -51,19 +63,28 @@ type FormPayloads = {
const api = {
validate(oauthData: OauthData) {
return request.get(
return request.get<{|
session: {|
scopes: Scope[],
|},
client: Client,
oAuth: {||},
|}>(
'/api/oauth2/v1/validate',
getOAuthRequest(oauthData)
).catch(handleOauthParamsValidation);
},
complete(oauthData: OauthData, params: {accept?: bool} = {}): Promise<Resp<{
complete(oauthData: OauthData, params: {accept?: bool} = {}): Promise<{
success: bool,
redirectUri: string,
}>> {
}> {
const query = request.buildQuery(getOAuthRequest(oauthData));
return request.post(
return request.post<{
success: bool,
redirectUri: string,
}>(
`/api/oauth2/v1/complete?${query}`,
typeof params.accept === 'undefined' ? {} : {accept: params.accept}
).catch((resp = {}) => {
@ -92,28 +113,28 @@ const api = {
});
},
create(type: string, formParams: FormPayloads): Promise<Resp<{success: bool, data: OauthAppResponse}>> {
return request.post(`/api/v1/oauth2/${type}`, formParams);
create(type: string, formParams: FormPayloads) {
return request.post<{success: bool, data: OauthAppResponse}>(`/api/v1/oauth2/${type}`, formParams);
},
update(clientId: string, formParams: FormPayloads): Promise<Resp<{success: bool, data: OauthAppResponse}>> {
return request.put(`/api/v1/oauth2/${clientId}`, formParams);
update(clientId: string, formParams: FormPayloads) {
return request.put<{success: bool, data: OauthAppResponse}>(`/api/v1/oauth2/${clientId}`, formParams);
},
getApp(clientId: string): Promise<Resp<OauthAppResponse>> {
return request.get(`/api/v1/oauth2/${clientId}`);
getApp(clientId: string) {
return request.get<OauthAppResponse>(`/api/v1/oauth2/${clientId}`);
},
getAppsByUser(userId: number): Promise<Resp<Array<OauthAppResponse>>> {
getAppsByUser(userId: number): Promise<OauthAppResponse[]> {
return request.get(`/api/v1/accounts/${userId}/oauth2/clients`);
},
reset(clientId: string, regenerateSecret: bool = false): Promise<Resp<{success: bool, data: OauthAppResponse}>> {
return request.post(`/api/v1/oauth2/${clientId}/reset${regenerateSecret ? '?regenerateSecret' : ''}`);
reset(clientId: string, regenerateSecret: bool = false) {
return request.post<{success: bool, data: OauthAppResponse}>(`/api/v1/oauth2/${clientId}/reset${regenerateSecret ? '?regenerateSecret' : ''}`);
},
delete(clientId: string): Promise<Resp<{success: bool}>> {
return request.delete(`/api/v1/oauth2/${clientId}`);
delete(clientId: string) {
return request.delete<{success: bool}>(`/api/v1/oauth2/${clientId}`);
},
};

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import request from 'services/request';
import options from './options';

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import AuthFlow from 'services/authFlow/AuthFlow';

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import AuthFlow from 'services/authFlow/AuthFlow';
@ -354,9 +354,9 @@ describe('AuthFlow', () => {
flow.handleRequest(request);
expect(flow.getRequest(), 'to equal', {
expect(flow.getRequest(), 'to satisfy', {
...request,
query: new URLSearchParams(),
query: expect.it('to be an', URLSearchParams),
params: {}
});
});

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import CompleteState from 'services/authFlow/CompleteState';

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import MfaState from './MfaState';

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import PasswordState from 'services/authFlow/PasswordState';

View File

@ -2,7 +2,7 @@
* A helper wrapper service around window.history
*/
import createBrowserHistory from 'history/createBrowserHistory';
import { createBrowserHistory } from 'history';
export const browserHistory = createBrowserHistory();
@ -11,9 +11,9 @@ browserHistory.listen(() => {
});
function patchHistory(history) {
Object.assign(history.location,
{query: new URLSearchParams(history.location.search)}
);
Object.assign(history.location, {
query: new URLSearchParams(history.location.search)
});
}
patchHistory(browserHistory);
@ -27,7 +27,10 @@ export default {
* @return {bool} - whether history.back() can be safetly called
*/
canGoBack() {
return document.referrer.includes(`${location.protocol}//${location.host}`)
|| this.initialLength < window.history.length;
return (
document.referrer.includes(
`${location.protocol}//${location.host}`
) || this.initialLength < window.history.length
);
}
};

View File

@ -1,5 +1,5 @@
import sinon from 'sinon';
import expect from 'unexpected';
import expect from 'test/unexpected';
import PromiseMiddlewareLayer from 'services/request/PromiseMiddlewareLayer';

View File

@ -6,8 +6,9 @@ import RequestAbortedError from './RequestAbortedError';
const middlewareLayer = new PromiseMiddlewareLayer();
export type Resp<T> = {
...$Exact<T>,
originalResponse: Response
} & T;
};
type Middleware = {
before?: () => Promise<*>,

View File

@ -1,4 +1,4 @@
import expect from 'unexpected';
import expect from 'test/unexpected';
import sinon from 'sinon';
import request from 'services/request';

View File

@ -1,9 +1,7 @@
import 'polyfills';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import expect from 'unexpected';
expect.use(require('unexpected-sinon'));
configure({ adapter: new Adapter() });
if (!window.localStorage) {

7
src/test/unexpected.js Normal file
View File

@ -0,0 +1,7 @@
import unexpected from 'unexpected';
const expect = unexpected.clone();
expect.use(require('unexpected-sinon'));
export default expect;

2600
yarn.lock

File diff suppressed because it is too large Load Diff