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

Commit 9be686d

Browse files
authored
Merge pull request #63 from ts-graphviz/add-tests
Add tests and error handlings
2 parents f87ad1c + 94fae58 commit 9be686d

37 files changed

+550
-81
lines changed

.eslintrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"prettier/prettier": "error",
4040
"import/extensions": "off",
4141
"import/prefer-default-export": "off",
42+
"import/no-extraneous-dependencies": "off",
4243
"react/forbid-prop-types": "off",
4344
"react/jsx-wrap-multilines": "off",
4445
"react/prop-types": [

jest.config.js

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,11 @@ module.exports = {
33
testEnvironment: 'node',
44
verbose: true,
55
collectCoverage: true,
6-
setupFilesAfterEnv: [
7-
'<rootDir>/config/jest/setup-jest.ts',
8-
],
9-
roots: [
10-
'<rootDir>/src'
11-
],
6+
setupFilesAfterEnv: ['<rootDir>/config/jest/setup-jest.ts'],
7+
roots: ['<rootDir>/src'],
128
transform: {
13-
'^.+\\.tsx?$': 'ts-jest'
9+
'^.+\\.tsx?$': 'ts-jest',
1410
},
15-
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
16-
moduleFileExtensions: [
17-
'ts',
18-
'tsx',
19-
'js',
20-
'jsx',
21-
'json',
22-
'node',
23-
],
11+
testMatch: ['**/(__tests__|__specs__)/**/*.(spec|test).(ts|tsx)'],
12+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
2413
};

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
},
4343
"devDependencies": {
4444
"@rollup/plugin-commonjs": "^11.1.0",
45+
"@testing-library/react-hooks": "^3.2.1",
4546
"@types/prop-types": "^15.7.3",
4647
"@types/react": "^16.9.17",
4748
"@types/react-dom": "^16.9.4",
@@ -58,10 +59,11 @@
5859
"eslint-plugin-react": "^7.19.0",
5960
"eslint-plugin-react-hooks": "^3.0.0",
6061
"jest": "^25.3.0",
61-
"jest-graphviz": "^0.2.0",
62+
"jest-graphviz": "^0.3.1",
6263
"prettier": "^1.19.1",
6364
"react": "^16.13.1",
6465
"react-dom": "^16.13.1",
66+
"react-test-renderer": "^16.13.1",
6567
"rollup": "^2.6.1",
6668
"rollup-plugin-terser": "^5.3.0",
6769
"rollup-plugin-typescript2": "^0.27.0",

src/components/Digraph.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import React, { FC, ReactElement } from 'react';
22
import PropTypes from 'prop-types';
3-
import { RootCluster } from './contexts/RootCluster';
3+
import { RootCluster, NoRootCluster } from './contexts/RootCluster';
44
import { Cluster } from './contexts/Cluster';
55
import { useDigraph, DigraphProps } from '../hooks/use-digraph';
66
import { useRenderedID } from '../hooks/use-rendered-id';
7+
import { useRootCluster } from '../hooks/use-root-cluster';
8+
import { DuplicatedRootClusterErrorMessage } from '../utils/errors';
79

810
type Props = Omit<DigraphProps, 'label'> & {
911
label?: ReactElement | string;
1012
};
1113

1214
export const Digraph: FC<Props> = ({ children, label, ...props }) => {
15+
const root = useRootCluster();
16+
if (root !== NoRootCluster) {
17+
throw Error(DuplicatedRootClusterErrorMessage);
18+
}
1319
const renderedLabel = useRenderedID(label);
1420
if (renderedLabel !== undefined) Object.assign(props, { label: renderedLabel });
1521
const digraph = useDigraph(props);

src/components/Graph.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import React, { FC, ReactElement } from 'react';
22
import PropTypes from 'prop-types';
3-
import { RootCluster } from './contexts/RootCluster';
3+
import { RootCluster, NoRootCluster } from './contexts/RootCluster';
44
import { Cluster } from './contexts/Cluster';
55
import { GraphProps, useGraph } from '../hooks/use-graph';
66
import { useRenderedID } from '../hooks/use-rendered-id';
7+
import { useRootCluster } from '../hooks/use-root-cluster';
8+
import { DuplicatedRootClusterErrorMessage } from '../utils/errors';
79

810
type Props = Omit<GraphProps, 'label'> & {
911
label?: ReactElement | string;
1012
};
1113

1214
export const Graph: FC<Props> = ({ children, label, ...props }) => {
15+
const root = useRootCluster();
16+
if (root !== NoRootCluster) {
17+
throw Error(DuplicatedRootClusterErrorMessage);
18+
}
1319
const renderedLabel = useRenderedID(label);
1420
if (renderedLabel !== undefined) Object.assign(props, { label: renderedLabel });
1521
const graph = useGraph(props);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from 'react';
2+
import { Digraph } from '../Digraph';
3+
import { DuplicatedRootClusterErrorMessage } from '../../utils/errors';
4+
import { renderExpectToThrow } from './utils/renderExpectToThrow';
5+
6+
describe('Digraph', () => {
7+
// eslint-disable-next-line jest/expect-expect
8+
test('An error occurs when duplicate <Digraph />', () => {
9+
renderExpectToThrow(
10+
<Digraph>
11+
<Digraph />
12+
</Digraph>,
13+
DuplicatedRootClusterErrorMessage,
14+
);
15+
});
16+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from 'react';
2+
import { Graph } from '../Graph';
3+
import { DuplicatedRootClusterErrorMessage } from '../../utils/errors';
4+
import { renderExpectToThrow } from './utils/renderExpectToThrow';
5+
6+
describe('Graph', () => {
7+
// eslint-disable-next-line jest/expect-expect
8+
test('An error occurs when duplicate <Graph />', () => {
9+
renderExpectToThrow(
10+
<Graph>
11+
<Graph />
12+
</Graph>,
13+
DuplicatedRootClusterErrorMessage,
14+
);
15+
});
16+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/* eslint-disable react/destructuring-assignment */
2+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
3+
import React, { Component, ReactElement } from 'react';
4+
import { Context } from 'ts-graphviz';
5+
import { render } from '../../../renderer/render';
6+
7+
export function renderExpectToThrow(element: ReactElement, expectedError: string) {
8+
const errors: Error[] = [];
9+
class ErrorBoundary extends Component<{}, { hasError: boolean }> {
10+
constructor(props: {}) {
11+
super(props);
12+
this.state = { hasError: false };
13+
}
14+
15+
static getDerivedStateFromError() {
16+
return { hasError: true };
17+
}
18+
19+
componentDidCatch(error: Error) {
20+
errors.push(error);
21+
}
22+
23+
render() {
24+
if (this.state.hasError) {
25+
return <></>;
26+
}
27+
return this.props.children;
28+
}
29+
}
30+
31+
const context = new Context();
32+
try {
33+
render(<ErrorBoundary>{element}</ErrorBoundary>, context);
34+
} catch (e) {
35+
errors.push(e);
36+
}
37+
38+
expect(errors.length).toBe(1);
39+
expect(errors[0].message).toContain(expectedError);
40+
}

src/components/contexts/Cluster.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import { ICluster } from 'ts-graphviz';
33

4-
export const Cluster = React.createContext<ICluster<string>>({} as ICluster<string>);
4+
export const NoCluster = {} as ICluster<string>;
5+
export const Cluster = React.createContext<ICluster<string>>(NoCluster);
56
Cluster.displayName = 'Cluster';
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React from 'react';
22
import { Context } from 'ts-graphviz';
33

4-
export const GraphvizContext = React.createContext<Context>(new Context());
4+
export const NoContext = {} as Context;
5+
6+
export const GraphvizContext = React.createContext<Context>(NoContext);
57
GraphvizContext.displayName = 'GraphvizContext';
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import { IRootCluster } from 'ts-graphviz';
33

4-
export const RootCluster = React.createContext<IRootCluster>({} as IRootCluster);
4+
export const NoRootCluster = {} as IRootCluster;
5+
export const RootCluster = React.createContext<IRootCluster>(NoRootCluster);
56
RootCluster.displayName = 'RootCluster';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`useRenderedID 1`] = `"hoge"`;
4+
5+
exports[`useRenderedID 2`] = `"<<b>bold</b>>"`;
6+
7+
exports[`useRenderedID 3`] = `"<<b>bold</b>>"`;
8+
9+
exports[`useRenderedID 4`] = `"<<table BORDER=\\"0\\" CELLBORDER=\\"1\\" CELLSPACING=\\"0\\"><tr><td>left</td><td PORT=\\"m\\">middle</td><td PORT=\\"r\\">right</td></tr></table>>"`;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Digraph, Graph, Subgraph } from 'ts-graphviz';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
import { useCluster } from '../use-cluster';
4+
import { digraph, graph, graphInSubgraph, digraphInSubgraph } from './utils/wrapper';
5+
import { NoClusterErrorMessage } from '../../utils/errors';
6+
7+
describe('useCluster', () => {
8+
describe('get parent cluster', () => {
9+
test('returns Diagram instance in digraph wrapper', () => {
10+
const { result } = renderHook(() => useCluster(), {
11+
wrapper: digraph(),
12+
});
13+
expect(result.current).toBeInstanceOf(Digraph);
14+
});
15+
16+
test('returns Graph instance in graph wrapper', () => {
17+
const { result } = renderHook(() => useCluster(), {
18+
wrapper: graph(),
19+
});
20+
expect(result.current).toBeInstanceOf(Graph);
21+
});
22+
23+
test('returns Subgraph instance in graphInSubgraph wrapper', () => {
24+
const { result } = renderHook(() => useCluster(), {
25+
wrapper: graphInSubgraph(),
26+
});
27+
expect(result.current).toBeInstanceOf(Subgraph);
28+
});
29+
30+
test('returns Subgraph instance in digraphInSubgraph wrapper', () => {
31+
const { result } = renderHook(() => useCluster(), {
32+
wrapper: digraphInSubgraph(),
33+
});
34+
expect(result.current).toBeInstanceOf(Subgraph);
35+
});
36+
});
37+
38+
test('An error occurs when called outside the cluster', () => {
39+
const { result } = renderHook(() => useCluster());
40+
expect(result.error).toStrictEqual(Error(NoClusterErrorMessage));
41+
});
42+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Digraph } from 'ts-graphviz';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
import { context } from './utils/wrapper';
4+
import { useDigraph } from '../use-digraph';
5+
6+
describe('useDigraph', () => {
7+
it('returns Digraph instance', () => {
8+
const { result } = renderHook(() => useDigraph(), {
9+
wrapper: context(),
10+
});
11+
expect(result.current).toBeInstanceOf(Digraph);
12+
});
13+
});

src/hooks/__tests__/use-edge.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Edge } from 'ts-graphviz';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
import { useEdge } from '../use-edge';
4+
import { digraph, graph } from './utils/wrapper';
5+
import { EdgeTargetLengthErrorMessage } from '../../utils/errors';
6+
7+
describe('useEdge', () => {
8+
it('returns Edge instance in digraph wrapper', () => {
9+
const { result } = renderHook(() => useEdge({ targets: ['a', 'b'] }), {
10+
wrapper: digraph(),
11+
});
12+
expect(result.current).toBeInstanceOf(Edge);
13+
});
14+
15+
it('returns Edge instance in graph wrapper', () => {
16+
const { result } = renderHook(() => useEdge({ targets: ['a', 'b'] }), {
17+
wrapper: graph(),
18+
});
19+
expect(result.current).toBeInstanceOf(Edge);
20+
});
21+
22+
test('throw error if the target is less than 2', () => {
23+
const { result } = renderHook(() => useEdge({ targets: ['a'] }), {
24+
wrapper: graph(),
25+
});
26+
expect(result.error).toStrictEqual(Error(EdgeTargetLengthErrorMessage));
27+
});
28+
});

src/hooks/__tests__/use-graph.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Graph } from 'ts-graphviz';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
import { context } from './utils/wrapper';
4+
import { useGraph } from '../use-graph';
5+
6+
describe('useGraph', () => {
7+
it('returns Graph instance', () => {
8+
const { result } = renderHook(() => useGraph(), {
9+
wrapper: context(),
10+
});
11+
expect(result.current).toBeInstanceOf(Graph);
12+
});
13+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Context } from 'ts-graphviz';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
import { context } from './utils/wrapper';
4+
import { useGraphvizContext } from '../use-graphviz-context';
5+
import { NoGraphvizContextErrorMessage } from '../../utils/errors';
6+
7+
describe('useGraphvizContext', () => {
8+
test('returns Context instance', () => {
9+
const { result } = renderHook(() => useGraphvizContext(), {
10+
wrapper: context(),
11+
});
12+
expect(result.current).toBeInstanceOf(Context);
13+
});
14+
15+
test('An error occurs when called outside the GraphvizContext', () => {
16+
const { result } = renderHook(() => useGraphvizContext());
17+
expect(result.error).toStrictEqual(Error(NoGraphvizContextErrorMessage));
18+
});
19+
});

src/hooks/__tests__/use-node.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Node } from 'ts-graphviz';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
4+
import { useNode } from '../use-node';
5+
import { digraph } from './utils/wrapper';
6+
7+
describe('useNode', () => {
8+
it('returns Node instance', () => {
9+
const { result } = renderHook(() => useNode({ id: 'hoge' }), {
10+
wrapper: digraph(),
11+
});
12+
expect(result.current).toBeInstanceOf(Node);
13+
});
14+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
import { context } from './utils/wrapper';
4+
import { useRenderedID } from '../use-rendered-id';
5+
import { DOT } from '../../components/HtmlLike';
6+
7+
describe('useRenderedID', () => {
8+
test.each([
9+
'hoge',
10+
'<<b>bold</b>>',
11+
<DOT.B>bold</DOT.B>,
12+
<DOT.TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
13+
<DOT.TR>
14+
<DOT.TD>left</DOT.TD>
15+
<DOT.TD PORT="m">middle</DOT.TD>
16+
<DOT.TD PORT="r">right</DOT.TD>
17+
</DOT.TR>
18+
</DOT.TABLE>,
19+
])('', id => {
20+
const { result } = renderHook(() => useRenderedID(id), {
21+
wrapper: context(),
22+
});
23+
expect(result.current).toMatchSnapshot();
24+
});
25+
});

0 commit comments

Comments
 (0)