Skip to content

Commit 829777d

Browse files
authored
Merge pull request #1290 from aidanm3341/refactoring+testing
refactor basic hub components and added tests
2 parents 4918887 + a45a0a5 commit 829777d

File tree

8 files changed

+168
-27
lines changed

8 files changed

+168
-27
lines changed

calm-hub-ui/src/hub/Hub.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect, useState } from 'react';
2-
import { ValueTable } from './components/value-table.js';
3-
import { JsonRenderer } from './components/json-view.js';
2+
import { ValueTable } from './components/value-table/ValueTable.js';
3+
import { JsonRenderer } from './components/json-renderer/JsonRenderer.js';
44
import { Namespace, PatternID, FlowID, ArchitectureID, Version, Data } from '../model/calm.js';
55
import {
66
fetchNamespaces,
@@ -143,7 +143,7 @@ function Hub() {
143143
/>
144144
)}
145145
</div>
146-
<JsonRenderer jsonString={data} />
146+
<JsonRenderer json={data} />
147147
</div>
148148
</>
149149
);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { render, screen, fireEvent } from '@testing-library/react';
2+
import { MemoryRouter, useNavigate } from 'react-router-dom';
3+
import { JsonRenderer } from './JsonRenderer.js';
4+
import { describe, it, expect, vi, beforeEach } from 'vitest';
5+
6+
vi.mock('react-router-dom', async () => {
7+
const actual = await vi.importActual('react-router-dom');
8+
return {
9+
...actual,
10+
useNavigate: vi.fn(),
11+
};
12+
});
13+
14+
describe('JsonRenderer', () => {
15+
const mockNavigate = vi.fn();
16+
17+
beforeEach(() => {
18+
vi.mocked(useNavigate).mockReturnValue(mockNavigate);
19+
mockNavigate.mockClear();
20+
});
21+
22+
it('renders default message when jsonString is undefined', () => {
23+
render(
24+
<MemoryRouter>
25+
<JsonRenderer json={undefined} />
26+
</MemoryRouter>
27+
);
28+
expect(screen.getByText(/please select a document to load/i)).toBeInTheDocument();
29+
expect(screen.queryByText(/visualize/i)).not.toBeInTheDocument();
30+
});
31+
32+
it('renders JsonView and Visualize button when jsonString is provided', () => {
33+
const data = { foo: 'bar', num: 42 };
34+
render(
35+
<MemoryRouter>
36+
<JsonRenderer json={data} />
37+
</MemoryRouter>
38+
);
39+
expect(screen.getByText(/visualize/i)).toBeInTheDocument();
40+
expect(screen.getByText(/foo/i)).toBeInTheDocument();
41+
expect(screen.getByText(/bar/i)).toBeInTheDocument();
42+
expect(screen.getByText(/num/i)).toBeInTheDocument();
43+
expect(screen.getByText(/42/i)).toBeInTheDocument();
44+
});
45+
46+
it('navigates to /visualizer with state when Visualize button is clicked', () => {
47+
const data = { foo: 'bar' };
48+
render(
49+
<MemoryRouter>
50+
<JsonRenderer json={data} />
51+
</MemoryRouter>
52+
);
53+
const button = screen.getByText(/visualize/i);
54+
fireEvent.click(button);
55+
expect(mockNavigate).toHaveBeenCalledWith('/visualizer', { state: data });
56+
});
57+
});
Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,36 @@
11
import { useNavigate } from 'react-router-dom';
22
import { allExpanded, defaultStyles, JsonView } from 'react-json-view-lite';
33
import 'react-json-view-lite/dist/index.css';
4-
import { Data } from '../../model/calm.js';
54

65
interface JsonRendererProps {
7-
jsonString: Data | undefined;
6+
json?: object;
87
}
98

10-
export function JsonRenderer({ jsonString }: JsonRendererProps) {
11-
const defaultMessage = <div className=" text-center">Please select a document to load.</div>;
12-
const navigate = useNavigate();
13-
const jsonView = (
9+
function NoData() {
10+
return <div className=" text-center">Please select a document to load.</div>;
11+
}
12+
13+
function JsonDisplay({ data, handleClick }: { data: object; handleClick: () => void }) {
14+
return (
1415
<div>
1516
<button
1617
className="bg-primary hover:bg-blue-500 text-white font-bold py-2 px-4 rounded float-right"
1718
onClick={handleClick}
1819
>
1920
Visualize
2021
</button>
21-
<JsonView
22-
data={jsonString || ''}
23-
shouldExpandNode={allExpanded}
24-
style={defaultStyles}
25-
/>
22+
<JsonView data={data} shouldExpandNode={allExpanded} style={defaultStyles} />
2623
</div>
2724
);
25+
}
26+
27+
export function JsonRenderer({ json }: JsonRendererProps) {
28+
const navigate = useNavigate();
2829
function handleClick() {
29-
navigate('/visualizer', { state: jsonString });
30+
navigate('/visualizer', { state: json });
3031
}
3132

32-
const content = jsonString ? jsonView : defaultMessage;
33+
const content = json ? <JsonDisplay data={json} handleClick={handleClick} /> : <NoData />;
3334

3435
return <div className="p-5 flex-1 overflow-auto bg-[#eee]">{content}</div>;
3536
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { render, screen, fireEvent } from '@testing-library/react';
2+
import { describe, it, expect, vi } from 'vitest';
3+
import { ValueTable } from './ValueTable.js';
4+
5+
describe('ValueTable', () => {
6+
const header = 'Test Header';
7+
const values = ['Value 1', 'Value 2', 'Value 3'];
8+
9+
it('renders the header', () => {
10+
render(
11+
<ValueTable
12+
header={header}
13+
values={values}
14+
callback={() => {}}
15+
currentValue={undefined}
16+
/>
17+
);
18+
expect(screen.getByText(header)).toBeInTheDocument();
19+
});
20+
21+
it('renders all values', () => {
22+
render(
23+
<ValueTable
24+
header={header}
25+
values={values}
26+
callback={() => {}}
27+
currentValue={undefined}
28+
/>
29+
);
30+
values.forEach((value) => {
31+
expect(screen.getByText(value)).toBeInTheDocument();
32+
});
33+
});
34+
35+
it('calls callback with correct value when a value is clicked', () => {
36+
const callback = vi.fn();
37+
render(
38+
<ValueTable
39+
header={header}
40+
values={values}
41+
callback={callback}
42+
currentValue={undefined}
43+
/>
44+
);
45+
fireEvent.click(screen.getByText('Value 2'));
46+
expect(callback).toHaveBeenCalledWith('Value 2');
47+
});
48+
49+
it('applies selected style to the currentValue', () => {
50+
render(
51+
<ValueTable
52+
header={header}
53+
values={values}
54+
callback={() => {}}
55+
currentValue="Value 3"
56+
/>
57+
);
58+
const selected = screen.getByText('Value 3');
59+
expect(selected.className).toContain('bg-[#eee]');
60+
});
61+
62+
it('does not apply selected style to non-current values', () => {
63+
render(
64+
<ValueTable
65+
header={header}
66+
values={values}
67+
callback={() => {}}
68+
currentValue="Value 1"
69+
/>
70+
);
71+
const notSelected = screen.getByText('Value 2');
72+
expect(notSelected.className).not.toContain('bg-[#eee]');
73+
});
74+
75+
it('renders nothing if values array is empty', () => {
76+
render(
77+
<ValueTable header={header} values={[]} callback={() => {}} currentValue={undefined} />
78+
);
79+
expect(screen.queryByText('Value 1')).not.toBeInTheDocument();
80+
expect(screen.queryByText('Value 2')).not.toBeInTheDocument();
81+
expect(screen.queryByText('Value 3')).not.toBeInTheDocument();
82+
});
83+
});
File renamed without changes.

calm-hub-ui/vite.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default defineConfig({
1010
globals: true,
1111
environment: 'jsdom',
1212
environmentMatchGlobs: [['./src/**/*.tsx', 'jsdom']],
13-
setupFiles: ['./src/tests/vitest.setup.ts'],
13+
setupFiles: ['./vitest.setup.ts'],
1414
},
1515
build: {
1616
outDir: 'build',

package-lock.json

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)