Skip to content

Commit 0e545ef

Browse files
committed
feat(week-05): add Testing, CI/CD & GitHub Actions week (scaffold, in-progress)
Wire week-05 into page.tsx + HeroPage so the hero now reads Week 05 / In Progress. Tasks listed as pending; report HTML scaffolded with goals filled in and the actual work marked todo.
1 parent 1eaba84 commit 0e545ef

4 files changed

Lines changed: 366 additions & 2 deletions

File tree

app/components/HeroPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import week01 from "../../data/weeks/week-01.json"
88
import week02 from "../../data/weeks/week-02.json"
99
import week03 from "../../data/weeks/week-03.json"
1010
import week04 from "../../data/weeks/week-04.json"
11+
import week05 from "../../data/weeks/week-05.json"
1112

1213
// Single source of truth — derived from the week data files so the hero
1314
// can never drift out of sync with the graph again.
1415
const TOTAL = 11
15-
const known = [week01.status, week02.status, week03.status, week04.status]
16+
const known = [week01.status, week02.status, week03.status, week04.status, week05.status]
1617
const weeks = Array.from({ length: TOTAL }, (_, i) => known[i] ?? "pending")
1718

1819
const doneCount = weeks.filter((s) => s === "done").length

app/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import week01 from "../data/weeks/week-01.json"
77
import week02 from "../data/weeks/week-02.json"
88
import week03 from "../data/weeks/week-03.json"
99
import week04 from "../data/weeks/week-04.json"
10+
import week05 from "../data/weeks/week-05.json"
1011

11-
const weeks = [week01, week02, week03, week04]
12+
const weeks = [week01, week02, week03, week04, week05]
1213

1314
export default function Home() {
1415
const [view, setView] = useState<"hero" | "graph">("hero")

data/weeks/week-05.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"id": 5,
3+
"title": "Testing, CI/CD & GitHub Actions",
4+
"status": "in-progress",
5+
"deadline": "2026-06-13",
6+
"report": "/reports/week-05.html",
7+
"tasks": [
8+
{
9+
"id": "add-tests",
10+
"type": "test",
11+
"title": "Add Tests Where There Are None",
12+
"status": "pending",
13+
"reflection": null,
14+
"commands": [],
15+
"output": null,
16+
"screenshot": null,
17+
"pr": null
18+
},
19+
{
20+
"id": "build-workflow",
21+
"type": "ci",
22+
"title": "Build a Real CI Workflow",
23+
"status": "pending",
24+
"reflection": null,
25+
"commands": [],
26+
"output": null,
27+
"screenshot": null,
28+
"pr": null
29+
},
30+
{
31+
"id": "run-act",
32+
"type": "tooling",
33+
"title": "Run It Locally with act",
34+
"status": "pending",
35+
"reflection": null,
36+
"commands": [],
37+
"output": null,
38+
"screenshot": null,
39+
"pr": null
40+
},
41+
{
42+
"id": "deep-work",
43+
"type": "soft-skill",
44+
"title": "Time Management & Deep Work",
45+
"status": "pending",
46+
"reflection": null,
47+
"commands": [],
48+
"output": null,
49+
"screenshot": null,
50+
"pr": null
51+
}
52+
]
53+
}

public/reports/week-05.html

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6+
<title>JOSA OpenLab, Week 05</title>
7+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"/>
8+
<style>
9+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
10+
11+
:root {
12+
--bg: #0d1117;
13+
--border: #21262d;
14+
--accent: #00b4d8;
15+
--red: #ff7b72;
16+
--green: #3fb950;
17+
--amber: #f59e0b;
18+
--text: #e6edf3;
19+
--muted: #6e7681;
20+
--sub: #8b949e;
21+
}
22+
23+
html { scroll-behavior: smooth; }
24+
body { background: var(--bg); color: var(--text); font-family: 'Inter', sans-serif; font-size: 15px; line-height: 1.75; }
25+
code { font-family: 'JetBrains Mono', monospace; font-size: 12.5px; color: var(--accent); }
26+
a { color: var(--accent); text-decoration: none; }
27+
a:hover { text-decoration: underline; }
28+
29+
.hero { padding: 72px 40px 56px; text-align: center; border-bottom: 1px solid var(--border); position: relative; overflow: hidden; }
30+
.hero::after { content: ''; position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 600px; height: 1px; background: linear-gradient(90deg, transparent, var(--accent), transparent); }
31+
.hero-eyebrow { font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: 3px; text-transform: uppercase; color: var(--accent); margin-bottom: 24px; }
32+
.hero h1 { font-size: clamp(30px, 5vw, 52px); font-weight: 700; letter-spacing: -1.5px; line-height: 1.1; margin-bottom: 10px; }
33+
.hero h1 span { color: var(--accent); }
34+
.hero h2 { font-size: 15px; font-weight: 400; color: var(--muted); margin-bottom: 48px; }
35+
36+
.badges { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-bottom: 48px; }
37+
.badge { display: flex; border-radius: 4px; overflow: hidden; font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 500; }
38+
.badge-label { background: #161b22; border: 1px solid var(--border); border-right: none; padding: 5px 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; border-radius: 4px 0 0 4px; }
39+
.badge-value { padding: 5px 10px; font-weight: 600; color: #0d1117; border-radius: 0 4px 4px 0; }
40+
.badge.blue .badge-value { background: var(--accent); }
41+
.badge.navy .badge-value { background: #0077b6; }
42+
.badge.amber .badge-value { background: var(--amber); }
43+
.badge.gray .badge-value { background: var(--muted); color: var(--text); }
44+
45+
.meta-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 0; }
46+
.meta-item { text-align: center; padding: 0 28px; }
47+
.meta-item + .meta-item { border-left: 1px solid var(--border); }
48+
.meta-item .label { font-size: 10px; letter-spacing: 2px; text-transform: uppercase; color: var(--muted); margin-bottom: 4px; }
49+
.meta-item .value { font-size: 14px; font-weight: 500; color: var(--text); }
50+
51+
.container { max-width: 800px; margin: 0 auto; padding: 64px 24px 80px; }
52+
.section { margin-bottom: 64px; }
53+
.section-header { display: flex; align-items: baseline; gap: 14px; margin-bottom: 32px; padding-bottom: 14px; border-bottom: 1px solid var(--border); }
54+
.section-number { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--accent); letter-spacing: 1px; }
55+
.section-header h2 { font-size: 18px; font-weight: 600; letter-spacing: -.3px; color: var(--text); }
56+
.section-header .tag { margin-left: auto; font-family: 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--amber); border: 1px solid var(--amber); border-radius: 4px; padding: 2px 8px; }
57+
58+
.block { border-left: 2px solid var(--border); padding: 4px 0 4px 24px; margin-bottom: 32px; }
59+
.block-title { font-size: 11px; font-weight: 600; letter-spacing: 2px; text-transform: uppercase; color: var(--muted); margin-bottom: 14px; }
60+
61+
.callout { border-left: 2px solid var(--accent); padding: 4px 0 4px 20px; margin-bottom: 32px; font-size: 14px; color: var(--sub); }
62+
.callout strong { color: var(--text); font-weight: 500; }
63+
64+
.code-block { background: #0d1117; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
65+
.code-bar { display: flex; align-items: center; gap: 6px; padding: 9px 14px; border-bottom: 1px solid var(--border); }
66+
.dot { width: 11px; height: 11px; border-radius: 50%; }
67+
.dot.r { background: #ff5f57; } .dot.y { background: #ffbd2e; } .dot.g { background: #28c840; }
68+
.code-lang { margin-left: auto; font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--muted); letter-spacing: 1px; text-transform: uppercase; }
69+
pre { padding: 18px 20px; overflow-x: auto; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.8; color: #c9d1d9; white-space: pre; scrollbar-width: thin; scrollbar-color: #2d333b transparent; }
70+
.cm { color: var(--muted); } .cmd { color: #79c0ff; } .out { color: #c9d1d9; } .hsh { color: #f0883e; } .key { color: #ff7b72; } .str { color: #a5d6ff; }
71+
72+
.reflection { border-left: 2px solid var(--accent); padding: 6px 0 6px 24px; font-size: 15px; line-height: 1.85; color: var(--sub); }
73+
.reflection strong { color: var(--text); font-weight: 500; }
74+
75+
.checklist { display: flex; flex-direction: column; gap: 10px; }
76+
.checklist li { list-style: none; font-size: 13px; color: var(--sub); padding-left: 22px; position: relative; line-height: 1.6; }
77+
.checklist li::before { content: '○'; position: absolute; left: 0; color: var(--muted); }
78+
.checklist li strong { color: var(--text); font-weight: 500; }
79+
80+
.journal-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
81+
@media (max-width: 580px) { .journal-grid { grid-template-columns: 1fr; } }
82+
.journal-block { border-left: 2px solid var(--border); padding: 4px 0 4px 20px; }
83+
.journal-block h3 { font-size: 10px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase; color: var(--muted); margin-bottom: 14px; }
84+
.journal-block ul { list-style: none; display: flex; flex-direction: column; gap: 8px; }
85+
.journal-block ul li { font-size: 13px; color: var(--sub); padding-left: 14px; position: relative; }
86+
.journal-block ul li::before { content: '–'; position: absolute; left: 0; color: var(--muted); }
87+
88+
.placeholder { font-size: 13px; color: var(--muted); font-style: italic; }
89+
.todo { display: inline-block; font-family: 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--amber); border: 1px dashed var(--amber); border-radius: 4px; padding: 1px 7px; margin-right: 8px; }
90+
91+
footer { border-top: 1px solid var(--border); padding: 28px 24px; text-align: center; font-size: 12px; color: var(--muted); letter-spacing: .3px; }
92+
footer strong { color: var(--sub); font-weight: 500; }
93+
</style>
94+
</head>
95+
<body>
96+
97+
<!-- HERO -->
98+
<div class="hero">
99+
<div class="hero-eyebrow">OpenLab Apprenticeship 2026</div>
100+
<h1>JOSA <span>OpenLab</span></h1>
101+
<h2>Week 05: Testing, CI/CD &amp; GitHub Actions</h2>
102+
103+
<div class="badges">
104+
<div class="badge blue">
105+
<span class="badge-label">Week</span>
106+
<span class="badge-value">05 of 11</span>
107+
</div>
108+
<div class="badge navy">
109+
<span class="badge-label">Phase</span>
110+
<span class="badge-value">Foundations</span>
111+
</div>
112+
<div class="badge amber">
113+
<span class="badge-label">Status</span>
114+
<span class="badge-value">In Progress</span>
115+
</div>
116+
</div>
117+
118+
<div class="meta-row">
119+
<div class="meta-item">
120+
<div class="label">Apprentice</div>
121+
<div class="value">Qutibah Ananzeh</div>
122+
</div>
123+
<div class="meta-item">
124+
<div class="label">Cohort</div>
125+
<div class="value">OpenLab 2026</div>
126+
</div>
127+
<div class="meta-item">
128+
<div class="label">Deadline</div>
129+
<div class="value">Jun 13, 2026</div>
130+
</div>
131+
</div>
132+
</div>
133+
134+
<!-- MAIN -->
135+
<div class="container">
136+
137+
<!-- THE STANDARD -->
138+
<div class="section">
139+
<div class="section-header">
140+
<span class="section-number">// 00</span>
141+
<h2>The Standard</h2>
142+
</div>
143+
144+
<div class="callout">
145+
<strong>The unifying principle:</strong> write the <em>cheapest</em> test that meaningfully exercises the behavior. A pure function gets a unit test. A function that talks to a database gets an integration test against a real (test) database, not a mocked one. A user-facing flow gets one happy-path E2E test.
146+
</div>
147+
148+
<div class="block">
149+
<div class="block-title">Test Pyramid &amp; Testing Trophy</div>
150+
<ul class="checklist">
151+
<li><strong>Pyramid (Cohn).</strong> Many fast unit tests at the bottom, fewer integration tests, very few slow E2E tests on top. The geometry is a cost model: a unit test runs in milliseconds; an E2E test can take minutes.</li>
152+
<li><strong>Trophy (Dodds).</strong> For modern frontend, lean on integration tests for the highest confidence per dollar, on a base of static analysis (TypeScript, ESLint).</li>
153+
</ul>
154+
</div>
155+
156+
<div class="block">
157+
<div class="block-title">Anatomy of a Good Unit Test</div>
158+
<ul class="checklist">
159+
<li><strong>Arrange / Act / Assert.</strong> One logical assertion per test, so a failure points at one thing.</li>
160+
<li><strong>Name the behavior, not the function.</strong> <code>test_parser_handles_trailing_comma</code>, not <code>test_parse</code>.</li>
161+
<li><strong>Deterministic.</strong> No unseeded randomness, no real <code>now()</code> (inject a clock), no real network (inject a transport).</li>
162+
<li><strong>Fakes &gt; mocks.</strong> Mock external services you don't own (Stripe, SendGrid); don't mock your own database, run a real one in CI.</li>
163+
</ul>
164+
</div>
165+
166+
<div class="block">
167+
<div class="block-title">Suggested Repositories</div>
168+
<p>Any project with a red CI badge &nbsp;&middot;&nbsp; <a href="https://github.com/sdras/awesome-actions" target="_blank">sdras/awesome-actions</a> &nbsp;&middot;&nbsp; one of my own repos for the workflow task.</p>
169+
</div>
170+
</div>
171+
172+
<!-- TASK 1 -->
173+
<div class="section">
174+
<div class="section-header">
175+
<span class="section-number">// 01</span>
176+
<h2>Add Tests Where There Are None</h2>
177+
<span class="tag">Pending</span>
178+
</div>
179+
180+
<div class="callout">
181+
<strong>Goal:</strong> Scout for untested code in a GitHub repo. Write a test file (or extend an existing one) with well-structured <strong>Arrange / Act / Assert</strong> tests, then submit a PR.
182+
</div>
183+
184+
<div class="block">
185+
<div class="block-title">Plan</div>
186+
<ul class="checklist">
187+
<li><span class="todo">todo</span>Pick a repo with thin coverage and identify a pure-ish function or module that has no tests.</li>
188+
<li><span class="todo">todo</span>Write A/A/A tests covering the happy path plus at least one edge case.</li>
189+
<li><span class="todo">todo</span>Open a PR and link it here.</li>
190+
</ul>
191+
<p class="placeholder" style="margin-top:14px;">Reflection, commands, and PR link to be filled in once the work is done.</p>
192+
</div>
193+
</div>
194+
195+
<!-- TASK 2 -->
196+
<div class="section">
197+
<div class="section-header">
198+
<span class="section-number">// 02</span>
199+
<h2>Build a Real CI Workflow</h2>
200+
<span class="tag">Pending</span>
201+
</div>
202+
203+
<div class="callout">
204+
<strong>Goal:</strong> Add a complete <code>.github/workflows/ci.yml</code> to one of my repos with lint, test, a matrix over 2+ versions, dependency caching, and a status badge in the README.
205+
</div>
206+
207+
<div class="block">
208+
<div class="block-title">Reference Shape</div>
209+
<div class="code-block">
210+
<div class="code-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><span class="code-lang">.github/workflows/ci.yml</span></div>
211+
<pre><span class="key">name</span>: CI
212+
<span class="key">on</span>:
213+
<span class="key">push</span>: { <span class="key">branches</span>: [main] }
214+
<span class="key">pull_request</span>:
215+
<span class="key">jobs</span>:
216+
<span class="key">test</span>:
217+
<span class="key">runs-on</span>: <span class="str">${{ matrix.os }}</span>
218+
<span class="key">strategy</span>:
219+
<span class="key">fail-fast</span>: false
220+
<span class="key">matrix</span>:
221+
<span class="key">os</span>: [ubuntu-latest, macos-latest]
222+
<span class="key">node</span>: [20, 22]
223+
<span class="key">steps</span>:
224+
- <span class="key">uses</span>: actions/checkout@v4
225+
- <span class="key">uses</span>: actions/setup-node@v4
226+
<span class="key">with</span>: { <span class="key">node-version</span>: <span class="str">${{ matrix.node }}</span>, <span class="key">cache</span>: npm }
227+
- <span class="key">run</span>: npm ci
228+
- <span class="key">run</span>: npm run lint
229+
- <span class="key">run</span>: npm test</pre>
230+
</div>
231+
<ul class="checklist" style="margin-top:16px;">
232+
<li><span class="todo">todo</span>Pick the target repo and wire in lint + test scripts.</li>
233+
<li><span class="todo">todo</span>Add the matrix + <code>cache: npm</code>, then add the status badge to the README.</li>
234+
</ul>
235+
<p class="placeholder" style="margin-top:14px;">Final workflow, badge, and reflection to be filled in once it's green on a real repo.</p>
236+
</div>
237+
</div>
238+
239+
<!-- TASK 3 -->
240+
<div class="section">
241+
<div class="section-header">
242+
<span class="section-number">// 03</span>
243+
<h2>Run It Locally with act</h2>
244+
<span class="tag">Pending</span>
245+
</div>
246+
247+
<div class="callout">
248+
<strong>Goal:</strong> Reproduce the workflow locally with <a href="https://github.com/nektos/act" target="_blank">act</a> before pushing, and document any differences from the hosted runners in the journal.
249+
</div>
250+
251+
<div class="block">
252+
<div class="block-title">Plan</div>
253+
<ul class="checklist">
254+
<li><span class="todo">todo</span>Install <code>act</code> and run the <code>test</code> job locally (<code>act -j test</code>).</li>
255+
<li><span class="todo">todo</span>Note where local diverges from hosted (Windows runners unsupported, action quirks, secrets via <code>--secret-file</code>).</li>
256+
</ul>
257+
<p class="placeholder" style="margin-top:14px;">Local run output and the list of differences to be filled in.</p>
258+
</div>
259+
</div>
260+
261+
<!-- SOFT SKILL -->
262+
<div class="section">
263+
<div class="section-header">
264+
<span class="section-number">// 04</span>
265+
<h2>Soft Skill, Time Management &amp; Deep Work</h2>
266+
<span class="tag">Pending</span>
267+
</div>
268+
269+
<div class="callout">
270+
<strong>Discipline:</strong> testing and CI work reward uninterrupted focus. <strong>Time-box</strong> debugging sessions ("90 minutes, then re-evaluate"), and do <strong>one thing at a time</strong>, task-switching costs ~25 minutes to reload context. An OSS-relevant habit: batch code-review responses once or twice a day instead of replying to every comment as it lands.
271+
</div>
272+
273+
<div class="block">
274+
<div class="block-title">Reflection</div>
275+
<p class="placeholder">To be written at the end of the week.</p>
276+
</div>
277+
</div>
278+
279+
<!-- JOURNAL -->
280+
<div class="section">
281+
<div class="section-header">
282+
<span class="section-number">// 05</span>
283+
<h2>Weekly Journal</h2>
284+
</div>
285+
286+
<div class="journal-grid">
287+
<div class="journal-block">
288+
<h3>What I learned</h3>
289+
<ul><li class="placeholder">To be filled in.</li></ul>
290+
</div>
291+
<div class="journal-block">
292+
<h3>What was hardest</h3>
293+
<ul><li class="placeholder">To be filled in.</li></ul>
294+
</div>
295+
<div class="journal-block">
296+
<h3>What's blocking me</h3>
297+
<ul><li class="placeholder">To be filled in.</li></ul>
298+
</div>
299+
</div>
300+
</div>
301+
302+
</div>
303+
304+
<footer>
305+
Jordan Open Source Association &nbsp;&middot;&nbsp; <strong>OpenLab 2026</strong> &nbsp;&middot;&nbsp; Qutibah Ananzeh
306+
</footer>
307+
308+
</body>
309+
</html>

0 commit comments

Comments
 (0)