Skip to content
This repository was archived by the owner on Mar 17, 2024. It is now read-only.

BRAKING CHANGE: refactor render function #323

Merged
merged 2 commits into from
Jul 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/__tests__/__snapshots__/render.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renderToDot render to container subgraph test 1`] = `
digraph {
subgraph "test" {
"a";
"b";
"a" -> "b";
}
}
`;
5 changes: 0 additions & 5 deletions src/__tests__/render-to-dot.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,4 @@ describe('renderToDot', () => {
);
expect(dot).toBeValidDotAndMatchSnapshot();
});

it('render to be blank string', () => {
const dot = renderToDot(<></>);
expect(dot).toBe('');
});
});
41 changes: 41 additions & 0 deletions src/__tests__/render.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* eslint-disable jest/expect-expect */
import React from 'react';
import 'jest-graphviz';
import { digraph, toDot } from 'ts-graphviz';
import { Edge } from '../components/Edge';
import { Node } from '../components/Node';
import { renderExpectToThrow } from '../components/__tests__/utils/renderExpectToThrow';
import { NoContainerErrorMessage } from '../errors';
import { render } from '../render';

describe('renderToDot', () => {
describe('no container error', () => {
test('Fragment', () => {
renderExpectToThrow(<></>, NoContainerErrorMessage);
});

test('Node', () => {
renderExpectToThrow(<Node id="test" />, NoContainerErrorMessage);
});

test('Edge', () => {
renderExpectToThrow(<Edge targets={['a', 'b']} />, NoContainerErrorMessage);
});
});

it('render to container subgraph test', () => {
const nodes = ['a', 'b'];
const G = digraph();
const subgraph = render(
<>
{nodes.map((id) => (
<Node id={id} key={id} />
))}
<Edge targets={nodes} />
</>,
G.subgraph('test'),
);
expect(G.subgraph('test')).toEqual(subgraph);
expect(toDot(G)).toBeValidDotAndMatchSnapshot();
});
});
21 changes: 12 additions & 9 deletions src/components/ClusterPortal.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import React, { FC, useContext, useMemo } from 'react';
import PropTypes from 'prop-types';
import { Cluster } from '../contexts/Cluster';
import { CurrentCluster } from '../contexts/CurrentCluster';
import { ClusterMap } from '../contexts/ClusterMap';
import { useRootCluster } from '../hooks/use-root-cluster';
import { ClusterPortalComponentProps } from '../types';
import { useContainerCluster } from '../hooks/use-container-cluster';
import { ClusterPortalProps } from '../types';

export const ClusterPortal: FC<ClusterPortalComponentProps> = ({ children, name }) => {
const root = useRootCluster();
/**
* ClusterPortal component.
*/
export const ClusterPortal: FC<ClusterPortalProps> = ({ children, id }) => {
const container = useContainerCluster();
const map = useContext(ClusterMap);
const cluster = useMemo(() => (name ? map.get(name) ?? root : root), [root, map, name]);
return <Cluster.Provider value={cluster}>{children}</Cluster.Provider>;
const cluster = useMemo(() => (id ? map.get(id) ?? container : container), [container, map, id]);
return <CurrentCluster.Provider value={cluster}>{children}</CurrentCluster.Provider>;
};

ClusterPortal.displayName = 'ClusterPortal';
ClusterPortal.defaultProps = {
name: undefined,
id: undefined,
};

ClusterPortal.propTypes = {
name: PropTypes.string,
id: PropTypes.string,
};
27 changes: 15 additions & 12 deletions src/components/Digraph.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
import React, { FC, useEffect } from 'react';
import PropTypes from 'prop-types';
import { RootCluster } from '../contexts/RootCluster';
import { Cluster } from '../contexts/Cluster';
import { ContainerCluster } from '../contexts/ContainerCluster';
import { CurrentCluster } from '../contexts/CurrentCluster';
import { useDigraph } from '../hooks/use-digraph';
import { useRenderedID } from '../hooks/use-rendered-id';
import { useRootCluster } from '../hooks/use-root-cluster';
import { useContainerCluster } from '../hooks/use-container-cluster';
import { DuplicatedRootClusterErrorMessage } from '../errors';
import { useClusterMap } from '../hooks/use-cluster-map';
import { RootClusterComponentProps } from '../types';
import { RootClusterProps } from '../types';

export const Digraph: FC<RootClusterComponentProps> = ({ children, label, ...props }) => {
const root = useRootCluster();
if (root !== null) {
/**
* `Digraph` component.
*/
export const Digraph: FC<RootClusterProps> = ({ children, label, ...options }) => {
const container = useContainerCluster();
if (container !== null) {
throw Error(DuplicatedRootClusterErrorMessage);
}
const renderedLabel = useRenderedID(label);
if (renderedLabel !== undefined) Object.assign(props, { label: renderedLabel });
const digraph = useDigraph(props);
if (renderedLabel !== undefined) Object.assign(options, { label: renderedLabel });
const digraph = useDigraph(options);
const clusters = useClusterMap();
useEffect(() => {
if (digraph.id !== undefined) {
clusters.set(digraph.id, digraph);
}
}, [clusters, digraph]);
return (
<RootCluster.Provider value={digraph}>
<Cluster.Provider value={digraph}>{children}</Cluster.Provider>
</RootCluster.Provider>
<ContainerCluster.Provider value={digraph}>
<CurrentCluster.Provider value={digraph}>{children}</CurrentCluster.Provider>
</ContainerCluster.Provider>
);
};

Expand Down
15 changes: 9 additions & 6 deletions src/components/Edge.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import React, { FC } from 'react';
import { VFC } from 'react';
import PropTypes from 'prop-types';
import { useEdge } from '../hooks/use-edge';
import { useRenderedID } from '../hooks/use-rendered-id';
import { EdgeComponentProps } from '../types';
import { EdgeProps } from '../types';

export const Edge: FC<EdgeComponentProps> = ({ children, label, ...props }) => {
/**
* `Edge` component.
*/
export const Edge: VFC<EdgeProps> = ({ targets, label, ...options }) => {
const renderedLabel = useRenderedID(label);
if (renderedLabel !== undefined) Object.assign(props, { label: renderedLabel });
useEdge(props);
return <>{children}</>;
if (renderedLabel !== undefined) Object.assign(options, { label: renderedLabel });
useEdge(targets, options);
return null;
};

Edge.displayName = 'Edge';
Expand Down
28 changes: 15 additions & 13 deletions src/components/Graph.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
import React, { FC, useEffect } from 'react';
import PropTypes from 'prop-types';
import { RootCluster } from '../contexts/RootCluster';
import { Cluster } from '../contexts/Cluster';
import { ContainerCluster } from '../contexts/ContainerCluster';
import { CurrentCluster } from '../contexts/CurrentCluster';
import { useGraph } from '../hooks/use-graph';
import { useRenderedID } from '../hooks/use-rendered-id';
import { useRootCluster } from '../hooks/use-root-cluster';
import { useContainerCluster } from '../hooks/use-container-cluster';
import { DuplicatedRootClusterErrorMessage } from '../errors';
import { useClusterMap } from '../hooks/use-cluster-map';
import { RootClusterComponentProps } from '../types';

export const Graph: FC<RootClusterComponentProps> = ({ children, label, ...props }) => {
const root = useRootCluster();
if (root !== null) {
import { RootClusterProps } from '../types';
/**
* `Graph` component.
*/
export const Graph: FC<RootClusterProps> = ({ children, label, ...options }) => {
const container = useContainerCluster();
if (container !== null) {
throw Error(DuplicatedRootClusterErrorMessage);
}
const renderedLabel = useRenderedID(label);
if (renderedLabel !== undefined) Object.assign(props, { label: renderedLabel });
const graph = useGraph(props);
if (renderedLabel !== undefined) Object.assign(options, { label: renderedLabel });
const graph = useGraph(options);
const clusters = useClusterMap();
useEffect(() => {
if (graph.id !== undefined) {
clusters.set(graph.id, graph);
}
}, [clusters, graph]);
return (
<RootCluster.Provider value={graph}>
<Cluster.Provider value={graph}>{children}</Cluster.Provider>
</RootCluster.Provider>
<ContainerCluster.Provider value={graph}>
<CurrentCluster.Provider value={graph}>{children}</CurrentCluster.Provider>
</ContainerCluster.Provider>
);
};

Expand Down
17 changes: 10 additions & 7 deletions src/components/Node.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import React, { FC } from 'react';
import { VFC } from 'react';
import PropTypes from 'prop-types';
import { useNode } from '../hooks/use-node';
import { useRenderedID } from '../hooks/use-rendered-id';
import { NodeComponentProps } from '../types';
import { NodeProps } from '../types';

export const Node: FC<NodeComponentProps> = ({ children, label, xlabel, ...props }) => {
/**
* `Node` component.
*/
export const Node: VFC<NodeProps> = ({ id, label, xlabel, ...options }) => {
const renderedLabel = useRenderedID(label);
const renderedXlabel = useRenderedID(xlabel);

if (renderedLabel !== undefined) Object.assign(props, { label: renderedLabel });
if (renderedXlabel !== undefined) Object.assign(props, { xlabel: renderedXlabel });
useNode(props);
return <>{children}</>;
if (renderedLabel !== undefined) Object.assign(options, { label: renderedLabel });
if (renderedXlabel !== undefined) Object.assign(options, { xlabel: renderedXlabel });
useNode(id, options);
return null;
};

Node.displayName = 'Node';
Expand Down
16 changes: 9 additions & 7 deletions src/components/Subgraph.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import React, { FC, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Cluster } from '../contexts/Cluster';
import { CurrentCluster } from '../contexts/CurrentCluster';
import { useSubgraph } from '../hooks/use-subgraph';
import { useRenderedID } from '../hooks/use-rendered-id';
import { useClusterMap } from '../hooks/use-cluster-map';
import { SubgraphComponentProps } from '../types';

export const Subgraph: FC<SubgraphComponentProps> = ({ children, label, ...props }) => {
import { SubgraphProps } from '../types';
/**
* `Subgraph` component.
*/
export const Subgraph: FC<SubgraphProps> = ({ children, label, ...options }) => {
const renderedLabel = useRenderedID(label);
if (renderedLabel !== undefined) Object.assign(props, { label: renderedLabel });
const subgraph = useSubgraph(props);
if (renderedLabel !== undefined) Object.assign(options, { label: renderedLabel });
const subgraph = useSubgraph(options);
const clusters = useClusterMap();
useEffect(() => {
if (subgraph.id !== undefined) {
clusters.set(subgraph.id, subgraph);
}
}, [subgraph, clusters]);
return <Cluster.Provider value={subgraph}>{children}</Cluster.Provider>;
return <CurrentCluster.Provider value={subgraph}>{children}</CurrentCluster.Provider>;
};

Subgraph.displayName = 'Subgraph';
Expand Down
8 changes: 4 additions & 4 deletions src/components/__tests__/utils/renderExpectToThrow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import React, { Component, ReactElement } from 'react';
import { render } from '../../../render';

export function renderExpectToThrow(element: ReactElement, expectedError: string): void {
export function renderExpectToThrow(element: ReactElement, ...expectedError: string[]): void {
const errors: Error[] = [];
class ErrorBoundary extends Component<Record<string, unknown>, { hasError: boolean }> {
constructor(props: Record<string, unknown>) {
Expand All @@ -28,11 +28,11 @@ export function renderExpectToThrow(element: ReactElement, expectedError: string
}

try {
render(<ErrorBoundary>{element}</ErrorBoundary>, {});
render(<ErrorBoundary>{element}</ErrorBoundary>);
} catch (e) {
errors.push(e);
}
expect(errors.length).toBeGreaterThanOrEqual(expectedError.length);

expect(errors.length).toBe(1);
expect(errors[0].message).toContain(expectedError);
expect(errors.map((e) => e.message)).toEqual(expect.arrayContaining(expectedError));
}
6 changes: 0 additions & 6 deletions src/contexts/Cluster.ts

This file was deleted.

5 changes: 5 additions & 0 deletions src/contexts/ContainerCluster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';
import { ICluster } from 'ts-graphviz';

export const ContainerCluster = React.createContext<ICluster | null>(null);
ContainerCluster.displayName = 'ContainerCluster';
5 changes: 5 additions & 0 deletions src/contexts/CurrentCluster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';
import { ICluster } from 'ts-graphviz';

export const CurrentCluster = React.createContext<ICluster | null>(null);
CurrentCluster.displayName = 'CurrentCluster';
6 changes: 5 additions & 1 deletion src/contexts/GraphvizContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import React from 'react';
import { IContext } from '../types';
import { ICluster } from 'ts-graphviz';

export interface IContext {
container?: ICluster;
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
export const GraphvizContext = React.createContext<IContext>(null!);
Expand Down
6 changes: 0 additions & 6 deletions src/contexts/RootCluster.ts

This file was deleted.

2 changes: 2 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export const NoGraphvizContextErrorMessage =
'Cannot call useGraphvizContext outside GraphvizContext.\nBasically, you need to use the render function provided by @ts-graphviz/react.';
export const NoClusterErrorMessage = 'useCluster must be called within a cluster such as Digraph, Graph, Subgraph.';
export const DuplicatedRootClusterErrorMessage = 'RootCluster is duplicated.\nUse only one of Digraph and Graph.';

export const NoContainerErrorMessage = 'There are no clusters of container(Subgraph, Digraph, Graph).';
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import { Digraph, Graph } from 'ts-graphviz';
import { renderHook } from '@testing-library/react-hooks';
import { digraph, graph, graphInSubgraph, digraphInSubgraph } from './utils/wrapper';
import { useRootCluster } from '../use-root-cluster';
import { useContainerCluster } from '../use-container-cluster';

describe('useRootCluster', () => {
describe('useContainerCluster', () => {
describe('get root cluster', () => {
test('returns Diagram instance in digraph wrapper', () => {
const { result } = renderHook(() => useRootCluster(), {
const { result } = renderHook(() => useContainerCluster(), {
wrapper: digraph(),
});
expect(result.current).toBeInstanceOf(Digraph);
});

test('returns Graph instance in graph wrapper', () => {
const { result } = renderHook(() => useRootCluster(), {
const { result } = renderHook(() => useContainerCluster(), {
wrapper: graph(),
});
expect(result.current).toBeInstanceOf(Graph);
});

test('returns Graph instance in graphInSubgraph wrapper', () => {
const { result } = renderHook(() => useRootCluster(), {
const { result } = renderHook(() => useContainerCluster(), {
wrapper: graphInSubgraph(),
});
expect(result.current).toBeInstanceOf(Graph);
});

test('returns Digraph instance in digraphInSubgraph wrapper', () => {
const { result } = renderHook(() => useRootCluster(), {
const { result } = renderHook(() => useContainerCluster(), {
wrapper: digraphInSubgraph(),
});
expect(result.current).toBeInstanceOf(Digraph);
Expand Down
Loading