Skip to content

Commit

Permalink
Added WithStacks component
Browse files Browse the repository at this point in the history
  • Loading branch information
viktor-podzigun committed Dec 10, 2024
1 parent 9dc48e8 commit 59bbcca
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 2 deletions.
41 changes: 41 additions & 0 deletions src/stack/WithStacks.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* @typedef {import("./WithStacksProps.mjs").WithStacksProps} WithStacksProps
*/
import React, { useContext } from "react";
import WithStacksProps from "./WithStacksProps.mjs";

const h = React.createElement;

/**
* @param {React.PropsWithChildren<WithStacksProps>} props
*/
const WithStacks = (props) => {
return h(
WithStacks.Context.Provider,
{
value: WithStacksProps(props.left, props.right),
},
props.children
);
};

WithStacks.displayName = "WithStacks";

WithStacks.Context = React.createContext(
/** @type {WithStacksProps | null} */ (null)
);
/**
* @type {() => WithStacksProps}
*/
WithStacks.useStacks = () => {
const ctx = useContext(WithStacks.Context);
if (!ctx) {
throw Error(
"WithStacks.Context is not found." +
"\nPlease, make sure you use WithStacks.Context.Provider in parent component."
);
}
return ctx;
};

export default WithStacks;
45 changes: 45 additions & 0 deletions src/stack/WithStacksProps.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @typedef {import("@farjs/blessed").Widgets.BlessedElement} BlessedElement
*/
import PanelStack from "./PanelStack.mjs";

/**
* @typedef {{
* readonly stack: PanelStack;
* readonly input: BlessedElement;
* }} WithStacksData
*/

/**
* @typedef {{
* readonly left: WithStacksData;
* readonly right: WithStacksData;
* }} WithStacksProps
*/

/**
* @param {WithStacksData} left
* @param {WithStacksData} right
* @returns {WithStacksProps}
*/
function WithStacksProps(left, right) {
return { left, right };
}

/**
* @param {WithStacksProps} stacks
* @returns {WithStacksData}
*/
WithStacksProps.active = (stacks) => {
return stacks.left.stack.isActive ? stacks.left : stacks.right;
};

/**
* @param {WithStacksProps} stacks
* @returns {WithStacksData}
*/
WithStacksProps.nonActive = (stacks) => {
return !stacks.left.stack.isActive ? stacks.left : stacks.right;
};

export default WithStacksProps;
1 change: 1 addition & 0 deletions test/all.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ await import("./sort/FileListSort.test.mjs");
await import("./stack/PanelStack.test.mjs");
await import("./stack/PanelStackItem.test.mjs");
await import("./stack/WithStack.test.mjs");
await import("./stack/WithStacks.test.mjs");

await import("./theme/FileListTheme.test.mjs");
4 changes: 2 additions & 2 deletions test/stack/WithStack.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @typedef {import("@farjs/blessed").Widgets.BlessedElement} BlessedElement
* @typedef {import("../../src/stack/WithStack.mjs").WithStackProps} WithStackProps
*/
import React, { useContext } from "react";
import React from "react";
import assert from "node:assert/strict";
import mockFunction from "mock-fn";
import TestRenderer from "react-test-renderer";
Expand Down Expand Up @@ -104,7 +104,7 @@ function getStackCtxHook() {
/** @type {React.MutableRefObject<WithStackProps | null>} */
const ref = React.createRef();
const comp = () => {
const ctx = useContext(WithStack.Context);
const ctx = WithStack.useStack();
ref.current = ctx;
return h(React.Fragment, null);
};
Expand Down
132 changes: 132 additions & 0 deletions test/stack/WithStacks.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* @typedef {import("@farjs/blessed").Widgets.BlessedElement} BlessedElement
* @typedef {import("../../src/stack/WithStacksProps.mjs").WithStacksProps} WithStacksProps
*/
import React from "react";
import assert from "node:assert/strict";
import mockFunction from "mock-fn";
import TestRenderer from "react-test-renderer";
import { assertComponents, TestErrorBoundary } from "react-assert";
import PanelStack from "../../src/stack/PanelStack.mjs";
import WithStacksProps from "../../src/stack/WithStacksProps.mjs";
import WithStacks from "../../src/stack/WithStacks.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);
})();

const props = WithStacksProps(
{
stack: new PanelStack(true, [], mockFunction()),
input: /** @type {BlessedElement} */ ({}),
},
{
stack: new PanelStack(false, [], mockFunction()),
input: /** @type {BlessedElement} */ ({}),
}
);

describe("WithStacks.test.mjs", () => {
it("should fail if no context when useStacks", () => {
//given
// suppress intended error
// see: https://github.com/facebook/react/issues/11098#issuecomment-412682721
const savedConsoleError = console.error;
const consoleErrorMock = mockFunction();
console.error = consoleErrorMock;

const Wrapper = () => {
WithStacks.useStacks();
return h(React.Fragment);
};

//when
const result = TestRenderer.create(
h(TestErrorBoundary, null, h(Wrapper))
).root;

//then
console.error = savedConsoleError;
assert.deepEqual(consoleErrorMock.times, 1);
assertComponents(
result.children,
h(
"div",
null,
"Error: WithStacks.Context is not found." +
"\nPlease, make sure you use WithStacks.Context.Provider in parent component."
)
);
});

it("should render component with context provider", () => {
//given
const [stacksCtx, stacksComp] = getStacksCtxHook();

//when
const result = TestRenderer.create(
h(
WithStacks,
props,
h(stacksComp, null),
h(React.Fragment, null, "some other content")
)
).root;

//then
assert.deepEqual(WithStacks.displayName, "WithStacks");
assert.deepEqual(stacksCtx.current, props);
assert.deepEqual(result.children.length, 2);
const [resCtxHook, otherContent] = result.children.map(
(_) => /** @type {TestRenderer.ReactTestInstance} */ (_)
);
assert.deepEqual(resCtxHook.type, stacksComp);
assert.deepEqual(otherContent, "some other content");
});

it("should return active stack when WithStacksProps.active()", () => {
//given
const { left, right } = props;

//when & then
assert.deepEqual(WithStacksProps.active(props), props.left);
assert.deepEqual(
WithStacksProps.active({ left: right, right: left }),
props.left
);
});

it("should return non-active stack when WithStacksProps.nonActive()", () => {
//given
const { left, right } = props;

//when & then
assert.deepEqual(WithStacksProps.nonActive(props), props.right);
assert.deepEqual(
WithStacksProps.nonActive({ left: right, right: left }),
props.right
);
});
});

/**
* @returns {[React.MutableRefObject<WithStacksProps | null>, () => React.ReactElement]}
*/
function getStacksCtxHook() {
/** @type {React.MutableRefObject<WithStacksProps | null>} */
const ref = React.createRef();
const comp = () => {
const ctx = WithStacks.useStacks();
ref.current = ctx;
return h(React.Fragment, null);
};

return [ref, comp];
}
13 changes: 13 additions & 0 deletions types/stack/WithStacks.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default WithStacks;
export type WithStacksProps = import("./WithStacksProps.mjs").WithStacksProps;
/**
* @param {React.PropsWithChildren<WithStacksProps>} props
*/
declare function WithStacks(props: React.PropsWithChildren<WithStacksProps>): React.FunctionComponentElement<React.ProviderProps<import("./WithStacksProps.mjs").WithStacksProps | null>>;
declare namespace WithStacks {
const displayName: string;
const Context: React.Context<import("./WithStacksProps.mjs").WithStacksProps | null>;
function useStacks(): import("./WithStacksProps.mjs").WithStacksProps;
}
import React from "react";
import WithStacksProps from "./WithStacksProps.mjs";
41 changes: 41 additions & 0 deletions types/stack/WithStacksProps.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export default WithStacksProps;
export type WithStacksData = {
readonly stack: PanelStack;
readonly input: BlessedElement;
};
export type WithStacksProps = {
readonly left: WithStacksData;
readonly right: WithStacksData;
};
export type BlessedElement = import("@farjs/blessed").Widgets.BlessedElement;
/**
* @typedef {{
* readonly stack: PanelStack;
* readonly input: BlessedElement;
* }} WithStacksData
*/
/**
* @typedef {{
* readonly left: WithStacksData;
* readonly right: WithStacksData;
* }} WithStacksProps
*/
/**
* @param {WithStacksData} left
* @param {WithStacksData} right
* @returns {WithStacksProps}
*/
declare function WithStacksProps(left: WithStacksData, right: WithStacksData): WithStacksProps;
declare namespace WithStacksProps {
/**
* @param {WithStacksProps} stacks
* @returns {WithStacksData}
*/
function active(stacks: WithStacksProps): WithStacksData;
/**
* @param {WithStacksProps} stacks
* @returns {WithStacksData}
*/
function nonActive(stacks: WithStacksProps): WithStacksData;
}
import PanelStack from "./PanelStack.mjs";

0 comments on commit 59bbcca

Please sign in to comment.