diff --git a/packages/di/di.tsx b/packages/di/di.tsx index ee2d4d93..5d253b07 100644 --- a/packages/di/di.tsx +++ b/packages/di/di.tsx @@ -20,8 +20,8 @@ export function withRegistry(...registries: Registry[]) { const overrides = contextRegistries[registry.id]; providedRegistries[registry.id] = registry.inverted - ? (overrides || registry) - : (registry || overrides); + ? overrides ? registry.merge(overrides) : registry + : (registry && overrides) ? overrides.merge(registry) : registry; }); return ( @@ -63,7 +63,6 @@ interface IRegistryComponents { export class Registry { id: string; inverted: boolean; - private components: IRegistryComponents = {}; constructor({ id, inverted = false }: IRegistryOptions) { @@ -98,7 +97,24 @@ export class Registry { return this.components[id]; } + /** + * Returns list of components from registry. + */ snapshot(): RT { return this.components as any; } + + /** + * Override components by external registry. + * + * @param registry external registry + */ + merge(registry: Registry) { + this.components = { + ...this.components, + ...(registry ? registry.snapshot() : {}), + }; + + return this; + } } diff --git a/packages/di/test/di.test.tsx b/packages/di/test/di.test.tsx index 7585cae0..6ab61180 100644 --- a/packages/di/test/di.test.tsx +++ b/packages/di/test/di.test.tsx @@ -1,25 +1,231 @@ +// tslint:disable no-shadowed-variable +import React from 'react'; +import { describe, it } from 'mocha'; import { expect } from 'chai'; +import { render } from 'enzyme'; -import { Registry } from '../di'; +import { Registry, withRegistry, RegistryConsumer, ComponentRegistryConsumer } from '../di'; + +interface ICommonProps { + className?: string; +} describe('@bem-react/di', () => { describe('Registry', () => { - it('should set components and return this by id', () => { - const registry = new Registry({ id: 'id' }); + it('should set and components by id', () => { + const registry = new Registry({ id: 'registry' }); + const Component1 = () => null; + const Component2 = () => ; + + registry + .set('id-1', Component1) + .set('id-2', Component2); + + expect(registry.get('id-1')).to.eq(Component1); + expect(registry.get('id-2')).to.eq(Component2); + }); + + it('should return list of components', () => { + const registry = new Registry({ id: 'registry' }); + const Component1 = () => null; + const Component2 = () => ; + + registry + .set('id-1', Component1) + .set('id-2', Component2); + + const snapshot = { + 'id-1': Component1, + 'id-2': Component2, + }; + + expect(registry.snapshot()).to.eql(snapshot); + }); + + it('should merge registries', () => { + const registry = new Registry({ id: 'registry' }); const Component1 = () => null; - const Component2 = () => null; + const Component2 = () => ; registry .set('id-1', Component1) .set('id-2', Component2); - expect(registry.get('id-1')).to.equal(Component1); - expect(registry.get('id-2')).to.equal(Component2); + const overrides = new Registry({ id: 'overrides' }); + const Component1Overrided = () =>
; + + overrides.set('id-1', Component1Overrided); + + const snapshot = { + 'id-1': Component1Overrided, + 'id-2': Component2, + }; + + expect(registry.merge(overrides).snapshot()).to.eql(snapshot); + }); + + it('should not affect registry in merge with undefined', () => { + const registry = new Registry({ id: 'registry' }); + const Component1 = () => null; + const Component2 = () => ; + + registry + .set('id-1', Component1) + .set('id-2', Component2); + + const snapshot = { + 'id-1': Component1, + 'id-2': Component2, + }; + + // @ts-ignore to check inside logic + expect(registry.merge().snapshot()).to.eql(snapshot); + }); + + it('should throw error when component doesn\'t exist', () => { + const registry = new Registry({ id: 'registry' }); + + expect(() => registry.get('id')).to.throw('Component with id \'id\' not found.'); }); }); - describe.skip('withRegistry', () => { - // TODO: Add test for withRegistry - return null; + describe('withRegistry', () => { + it('should provide registry to context', () => { + const compositorRegistry = new Registry({ id: 'Compositor' }); + const Element: React.SFC = () => content; + + interface ICompositorRegistry { + Element: React.ComponentType; + } + + compositorRegistry.set('Element', Element); + + const CompositorPresenter: React.SFC = () => ( + + {registries => { + 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 component', () => { + const compositorRegistry = new Registry({ id: 'Compositor' }); + const Element: React.SFC = () => content; + + interface ICompositorRegistry { + Element: React.ComponentType; + } + + compositorRegistry.set('Element', Element); + + const CompositorPresenter: React.SFC = () => ( + + {({ Element }: ICompositorRegistry) => } + + ); + + const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); + + 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 ; + }; + + const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); + + expect(render().text()).eq('overrided'); + }); + + 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; + + interface ICompositorRegistry { + Element: React.ComponentType; + } + + compositorRegistry.set('Element', Element); + overridedCompositorRegistry.set('Element', OverridedElement); + + const CompositorPresenter: React.SFC = () => ( + + {({ Element }: ICompositorRegistry) => } + + ); + + const Compositor = withRegistry(compositorRegistry)(CompositorPresenter); + const OverridedCompositor = withRegistry(overridedCompositorRegistry)(Compositor); + + expect(render().text()).eq('content'); + 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; + + 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('contentextra'); + expect(render().text()).eq('overridedextra'); + }); }); });