diff --git a/src/history/HistoryProvider.mjs b/src/history/HistoryProvider.mjs new file mode 100644 index 0000000..2093ce5 --- /dev/null +++ b/src/history/HistoryProvider.mjs @@ -0,0 +1,48 @@ +import React, { useContext } from "react"; + +/** + * @typedef {{ + * readonly name: string; + * readonly maxItemsCount: number; + * }} HistoryKind + */ + +/** + * @typedef {{ + * readonly item: string; + * readonly params?: object; + * }} History + */ + +/** + * @typedef {{ + * getAll(): Promise; + * getOne(item: string): Promise; + * save(h: History): Promise; + * }} HistoryService + */ + +/** + * @typedef {{ + * get(kind: HistoryKind): Promise; + * }} HistoryProvider + */ + +const HistoryProvider = {}; + +HistoryProvider.Context = React.createContext( + /** @type {HistoryProvider | null} */ (null) +); + +HistoryProvider.useHistoryProvider = () => { + const ctx = useContext(HistoryProvider.Context); + if (!ctx) { + throw Error( + "HistoryProvider.Context is not found." + + "\nPlease, make sure you use HistoryProvider.Context.Provider in parent component." + ); + } + return ctx; +}; + +export default HistoryProvider; diff --git a/test/all.mjs b/test/all.mjs index 8f37aba..ebb458d 100644 --- a/test/all.mjs +++ b/test/all.mjs @@ -8,4 +8,6 @@ await import("./api/MockFileListApi.test.mjs"); await import("./api/MockFileSource.test.mjs"); await import("./api/MockFileTarget.test.mjs"); +await import("./history/HistoryProvider.test.mjs"); + await import("./sort/FileListSort.test.mjs"); diff --git a/test/history/HistoryProvider.test.mjs b/test/history/HistoryProvider.test.mjs new file mode 100644 index 0000000..c441228 --- /dev/null +++ b/test/history/HistoryProvider.test.mjs @@ -0,0 +1,75 @@ +import React from "react"; +import TestRenderer from "react-test-renderer"; +import assert from "node:assert/strict"; +import mockFunction from "mock-fn"; +import { assertComponents, TestErrorBoundary } from "react-assert"; +import HistoryProvider from "../../src/history/HistoryProvider.mjs"; + +const h = React.createElement; + +const { describe, it } = await (async () => { + // @ts-ignore + const module = process.isBun ? "bun:test" : "node:test"; + // @ts-ignore + return process.isBun // @ts-ignore + ? Promise.resolve({ describe: (_, fn) => fn(), it: test }) + : import(module); +})(); + +describe("HistoryProvider.test.mjs", () => { + it("should fail if no HistoryProvider.Context when useHistoryProvider", () => { + //given + // suppress intended error + // see: https://github.com/facebook/react/issues/11098#issuecomment-412682721 + const savedConsoleError = console.error; + const consoleErrorMock = mockFunction(() => { + console.error = savedConsoleError; + }); + console.error = consoleErrorMock; + + const TestComp = () => { + HistoryProvider.useHistoryProvider(); + return null; + }; + const comp = h(TestComp, null, h(React.Fragment)); + + //when + const result = TestRenderer.create(h(TestErrorBoundary, null, comp)).root; + + //then + assert.deepEqual(consoleErrorMock.times, 1); + assert.deepEqual(console.error, savedConsoleError); + assertComponents( + result.children, + h( + "div", + null, + "Error: HistoryProvider.Context is not found." + + "\nPlease, make sure you use HistoryProvider.Context.Provider in parent component." + ) + ); + }); + + it("should return HistoryProvider when useHistoryProvider", () => { + //given + const HistoryProviderMock = /** @type {HistoryProvider} */ ({}); + let capturedProvider = null; + const TestComp = () => { + capturedProvider = HistoryProvider.useHistoryProvider(); + return null; + }; + + //when + const result = TestRenderer.create( + h( + HistoryProvider.Context.Provider, + { value: HistoryProviderMock }, + h(TestComp, null) + ) + ).root; + + //then + assert.deepEqual(result.type, TestComp); + assert.deepEqual(capturedProvider === HistoryProviderMock, true); + }); +}); diff --git a/types/history/HistoryProvider.d.mts b/types/history/HistoryProvider.d.mts new file mode 100644 index 0000000..15756cc --- /dev/null +++ b/types/history/HistoryProvider.d.mts @@ -0,0 +1,22 @@ +export default HistoryProvider; +export type HistoryKind = { + readonly name: string; + readonly maxItemsCount: number; +}; +export type History = { + readonly item: string; + readonly params?: object; +}; +export type HistoryService = { + getAll(): Promise; + getOne(item: string): Promise; + save(h: History): Promise; +}; +export type HistoryProvider = { + get(kind: HistoryKind): Promise; +}; +declare namespace HistoryProvider { + const Context: React.Context; + function useHistoryProvider(): HistoryProvider; +} +import React from "react";