Add packages/client/src/crumbs
This commit is contained in:
parent
9b5809a1d5
commit
d3f294a57c
@ -1 +0,0 @@
|
||||
export * from './Breadcrumbs'
|
||||
@ -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>
|
||||
)
|
||||
@ -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',
|
||||
25
packages/client/src/crumbs/CrumbsActions.ts
Normal file
25
packages/client/src/crumbs/CrumbsActions.ts
Normal 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',
|
||||
}
|
||||
}
|
||||
}
|
||||
52
packages/client/src/crumbs/CrumbsConnector.test.tsx
Normal file
52
packages/client/src/crumbs/CrumbsConnector.test.tsx
Normal 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/)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
17
packages/client/src/crumbs/CrumbsConnector.tsx
Normal file
17
packages/client/src/crumbs/CrumbsConnector.tsx
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
22
packages/client/src/crumbs/CrumbsReducer.tsx
Normal file
22
packages/client/src/crumbs/CrumbsReducer.tsx
Normal 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
|
||||
}
|
||||
}
|
||||
4
packages/client/src/crumbs/index.ts
Normal file
4
packages/client/src/crumbs/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './Crumb'
|
||||
export * from './CrumbsActions'
|
||||
export * from './CrumbsConnector'
|
||||
export * from './CrumbsReducer'
|
||||
@ -1,5 +1,6 @@
|
||||
export * from './actions'
|
||||
export * from './components'
|
||||
export * from './crumbs'
|
||||
export * from './csrf'
|
||||
export * from './http'
|
||||
export * from './login'
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user