From 3cb47ab9511aad867a8e77678f890b3e371e7606 Mon Sep 17 00:00:00 2001 From: William Wong Date: Fri, 14 Jun 2024 23:38:28 -0700 Subject: [PATCH] Support ``-less usage (#65) * Support provider-less usage * Update PR number of provider-less usage * Fix typing * Use null for no rendering --- CHANGELOG.md | 2 + README.md | 2 +- ...entCallback.withFallbackComponent.test.tsx | 40 +++++++++++++++++++ ...allback.withoutFallbackComponent.test.tsx} | 12 +++--- ...enderProxy.withFallbackComponent.test.tsx} | 8 +++- ...derProxy.withoutFallbackComponent.test.tsx | 30 ++++++++++++++ .../src/createChainOfResponsibility.tsx | 19 ++++----- 7 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.callUseBuildComponentCallback.withFallbackComponent.test.tsx rename packages/react-chain-of-responsibility/src/{createChainOfResponsibility.outsideProvider.callUseBuildComponentCallback.shouldThrow.test.tsx => createChainOfResponsibility.outsideProvider.callUseBuildComponentCallback.withoutFallbackComponent.test.tsx} (70%) rename packages/react-chain-of-responsibility/src/{createChainOfResponsibility.outsideProvider.renderProxy.shouldThrow.test.tsx => createChainOfResponsibility.outsideProvider.renderProxy.withFallbackComponent.test.tsx} (74%) create mode 100644 packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.renderProxy.withoutFallbackComponent.test.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index e53562f..4403c4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use `import { createChainOfResponsibility } from 'react-chain-of-responsibility'` instead - `import { createChainOfResponsibilityForFluentUI } from 'react-chain-of-responsibility/fluentUI'` for Fluent UI renderer function - Moved build tools from Babel to tsup/esbuild +- Outside of ``, when `useBuildComponentCallback` and `` is used with `fallbackComponent`, they will render the fallback component and no longer throwing exception ### Added - Support nested provider of same type, by [@compulim](https://github.com/compulim) in PR [#64](https://github.com/compulim/react-chain-of-responsibility/pull/64) - Components will be built using middleware from `` closer to the `` and fallback to those farther away +- Support ``-less usage if `fallbackComponent` is specified, by [@compulim](https://github.com/compulim) in PR [#65](https://github.com/compulim/react-chain-of-responsibility/pull/65) ### Changed diff --git a/README.md b/README.md index 2a7613d..22155e3 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,7 @@ type UseBuildComponentCallback = ( ) => ComponentType | false | null | undefined; ``` -The `fallbackComponent` is a component which all unhandled requests will sink into. +The `fallbackComponent` is a component which all unhandled requests will sink into, including calls without ancestral ``. ### API for Fluent UI diff --git a/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.callUseBuildComponentCallback.withFallbackComponent.test.tsx b/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.callUseBuildComponentCallback.withFallbackComponent.test.tsx new file mode 100644 index 0000000..19e86e8 --- /dev/null +++ b/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.callUseBuildComponentCallback.withFallbackComponent.test.tsx @@ -0,0 +1,40 @@ +/** @jest-environment jsdom */ +/// + +import { render } from '@testing-library/react'; +import React from 'react'; + +import createChainOfResponsibility from './createChainOfResponsibility'; + +type Props = { children?: never }; + +let consoleErrorMock: jest.SpyInstance; + +beforeEach(() => { + // Currently, there is no way to hide the caught exception thrown by render(). + // We are mocking `console.log` to hide the exception. + consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => jest.fn()); +}); + +afterEach(() => { + consoleErrorMock.mockRestore(); +}); + +test('when calling useBuildComponentCallback() outside of its with fallbackComponent should render', () => { + // GIVEN: useBuildComponentCallback() from a newly created chain of responsibility. + const { useBuildComponentCallback } = createChainOfResponsibility(); + + const Fallback = () =>
Hello, World!
; + + const App = () => { + const Component = useBuildComponentCallback()(undefined, { fallbackComponent: Fallback }); + + return Component ? : null; + }; + + // WHEN: Render. + const result = render(); + + // THEN: Should render fallbackComponent. + expect(result.container).toHaveProperty('textContent', 'Hello, World!'); +}); diff --git a/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.callUseBuildComponentCallback.shouldThrow.test.tsx b/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.callUseBuildComponentCallback.withoutFallbackComponent.test.tsx similarity index 70% rename from packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.callUseBuildComponentCallback.shouldThrow.test.tsx rename to packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.callUseBuildComponentCallback.withoutFallbackComponent.test.tsx index c60c1df..f3b45b3 100644 --- a/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.callUseBuildComponentCallback.shouldThrow.test.tsx +++ b/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.callUseBuildComponentCallback.withoutFallbackComponent.test.tsx @@ -2,7 +2,7 @@ /// import { render } from '@testing-library/react'; -import React, { Fragment } from 'react'; +import React from 'react'; import createChainOfResponsibility from './createChainOfResponsibility'; @@ -25,14 +25,12 @@ test('when calling useBuildComponentCallback() outside of its should const { useBuildComponentCallback } = createChainOfResponsibility(); const App = () => { - useBuildComponentCallback(); + const Component = useBuildComponentCallback()(undefined); - return ; + return Component ? : null; }; // WHEN: Render. - // THEN: It should throw an error saying useBuildComponentCallback() hook cannot be used outside of its corresponding . - expect(() => render()).toThrow( - 'useBuildComponentCallback() hook cannot be used outside of its corresponding ' - ); + // THEN: It should throw an error saying "This component/hook cannot be used outside of its corresponding ". + expect(() => render()).toThrow('This component/hook cannot be used outside of its corresponding '); }); diff --git a/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.renderProxy.shouldThrow.test.tsx b/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.renderProxy.withFallbackComponent.test.tsx similarity index 74% rename from packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.renderProxy.shouldThrow.test.tsx rename to packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.renderProxy.withFallbackComponent.test.tsx index 8dc992c..b379da9 100644 --- a/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.renderProxy.shouldThrow.test.tsx +++ b/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.renderProxy.withFallbackComponent.test.tsx @@ -20,11 +20,15 @@ afterEach(() => { consoleErrorMock.mockRestore(); }); -test('when rendering outside of its should throw', () => { +test('when rendering outside of its with fallbackComponent should render', () => { // GIVEN: A of a newly created chain of responsibility. const { Proxy } = createChainOfResponsibility(); + const Fallback = () =>
Hello, World!
; + // WHEN: Render. + const result = render(); + // THEN: It should throw an error saying cannot be used outside of its corresponding . - expect(() => render()).toThrow(' cannot be used outside of its corresponding '); + expect(result.container).toHaveProperty('textContent', 'Hello, World!'); }); diff --git a/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.renderProxy.withoutFallbackComponent.test.tsx b/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.renderProxy.withoutFallbackComponent.test.tsx new file mode 100644 index 0000000..42885a3 --- /dev/null +++ b/packages/react-chain-of-responsibility/src/createChainOfResponsibility.outsideProvider.renderProxy.withoutFallbackComponent.test.tsx @@ -0,0 +1,30 @@ +/** @jest-environment jsdom */ +/// + +import { render } from '@testing-library/react'; +import React from 'react'; + +import createChainOfResponsibility from './createChainOfResponsibility'; + +type Props = { children?: never }; + +let consoleErrorMock: jest.SpyInstance; + +beforeEach(() => { + // Currently, there is no way to hide the caught exception thrown by render(). + // We are mocking `console.log` to hide the exception. + consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => jest.fn()); +}); + +afterEach(() => { + consoleErrorMock.mockRestore(); +}); + +test('when rendering outside of its without fallbackComponent should throw', () => { + // GIVEN: A of a newly created chain of responsibility. + const { Proxy } = createChainOfResponsibility(); + + // WHEN: Render. + // THEN: It should throw an error saying "This component/hook cannot be used outside of its corresponding ". + expect(() => render()).toThrow('This component/hook cannot be used outside of its corresponding '); +}); diff --git a/packages/react-chain-of-responsibility/src/createChainOfResponsibility.tsx b/packages/react-chain-of-responsibility/src/createChainOfResponsibility.tsx index e863258..4e5afb1 100644 --- a/packages/react-chain-of-responsibility/src/createChainOfResponsibility.tsx +++ b/packages/react-chain-of-responsibility/src/createChainOfResponsibility.tsx @@ -25,7 +25,7 @@ type UseBuildComponentCallback = ( type ProviderContext = { get enhancer(): Enhancer<[Request], ResultComponent> | undefined; - get useBuildComponentCallback(): UseBuildComponentCallback; + useBuildComponentCallback: UseBuildComponentCallback; }; type ProviderProps = PropsWithChildren<{ @@ -70,8 +70,12 @@ export default function createChainOfResponsibility< get enhancer() { return undefined; }, - get useBuildComponentCallback(): ProviderContext['useBuildComponentCallback'] { - throw new Error('useBuildComponentCallback() hook cannot be used outside of its corresponding '); + useBuildComponentCallback(_, options) { + if (options?.fallbackComponent) { + return options.fallbackComponent; + } + + throw new Error('This component/hook cannot be used outside of its corresponding '); } }; @@ -169,14 +173,7 @@ export default function createChainOfResponsibility< // False positive: "children" is not a prop. // eslint-disable-next-line react/prop-types ({ children, fallbackComponent, request, ...props }) => { - let enhancer: ReturnType; - - try { - enhancer = useBuildComponentCallback(); - } catch { - throw new Error(' cannot be used outside of its corresponding '); - } - + const enhancer = useBuildComponentCallback(); const Component = enhancer(request as Request, { fallbackComponent }); return Component ? {children} : null;