Use JSONRPC in TeamService (needs more testing)

This commit is contained in:
Jerko Steiner 2019-09-11 00:40:10 +07:00
parent 9199961fdb
commit 055d9588bf
16 changed files with 270 additions and 353 deletions

View File

@ -1,92 +0,0 @@
import {IAPIDef} from '@rondo.dev/common'
import {TGetPendingAction, TAsyncAction, PendingAction} from '@rondo.dev/redux'
import {IHTTPClient} from '@rondo.dev/http-client'
import {ITeam, IUser, IUserInTeam} from '@rondo.dev/common'
export type TTeamAction =
TAsyncAction<ITeam[], 'TEAMS'>
| TAsyncAction<ITeam, 'TEAM_CREATE'>
| TAsyncAction<ITeam, 'TEAM_UPDATE'>
| TAsyncAction<{id: number}, 'TEAM_REMOVE'>
| TAsyncAction<IUserInTeam, 'TEAM_USER_ADD'>
| TAsyncAction<{userId: number, teamId: number}, 'TEAM_USER_REMOVE'>
| TAsyncAction<{teamId: number, usersInTeam: IUserInTeam[]}, 'TEAM_USERS'>
| TAsyncAction<IUser | undefined, 'TEAM_USER_FIND'>
type Action<T extends string> = TGetPendingAction<TTeamAction, T>
export class TeamActions {
constructor(protected readonly http: IHTTPClient<IAPIDef>) {}
fetchMyTeams = (): Action<'TEAMS'> => {
return new PendingAction(this.http.get('/my/teams'), 'TEAMS')
}
createTeam = (team: {name: string}): Action<'TEAM_CREATE'> => {
return new PendingAction(this.http.post('/teams', team), 'TEAM_CREATE')
}
updateTeam = ({id, name}: {id: number, name: string})
: Action<'TEAM_UPDATE'> => {
return new PendingAction(
this.http.put('/teams/:id', {name}, {id}),
'TEAM_UPDATE',
)
}
removeTeam = ({id}: {id: number}): Action<'TEAM_REMOVE'> => {
return new PendingAction(
this.http.delete('/teams/:id', {}, {id}),
'TEAM_REMOVE',
)
}
addUser(
{userId, teamId, roleId = 1}: {
userId: number,
teamId: number,
roleId: number,
})
: Action<'TEAM_USER_ADD'> {
return new PendingAction(
this.http.post('/teams/:teamId/users/:userId', {}, {
userId,
teamId,
}),
'TEAM_USER_ADD',
)
}
removeUser = (
{userId, teamId}: {
userId: number,
teamId: number,
})
: Action<'TEAM_USER_REMOVE'> => {
return new PendingAction(
this.http.delete('/teams/:teamId/users/:userId', {}, {
userId,
teamId,
}),
'TEAM_USER_REMOVE',
)
}
fetchUsersInTeam = ({teamId}: {teamId: number})
: Action<'TEAM_USERS'> => {
return new PendingAction(
this.http.get('/teams/:teamId/users', {}, {
teamId,
})
.then(usersInTeam => ({teamId, usersInTeam})),
'TEAM_USERS',
)
}
findUserByEmail = (email: string): Action<'TEAM_USER_FIND'> => {
return new PendingAction(
this.http.get('/users/emails/:email', {}, {email}),
'TEAM_USER_FIND',
)
}
}

View File

@ -1,12 +1,13 @@
import * as Feature from './'
// export ReactDOM from 'react-dom'
import T from 'react-dom/test-utils'
import {TestUtils} from '../test-utils'
import {HTTPClientMock} from '@rondo.dev/http-client'
import {getError} from '@rondo.dev/test-utils'
import {IAPIDef, ITeam, IUserInTeam} from '@rondo.dev/common'
import { IAPIDef, IUserInTeam, team as Team, user as User } from '@rondo.dev/common'
import { HTTPClientMock } from '@rondo.dev/http-client'
import { getError } from '@rondo.dev/test-utils'
import React from 'react'
import {MemoryRouter} from 'react-router-dom'
import T from 'react-dom/test-utils'
import { MemoryRouter } from 'react-router-dom'
import { TestUtils } from '../test-utils'
import * as Feature from './'
import { createActions, createRemoteClient } from '@rondo.dev/jsonrpc'
import createClientMock from '@rondo.dev/jsonrpc/lib/createClientMock'
const test = new TestUtils()
@ -16,24 +17,22 @@ describe('TeamConnector', () => {
push: jest.fn(),
}
let teamActions!: Feature.TeamActions
let http: HTTPClientMock<IAPIDef>
const [teamClient, teamClientMock] =
createClientMock<Team.ITeamService>(Team.TeamServiceMethods)
const [userClient, userClientMock] =
createClientMock<User.IUserService>(User.UserServiceMethods)
let teamActions!: Team.TeamActions
let userActions!: User.UserActions
beforeEach(() => {
http = new HTTPClientMock<IAPIDef>()
teamClientMock.find.mockResolvedValue(teams)
teamClientMock.findUsers.mockResolvedValue(users)
http.mockAdd({
method: 'get',
url: '/my/teams',
}, teams)
http.mockAdd({
method: 'get',
url: '/teams/:teamId/users',
params: {
teamId: 123,
},
}, users)
teamActions = createActions(teamClient, 'teamService')
userActions = createActions(userClient, 'userService')
})
teamActions = new Feature.TeamActions(http)
afterEach(() => {
jest.clearAllMocks()
})
let historyEntries = ['/teams']
@ -42,30 +41,41 @@ describe('TeamConnector', () => {
reducers: {Team: Feature.Team},
select: state => state.Team,
})
.withComponent(select => Feature.configure(teamActions, select))
.withComponent(select => Feature.configure(teamActions, userActions, select))
.withJSX((Component, props) =>
<MemoryRouter initialEntries={historyEntries}>
<Component {...props} />
</MemoryRouter>,
)
const teams: ITeam[] = [{id: 100, name: 'my-team', userId: 1}]
const teams: Team.Team[] = [{
id: 100,
name: 'my-team',
userId: 1,
createDate: '',
updateDate: '',
userTeams: [],
}]
const users: IUserInTeam[] = [{
const users: Team.ITeamUsers = {
teamId: 123,
usersInTeam: [{
teamId: 123,
userId: 1,
displayName: 'test test',
roleId: 1,
roleName: 'ADMIN',
}]
}],
}
it('it fetches user teams on render', async () => {
const {node} = createTestProvider().render({
const {waitForActions, render} = createTestProvider()
const {node} = render({
history,
location: {} as any,
match: {} as any,
})
await http.wait()
await waitForActions()
expect(node.innerHTML).toContain('my-team')
})
@ -74,14 +84,17 @@ describe('TeamConnector', () => {
historyEntries = ['/teams/new']
})
it('sends a POST request to POST /teams', async () => {
const newTeam: Partial<ITeam> = {id: 101, name: 'new-team'}
http.mockAdd({
method: 'post',
url: '/teams',
data: {name: 'new-team'},
}, newTeam)
const {render, store} = createTestProvider()
it('creates a new team', async () => {
const newTeam: Team.Team = {
id: 101,
name: 'new-team',
createDate: '',
updateDate: '',
userId: 9,
userTeams: [],
}
teamClientMock.create.mockResolvedValue(newTeam)
const {render, store, waitForActions} = createTestProvider()
const {node} = render({
history,
location: {} as any,
@ -92,20 +105,16 @@ describe('TeamConnector', () => {
.querySelector('input') as HTMLInputElement
T.Simulate.change(nameInput, {target: {value: newTeam.name}} as any)
T.Simulate.submit(addTeamForm)
await http.wait()
const {Team} = store.getState()
expect(Team.teamIds).toEqual([100, 101])
expect(Team.teamsById[101]).toEqual(newTeam)
await waitForActions()
const state = store.getState()
expect(state.Team.teamIds).toEqual([100, 101])
expect(state.Team.teamsById[101]).toEqual(newTeam)
})
it('displays an error', async () => {
const error = {error: 'An error'}
http.mockAdd({
method: 'post',
url: '/teams',
data: {name: 'test'},
}, error, 400)
const {render} = createTestProvider()
teamClientMock.create.mockRejectedValue(new Error('Test Error'))
const {render, waitForActions} = createTestProvider()
const {node} = render({
history,
location: {} as any,
@ -116,10 +125,10 @@ describe('TeamConnector', () => {
.querySelector('input') as HTMLInputElement
T.Simulate.change(nameInput, {target: {value: 'test'}} as any)
T.Simulate.submit(addTeamForm)
const error2 = await getError(http.wait())
expect(error2.message).toMatch(/HTTP Status: 400/)
const error2 = await getError(waitForActions())
expect(error2.message).toMatch(/Test Error/)
expect(nameInput.value).toEqual('test')
expect(addTeamForm.innerHTML).toMatch(/HTTP Status: 400/)
expect(addTeamForm.innerHTML).toMatch(/Test Error/)
})
})

View File

@ -1,29 +1,21 @@
import {Connector} from '../redux/Connector'
import {pack, TStateSelector} from '@rondo.dev/redux'
import {ITeamState} from './TeamReducer'
import {TeamActions} from './TeamActions'
import {TeamManager} from './TeamManager'
import {bindActionCreators} from 'redux'
import {withRouter} from 'react-router-dom'
import { team, user } from '@rondo.dev/common'
import { TReduxed } from '@rondo.dev/jsonrpc'
import { bindActionCreators, pack, TStateSelector } from '@rondo.dev/redux'
import { TeamManager } from './TeamManager'
import { ITeamState } from './TeamReducer'
export function configure<State>(
teamActions: TeamActions,
teamActions: team.TeamActions,
userActions: user.UserActions,
getLocalState: TStateSelector<State, ITeamState>,
) {
const Component = pack(
getLocalState,
state => ({...state}),
d => ({
addUser: bindActionCreators(teamActions.addUser, d),
removeUser: bindActionCreators(teamActions.removeUser, d),
createTeam: bindActionCreators(teamActions.createTeam, d),
updateTeam: bindActionCreators(teamActions.updateTeam, d),
removeTeam: bindActionCreators(teamActions.removeTeam, d),
fetchMyTeams: bindActionCreators(teamActions.fetchMyTeams, d),
fetchUsersInTeam:
bindActionCreators(teamActions.fetchUsersInTeam, d),
findUserByEmail:
bindActionCreators(teamActions.findUserByEmail, d),
dispatch => ({
teamActions: bindActionCreators(teamActions, dispatch),
findUserByEmail: bindActionCreators(
userActions.findUserByEmail, dispatch),
}),
TeamManager,
)

View File

@ -1,16 +1,15 @@
import { team as Team } from '@rondo.dev/common'
import { Button, Control, Heading, Help, Input } from 'bloomer'
import React from 'react'
import {Button, Control, Heading, Help, Input} from 'bloomer'
import {ITeam} from '@rondo.dev/common'
import {TeamActions} from './TeamActions'
import {FaPlusSquare, FaCheck, FaEdit} from 'react-icons/fa'
import { FaCheck, FaEdit, FaPlusSquare } from 'react-icons/fa'
export type TTeamEditorProps = {
type: 'add'
onAddTeam: TeamActions['createTeam']
onAddTeam: Team.TeamActions['create']
} | {
type: 'update'
onUpdateTeam: TeamActions['updateTeam']
team: ITeam
onUpdateTeam: Team.TeamActions['update']
team: Team.Team
}
export interface ITeamEditorState {
@ -28,7 +27,7 @@ extends React.PureComponent<TTeamEditorProps, ITeamEditorState> {
name: props.type === 'update' ? this.getName(props.team) : '',
}
}
getName(team?: ITeam) {
getName(team?: Team.Team) {
return team ? team.name : ''
}
componentWillReceiveProps(nextProps: TTeamEditorProps) {

View File

@ -1,23 +1,21 @@
import React from 'react'
import {Button, Panel, PanelHeading, PanelBlock} from 'bloomer'
import {FaPlus, FaEdit, FaTimes} from 'react-icons/fa'
import {ITeam, TReadonlyRecord} from '@rondo.dev/common'
import {Link} from 'react-router-dom'
import {TeamActions} from './TeamActions'
import {TeamEditor} from './TeamEditor'
import { team as Team, TReadonlyRecord } from '@rondo.dev/common';
import { Button, Panel, PanelBlock, PanelHeading } from 'bloomer';
import React from 'react';
import { FaEdit, FaPlus, FaTimes } from 'react-icons/fa';
import { Link } from 'react-router-dom';
export interface ITeamListProps {
ListButtons?: React.ComponentType<{team: ITeam}>
teamsById: TReadonlyRecord<number, ITeam>
ListButtons?: React.ComponentType<{team: Team.Team}>
teamsById: TReadonlyRecord<number, Team.Team>
teamIds: ReadonlyArray<number>
onAddTeam: TeamActions['createTeam']
onRemoveTeam: TeamActions['removeTeam']
onAddTeam: Team.TeamActions['create']
onRemoveTeam: Team.TeamActions['remove']
}
export interface ITeamProps {
ListButtons?: React.ComponentType<{team: ITeam}>
team: ITeam
onRemoveTeam: TeamActions['removeTeam']
ListButtons?: React.ComponentType<{team: Team.Team}>
team: Team.Team
onRemoveTeam: Team.TeamActions['remove']
}
export class TeamRow extends React.PureComponent<ITeamProps> {

View File

@ -1,32 +1,24 @@
import { IUserInTeam, team as Team, TReadonlyRecord, user as User } from '@rondo.dev/common'
import { Panel, PanelBlock, PanelHeading } from 'bloomer'
import { History, Location } from 'history'
import React from 'react'
import {History, Location} from 'history'
import {ITeam, IUserInTeam, TReadonlyRecord} from '@rondo.dev/common'
import {Panel, PanelBlock, PanelHeading} from 'bloomer'
import {Route, Switch} from 'react-router-dom'
import {TeamActions} from './TeamActions'
import {TeamEditor} from './TeamEditor'
import {TeamList} from './TeamList'
import {TeamUserList} from './TeamUserList'
import {match as Match} from 'react-router'
import { match as Match } from 'react-router'
import { Route, Switch } from 'react-router-dom'
import { TeamEditor } from './TeamEditor'
import { TeamList } from './TeamList'
import { TeamUserList } from './TeamUserList'
export interface ITeamManagerProps {
history: History
location: Location
match: Match<any>
ListButtons?: React.ComponentType<{team: ITeam}>
ListButtons?: React.ComponentType<{team: Team.Team}>
createTeam: TeamActions['createTeam']
updateTeam: TeamActions['updateTeam']
removeTeam: TeamActions['removeTeam']
teamActions: Team.TeamActions
findUserByEmail: User.UserActions['findUserByEmail']
addUser: TeamActions['addUser']
removeUser: TeamActions['removeUser']
fetchMyTeams: TeamActions['fetchMyTeams']
fetchUsersInTeam: TeamActions['fetchUsersInTeam']
findUserByEmail: TeamActions['findUserByEmail']
teamsById: TReadonlyRecord<number, ITeam>
teamsById: TReadonlyRecord<number, Team.Team>
teamIds: ReadonlyArray<number>
userKeysByTeamId: TReadonlyRecord<number, ReadonlyArray<string>>
@ -35,10 +27,10 @@ export interface ITeamManagerProps {
export class TeamManager extends React.PureComponent<ITeamManagerProps> {
async componentDidMount() {
await this.props.fetchMyTeams()
await this.props.teamActions.find()
}
handleCreateNewTeam = (team: {name: string}) => {
const action = this.props.createTeam(team)
const action = this.props.teamActions.create(team)
action.payload
.then(() => this.props.history.push('/teams'))
.catch(() => {/* do nothing */})
@ -62,8 +54,8 @@ export class TeamManager extends React.PureComponent<ITeamManagerProps> {
ListButtons={this.props.ListButtons}
teamsById={teamsById}
teamIds={this.props.teamIds}
onAddTeam={this.props.createTeam}
onRemoveTeam={this.props.removeTeam}
onAddTeam={this.props.teamActions.create}
onRemoveTeam={this.props.teamActions.remove}
/>
</>
}/>
@ -79,16 +71,16 @@ export class TeamManager extends React.PureComponent<ITeamManagerProps> {
{team && <TeamEditor
type='update'
team={team}
onUpdateTeam={this.props.updateTeam}
onUpdateTeam={this.props.teamActions.update}
/>}
{!team && 'No team loaded'}
</PanelBlock>
</Panel>
{team && <TeamUserList
onAddUser={this.props.addUser}
onRemoveUser={this.props.removeUser}
onAddUser={this.props.teamActions.addUser}
onRemoveUser={this.props.teamActions.removeUser}
findUserByEmail={this.props.findUserByEmail}
fetchUsersInTeam={this.props.fetchUsersInTeam}
fetchUsersInTeam={this.props.teamActions.findUsers}
team={team}
userKeysByTeamId={this.props.userKeysByTeamId}
usersByKey={this.props.usersByKey}

View File

@ -1,10 +1,8 @@
import {
ITeam, IUserInTeam, TReadonlyRecord, indexBy, without,
} from '@rondo.dev/common'
import {TTeamAction} from './TeamActions'
import {TGetResolvedAction} from '@rondo.dev/redux'
import { indexBy, ITeam, IUserInTeam, team as T, TReadonlyRecord, without } from '@rondo.dev/common';
import { createReducer } from '@rondo.dev/jsonrpc';
export interface ITeamState {
readonly loading: number
readonly error: string
readonly teamIds: ReadonlyArray<number>
@ -15,6 +13,7 @@ export interface ITeamState {
}
const defaultState: ITeamState = {
loading: 0,
error: '',
teamIds: [],
@ -24,11 +23,56 @@ const defaultState: ITeamState = {
usersByKey: {},
}
function removeUser(
state: ITeamState,
action: TGetResolvedAction<TTeamAction, 'TEAM_USER_REMOVE'>,
) {
function getUserKey(userInTeam: {userId: number, teamId: number}) {
return `${userInTeam.teamId}_${userInTeam.userId}`
}
export const Team = createReducer('teamService', defaultState)
.withMapping<T.TeamActions>({
create(state, action) {
return {
teamIds: state.teamIds.indexOf(action.payload.id) >= 0
? state.teamIds
: [...state.teamIds, action.payload.id],
teamsById: {
...state.teamsById,
[action.payload.id]: action.payload,
},
}
},
update(state, action) {
return {
teamIds: state.teamIds.indexOf(action.payload.id) >= 0
? state.teamIds
: [...state.teamIds, action.payload.id],
teamsById: {
...state.teamsById,
[action.payload.id]: action.payload,
},
}
},
remove(state, action) {
return {
teamIds: state.teamIds.filter(id => id !== action.payload.id),
teamsById: without(state.teamsById, action.payload.id),
}
},
addUser(state, action) {
return {
userKeysByTeamId: {
...state.userKeysByTeamId,
[action.payload.teamId]: [
...state.userKeysByTeamId[action.payload.teamId],
getUserKey(action.payload),
],
},
usersByKey: {
...state.usersByKey,
[getUserKey(action.payload)]: action.payload,
},
}
},
removeUser(state, action) {
const {payload} = action
const {teamId} = payload
const userKey = getUserKey(payload)
@ -42,63 +86,20 @@ function removeUser(
delete usersByKey[getUserKey(payload)]
return {
...state,
userKeysByTeamId,
usersByKey,
}
}
function getUserKey(userInTeam: {userId: number, teamId: number}) {
return `${userInTeam.teamId}_${userInTeam.userId}`
}
export function Team(state = defaultState, action: TTeamAction): ITeamState {
switch (action.status) {
case 'pending':
return state
case 'rejected':
},
find(state, action) {
return {
...state,
error: action.payload.message,
}
case 'resolved':
switch (action.type) {
case 'TEAMS':
return {
...state,
teamIds: action.payload.map(team => team.id),
teamsById: indexBy(action.payload, 'id'),
}
case 'TEAM_CREATE':
case 'TEAM_UPDATE':
return {
...state,
teamIds: state.teamIds.indexOf(action.payload.id) >= 0
? state.teamIds
: [...state.teamIds, action.payload.id],
teamsById: {
...state.teamsById,
[action.payload.id]: action.payload,
},
}
case 'TEAM_USER_ADD':
return {
...state,
userKeysByTeamId: {
...state.userKeysByTeamId,
[action.payload.teamId]: [
...state.userKeysByTeamId[action.payload.teamId],
getUserKey(action.payload),
],
findOne(state, action) {
throw new Error('TeamReducer#findOne not implemented')
},
usersByKey: {
...state.usersByKey,
[getUserKey(action.payload)]: action.payload,
},
}
case 'TEAM_USER_REMOVE':
return removeUser(state, action)
case 'TEAM_USERS':
findUsers(state, action) {
const usersByKey = action.payload.usersInTeam
.reduce((obj, userInTeam) => {
obj[getUserKey(userInTeam)] = userInTeam
@ -117,16 +118,5 @@ export function Team(state = defaultState, action: TTeamAction): ITeamState {
...usersByKey,
},
}
case 'TEAM_REMOVE':
return {
...state,
teamIds: state.teamIds.filter(id => id !== action.payload.id),
teamsById: without(state.teamsById, action.payload.id),
}
default:
return state
}
default:
return state
}
}
},
})

View File

@ -1,35 +1,33 @@
import React from 'react'
import {ITeam, IUser, IUserInTeam, TReadonlyRecord} from '@rondo.dev/common'
import {TeamActions} from './TeamActions'
import {FaUser, FaCheck, FaTimes} from 'react-icons/fa'
import { IUser, IUserInTeam, team as Team, TReadonlyRecord, user as User } from '@rondo.dev/common';
import { Button, Control, Heading, Help, Input, Panel, PanelBlock, PanelHeading } from 'bloomer';
import React from 'react';
import { FaCheck, FaTimes, FaUser } from 'react-icons/fa';
import {
Button, Control, Heading, Help, Input, Panel, PanelHeading, PanelBlock
} from 'bloomer'
const EMPTY_ARRAY: ReadonlyArray<string> = []
export interface ITeamUsersProps {
// fetchMyTeams: () => void,
fetchUsersInTeam: TeamActions['fetchUsersInTeam']
findUserByEmail: TeamActions['findUserByEmail']
fetchUsersInTeam: Team.TeamActions['findUsers']
findUserByEmail: User.UserActions['findUserByEmail']
onAddUser: TeamActions['addUser']
onRemoveUser: TeamActions['removeUser']
onAddUser: Team.TeamActions['addUser']
onRemoveUser: Team.TeamActions['removeUser']
team: ITeam
team: Team.Team
userKeysByTeamId: TReadonlyRecord<number, ReadonlyArray<string>>
usersByKey: TReadonlyRecord<string, IUserInTeam>
}
export interface ITeamUserProps {
onRemoveUser: (params: {userId: number, teamId: number}) => void
onRemoveUser: (
params: {userId: number, teamId: number, roleId: number}) => void
user: IUserInTeam
}
export interface IAddUserProps {
onAddUser: TeamActions['addUser']
onSearchUser: TeamActions['findUserByEmail']
onAddUser: Team.TeamActions['addUser']
onSearchUser: User.UserActions['findUserByEmail']
teamId: number
}
@ -42,7 +40,7 @@ export interface IAddUserState {
export class TeamUser extends React.PureComponent<ITeamUserProps> {
handleRemoveUser = async () => {
const {onRemoveUser, user} = this.props
await onRemoveUser(user)
await onRemoveUser({...user, roleId: 1})
}
render() {
const {user} = this.props
@ -145,7 +143,7 @@ export class TeamUserList extends React.PureComponent<ITeamUsersProps> {
}
async fetchUsersInTeam(teamId: number) {
if (teamId) {
await this.props.fetchUsersInTeam({teamId})
await this.props.fetchUsersInTeam(teamId)
}
}
render() {

View File

@ -1,4 +1,3 @@
export * from './TeamActions'
export * from './TeamConnector'
export * from './TeamList'
export * from './TeamManager'

View File

@ -1,7 +1,7 @@
import React from 'react'
import ReactDOM from 'react-dom'
import T from 'react-dom/test-utils'
import {createStore, TStateSelector} from '@rondo.dev/redux'
import {createStore, TStateSelector, WaitMiddleware} from '@rondo.dev/redux'
import {Provider} from 'react-redux'
import {
Action,
@ -10,7 +10,10 @@ import {
Reducer,
ReducersMapObject,
combineReducers,
Store as ReduxStore,
Unsubscribe,
} from 'redux'
import { format } from 'util'
interface IRenderParams<State, LocalState> {
reducers: ReducersMapObject<State, any>
@ -56,8 +59,12 @@ export class TestUtils {
) {
const {reducers, select} = params
const waitMiddleware = new WaitMiddleware()
const recorder = waitMiddleware.record()
let store = this.createStore({
reducer: this.combineReducers(reducers),
extraMiddleware: [waitMiddleware.handle],
})()
const withState = (state: DeepPartial<State>) => {
@ -104,6 +111,9 @@ export class TestUtils {
store,
Component,
withJSX,
async waitForActions() {
await waitMiddleware.waitForRecorded(recorder, 2000)
},
}
return self
@ -119,4 +129,5 @@ interface ISelf<Props, Store, Component, CreateJSX> {
Component: Component
withJSX: (localCreateJSX: CreateJSX)
=> ISelf<Props, Store, Component, CreateJSX>
waitForActions(): Promise<void>
}

View File

@ -1,7 +1,10 @@
// import {ITeam} from './ITeam'
import {Team} from './entities'
import {IUserInTeam} from './IUserInTeam'
import { TReduxed } from '@rondo.dev/jsonrpc'
import { keys } from 'ts-transformer-keys'
import { Team } from './entities'
import { IUserInTeam } from './IUserInTeam'
export { Team }
export interface ITeamAddUserParams {
teamId: number
@ -22,6 +25,11 @@ export interface ITeamUpdateParams {
name: string
}
export interface ITeamUsers {
teamId: number
usersInTeam: IUserInTeam[]
}
export interface IContext {
userId: number
}
@ -41,9 +49,10 @@ export interface ITeamService {
find(): Promise<Team[]>
findUsers(teamId: number): Promise<IUserInTeam[]>
findUsers(teamId: number): Promise<ITeamUsers>
// TODO add other methods
}
export const TeamServiceMethods = keys<ITeamService>()
export type TeamActions = TReduxed<ITeamService, 'teamService'>

View File

@ -1,7 +1,7 @@
import {ICredentials} from './ICredentials'
import {IUser} from './IUser'
import * as e from './entities'
import {keys} from 'ts-transformer-keys'
import { TReduxed } from '@rondo.dev/jsonrpc'
import { keys } from 'ts-transformer-keys'
import { ICredentials } from './ICredentials'
import { IUser } from './IUser'
export interface IChangePasswordParams {
oldPassword: string
@ -19,3 +19,4 @@ export interface IUserService {
}
export const UserServiceMethods = keys<IUserService>()
export type UserActions = TReduxed<IUserService, 'userService'>

View File

@ -0,0 +1,8 @@
import { bindActionCreators as bind, Dispatch } from 'redux'
export function bindActionCreators<T extends object>(
obj: T,
dispatch: Dispatch,
): T {
return bind(obj as any, dispatch)
}

View File

@ -1,4 +1,5 @@
export * from './actions'
export * from './bindActionCreators'
export * from './middleware'
export * from './store'
export * from './pack'

View File

@ -49,11 +49,10 @@ export class WaitMiddleware {
}
const actionsByName = actions.reduce((obj, type) => {
obj[type] = true
obj[type] = (obj[type] || 0) + 1
return obj
}, {} as Record<string, boolean>)
// no duplicates here so we cannot use actions.length
let count = Object.keys(actionsByName).length
}, {} as Record<string, number>)
let count = actions.length
return new Promise((resolve, reject) => {
if (!actions.length) {
@ -75,7 +74,7 @@ export class WaitMiddleware {
case 'pending':
return
case 'resolved':
actionsByName[action.type] = false
actionsByName[action.type]--
count--
if (count === 0) {
resolve()

View File

@ -137,7 +137,10 @@ export class TeamService implements RPC<t.ITeamService> {
})
.getMany()
return userTeams.map(this._mapUserInTeam)
return {
teamId,
usersInTeam: userTeams.map(this._mapUserInTeam),
}
}
protected _mapUserInTeam(ut: UserTeam): IUserInTeam {