Skip to content

Commit

Permalink
feat(hoc): properly expose WrappedComponent type (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hotell authored Jul 27, 2018
1 parent 3077d32 commit 883074c
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 10 deletions.
24 changes: 22 additions & 2 deletions src/__tests__/__snapshots__/hoc.spec.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Hoc wrappers should should return wrapped component with proper prop annotation 1`] = `
<CounterModule
title="Hello"
/>
`;

exports[`Hoc wrappers should should return wrapped component with proper prop annotation 2`] = `
<Counter
counterService={
CounterService {
"logger": Logger {},
"state": Object {
"count": 0,
},
}
}
logger={Logger {}}
/>
`;

exports[`Hoc wrappers should work with HoC 1`] = `
<App>
<main>
<WithProvider(CounterModule
<WithProvider(CounterModule)
title="count module"
>
<Provider
Expand Down Expand Up @@ -137,7 +157,7 @@ exports[`Hoc wrappers should work with HoC 1`] = `
</div>
</CounterModule>
</Provider>
</WithProvider(CounterModule>
</WithProvider(CounterModule)>
</main>
</App>
`;
2 changes: 1 addition & 1 deletion src/__tests__/guards.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ describe(`guards`, () => {
expect(isJsLikeObject(undefined)).toBe(false)
expect(isJsLikeObject(true)).toBe(false)
expect(isJsLikeObject(noop)).toBe(false)
expect(isJsLikeObject(null)).toBe(false)

expect(isJsLikeObject(null)).toBe(true)
expect(isJsLikeObject(emptyArr)).toBe(true)
expect(isJsLikeObject(emptyObj)).toBe(true)
})
Expand Down
30 changes: 30 additions & 0 deletions src/__tests__/hoc.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { mount } from 'enzyme'
import React, { Component } from 'react'

import { ReflectiveInjector } from 'injection-js'
import { noop } from '../helpers'
import { withInjectables } from '../with-injectables'
import { withProvider } from '../with-provider'
Expand Down Expand Up @@ -79,4 +80,33 @@ describe('Hoc wrappers', () => {

tree.unmount()
})

it(`should should return wrapped component with proper prop annotation`, () => {
const injector = ReflectiveInjector.resolveAndCreate([
Logger,
CounterService,
])
const logger: Logger = injector.get(Logger)
const counter: CounterService = injector.get(CounterService)

expect(CounterModuleEnhanced.WrappedComponent).toBe(CounterModule)
expect(CounterEnhanced.WrappedComponent).toBe(Counter)

expect(
<CounterModuleEnhanced.WrappedComponent title="Hello" />
).toMatchSnapshot()
expect(
<CounterEnhanced.WrappedComponent
counterService={counter}
logger={logger}
/>
).toMatchSnapshot()
})

it(`should should create proper displayName`, () => {
expect(CounterModuleEnhanced.displayName).toBe(
'WithProvider(CounterModule)'
)
expect(CounterEnhanced.displayName).toBe('WithInjectables(Counter)')
})
})
2 changes: 1 addition & 1 deletion src/guards.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Provider, Type, TypeProvider } from 'injection-js'

export const isJsLikeObject = <T extends object>(value: any): value is T =>
typeof value === 'object'
value !== null && typeof value === 'object'
export const isBlank = <T>(
value: any
): value is T extends undefined | null ? T : never => value == null
Expand Down
8 changes: 8 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,13 @@ export const getComponentDisplayName = <P>(Component: ComponentType<P>) =>
(Component.constructor && Component.constructor.name) ||
'Component'

export const createHOCName = <P>(
Wrapper: ComponentType<any>,
WrappedComponent: ComponentType<P>
) =>
`${getComponentDisplayName(Wrapper)}(${getComponentDisplayName(
WrappedComponent
)})`

// tslint:disable-next-line:no-empty
export const noop = () => {}
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Type } from 'injection-js'
import { ComponentClass, ComponentType } from 'react'

export type StateCallback<T = {}> = (state: T) => Partial<T>
export type WrapperProps<
Expand All @@ -7,3 +8,9 @@ export type WrapperProps<
> = Pick<OriginalProps, Exclude<keyof OriginalProps, keyof P>>

export type ProvidersMap<T = any> = { [key: string]: Type<T> }

export type HoCComponentClass<P, O extends ComponentType<any>> = ComponentClass<
P
> & {
WrappedComponent: O
}
4 changes: 2 additions & 2 deletions src/with-injectables.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { Component, ComponentType } from 'react'

import { getComponentDisplayName } from './helpers'
import { Inject } from './inject'
import { ProvidersMap, WrapperProps } from './types'
import { HoCComponentClass, ProvidersMap, WrapperProps } from './types'

/**
* If you need to access injected service instances outside of render, you can use this high order component.
Expand All @@ -13,7 +13,7 @@ import { ProvidersMap, WrapperProps } from './types'
export const withInjectables = <T extends ProvidersMap>(providers: T) => {
return <OriginalProps extends {}>(
Cmp: ComponentType<OriginalProps>
): ComponentType<WrapperProps<OriginalProps, T>> => {
): HoCComponentClass<WrapperProps<OriginalProps, T>, typeof Cmp> => {
class WithInjectables extends Component<WrapperProps<OriginalProps, T>> {
static displayName = `WithInjectables(${getComponentDisplayName(Cmp)})`
static readonly WrappedComponent = Cmp
Expand Down
8 changes: 4 additions & 4 deletions src/with-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Provider as ProviderConfig } from 'injection-js'
import React, { Component, ComponentType } from 'react'

import { getComponentDisplayName } from './helpers'
import { createHOCName } from './helpers'
import { Provider } from './provider'
import { WrapperProps } from './types'
import { HoCComponentClass, WrapperProps } from './types'

type ProvidersSetup = { provide: ProviderConfig[] }

Expand All @@ -15,9 +15,9 @@ type ProvidersSetup = { provide: ProviderConfig[] }
export const withProvider = <T extends ProvidersSetup>(provideConfig: T) => {
return <OriginalProps extends {}>(
Cmp: ComponentType<OriginalProps>
): ComponentType<WrapperProps<OriginalProps, T>> => {
): HoCComponentClass<WrapperProps<OriginalProps, T>, typeof Cmp> => {
class WithProvider extends Component<WrapperProps<OriginalProps, T>> {
static displayName = `WithProvider(${getComponentDisplayName(Cmp)}`
static displayName: string = createHOCName(WithProvider, Cmp)

static readonly WrappedComponent = Cmp

Expand Down

0 comments on commit 883074c

Please sign in to comment.