diff --git a/packages/client/src/team/TeamActions.ts b/packages/client/src/team/TeamActions.ts deleted file mode 100644 index 0ee229b..0000000 --- a/packages/client/src/team/TeamActions.ts +++ /dev/null @@ -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 - | TAsyncAction - | TAsyncAction - | TAsyncAction<{id: number}, 'TEAM_REMOVE'> - | TAsyncAction - | TAsyncAction<{userId: number, teamId: number}, 'TEAM_USER_REMOVE'> - | TAsyncAction<{teamId: number, usersInTeam: IUserInTeam[]}, 'TEAM_USERS'> - | TAsyncAction - -type Action = TGetPendingAction - -export class TeamActions { - constructor(protected readonly http: IHTTPClient) {} - - 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', - ) - } -} diff --git a/packages/client/src/team/TeamConnector.test.tsx b/packages/client/src/team/TeamConnector.test.tsx index d6630ee..b1dcd78 100644 --- a/packages/client/src/team/TeamConnector.test.tsx +++ b/packages/client/src/team/TeamConnector.test.tsx @@ -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 + const [teamClient, teamClientMock] = + createClientMock(Team.TeamServiceMethods) + const [userClient, userClientMock] = + createClientMock(User.UserServiceMethods) + let teamActions!: Team.TeamActions + let userActions!: User.UserActions beforeEach(() => { - http = new HTTPClientMock() + 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) => , ) - const teams: ITeam[] = [{id: 100, name: 'my-team', userId: 1}] - - const users: IUserInTeam[] = [{ - teamId: 123, + const teams: Team.Team[] = [{ + id: 100, + name: 'my-team', userId: 1, - displayName: 'test test', - roleId: 1, - roleName: 'ADMIN', + createDate: '', + updateDate: '', + userTeams: [], }] + 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 = {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/) }) }) diff --git a/packages/client/src/team/TeamConnector.ts b/packages/client/src/team/TeamConnector.ts index 2f21d9a..eca5f2f 100644 --- a/packages/client/src/team/TeamConnector.ts +++ b/packages/client/src/team/TeamConnector.ts @@ -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( - teamActions: TeamActions, + teamActions: team.TeamActions, + userActions: user.UserActions, getLocalState: TStateSelector, ) { 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, ) diff --git a/packages/client/src/team/TeamEditor.tsx b/packages/client/src/team/TeamEditor.tsx index b012058..9423f7b 100644 --- a/packages/client/src/team/TeamEditor.tsx +++ b/packages/client/src/team/TeamEditor.tsx @@ -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 { name: props.type === 'update' ? this.getName(props.team) : '', } } - getName(team?: ITeam) { + getName(team?: Team.Team) { return team ? team.name : '' } componentWillReceiveProps(nextProps: TTeamEditorProps) { diff --git a/packages/client/src/team/TeamList.tsx b/packages/client/src/team/TeamList.tsx index b32c7e2..27903d9 100644 --- a/packages/client/src/team/TeamList.tsx +++ b/packages/client/src/team/TeamList.tsx @@ -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 + ListButtons?: React.ComponentType<{team: Team.Team}> + teamsById: TReadonlyRecord teamIds: ReadonlyArray - 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 { diff --git a/packages/client/src/team/TeamManager.tsx b/packages/client/src/team/TeamManager.tsx index 33b4478..ded126a 100644 --- a/packages/client/src/team/TeamManager.tsx +++ b/packages/client/src/team/TeamManager.tsx @@ -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 - 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 + teamsById: TReadonlyRecord teamIds: ReadonlyArray userKeysByTeamId: TReadonlyRecord> @@ -35,10 +27,10 @@ export interface ITeamManagerProps { export class TeamManager extends React.PureComponent { 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 { 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 { {team && } {!team && 'No team loaded'} {team && @@ -15,6 +13,7 @@ export interface ITeamState { } const defaultState: ITeamState = { + loading: 0, error: '', teamIds: [], @@ -24,109 +23,100 @@ const defaultState: ITeamState = { usersByKey: {}, } -function removeUser( - state: ITeamState, - action: TGetResolvedAction, -) { - - const {payload} = action - const {teamId} = payload - const userKey = getUserKey(payload) - - const userKeysByTeamId = { - ...state.userKeysByTeamId, - [teamId]: state.userKeysByTeamId[teamId].filter(u => u !== userKey), - } - - const usersByKey = {...state.usersByKey} - 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': - 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), - ], - }, - usersByKey: { - ...state.usersByKey, - [getUserKey(action.payload)]: action.payload, - }, - } - case 'TEAM_USER_REMOVE': - return removeUser(state, action) - case 'TEAM_USERS': - const usersByKey = action.payload.usersInTeam - .reduce((obj, userInTeam) => { - obj[getUserKey(userInTeam)] = userInTeam - return obj - }, {} as Record) +export const Team = createReducer('teamService', defaultState) +.withMapping({ + 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) - return { - ...state, - userKeysByTeamId: { - ...state.userKeysByTeamId, - [action.payload.teamId]: action.payload.usersInTeam - .map(ut => getUserKey(ut)), - }, - usersByKey: { - ...state.usersByKey, - ...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 - } -} + const userKeysByTeamId = { + ...state.userKeysByTeamId, + [teamId]: state.userKeysByTeamId[teamId].filter(u => u !== userKey), + } + + const usersByKey = {...state.usersByKey} + delete usersByKey[getUserKey(payload)] + + return { + userKeysByTeamId, + usersByKey, + } + }, + find(state, action) { + return { + teamIds: action.payload.map(team => team.id), + teamsById: indexBy(action.payload, 'id'), + } + }, + findOne(state, action) { + throw new Error('TeamReducer#findOne not implemented') + }, + findUsers(state, action) { + const usersByKey = action.payload.usersInTeam + .reduce((obj, userInTeam) => { + obj[getUserKey(userInTeam)] = userInTeam + return obj + }, {} as Record) + + return { + ...state, + userKeysByTeamId: { + ...state.userKeysByTeamId, + [action.payload.teamId]: action.payload.usersInTeam + .map(ut => getUserKey(ut)), + }, + usersByKey: { + ...state.usersByKey, + ...usersByKey, + }, + } + }, +}) diff --git a/packages/client/src/team/TeamUserList.tsx b/packages/client/src/team/TeamUserList.tsx index 40fbae7..2cb0778 100644 --- a/packages/client/src/team/TeamUserList.tsx +++ b/packages/client/src/team/TeamUserList.tsx @@ -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 = [] 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> usersByKey: TReadonlyRecord } 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 { 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 { } async fetchUsersInTeam(teamId: number) { if (teamId) { - await this.props.fetchUsersInTeam({teamId}) + await this.props.fetchUsersInTeam(teamId) } } render() { diff --git a/packages/client/src/team/index.ts b/packages/client/src/team/index.ts index 30528e4..761b825 100644 --- a/packages/client/src/team/index.ts +++ b/packages/client/src/team/index.ts @@ -1,4 +1,3 @@ -export * from './TeamActions' export * from './TeamConnector' export * from './TeamList' export * from './TeamManager' diff --git a/packages/client/src/test-utils/TestUtils.tsx b/packages/client/src/test-utils/TestUtils.tsx index fee6bd8..33d84e0 100644 --- a/packages/client/src/test-utils/TestUtils.tsx +++ b/packages/client/src/test-utils/TestUtils.tsx @@ -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 { reducers: ReducersMapObject @@ -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) => { @@ -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 { Component: Component withJSX: (localCreateJSX: CreateJSX) => ISelf + waitForActions(): Promise } diff --git a/packages/common/src/team.ts b/packages/common/src/team.ts index 12bacc9..0fdd2da 100644 --- a/packages/common/src/team.ts +++ b/packages/common/src/team.ts @@ -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 - findUsers(teamId: number): Promise + findUsers(teamId: number): Promise // TODO add other methods } export const TeamServiceMethods = keys() +export type TeamActions = TReduxed diff --git a/packages/common/src/user.ts b/packages/common/src/user.ts index 76e30e6..8f1cffa 100644 --- a/packages/common/src/user.ts +++ b/packages/common/src/user.ts @@ -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() +export type UserActions = TReduxed diff --git a/packages/redux/src/bindActionCreators.ts b/packages/redux/src/bindActionCreators.ts new file mode 100644 index 0000000..cf69bf5 --- /dev/null +++ b/packages/redux/src/bindActionCreators.ts @@ -0,0 +1,8 @@ +import { bindActionCreators as bind, Dispatch } from 'redux' + +export function bindActionCreators( + obj: T, + dispatch: Dispatch, +): T { + return bind(obj as any, dispatch) +} diff --git a/packages/redux/src/index.ts b/packages/redux/src/index.ts index 922bfbb..248be36 100644 --- a/packages/redux/src/index.ts +++ b/packages/redux/src/index.ts @@ -1,4 +1,5 @@ export * from './actions' +export * from './bindActionCreators' export * from './middleware' export * from './store' export * from './pack' diff --git a/packages/redux/src/middleware/WaitMiddleware.ts b/packages/redux/src/middleware/WaitMiddleware.ts index a195249..e20f59d 100644 --- a/packages/redux/src/middleware/WaitMiddleware.ts +++ b/packages/redux/src/middleware/WaitMiddleware.ts @@ -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) - // no duplicates here so we cannot use actions.length - let count = Object.keys(actionsByName).length + }, {} as Record) + 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() diff --git a/packages/server/src/rpc/TeamService.ts b/packages/server/src/rpc/TeamService.ts index 4714f16..b48661a 100644 --- a/packages/server/src/rpc/TeamService.ts +++ b/packages/server/src/rpc/TeamService.ts @@ -137,7 +137,10 @@ export class TeamService implements RPC { }) .getMany() - return userTeams.map(this._mapUserInTeam) + return { + teamId, + usersInTeam: userTeams.map(this._mapUserInTeam), + } } protected _mapUserInTeam(ut: UserTeam): IUserInTeam {