Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0fe6f52

Browse files
committed
feat(site): add TargetCell component
Summary: This is a direct follow-up to #484 and #500. It is a part of many steps in porting/refactoring the AuditLog from v1. Details: - Port over TargetCell from v1, with refactorings - Add tests and stories Impact: This change does not have any user-facing impact yet because AuditLog is not yet available in the product. This is part of an incremental approach; the FE is still waiting on the BE port. Relations: - This commit relates to #472, but does not finish it - This commit should not be merged until after #484 and #500, because it builds off of them.
1 parent 7bd039e commit 0fe6f52

File tree

3 files changed

+207
-0
lines changed

3 files changed

+207
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ComponentMeta, Story } from "@storybook/react"
2+
import React from "react"
3+
import { TargetCell, TargetCellProps } from "./TargetCell"
4+
5+
export default {
6+
title: "AuditLog/Cells/TargetCell",
7+
component: TargetCell,
8+
argTypes: {
9+
onSelect: {
10+
action: "onSelect",
11+
},
12+
},
13+
} as ComponentMeta<typeof TargetCell>
14+
15+
const Template: Story<TargetCellProps> = (args) => <TargetCell {...args} />
16+
17+
export const Example = Template.bind({})
18+
Example.args = {
19+
name: "Coder frontend",
20+
type: "project",
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import React from "react"
2+
import { WrapperComponent } from "../../test_helpers"
3+
import { TargetCell, TargetCellProps, LANGUAGE } from "./TargetCell"
4+
import { fireEvent, render, screen } from "@testing-library/react"
5+
6+
namespace Helpers {
7+
export const Props: TargetCellProps = {
8+
name: "name",
9+
type: "test",
10+
onSelect: jest.fn(),
11+
}
12+
13+
export const Component: React.FC<TargetCellProps> = (props) => (
14+
<WrapperComponent>
15+
<TargetCell {...props} />
16+
</WrapperComponent>
17+
)
18+
}
19+
20+
describe("TargetCellProps", () => {
21+
// eslint-disable-next-line @typescript-eslint/no-empty-function
22+
const noop = () => {}
23+
24+
it.each<[TargetCellProps, TargetCellProps, boolean]>([
25+
[
26+
{
27+
name: "test",
28+
type: "test",
29+
onSelect: noop,
30+
},
31+
{
32+
name: "test",
33+
type: "test",
34+
onSelect: noop,
35+
},
36+
false,
37+
],
38+
[
39+
{
40+
name: "",
41+
type: " test ",
42+
onSelect: noop,
43+
},
44+
{
45+
name: "",
46+
type: "test",
47+
onSelect: noop,
48+
},
49+
false,
50+
],
51+
[
52+
{
53+
name: "test",
54+
type: "",
55+
onSelect: noop,
56+
},
57+
{
58+
name: "test",
59+
type: "",
60+
onSelect: noop,
61+
},
62+
true,
63+
],
64+
])(`validate(%p) -> %p throws: %p`, (props, expected, throws) => {
65+
const validate = () => {
66+
return TargetCellProps.validate(props)
67+
}
68+
69+
if (throws) {
70+
expect(validate).toThrowError()
71+
} else {
72+
expect(validate()).toStrictEqual(expected)
73+
}
74+
})
75+
})
76+
77+
describe("TargetCell", () => {
78+
// onSelect callback
79+
it("calls onSelect when the name is clicked", () => {
80+
// Given
81+
const onSelectMock = jest.fn()
82+
const props: TargetCellProps = {
83+
...Helpers.Props,
84+
onSelect: onSelectMock,
85+
}
86+
87+
// When
88+
render(<Helpers.Component {...props} />)
89+
fireEvent.click(screen.getByText(props.name))
90+
91+
// Then
92+
expect(onSelectMock).toHaveBeenCalledTimes(1)
93+
})
94+
95+
// target name cases
96+
it("renders a non-empty name", () => {
97+
// Given
98+
const props = Helpers.Props
99+
100+
// When
101+
render(<Helpers.Component {...props} />)
102+
103+
// Then
104+
expect(screen.getByText(props.name)).toBeDefined()
105+
})
106+
it(`renders ${LANGUAGE.emptyDisplayName} when name is '""'`, () => {
107+
// Given
108+
const props: TargetCellProps = {
109+
...Helpers.Props,
110+
name: "",
111+
}
112+
113+
// When
114+
render(<Helpers.Component {...props} />)
115+
116+
// Then
117+
expect(screen.getByText(LANGUAGE.emptyDisplayName)).toBeDefined()
118+
})
119+
120+
// target type
121+
it("renders target type", () => {
122+
// Given
123+
const props = Helpers.Props
124+
125+
// When
126+
render(<Helpers.Component {...props} />)
127+
128+
// Then
129+
expect(screen.getByText(props.type)).toBeDefined()
130+
})
131+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import Box from "@material-ui/core/Box"
2+
import Link from "@material-ui/core/Link"
3+
import Typography from "@material-ui/core/Typography"
4+
import React from "react"
5+
6+
export const LANGUAGE = {
7+
emptyDisplayName: "*",
8+
}
9+
10+
const TargetCellName = (displayName: string, onSelect: () => void): JSX.Element => {
11+
return displayName ? (
12+
<Link onClick={onSelect}>{displayName}</Link>
13+
) : (
14+
<Typography variant="caption">{LANGUAGE.emptyDisplayName}</Typography>
15+
)
16+
}
17+
18+
export interface TargetCellProps {
19+
name: string
20+
type: string
21+
onSelect: () => void
22+
}
23+
export namespace TargetCellProps {
24+
/**
25+
* @throws Error if invalid
26+
*/
27+
export const validate = (props: TargetCellProps): TargetCellProps => {
28+
const sanitizedName = props.name.trim()
29+
const sanitizedType = props.type.trim()
30+
31+
if (!sanitizedType) {
32+
throw new Error(`invalid type: '${props.type}'`)
33+
}
34+
35+
return {
36+
name: sanitizedName,
37+
type: sanitizedType,
38+
onSelect: props.onSelect,
39+
}
40+
}
41+
}
42+
43+
export const TargetCell: React.FC<TargetCellProps> = (props) => {
44+
const { name, type, onSelect } = TargetCellProps.validate(props)
45+
46+
return (
47+
<Box display="flex" flexDirection="column">
48+
{TargetCellName(name, onSelect)}
49+
50+
<Typography color="textSecondary" variant="caption">
51+
{type}
52+
</Typography>
53+
</Box>
54+
)
55+
}

0 commit comments

Comments
 (0)