diff --git a/packages/di/di.tsx b/packages/di/di.tsx index d78691a8..2a52dfcd 100644 --- a/packages/di/di.tsx +++ b/packages/di/di.tsx @@ -1,4 +1,11 @@ -import React, { StatelessComponent, ComponentType, createContext } from 'react'; +import React, { + ReactNode, + FunctionComponent, + StatelessComponent, + ComponentType, + createContext, + useContext, +} from 'react'; export type GetNonDefaultProps = keyof T extends never ? never : T; export type RegistryContext = Record; @@ -40,12 +47,12 @@ export function withRegistry(...registries: Registry[]) { }; } -export interface IComponentRegistryConsumer { +export interface IComponentRegistryConsumerProps { id: string; - children: (registry: any) => React.ReactNode; + children: (registry: any) => ReactNode; } -export const ComponentRegistryConsumer: React.SFC = props => ( +export const ComponentRegistryConsumer: FunctionComponent = props => ( {registries => { if (__DEV__) { @@ -59,6 +66,16 @@ export const ComponentRegistryConsumer: React.SFC = ); +export const useRegistries = () => { + return useContext(registryContext); +}; + +export const useComponentRegistry = (id: string) => { + const registries = useRegistries(); + + return registries[id].snapshot(); +}; + export interface IRegistryOptions { id: string; inverted?: boolean; diff --git a/packages/di/package.json b/packages/di/package.json index cf3a8f39..ac1cf426 100644 --- a/packages/di/package.json +++ b/packages/di/package.json @@ -30,7 +30,7 @@ "test": "../../node_modules/.bin/nyc npm run unit test/*.test.tsx" }, "peerDependencies": { - "react": "^16.0.0" + "react": "^16.8.0" }, "nyc": { "include": [ diff --git a/packages/di/test/di.test.tsx b/packages/di/test/di.test.tsx index c8718143..a7b016b9 100644 --- a/packages/di/test/di.test.tsx +++ b/packages/di/test/di.test.tsx @@ -4,7 +4,14 @@ import { describe, it } from 'mocha'; import { expect } from 'chai'; import { render } from 'enzyme'; -import { Registry, withRegistry, RegistryConsumer, ComponentRegistryConsumer } from '../di'; +import { + Registry, + withRegistry, + RegistryConsumer, + ComponentRegistryConsumer, + useRegistries, + useComponentRegistry, +} from '../di'; interface ICommonProps { className?: string; @@ -90,204 +97,252 @@ describe('@bem-react/di', () => { }); describe('withRegistry', () => { - it('should provide registry to context', () => { - const compositorRegistry = new Registry({ id: 'Compositor' }); - const Element: React.SFC = () => content; + describe('consumer', () => { + it('should provide registry to context', () => { + const compositorRegistry = new Registry({ id: 'Compositor' }); + const Element: React.SFC = () => content; - interface ICompositorRegistry { - Element: React.ComponentType; - } + interface ICompositorRegistry { + Element: React.ComponentType; + } - compositorRegistry.set('Element', Element); + compositorRegistry.set('Element', Element); - const CompositorPresenter: React.SFC = () => ( - - {registries => { - const registry = registries['Compositor']; - const { Element } = registry.snapshot(); + const CompositorPresenter: React.SFC = () => ( + + {registries => { + const registry = registries['Compositor']; + const { Element } = registry.snapshot(); - return ; - }} - - ); + return ; + }} + + ); - const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); + const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); - expect(render().text()).eq('content'); - }); + expect(render().text()).eq('content'); + }); + + it('should provide assign registry with component', () => { + const compositorRegistry = new Registry({ id: 'Compositor' }); + const Element: React.SFC = () => content; - it('should provide assign registry with component', () => { - const compositorRegistry = new Registry({ id: 'Compositor' }); - const Element: React.SFC = () => content; + interface ICompositorRegistry { + Element: React.ComponentType; + } - interface ICompositorRegistry { - Element: React.ComponentType; - } + compositorRegistry.set('Element', Element); - compositorRegistry.set('Element', Element); + const CompositorPresenter: React.SFC = () => ( + + {({ Element }: ICompositorRegistry) => } + + ); - const CompositorPresenter: React.SFC = () => ( - - {({ Element }: ICompositorRegistry) => } - - ); + const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); - const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); + expect(render().text()).eq('content'); + }); - expect(render().text()).eq('content'); - }); + it('should override components in registry by context', () => { + const compositorRegistry = new Registry({ id: 'Compositor' }); + const Element: React.SFC = () => content; + + const overridedCompositorRegistry = new Registry({ id: 'Compositor' }); + const OverridedElement: React.SFC = () => overrided; + + interface ICompositorRegistry { + Element: React.ComponentType; + } + + compositorRegistry.set('Element', Element); + overridedCompositorRegistry.set('Element', OverridedElement); + + const CompositorPresenter: React.SFC = () => { + const Content: React.SFC = withRegistry(overridedCompositorRegistry)(() => ( + + {({ Element }: ICompositorRegistry) => } + + )); + + return ; + }; - it('should override components in registry by context', () => { - const compositorRegistry = new Registry({ id: 'Compositor' }); - const Element: React.SFC = () => content; + const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); - const overridedCompositorRegistry = new Registry({ id: 'Compositor' }); - const OverridedElement: React.SFC = () => overrided; + expect(render().text()).eq('overrided'); + }); - interface ICompositorRegistry { - Element: React.ComponentType; - } + it('should override components in registry from top node', () => { + const compositorRegistry = new Registry({ id: 'Compositor', inverted: true }); + const Element: React.SFC = () => content; - compositorRegistry.set('Element', Element); - overridedCompositorRegistry.set('Element', OverridedElement); + const overridedCompositorRegistry = new Registry({ id: 'Compositor' }); + const OverridedElement: React.SFC = () => overrided; - const CompositorPresenter: React.SFC = () => { - const Content: React.SFC = withRegistry(overridedCompositorRegistry)(() => ( + interface ICompositorRegistry { + Element: React.ComponentType; + } + + compositorRegistry.set('Element', Element); + overridedCompositorRegistry.set('Element', OverridedElement); + + const CompositorPresenter: React.SFC = () => ( {({ Element }: ICompositorRegistry) => } - )); + ); - return ; - }; + const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); + const OverridedCompositor = withRegistry(overridedCompositorRegistry)(Compositor); - const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); + expect(render().text()).eq('content'); + expect(render().text()).eq('overrided'); + }); - expect(render().text()).eq('overrided'); - }); + it('should partially override components in registry', () => { + const compositorRegistry = new Registry({ id: 'Compositor', inverted: true }); + const Element1: React.SFC = () => content; + const Element2: React.SFC = () => extra; - it('should override components in registry from top node', () => { - const compositorRegistry = new Registry({ id: 'Compositor', inverted: true }); - const Element: React.SFC = () => content; + const overridedCompositorRegistry = new Registry({ id: 'Compositor' }); + const OverridedElement: React.SFC = () => overrided; - const overridedCompositorRegistry = new Registry({ id: 'Compositor' }); - const OverridedElement: React.SFC = () => overrided; + interface ICompositorRegistry { + Element1: React.ComponentType; + Element2: React.ComponentType; + } - interface ICompositorRegistry { - Element: React.ComponentType; - } + compositorRegistry.set('Element1', Element1); + compositorRegistry.set('Element2', Element2); + overridedCompositorRegistry.set('Element1', OverridedElement); - compositorRegistry.set('Element', Element); - overridedCompositorRegistry.set('Element', OverridedElement); + const CompositorPresenter: React.SFC = () => ( + + {({ Element1, Element2 }: ICompositorRegistry) => ( + <> + + + + )} + + ); - const CompositorPresenter: React.SFC = () => ( - - {({ Element }: ICompositorRegistry) => } - - ); + const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); + const OverridedCompositor = withRegistry(overridedCompositorRegistry)(Compositor); - const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); - const OverridedCompositor = withRegistry(overridedCompositorRegistry)(Compositor); + expect(render().text()).eq('overridedextra'); + expect(render().text()).eq('contentextra'); + }); - expect(render().text()).eq('content'); - expect(render().text()).eq('overrided'); - }); + it('should allow to use any registry in context', () => { + const compositorRegistry = new Registry({ id: 'Compositor', inverted: true }); + const element2Registry = new Registry({ id: 'Element2', inverted: true }); + const Element1: React.SFC = () => content; + const Element2Presenter: React.SFC = () => ( + + {({ Element }: ICompositorRegistry) => <>extra} + + ); + const Element2 = withRegistry(element2Registry)(Element2Presenter); - it('should partially override components in registry', () => { - const compositorRegistry = new Registry({ id: 'Compositor', inverted: true }); - const Element1: React.SFC = () => content; - const Element2: React.SFC = () => extra; - - const overridedCompositorRegistry = new Registry({ id: 'Compositor' }); - const OverridedElement: React.SFC = () => overrided; - - interface ICompositorRegistry { - Element1: React.ComponentType; - Element2: React.ComponentType; - } - - compositorRegistry.set('Element1', Element1); - compositorRegistry.set('Element2', Element2); - overridedCompositorRegistry.set('Element1', OverridedElement); - - const CompositorPresenter: React.SFC = () => ( - - {({ Element1, Element2 }: ICompositorRegistry) => ( - <> - - - - )} - - ); - - const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); - const OverridedCompositor = withRegistry(overridedCompositorRegistry)(Compositor); - - expect(render().text()).eq('overridedextra'); - expect(render().text()).eq('contentextra'); - }); + interface ICompositorRegistry { + Element: React.ComponentType; + Element2: React.ComponentType; + } - it('should allow to use any registry in context', () => { - const compositorRegistry = new Registry({ id: 'Compositor', inverted: true }); - const element2Registry = new Registry({ id: 'Element2', inverted: true }); - const Element1: React.SFC = () => content; - const Element2Presenter: React.SFC = () => ( - - {({ Element }: ICompositorRegistry) => <>extra} - - ); - const Element2 = withRegistry(element2Registry)(Element2Presenter); - - interface ICompositorRegistry { - Element: React.ComponentType; - Element2: React.ComponentType; - } - - compositorRegistry.set('Element', Element1); - compositorRegistry.set('Element2', Element2); - - const CompositorPresenter: React.SFC = () => ( - - {({ Element2 }: ICompositorRegistry) => } - - ); - - const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); - - expect(render().text()).eq('contentextra'); + compositorRegistry.set('Element', Element1); + compositorRegistry.set('Element2', Element2); + + const CompositorPresenter: React.SFC = () => ( + + {({ Element2 }: ICompositorRegistry) => } + + ); + + const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); + + expect(render().text()).eq('contentextra'); + }); + + it('should not influence adjacent context', () => { + const registry = new Registry({ id: 'RegistryParent' }); + const registryA = new Registry({ id: 'TestRegistry' }); + const registryB = new Registry({ id: 'TestRegistry' }); + const elementA: React.SFC = () => a; + const elementB: React.SFC = () => b; + + registryA.set('Element', elementA); + registryB.set('Element', elementB); + + const ElementPresenter: React.SFC = () => ( + + {({ Element }) => } + + ); + + const BranchA = withRegistry(registryA)(ElementPresenter); + const BranchB = withRegistry(registryB)(ElementPresenter); + + const AppPresenter: React.SFC = () => ( + <> + + + + + ); + + const App = withRegistry(registry)(AppPresenter); + + expect(render().text()).eq('aba'); + }); }); - it('should not influence adjacent context', () => { - const registry = new Registry({ id: 'RegistryParent' }); - const registryA = new Registry({ id: 'TestRegistry' }); - const registryB = new Registry({ id: 'TestRegistry' }); - const elementA: React.SFC = () => a; - const elementB: React.SFC = () => b; + describe('hooks', () => { + it('should provide registry with useRegistries', () => { + const compositorRegistry = new Registry({ id: 'Compositor' }); + const Element = (props: ICommonProps) => content; + + interface ICompositorRegistry { + Element: typeof Element; + } + + compositorRegistry.set('Element', Element); + + const CompositorPresenter = (props: ICommonProps) => { + const registries = useRegistries(); + const registry = registries['Compositor']; + const { Element } = registry.snapshot(); + + return ; + }; + + const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); + + expect(render().text()).eq('content'); + }); + + it('should provide assign registry with useComponentRegistry', () => { + const compositorRegistry = new Registry({ id: 'Compositor' }); + const Element = (props: ICommonProps) => content; - registryA.set('Element', elementA); - registryB.set('Element', elementB); + interface ICompositorRegistry { + Element: typeof Element; + } - const ElementPresenter: React.SFC = () => ( - - {({ Element }) => } - - ); + compositorRegistry.set('Element', Element); - const BranchA = withRegistry(registryA)(ElementPresenter); - const BranchB = withRegistry(registryB)(ElementPresenter); + const CompositorPresenter = (props: ICommonProps) => { + const { Element } = useComponentRegistry('Compositor'); - const AppPresenter: React.SFC = () => ( - <> - - - - - ); + return ; + }; - const App = withRegistry(registry)(AppPresenter); + const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); - expect(render().text()).eq('aba'); + expect(render().text()).eq('content'); + }); }); }); });