Skip to content

Commit

Permalink
Merge pull request #117 from megbailey/additional-props
Browse files Browse the repository at this point in the history
Propagate additional information to nodes with INode metadata
  • Loading branch information
mellis481 authored Jun 8, 2023
2 parents 1ab32b3 + aafaa32 commit 43e5eba
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 8 deletions.
7 changes: 5 additions & 2 deletions src/TreeView/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { clickActions, nodeActions } from "./constants";
import { ITreeViewState, TreeViewAction } from "./reducer";
import { IFlatMetadata } from "./utils";

export type ValueOf<T> = T[keyof T];
export type ClickActions = ValueOf<typeof clickActions>;
Expand All @@ -17,7 +18,7 @@ export type EventCallback = <T, E>(
event: React.MouseEvent<T, E> | React.KeyboardEvent<T>
) => void;

export interface INode {
export interface INode<M extends IFlatMetadata = IFlatMetadata> {
/** A non-negative integer that uniquely identifies the node */
id: NodeId;
/** Used to match on key press */
Expand All @@ -28,6 +29,8 @@ export type EventCallback = <T, E>(
parent: NodeId | null;
/** Used to indicated whether a node is branch to be able load async data onExpand*/
isBranch?: boolean;
/** User-defined metadata */
metadata?: M;
}

export interface INodeRendererProps {
Expand Down Expand Up @@ -88,4 +91,4 @@ export type EventCallback = <T, E>(
export interface IBranchProps {
onClick: EventCallback;
className: string;
}
}
19 changes: 13 additions & 6 deletions src/TreeView/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,22 +284,29 @@ export const getAccessibleRange = ({
return range;
};

interface ITreeNode {
/**
* This is to help consumers to understand that we do not currently support metadata that is a nested object. If this is needed, make an issue in Github
*/
export type IFlatMetadata = Record<string, string | number | undefined | null>;

interface ITreeNode<M extends IFlatMetadata> {
id?: NodeId;
name: string;
children?: ITreeNode[];
children?: ITreeNode<M>[];
metadata?: M;
}

export const flattenTree = function(tree: ITreeNode): INode[] {
export const flattenTree = <M extends IFlatMetadata>(tree: ITreeNode<M>): INode<M>[] => {
let internalCount = 0;
const flattenedTree: INode[] = [];
const flattenedTree: INode<M>[] = [];

const flattenTreeHelper = function(tree: ITreeNode, parent: NodeId | null) {
const node: INode = {
const flattenTreeHelper = (tree: ITreeNode<M>, parent: NodeId | null) => {
const node: INode<M> = {
id: tree.id || internalCount,
name: tree.name,
children: [],
parent,
metadata: tree.metadata ? { ...tree.metadata} : undefined
};

if (flattenedTree.find((x) => x.id === node.id)) {
Expand Down
55 changes: 55 additions & 0 deletions src/__tests__/FlattenTree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,58 @@ describe("FlattenTree helper", () => {
);
});
});

test("should retain metadata", () => {
const initialTreeNode = {
name: "",
children: [
{
name: "Fruits",
children: [
{ name: "Avocados", metadata: { color: "green" } },
{ name: "Bananas", metadata: { color: "yellow" } },
],
},
{
name: "Drinks",
children: [
{ name: "Apple Juice", metadata: { color: "yellow" } },
{ name: "Coffee", metadata: { color: "brown" } },
{
name: "Tea",
children: [
{ name: "Black Tea", metadata: { color: "brown" } },
{ name: "Green Tea", metadata: { color: "green" } },
{
name: "Matcha",
children: [{ name: "Matcha 1", metadata: { color: "green" } }],
},
],
},
],
},
{
name: "Vegetables",
},
],
};

const expectedTree = [
{ id: 0, name: "", parent: null, children: [1, 4, 12] },
{ id: 1, name: "Fruits", children: [2, 3], parent: 0 },
{ id: 2, name: "Avocados", children: [], parent: 1, metadata: { color: "green" } },
{ id: 3, name: "Bananas", children: [], parent: 1, metadata: { color: "yellow" } },
{ id: 4, name: "Drinks", children: [5, 6, 7], parent: 0 },
{ id: 5, name: "Apple Juice", children: [], parent: 4 , metadata: { color: "yellow" } },
{ id: 6, name: "Coffee", children: [], parent: 4, metadata: { color: "brown" } },
{ id: 7, name: "Tea", children: [8, 9, 10], parent: 4 },
{ id: 8, name: "Black Tea", children: [], parent: 7, metadata: { color: "brown" } },
{ id: 9, name: "Green Tea", children: [], parent: 7, metadata: { color: "green" } },
{ id: 10, name: "Matcha", children: [11], parent: 7 },
{ id: 11, name: "Matcha 1", children: [], parent: 10, metadata: { color: "green" } },
{ id: 12, name: "Vegetables", children: [], parent: 0 },
];

const expected = flattenTree(initialTreeNode);
expect(expected).toEqual(expectedTree);
});
113 changes: 113 additions & 0 deletions src/__tests__/TreeViewMetadata.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import "@testing-library/jest-dom/extend-expect";
import React from "react";
import TreeView from "../TreeView";
import { render } from "@testing-library/react";
import { flattenTree } from "../TreeView/utils";
import { INode } from "../TreeView/types";

const initialTreeNode = {
name: "",
id: 12,
children: [
{
name: "Fruits",
id: 54,
children: [
{ name: "Avocados", id: 98, metadata: { color: 'green' } },
{ name: "Bananas", id: 789, metadata: { color: 'yellow' } },
],
},
{
id: 888,
name: "Drinks",
children: [
{ name: "Apple Juice", id: 990, metadata: { color: 'yellow' } },
{ name: "Coffee", id: 9 , metadata: { color: 'brown' }},
{
id: 43,
name: "Tea",
children: [
{ name: "Black Tea", id: 4, metadata: { color: 'black' } },
{ name: "Green Tea", id: 44, metadata: { color: 'green' } },
{
id: 53,
name: "Matcha",
metadata: { color: 'green' },
children: [{ name: "Matcha 1", id: 2, metadata: { color: 'green' } }],
},
],
},
],
},
{
id: 24,
name: "Vegetables",
},
],
};

const mapDataType = flattenTree(initialTreeNode);

interface TreeViewDataTypeProps {
data: INode[];
}

function TreeViewMetadata(props: TreeViewDataTypeProps) {
const { data } = props;
return (
<div>
<TreeView
data={data}
aria-label="Data type"
multiSelect
defaultExpandedIds={[54, 88]}
propagateSelect
propagateSelectUpwards
togglableSelect
nodeAction="check"
nodeRenderer={({
element,
getNodeProps,
handleSelect,
handleExpand,
}) => {
return (

<div {...getNodeProps({ onClick: handleExpand })}>
<div
className="checkbox-icon"
onClick={(e) => {
handleSelect(e);
e.stopPropagation();
}}
/>
<span className="element" >
{ element.metadata ? `-${element.metadata.color}`: `${element.name}-${element.id}` }
</span>
</div>
);
}}
/>
</div>
);
}

test("Treeview should propogate any meta data that it has", () => {
const { queryAllByRole } = render(<TreeViewMetadata data={mapDataType} />);

const nodes = queryAllByRole("treeitem");

mapDataType.forEach(nodeData => {
const thisNodesMetadata = nodeData.metadata;
if (thisNodesMetadata !== undefined) {
const node = nodes.find((x) => {
return x.innerHTML.includes(`${thisNodesMetadata.color}`)
});
expect(node).toBeDefined;
} else {
const node = nodes.find((x) => x.innerHTML.includes(`${nodeData.name}-${nodeData.id}`));
expect(node).toBeDefined;
}
})
});

0 comments on commit 43e5eba

Please sign in to comment.