import { render, renderHook } from '@testing-library/react';
import { Container, createContainer, Token, token } from 'ditox';
import React, { useEffect } from 'react';
import { describe, expect, it, Mock, vi } from 'vitest';
import {
CustomDependencyContainer,
DependencyContainer,
} from './DependencyContainer';
import { useDependencyContainer } from './hooks';
describe('DependencyContainer', () => {
it('should provide a new container', () => {
const { result } = renderHook(useDependencyContainer, {
wrapper: ({ children }) => (
{children}
),
});
expect(result.current).toBeDefined();
});
it('should connect a new container with a parent one in case "root" property is not set', () => {
const FOO = token('FOO');
const BAR = token('BAR');
const parentContainer = createContainer();
parentContainer.bindValue(FOO, 'foo');
const binder = (container: Container) => {
container.bindValue(BAR, 'bar');
};
const { result } = renderHook(() => useDependencyContainer('strict'), {
wrapper: ({ children }) => (
{children}
),
});
expect(result.current.resolve(BAR)).toEqual('bar');
expect(result.current.resolve(FOO)).toEqual('foo');
});
it('should not connect a new container with a parent one in case "root" is "true"', () => {
const FOO = token('FOO');
const BAR = token('BAR');
const parentContainer = createContainer();
parentContainer.bindValue(FOO, 'foo');
const binder = (container: Container) => {
container.bindValue(BAR, 'bar');
};
const { result } = renderHook(() => useDependencyContainer('strict'), {
wrapper: ({ children }) => (
{children}
),
});
expect(result.current.resolve(BAR)).toEqual('bar');
expect(result.current.get(FOO)).toBeUndefined();
});
it('should disconnect a container from a parent one in case "root" is changed from "false" to "true"', () => {
const FOO = token('FOO');
const BAR = token('BAR');
const parentContainer = createContainer();
parentContainer.bindValue(FOO, 'foo');
const binder = (container: Container) => container.bindValue(BAR, 'bar');
const [FooMonitor, fooCallback] = createMonitor(FOO);
const [BarMonitor, barCallback] = createMonitor(BAR);
const { rerender } = render(
,
);
expect(fooCallback).toBeCalledTimes(1);
expect(fooCallback).lastCalledWith('foo');
expect(barCallback).toBeCalledTimes(1);
expect(barCallback).lastCalledWith('bar');
fooCallback.mockClear();
barCallback.mockClear();
rerender(
,
);
expect(fooCallback).toBeCalledTimes(1);
expect(fooCallback).lastCalledWith(undefined);
expect(barCallback).toBeCalledTimes(1);
expect(barCallback).lastCalledWith('bar');
});
it('should connect a container to a parent one in case "root" is changed from "true" to "false"', () => {
const FOO = token('FOO');
const BAR = token('BAR');
const parentContainer = createContainer();
parentContainer.bindValue(FOO, 'foo');
const binder = (container: Container) => container.bindValue(BAR, 'bar');
const [FooMonitor, fooCallback] = createMonitor(FOO);
const [BarMonitor, barCallback] = createMonitor(BAR);
const { rerender } = render(
,
);
expect(fooCallback).toBeCalledTimes(1);
expect(fooCallback).lastCalledWith(undefined);
expect(barCallback).toBeCalledTimes(1);
expect(barCallback).lastCalledWith('bar');
fooCallback.mockClear();
barCallback.mockClear();
rerender(
,
);
expect(fooCallback).toBeCalledTimes(1);
expect(fooCallback).lastCalledWith('foo');
expect(barCallback).toBeCalledTimes(1);
expect(barCallback).lastCalledWith('bar');
});
it('should create a new container and initialize it with "binder"', () => {
const FOO = token('FOO');
const binder = (container: Container) => container.bindValue(FOO, 'foo');
const { result } = renderHook(() => useDependencyContainer('strict'), {
wrapper: ({ children }) => (
{children}
),
});
expect(result.current.resolve(FOO)).toEqual('foo');
});
it('should remove all bindings from a previous container in case "binder" is changed', () => {
const FOO = token('FOO');
const removeHandler1 = vi.fn();
const binder1 = (container: Container) =>
container.bindFactory(FOO, () => 'foo1', { onRemoved: removeHandler1 });
const removeHandler2 = vi.fn();
const binder2 = (container: Container) =>
container.bindFactory(FOO, () => 'foo2', { onRemoved: removeHandler2 });
const [Monitor, monitorCallback] = createMonitor(FOO);
const { rerender } = render(
,
);
expect(monitorCallback).toBeCalledTimes(1);
expect(monitorCallback).lastCalledWith('foo1');
expect(removeHandler1).toBeCalledTimes(0);
expect(removeHandler2).toBeCalledTimes(0);
monitorCallback.mockClear();
removeHandler1.mockClear();
removeHandler2.mockClear();
rerender(
,
);
expect(monitorCallback).toBeCalledTimes(1);
expect(monitorCallback).lastCalledWith('foo2');
expect(removeHandler1).toBeCalledTimes(1);
expect(removeHandler2).toBeCalledTimes(0);
monitorCallback.mockClear();
removeHandler1.mockClear();
removeHandler2.mockClear();
rerender(<>>);
expect(monitorCallback).toBeCalledTimes(0);
expect(removeHandler1).toBeCalledTimes(0);
expect(removeHandler2).toBeCalledTimes(1);
});
it('should remove all bindings from a previous container in case "root" is changed', () => {
const FOO = token('FOO');
const removeHandler1 = vi.fn();
const binder1 = (container: Container) =>
container.bindFactory(FOO, () => 'foo1', { onRemoved: removeHandler1 });
const [Monitor, monitorCallback] = createMonitor(FOO);
const { rerender } = render(
,
);
expect(monitorCallback).toBeCalledTimes(1);
expect(monitorCallback).lastCalledWith('foo1');
expect(removeHandler1).toBeCalledTimes(0);
monitorCallback.mockClear();
removeHandler1.mockClear();
rerender(
,
);
expect(monitorCallback).toBeCalledTimes(1);
expect(monitorCallback).lastCalledWith('foo1');
expect(removeHandler1).toBeCalledTimes(1);
monitorCallback.mockClear();
removeHandler1.mockClear();
rerender(<>>);
expect(monitorCallback).toBeCalledTimes(0);
expect(removeHandler1).toBeCalledTimes(1);
});
});
describe('CustomDependencyContainer', () => {
it('should provide a custom container', () => {
const FOO = token('FOO');
const container = createContainer();
container.bindValue(FOO, 'foo');
const { result } = renderHook(useDependencyContainer, {
wrapper: ({ children }) => (
{children}
),
});
expect(result.current?.resolve(FOO)).toBe('foo');
});
it('should not clean a custom container on rerender or unmount', () => {
const FOO = token('FOO');
const removeHandler1 = vi.fn();
const container1 = createContainer();
container1.bindFactory(FOO, () => 'foo1', { onRemoved: removeHandler1 });
const removeHandler2 = vi.fn();
const container2 = createContainer();
container2.bindFactory(FOO, () => 'foo2', { onRemoved: removeHandler2 });
const [Monitor, monitorCallback] = createMonitor(FOO);
const { rerender } = render(
,
);
rerender(
,
);
rerender(<>>);
expect(monitorCallback).toBeCalledTimes(2);
expect(monitorCallback).nthCalledWith(1, 'foo1');
expect(monitorCallback).nthCalledWith(2, 'foo2');
expect(removeHandler1).toBeCalledTimes(0);
expect(removeHandler2).toBeCalledTimes(0);
});
});
function createMonitor(token: Token): [() => null, Mock] {
const callback = vi.fn();
const Monitor = () => {
const container = useDependencyContainer();
useEffect(() => callback(container?.get(token)), [container]);
return null;
};
return [Monitor, callback];
}