Skip to content

Commit 6e2e734

Browse files
committed
Add tests
1 parent 584d3a3 commit 6e2e734

File tree

7 files changed

+231
-15
lines changed

7 files changed

+231
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import Avatar from "@material-ui/core/Avatar"
2+
import Button from "@material-ui/core/Button"
3+
import Link from "@material-ui/core/Link"
4+
import { makeStyles } from "@material-ui/core/styles"
5+
import Table from "@material-ui/core/Table"
6+
import TableBody from "@material-ui/core/TableBody"
7+
import TableCell from "@material-ui/core/TableCell"
8+
import TableHead from "@material-ui/core/TableHead"
9+
import TableRow from "@material-ui/core/TableRow"
10+
import AddCircleOutline from "@material-ui/icons/AddCircleOutline"
11+
import dayjs from "dayjs"
12+
import relativeTime from "dayjs/plugin/relativeTime"
13+
import React from "react"
14+
import { Link as RouterLink } from "react-router-dom"
15+
import * as TypesGen from "../../api/typesGenerated"
16+
import { Margins } from "../../components/Margins/Margins"
17+
import { Stack } from "../../components/Stack/Stack"
18+
import { firstLetter } from "../../util/firstLetter"
19+
20+
dayjs.extend(relativeTime)
21+
22+
export const Language = {
23+
createButton: "Create Template",
24+
emptyViewCreate: "to standardize development workspaces for your team.",
25+
emptyViewNoPerms: "No templates have been created! Contact your Coder administrator.",
26+
}
27+
28+
export interface TemplatesPageViewProps {
29+
loading?: boolean
30+
canCreateTemplate?: boolean
31+
templates?: TypesGen.Template[]
32+
error?: unknown
33+
}
34+
35+
export const TemplatesPageView: React.FC<TemplatesPageViewProps> = (props) => {
36+
const styles = useStyles()
37+
return (
38+
<Stack spacing={4}>
39+
<Margins>
40+
<div className={styles.actions}>
41+
{props.canCreateTemplate && <Button startIcon={<AddCircleOutline />}>{Language.createButton}</Button>}
42+
</div>
43+
<Table>
44+
<TableHead>
45+
<TableRow>
46+
<TableCell>Name</TableCell>
47+
<TableCell>Used By</TableCell>
48+
<TableCell>Last Updated</TableCell>
49+
</TableRow>
50+
</TableHead>
51+
<TableBody>
52+
{!props.loading && !props.templates?.length && (
53+
<TableRow>
54+
<TableCell colSpan={999}>
55+
<div className={styles.welcome}>
56+
{props.canCreateTemplate ? (
57+
<span>
58+
<Link component={RouterLink} to="/templates/new">
59+
Create a template
60+
</Link>
61+
&nbsp;{Language.emptyViewCreate}
62+
</span>
63+
) : (
64+
<span>{Language.emptyViewNoPerms}</span>
65+
)}
66+
</div>
67+
</TableCell>
68+
</TableRow>
69+
)}
70+
{props.templates?.map((template) => {
71+
return (
72+
<TableRow key={template.id} className={styles.templateRow}>
73+
<TableCell>
74+
<div className={styles.templateName}>
75+
<Avatar variant="square" className={styles.templateAvatar}>
76+
{firstLetter(template.name)}
77+
</Avatar>
78+
<Link component={RouterLink} to={`/templates/${template.id}`} className={styles.templateLink}>
79+
<b>{template.name}</b>
80+
<span>{template.description}</span>
81+
</Link>
82+
</div>
83+
</TableCell>
84+
<TableCell>
85+
{template.workspace_owner_count} developer{template.workspace_owner_count !== 1 && "s"}
86+
</TableCell>
87+
<TableCell>{dayjs().to(dayjs(template.updated_at))}</TableCell>
88+
</TableRow>
89+
)
90+
})}
91+
</TableBody>
92+
</Table>
93+
</Margins>
94+
</Stack>
95+
)
96+
}
97+
98+
const useStyles = makeStyles((theme) => ({
99+
actions: {
100+
marginTop: theme.spacing(3),
101+
marginBottom: theme.spacing(3),
102+
display: "flex",
103+
height: theme.spacing(6),
104+
105+
"& button": {
106+
marginLeft: "auto",
107+
},
108+
},
109+
welcome: {
110+
paddingTop: theme.spacing(12),
111+
paddingBottom: theme.spacing(12),
112+
display: "flex",
113+
flexDirection: "column",
114+
alignItems: "center",
115+
justifyContent: "center",
116+
"& span": {
117+
maxWidth: 600,
118+
textAlign: "center",
119+
fontSize: theme.spacing(2),
120+
lineHeight: `${theme.spacing(3)}px`,
121+
},
122+
},
123+
templateRow: {
124+
"& > td": {
125+
paddingTop: theme.spacing(2),
126+
paddingBottom: theme.spacing(2),
127+
},
128+
},
129+
templateName: {
130+
display: "flex",
131+
alignItems: "center",
132+
},
133+
templateLink: {
134+
display: "flex",
135+
flexDirection: "column",
136+
color: theme.palette.text.primary,
137+
textDecoration: "none",
138+
"&:hover": {
139+
textDecoration: "underline",
140+
},
141+
"& span": {
142+
fontSize: 12,
143+
color: theme.palette.text.secondary,
144+
},
145+
},
146+
}))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { screen } from "@testing-library/react"
2+
import { rest } from "msw"
3+
import React from "react"
4+
import { MockTemplate } from "../../testHelpers/entities"
5+
import { history, render } from "../../testHelpers/renderHelpers"
6+
import { server } from "../../testHelpers/server"
7+
import TemplatesPage from "./TemplatesPage"
8+
import { Language } from "./TemplatesPageView"
9+
10+
describe("TemplatesPage", () => {
11+
beforeEach(() => {
12+
history.replace("/workspaces")
13+
})
14+
15+
it("renders an empty templates page", async () => {
16+
// Given
17+
server.use(
18+
rest.get("/api/v2/organizations/:organizationId/templates", (req, res, ctx) => {
19+
return res(ctx.status(200), ctx.json([]))
20+
}),
21+
rest.post("/api/v2/users/:userId/authorization", async (req, res, ctx) => {
22+
return res(
23+
ctx.status(200),
24+
ctx.json({
25+
createTemplates: true,
26+
}),
27+
)
28+
}),
29+
)
30+
31+
// When
32+
render(<TemplatesPage />)
33+
34+
// Then
35+
await screen.findByText(Language.emptyViewCreate)
36+
})
37+
38+
it("renders a filled templates page", async () => {
39+
// When
40+
render(<TemplatesPage />)
41+
42+
// Then
43+
await screen.findByText(MockTemplate.name)
44+
})
45+
46+
it("shows empty view without permissions to create", async () => {
47+
server.use(
48+
rest.get("/api/v2/organizations/:organizationId/templates", (req, res, ctx) => {
49+
return res(ctx.status(200), ctx.json([]))
50+
}),
51+
rest.post("/api/v2/users/:userId/authorization", async (req, res, ctx) => {
52+
return res(
53+
ctx.status(200),
54+
ctx.json({
55+
createTemplates: false,
56+
}),
57+
)
58+
}),
59+
)
60+
61+
// When
62+
render(<TemplatesPage />)
63+
64+
// Then
65+
await screen.findByText(Language.emptyViewNoPerms)
66+
})
67+
})

site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx

+10-10
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ export const AllStates = Template.bind({})
1414
AllStates.args = {
1515
canCreateTemplate: true,
1616
templates: [
17-
MockTemplate,
18-
{
19-
...MockTemplate,
20-
description: "🚀 Some magical template that does some magical things!",
21-
},
22-
{
23-
...MockTemplate,
24-
workspace_owner_count: 150,
25-
description: "😮 Wow, this one has a bunch of usage!"
26-
}
17+
MockTemplate,
18+
{
19+
...MockTemplate,
20+
description: "🚀 Some magical template that does some magical things!",
21+
},
22+
{
23+
...MockTemplate,
24+
workspace_owner_count: 150,
25+
description: "😮 Wow, this one has a bunch of usage!",
26+
},
2727
],
2828
}
2929

site/src/pages/TemplatesPage/TemplatesPageView.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ dayjs.extend(relativeTime)
2121

2222
export const Language = {
2323
createButton: "Create Template",
24-
emptyView: "to standardize development workspaces for your team.",
24+
emptyViewCreate: "to standardize development workspaces for your team.",
25+
emptyViewNoPerms: "No templates have been created! Contact your Coder administrator.",
2526
}
2627

2728
export interface TemplatesPageViewProps {
@@ -57,10 +58,10 @@ export const TemplatesPageView: React.FC<TemplatesPageViewProps> = (props) => {
5758
<Link component={RouterLink} to="/templates/new">
5859
Create a template
5960
</Link>
60-
&nbsp;{Language.emptyView}
61+
&nbsp;{Language.emptyViewCreate}
6162
</span>
6263
) : (
63-
<span>No templates have been created! Contact your Coder administrator.</span>
64+
<span>{Language.emptyViewNoPerms}</span>
6465
)}
6566
</div>
6667
</TableCell>

site/src/testHelpers/handlers.ts

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ export const handlers = [
1717
rest.get("/api/v2/organizations/:organizationId/templates/:templateId", async (req, res, ctx) => {
1818
return res(ctx.status(200), ctx.json(M.MockTemplate))
1919
}),
20+
rest.get("/api/v2/organizations/:organizationId/templates", async (req, res, ctx) => {
21+
return res(ctx.status(200), ctx.json([M.MockTemplate]))
22+
}),
2023

2124
// templates
2225
rest.get("/api/v2/templates/:templateId", async (req, res, ctx) => {

site/static/terraform-logo.svg

-1
This file was deleted.

site/webpack.dev.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const config: Configuration = {
6161
port: process.env.PORT || 8080,
6262
proxy: {
6363
"/api": {
64-
target: "https://dev.coder.com",
64+
target: "http://localhost:3000",
6565
ws: true,
6666
secure: false,
6767
},

0 commit comments

Comments
 (0)