Skip to content

Commit

Permalink
Added PanelStackItem
Browse files Browse the repository at this point in the history
  • Loading branch information
viktor-podzigun committed Dec 5, 2024
1 parent e364b9b commit 203093f
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 0 deletions.
29 changes: 29 additions & 0 deletions src/FileListData.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @typedef {import("./FileListActions.mjs").FileListAction} FileListAction
* @typedef {import("./FileListState.mjs").FileListState} FileListState
*/
import FileListActions from "./FileListActions.mjs";

/**
* @typedef {(a: any) => void} Dispatch
*/

/**
* @typedef {{
* readonly dispatch: Dispatch;
* readonly actions: FileListActions;
* readonly state: FileListState;
* }} FileListData
*/

/**
* @param {Dispatch} dispatch
* @param {FileListActions} actions
* @param {FileListState} state
* @returns {FileListData}
*/
function FileListData(dispatch, actions, state) {
return { dispatch, actions, state };
}

export default FileListData;
76 changes: 76 additions & 0 deletions src/stack/PanelStackItem.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* @typedef {import("../FileListData.mjs").Dispatch} Dispatch
* @typedef {import("../FileListData.mjs").FileListData} FileListData
*/
import React from "react";
import FileListData from "../FileListData.mjs";
import FileListState from "../FileListState.mjs";
import FileListActions from "../FileListActions.mjs";

/**
* @template T
*/
class PanelStackItem {
/**
* @param {React.FunctionComponent<any> | React.ComponentClass<any>} component
* @param {Dispatch} [dispatch]
* @param {FileListActions} [actions]
* @param {T} [state]
*/
constructor(component, dispatch, actions, state) {
/** @readonly @type {React.FunctionComponent<any> | React.ComponentClass<any>} */
this.component = component;

/** @readonly @type {Dispatch | undefined} */
this.dispatch = dispatch;

/** @readonly @type {FileListActions | undefined} */
this.actions = actions;

/** @readonly @type {T | undefined} */
this.state = state;
}

/**
* @param {T} s
* @returns {PanelStackItem<T>}
*/
withState(s) {
return new PanelStackItem(this.component, this.dispatch, this.actions, s);
}

/**
* @param {(s: T) => T} f
* @returns {PanelStackItem<T>}
*/
updateState(f) {
return new PanelStackItem(
this.component,
this.dispatch,
this.actions,
this.state ? f(this.state) : this.state
);
}

/**
* @returns {FileListData | undefined}
*/
getData() {
if (
this.dispatch &&
this.actions &&
this.state &&
FileListState.isFileListState(this.state)
) {
return FileListData(
this.dispatch,
this.actions,
/** @type {any} */ (this.state)
);
}

return undefined;
}
}

export default PanelStackItem;
2 changes: 2 additions & 0 deletions test/all.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ await import("./history/HistoryProvider.test.mjs");

await import("./sort/FileListSort.test.mjs");

await import("./stack/PanelStackItem.test.mjs");

await import("./theme/FileListTheme.test.mjs");
135 changes: 135 additions & 0 deletions test/stack/PanelStackItem.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import assert from "node:assert/strict";
import mockFunction from "mock-fn";
import MockFileListApi from "../../src/api/MockFileListApi.mjs";
import FileListState from "../../src/FileListState.mjs";
import FileListActions from "../../src/FileListActions.mjs";
import PanelStackItem from "../../src/stack/PanelStackItem.mjs";

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 Component = () => {
return null;
};
const dispatch = () => {};
const actions = new FileListActions(new MockFileListApi());
const state = FileListState();

describe("PanelStackItem.test.mjs", () => {
it("should create new item with component only", () => {
//when
const result = new PanelStackItem(Component);

//then
assert.deepEqual(result.component == Component, true);
assert.deepEqual(result.dispatch, undefined);
assert.deepEqual(result.actions, undefined);
assert.deepEqual(result.state, undefined);
});

it("should create new item with all data", () => {
//when
const result = new PanelStackItem(Component, dispatch, actions, state);

//then
assert.deepEqual(result.component == Component, true);
assert.deepEqual(result.dispatch == dispatch, true);
assert.deepEqual(result.actions == actions, true);
assert.deepEqual(result.state == state, true);
});

it("should return new item with updated state when withState", () => {
//given
const item = new PanelStackItem(Component, dispatch, actions);
assert.deepEqual(item.state, undefined);

//when
const result = item.withState(state);

//then
assert.deepEqual(item.state, undefined);
assert.deepEqual(result !== item, true);
assert.deepEqual(result.dispatch == dispatch, true);
assert.deepEqual(result.actions == actions, true);
assert.deepEqual(result.state == state, true);
});

it("should return new item with undefined state when updateState", () => {
//given
const onState = mockFunction();
const item = new PanelStackItem(Component, dispatch, actions);
assert.deepEqual(item.state, undefined);

//when
const result = item.updateState(onState);

//then
assert.deepEqual(onState.times, 0);
assert.deepEqual(item.state, undefined);
assert.deepEqual(result !== item, true);
assert.deepEqual(result.dispatch == dispatch, true);
assert.deepEqual(result.actions == actions, true);
assert.deepEqual(result.state, undefined);
});

it("should return new item with updated state when updateState", () => {
//given
const item = new PanelStackItem(Component, dispatch, actions, state);
const newState = FileListState();
let capturedState = null;
const onState = mockFunction((s) => {
capturedState = s;
return newState;
});

//when
const result = item.updateState(onState);

//then
assert.deepEqual(onState.times, 1);
assert.deepEqual(capturedState == state, true);
assert.deepEqual(item.state == state, true);
assert.deepEqual(result !== item, true);
assert.deepEqual(result.dispatch == dispatch, true);
assert.deepEqual(result.actions == actions, true);
assert.deepEqual(result.state == newState, true);
});

it("should return undefined when getData", () => {
//when & then
assert.deepEqual(new PanelStackItem(Component).getData(), undefined);
assert.deepEqual(
new PanelStackItem(Component, undefined, actions, state).getData(),
undefined
);
assert.deepEqual(
new PanelStackItem(Component, dispatch, undefined, state).getData(),
undefined
);
assert.deepEqual(
new PanelStackItem(Component, dispatch, actions, undefined).getData(),
undefined
);
assert.deepEqual(
new PanelStackItem(Component, dispatch, actions, {}).getData(),
undefined
);
});

it("should return FileListData if FileListState when getData", () => {
//given
const item = new PanelStackItem(Component, dispatch, actions, state);

//when
const result = item.getData();

//then
assert.deepEqual(result, { dispatch, actions, state });
});
});
27 changes: 27 additions & 0 deletions types/FileListData.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export default FileListData;
export type Dispatch = (a: any) => void;
export type FileListData = {
readonly dispatch: Dispatch;
readonly actions: FileListActions;
readonly state: FileListState;
};
export type FileListAction = import("./FileListActions.mjs").FileListAction;
export type FileListState = import("./FileListState.mjs").FileListState;
/**
* @typedef {(a: any) => void} Dispatch
*/
/**
* @typedef {{
* readonly dispatch: Dispatch;
* readonly actions: FileListActions;
* readonly state: FileListState;
* }} FileListData
*/
/**
* @param {Dispatch} dispatch
* @param {FileListActions} actions
* @param {FileListState} state
* @returns {FileListData}
*/
declare function FileListData(dispatch: Dispatch, actions: FileListActions, state: FileListState): FileListData;
import FileListActions from "./FileListActions.mjs";
40 changes: 40 additions & 0 deletions types/stack/PanelStackItem.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export default PanelStackItem;
export type Dispatch = import("../FileListData.mjs").Dispatch;
export type FileListData = import("../FileListData.mjs").FileListData;
/**
* @template T
*/
declare class PanelStackItem<T> {
/**
* @param {React.FunctionComponent<any> | React.ComponentClass<any>} component
* @param {Dispatch} [dispatch]
* @param {FileListActions} [actions]
* @param {T} [state]
*/
constructor(component: React.FunctionComponent<any> | React.ComponentClass<any>, dispatch?: import("../FileListData.mjs").Dispatch | undefined, actions?: FileListActions | undefined, state?: T | undefined);
/** @readonly @type {React.FunctionComponent<any> | React.ComponentClass<any>} */
readonly component: React.FunctionComponent<any> | React.ComponentClass<any>;
/** @readonly @type {Dispatch | undefined} */
readonly dispatch: Dispatch | undefined;
/** @readonly @type {FileListActions | undefined} */
readonly actions: FileListActions | undefined;
/** @readonly @type {T | undefined} */
readonly state: T | undefined;
/**
* @param {T} s
* @returns {PanelStackItem<T>}
*/
withState(s: T): PanelStackItem<T>;
/**
* @param {(s: T) => T} f
* @returns {PanelStackItem<T>}
*/
updateState(f: (s: T) => T): PanelStackItem<T>;
/**
* @returns {FileListData | undefined}
*/
getData(): FileListData | undefined;
}
import React from "react";
import FileListActions from "../FileListActions.mjs";
import FileListData from "../FileListData.mjs";

0 comments on commit 203093f

Please sign in to comment.