Add packages/client/src/crumbs

This commit is contained in:
Jerko Steiner 2019-03-25 18:27:32 +08:00
parent 9b5809a1d5
commit d3f294a57c
11 changed files with 156 additions and 22 deletions

View File

@ -1 +0,0 @@
export * from './Breadcrumbs'

View File

@ -2,7 +2,7 @@ import React from 'react'
import {Breadcrumb, BreadcrumbItem} from 'bloomer'
import {Link} from 'react-router-dom'
export interface IBreadcrumbsProps {
export interface ICrumbProps {
links: Array<{
name: string
to: string
@ -10,17 +10,22 @@ export interface IBreadcrumbsProps {
current: string
}
export class Breadcrumbs extends React.PureComponent<IBreadcrumbsProps> {
export class Crumb extends React.PureComponent<ICrumbProps> {
render() {
return (
<Breadcrumb>
<ul>
<BreadcrumbItem>
<Link to='/'>Home</Link>
</BreadcrumbItem>
{this.props.links.map((link, i) => (
<BreadcrumbItem key={i}>
<Link to={link.to}>{link.name}</Link>
</BreadcrumbItem>
))}
<BreadcrumbItem>{this.props.current}</BreadcrumbItem>
<BreadcrumbItem>
<a>{this.props.current}</a>
</BreadcrumbItem>
</ul>
</Breadcrumb>
)

View File

@ -1,17 +1,17 @@
import React from 'react'
import {Breadcrumbs} from './Breadcrumbs'
import {Crumb} from './Crumb'
import {TestUtils} from '../test-utils'
import {MemoryRouter} from 'react-router-dom'
const t = new TestUtils()
describe('Breadcrumbs', () => {
describe('Crumb', () => {
describe('render', () => {
it('renders', () => {
const {node} = t.render(
<MemoryRouter>
<Breadcrumbs
<Crumb
links={[{
name: 'one',
to: '/one',

View File

@ -0,0 +1,25 @@
import {GetAction, IResolvedAction} from '../actions'
export interface ICrumbLink {
name: string
to: string
}
export interface ICrumbs {
links: ICrumbLink[]
current: string
}
export type CrumbsActionType =
IResolvedAction<ICrumbs, 'BREADCRUMBS_SET'>
type Action<T extends string> = GetAction<CrumbsActionType, T>
export class CrumbsActions {
setCrumbs(breadcrumbs: ICrumbs): Action<'BREADCRUMBS_SET'> {
return {
payload: breadcrumbs,
type: 'BREADCRUMBS_SET',
}
}
}

View File

@ -0,0 +1,52 @@
import * as Feature from './'
import React from 'react'
import {MemoryRouter} from 'react-router-dom'
import {TestUtils} from '../test-utils'
const t = new TestUtils()
describe('CrumbsConnector', () => {
const createTestCase = () => t.withProvider({
reducers: {Crumbs: Feature.Crumbs},
select: s => s.Crumbs,
})
.withState({
Crumbs: {
links: [{
name: 'One',
to: '/one',
}, {
name: 'Two',
to: '/two',
}],
current: 'Three',
},
})
.withComponent(select => new Feature.CrumbsConnector().connect(select))
.withJSX(Component => <MemoryRouter><Component /></MemoryRouter>)
describe('render', () => {
it('renders', () => {
const {node} = createTestCase().render({})
expect(node.innerHTML).toMatch(/href="\/one"/)
expect(node.innerHTML).toMatch(/href="\/two"/)
expect(node.innerHTML).toMatch(/Three/)
})
})
describe('BREADCRUMBS_SET', () => {
it('updates breadcrumbs', () => {
const {render, store} = createTestCase()
const actions = new Feature.CrumbsActions()
store.dispatch(actions.setCrumbs({
links: [],
current: 'Crumbtest',
}))
const {node} = render({})
expect(node.innerHTML).toMatch(/Crumbtest/)
})
})
})

View File

@ -0,0 +1,17 @@
import {Crumb} from './Crumb'
import {Connector, IStateSelector} from '../redux'
import {ICrumbsState} from './CrumbsReducer'
import {CrumbsActions} from './CrumbsActions'
export class CrumbsConnector extends Connector<ICrumbsState> {
protected readonly breadcrumbsActions = new CrumbsActions()
connect<State>(getLocalState: IStateSelector<State, ICrumbsState>) {
return this.wrap(
getLocalState,
state => state,
dispatch => ({}),
Crumb,
)
}
}

View File

@ -0,0 +1,22 @@
import {ICrumbs, CrumbsActionType} from './CrumbsActions'
export interface ICrumbsState extends ICrumbs {
}
const defaultState: ICrumbsState = {
links: [],
current: 'Home',
}
export function Crumbs(state = defaultState, action: CrumbsActionType)
: ICrumbsState {
switch (action.type) {
case 'BREADCRUMBS_SET':
return {
links: action.payload.links,
current: action.payload.current,
}
default:
return state
}
}

View File

@ -0,0 +1,4 @@
export * from './Crumb'
export * from './CrumbsActions'
export * from './CrumbsConnector'
export * from './CrumbsReducer'

View File

@ -1,5 +1,6 @@
export * from './actions'
export * from './components'
export * from './crumbs'
export * from './csrf'
export * from './http'
export * from './login'

View File

@ -1,11 +1,11 @@
import React from 'react'
import {ITeam, IUserInTeam, ReadonlyRecord} from '@rondo/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 {Panel, PanelBlock, PanelHeading} from 'bloomer'
export interface ITeamManagerProps {
createTeam: TeamActions['createTeam']
@ -36,6 +36,7 @@ export class TeamManager extends React.PureComponent<ITeamManagerProps> {
<div className='team-manager'>
<Switch>
<Route exact path='/teams' render={() =>
<>
<TeamList
teamsById={teamsById}
teamIds={this.props.teamIds}
@ -43,13 +44,14 @@ export class TeamManager extends React.PureComponent<ITeamManagerProps> {
onRemoveTeam={this.props.removeTeam}
onUpdateTeam={this.props.updateTeam}
/>
</>
}/>
<Route exact path='/teams/:teamId/users' render={({match}) => {
const {teamId: teamIdParam} = match.params
const teamId = teamIdParam ? Number(teamIdParam) : undefined
const team = teamId ? teamsById[teamId] : undefined
return (
<React.Fragment>
<>
<Panel>
<PanelHeading>Edit Team: {team && team.name}</PanelHeading>
<PanelBlock isDisplay='block'>
@ -70,7 +72,7 @@ export class TeamManager extends React.PureComponent<ITeamManagerProps> {
userKeysByTeamId={this.props.userKeysByTeamId}
usersByKey={this.props.usersByKey}
/>}
</React.Fragment>
</>
)
}}/>
</Switch>

View File

@ -15,7 +15,6 @@ import {
interface IRenderParams<State, LocalState> {
reducers: ReducersMapObject<State, any>
state?: DeepPartial<State>
select: IStateSelector<State, LocalState>
// getComponent: (
// select: IStateSelector<State, LocalState>) => React.ComponentType<Props>,
@ -51,12 +50,20 @@ export class TestUtils {
withProvider<State, LocalState, A extends Action<any> = AnyAction>(
params: IRenderParams<State, LocalState>,
) {
const {reducers, state, select} = params
const {reducers, select} = params
const store = this.createStore({
let store = this.createStore({
reducer: this.combineReducers(reducers),
})()
const withState = (state: DeepPartial<State>) => {
store = this.createStore({
reducer: this.combineReducers(reducers),
})(state)
return {withComponent}
}
const withComponent = <Props extends {}>(
getComponent: (select: IStateSelector<State, LocalState>) =>
React.ComponentType<Props>,
@ -96,6 +103,6 @@ export class TestUtils {
return self
}
return {withComponent}
return {withState, withComponent}
}
}