Skip to content

Commit 54715c1

Browse files
committed
feat(footer): show last-built timestamp and linked commit SHA
Closes #341
1 parent 8ca4cfb commit 54715c1

2 files changed

Lines changed: 117 additions & 12 deletions

File tree

src/components/footer.jsx

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@ import PropTypes from "prop-types"
33
import React from "react"
44

55
import { getConfigAndConceptSchemes } from "../hooks/configAndConceptSchemes"
6+
import { commitUrl, formatBuildTime, shortSha } from "../buildInfo"
67

78
const Footer = () => {
8-
const { config } = getConfigAndConceptSchemes()
9+
const { config, buildTime } = getConfigAndConceptSchemes()
10+
const { repositoryUrl, gitCommit } = config
11+
12+
const formattedTime = formatBuildTime(buildTime)
13+
const sha = shortSha(gitCommit)
14+
const href = commitUrl(repositoryUrl, gitCommit)
915

1016
const style = css`
1117
background: ${config.colors.skoHubMiddleColor};
@@ -54,17 +60,28 @@ const Footer = () => {
5460
<footer css={style}>
5561
<div className="footerContent">
5662
<ul>
57-
{process.env.GATSBY_RESPOSITORY_URL && (
63+
{repositoryUrl && (
5864
<li>
59-
<a
60-
href={process.env.GATSBY_RESPOSITORY_URL}
61-
target="_blank"
62-
rel="noopener noreferrer"
63-
>
65+
<a href={repositoryUrl} target="_blank" rel="noopener noreferrer">
6466
Source
6567
</a>
6668
</li>
6769
)}
70+
{formattedTime && (
71+
<li>
72+
Last built: {formattedTime}
73+
{sha && href && (
74+
<>
75+
{" ("}
76+
<a href={href} target="_blank" rel="noopener noreferrer">
77+
{sha}
78+
</a>
79+
{")"}
80+
</>
81+
)}
82+
{sha && !href && ` (${sha})`}
83+
</li>
84+
)}
6885
{links.map((link, idx) => (
6986
<li key={idx} className={idx === 0 ? "push-right" : ""}>
7087
<a

test/footer.test.jsx

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { beforeEach, describe, expect, it, vi } from "vitest"
1+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
22
import { render, screen } from "@testing-library/react"
33
import React from "react"
44
import Footer from "../src/components/footer"
@@ -7,14 +7,102 @@ import { mockConfig } from "./mocks/mockConfig"
77

88
const useStaticQuery = vi.spyOn(Gatsby, `useStaticQuery`)
99

10+
const withMock = (overrides = {}) => ({
11+
...mockConfig,
12+
site: {
13+
...mockConfig.site,
14+
...(overrides.site || {}),
15+
siteMetadata: {
16+
...mockConfig.site.siteMetadata,
17+
...((overrides.site && overrides.site.siteMetadata) || {}),
18+
},
19+
},
20+
})
21+
1022
describe("Footer", () => {
1123
beforeEach(() => {
12-
useStaticQuery.mockImplementation(() => mockConfig)
24+
delete process.env.GATSBY_RESPOSITORY_URL
25+
})
26+
27+
afterEach(() => {
28+
delete process.env.GATSBY_RESPOSITORY_URL
1329
})
1430

15-
it("renders footer", () => {
16-
process.env.GATSBY_RESPOSITORY_URL = "http://test.com"
17-
render(<Footer></Footer>)
31+
it("renders Source link when repositoryUrl is set", () => {
32+
useStaticQuery.mockImplementation(() =>
33+
withMock({
34+
site: {
35+
siteMetadata: {
36+
repositoryUrl: "https://github.com/skohub-io/skohub-vocabs",
37+
},
38+
},
39+
})
40+
)
41+
render(<Footer />)
1842
expect(screen.getByRole("link", { name: "Source" })).toBeInTheDocument()
1943
})
44+
45+
it("renders timestamp only when no commit info is available", () => {
46+
useStaticQuery.mockImplementation(() =>
47+
withMock({
48+
site: {
49+
buildTime: "2026-04-30T14:30:42.000Z",
50+
siteMetadata: { gitCommit: "", repositoryUrl: "" },
51+
},
52+
})
53+
)
54+
render(<Footer />)
55+
expect(
56+
screen.getByText("Last built: 2026-04-30 14:30 UTC")
57+
).toBeInTheDocument()
58+
expect(
59+
screen.queryByRole("link", { name: /a1b2c3d/ })
60+
).not.toBeInTheDocument()
61+
})
62+
63+
it("renders timestamp + plain short SHA when no repositoryUrl", () => {
64+
useStaticQuery.mockImplementation(() =>
65+
withMock({
66+
site: {
67+
buildTime: "2026-04-30T14:30:42.000Z",
68+
siteMetadata: {
69+
gitCommit: "a1b2c3d4e5f67890abcdef1234567890abcdef12",
70+
repositoryUrl: "",
71+
},
72+
},
73+
})
74+
)
75+
render(<Footer />)
76+
expect(
77+
screen.getByText("Last built: 2026-04-30 14:30 UTC (a1b2c3d)")
78+
).toBeInTheDocument()
79+
})
80+
81+
it("renders timestamp + linked short SHA when both are set", () => {
82+
useStaticQuery.mockImplementation(() =>
83+
withMock({
84+
site: {
85+
buildTime: "2026-04-30T14:30:42.000Z",
86+
siteMetadata: {
87+
gitCommit: "a1b2c3d4e5f67890abcdef1234567890abcdef12",
88+
repositoryUrl: "https://github.com/skohub-io/skohub-vocabs",
89+
},
90+
},
91+
})
92+
)
93+
render(<Footer />)
94+
const link = screen.getByRole("link", { name: "a1b2c3d" })
95+
expect(link).toHaveAttribute(
96+
"href",
97+
"https://github.com/skohub-io/skohub-vocabs/commit/a1b2c3d4e5f67890abcdef1234567890abcdef12"
98+
)
99+
})
100+
101+
it("does not render the Last built line when buildTime is missing", () => {
102+
useStaticQuery.mockImplementation(() =>
103+
withMock({ site: { buildTime: null } })
104+
)
105+
render(<Footer />)
106+
expect(screen.queryByText(/Last built:/)).not.toBeInTheDocument()
107+
})
20108
})

0 commit comments

Comments
 (0)