Add initial slides for TDD workshop presentation with custom styles and navigation
This commit is contained in:
parent
3a4521f52a
commit
5b4df9ebd6
4 changed files with 1043 additions and 227 deletions
|
|
@ -1,10 +1,13 @@
|
||||||
# Workshop Facilitator Guide — Quick Reference
|
# Workshop Facilitator Guide — Quick Reference
|
||||||
|
|
||||||
**Duration:** 2 hours
|
**Duration:** 80 minutes
|
||||||
**Format:** Live demo + hands-on practice with Password Validator or Shopping Cart katas
|
**Format:** Live demo + hands-on practice with Password Validator or Shopping Cart katas using 3-Way Ping-Pong (mob programming)
|
||||||
|
**Team Size:** 3 people (2 developers + you as facilitator/participant)
|
||||||
|
|
||||||
> **Note:** For the complete detailed plan with minute-by-minute timing, scripts, and checklists, see **WORKSHOP_PLAN.md**. This guide focuses on facilitation tips and kata-specific insights.
|
> **Note:** For the complete detailed plan with minute-by-minute timing, scripts, and checklists, see **WORKSHOP_PLAN.md**. This guide focuses on facilitation tips and kata-specific insights.
|
||||||
|
|
||||||
|
> **Facilitator Role:** You'll participate as the 3rd person in the rotation, experiencing TDD alongside the developers. This creates a collaborative learning environment rather than traditional top-down teaching.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Essential Documents
|
## Essential Documents
|
||||||
|
|
@ -37,14 +40,11 @@ Before the workshop, familiarize yourself with:
|
||||||
|
|
||||||
| Time | Activity | See |
|
| Time | Activity | See |
|
||||||
|------|----------|-----|
|
|------|----------|-----|
|
||||||
| 0:00-0:10 | Welcome & Setup Check | WORKSHOP_PLAN.md |
|
| 0:00-0:05 | Welcome & Setup Check | WORKSHOP_PLAN.md |
|
||||||
| 0:10-0:25 | **Live Demo: FizzBuzz TDD** | fizzbuzz/DEMO_SCRIPT.md |
|
| 0:05-0:20 | **Live Demo: FizzBuzz TDD** | fizzbuzz/DEMO_SCRIPT.md |
|
||||||
| 0:25-0:30 | Exercise Introduction | Section below |
|
| 0:20-0:25 | Exercise Intro (3-Way Ping-Pong) | Section below |
|
||||||
| 0:30-1:15 | Hands-on Practice (Part 1) | Circulate & nudge |
|
| 0:25-1:05 | Hands-on Practice (Mob Programming) | Circulate & nudge |
|
||||||
| 1:15-1:30 | Mid-Point Check-in | WORKSHOP_PLAN.md |
|
| 1:05-1:20 | Combined Retrospective | WORKSHOP_PLAN.md |
|
||||||
| 1:30-2:00 | Hands-on Practice (Part 2) | Circulate & nudge |
|
|
||||||
| 2:00-2:10 | Show & Tell | 2-3 volunteers |
|
|
||||||
| 2:10-2:20 | Retrospective | WORKSHOP_PLAN.md |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -77,17 +77,26 @@ Before the workshop, familiarize yourself with:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## What to watch for while circulating
|
## What to watch for while participating
|
||||||
|
|
||||||
|
**Your dual role:** You're both a participant in the rotation AND a gentle guide.
|
||||||
|
|
||||||
**Good signs:**
|
**Good signs:**
|
||||||
- Running `dart test` after every small change
|
- Running `dart test` after every small change
|
||||||
- Seeing RED before touching `lib/`
|
- Seeing RED before touching `lib/`
|
||||||
- Short, focused commits (or at least talking through each step)
|
- Short, focused commits (or at least talking through each step)
|
||||||
|
- Smooth keyboard passing between the 3 roles (Red, Green, Refactor)
|
||||||
|
|
||||||
**Gentle interventions:**
|
**Gentle interventions (when it's not your turn at keyboard):**
|
||||||
- *"Have you run the test yet? What did it say?"* — nudge anyone writing too much code before testing
|
- *"Have you run the test yet? What did it say?"* — nudge anyone writing too much code before testing
|
||||||
- *"Can you make it pass with even less code?"* — push toward minimal Green
|
- *"Can you make it pass with even less code?"* — push toward minimal Green
|
||||||
- *"Now that it's Green, what could you clean up?"* — prompt the Refactor step
|
- *"Now that it's Green, what could you clean up?"* — prompt the Refactor step
|
||||||
|
- *"It's time to pass the keyboard! Let's rotate."* — enforce the rotation
|
||||||
|
|
||||||
|
**When it's your turn:**
|
||||||
|
- Model the behavior you want to see (run tests frequently, minimal code, think aloud)
|
||||||
|
- Ask questions rather than giving answers: *"What's the simplest thing that could work here?"*
|
||||||
|
- Show vulnerability: *"Hmm, I'm not sure. What do you think?"*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -142,7 +151,8 @@ The point is: TDD forces you to decide, and the test documents the decision perm
|
||||||
1. *"What was the first test you wrote? Why that one?"*
|
1. *"What was the first test you wrote? Why that one?"*
|
||||||
2. *"Did your design change as you added more tests? How?"*
|
2. *"Did your design change as you added more tests? How?"*
|
||||||
3. *"Did you ever feel tempted to skip the Red step and just write the code?"*
|
3. *"Did you ever feel tempted to skip the Red step and just write the code?"*
|
||||||
4. *"What would have been different if you'd designed the class first, then written tests?"*
|
4. *"How did the 3-way ping-pong dynamic change how you wrote code?"*
|
||||||
|
5. *"What would have been different if you'd designed the class first, then written tests?"*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
17
README.md
17
README.md
|
|
@ -1,6 +1,6 @@
|
||||||
# TDD Workshop
|
# TDD Workshop
|
||||||
|
|
||||||
**A hands-on, 2-hour workshop for learning Test-Driven Development through deliberate practice.**
|
**A hands-on, 80-minute workshop for learning Test-Driven Development through deliberate practice.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -11,8 +11,8 @@ This workshop teaches the RED-GREEN-REFACTOR cycle through:
|
||||||
2. **Hands-on practice** (Password Validator or Shopping Cart)
|
2. **Hands-on practice** (Password Validator or Shopping Cart)
|
||||||
3. **Group reflection** and retrospective
|
3. **Group reflection** and retrospective
|
||||||
|
|
||||||
**Target audience:** Developers familiar with TDD concepts but lacking hands-on practice
|
**Target audience:** 3-person team (2 developers + facilitator)
|
||||||
**Duration:** 2 hours
|
**Duration:** 80 minutes
|
||||||
**Language:** Dart
|
**Language:** Dart
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -118,12 +118,11 @@ tdd-workshop/
|
||||||
2. **Choose a kata:**
|
2. **Choose a kata:**
|
||||||
- **Password Validator:** Rules-based validation, interesting refactoring
|
- **Password Validator:** Rules-based validation, interesting refactoring
|
||||||
- **Shopping Cart:** Stateful domain object, data structure decisions
|
- **Shopping Cart:** Stateful domain object, data structure decisions
|
||||||
3. **Follow the workflow:**
|
3. **Follow the 3-Way Ping-Pong workflow:**
|
||||||
- Uncomment one test at a time
|
- **Dev A (RED):** Uncomment one test, run it, watch it fail. Pass the keyboard.
|
||||||
- Make it RED (watch it fail)
|
- **Dev B (GREEN):** Write minimal code to pass. Pass the keyboard.
|
||||||
- Make it GREEN (minimal code to pass)
|
- **Dev C (REFACTOR):** Clean up the code. Write the next test (RED). Pass the keyboard.
|
||||||
- Refactor (clean up duplication)
|
- Rotate roles and repeat
|
||||||
- Repeat
|
|
||||||
|
|
||||||
### Kata Instructions
|
### Kata Instructions
|
||||||
|
|
||||||
|
|
|
||||||
308
WORKSHOP_PLAN.md
308
WORKSHOP_PLAN.md
|
|
@ -1,25 +1,27 @@
|
||||||
# TDD Workshop - Complete Plan
|
# TDD Workshop - Complete Plan
|
||||||
|
|
||||||
**Title:** Test-Driven Development Workshop: From Theory to Practice
|
**Title:** Test-Driven Development Workshop: From Theory to Practice
|
||||||
**Duration:** 2 hours (120 minutes)
|
**Duration:** 80 minutes
|
||||||
**Format:** In-person, hands-on
|
**Format:** In-person, hands-on
|
||||||
**Target Audience:** Developers familiar with TDD concepts but lacking hands-on practice
|
**Target Audience:** 3-person team (2 developers + facilitator)
|
||||||
**Language:** Dart
|
**Language:** Dart
|
||||||
**Primary Goal:** Build muscle memory for the RED-GREEN-REFACTOR cycle through deliberate practice
|
**Primary Goal:** Build muscle memory for the RED-GREEN-REFACTOR cycle through deliberate practice using 3-Way Ping-Pong (mob programming)
|
||||||
|
|
||||||
|
> **Facilitator Note:** You'll participate as the 3rd person in the rotation rather than observing from the sidelines. This creates a collaborative learning environment where you model TDD practices while gently guiding the team.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Pre-Workshop Preparation
|
## Pre-Workshop Preparation
|
||||||
|
|
||||||
### 1 Week Before
|
### 1 Week Before
|
||||||
- [ ] Confirm participant count and send calendar invite
|
- [x] Confirm participant count and send calendar invite
|
||||||
- [ ] Share repository link with setup instructions (SETUP_GUIDE.md)
|
- [ ] Share repository link with setup instructions (SETUP_GUIDE.md)
|
||||||
- [ ] Ask participants to complete setup and verify `dart pub get` + `dart test` work
|
- [x] Ask participants to complete setup and verify `dart pub get` + `dart test` work
|
||||||
- [ ] Prepare a backup laptop with environment pre-configured
|
- [x] Prepare a backup laptop with environment pre-configured
|
||||||
- [ ] Review this plan and the FACILITATOR_GUIDE.md
|
- [x] Review this plan and the FACILITATOR_GUIDE.md
|
||||||
|
|
||||||
### 1 Day Before
|
### 1 Day Before
|
||||||
- [ ] Practice FizzBuzz live demo (use DEMO_SCRIPT.md)
|
- [x] Practice FizzBuzz live demo (use DEMO_SCRIPT.md)
|
||||||
- [ ] Review password_validator and shopping_cart solution files
|
- [ ] Review password_validator and shopping_cart solution files
|
||||||
- [ ] Test all katas on your machine (`dart test` in each directory)
|
- [ ] Test all katas on your machine (`dart test` in each directory)
|
||||||
- [ ] Print this workshop plan as backup reference
|
- [ ] Print this workshop plan as backup reference
|
||||||
|
|
@ -59,20 +61,17 @@
|
||||||
|
|
||||||
| Start | Duration | Segment | Format |
|
| Start | Duration | Segment | Format |
|
||||||
|-------|----------|---------|--------|
|
|-------|----------|---------|--------|
|
||||||
| 0:00 | 10 min | Welcome & Setup Check | Facilitator-led |
|
| 0:00 | 5 min | Welcome & Setup Check | Facilitator-led |
|
||||||
| 0:10 | 15 min | Live Demo: FizzBuzz TDD | Live-coding |
|
| 0:05 | 15 min | Live Demo: FizzBuzz TDD | Live-coding |
|
||||||
| 0:25 | 5 min | Exercise Introduction | Facilitator-led |
|
| 0:20 | 5 min | Exercise Introduction (3-Way Ping-Pong) | Facilitator-led |
|
||||||
| 0:30 | 45 min | Hands-on Practice (Part 1) | Solo/pairs coding |
|
| 0:25 | 40 min | Hands-on Practice (Mob Programming) | Team coding |
|
||||||
| 1:15 | 15 min | Mid-Point Check-in | Group discussion |
|
| 1:05 | 15 min | Combined Retrospective | Facilitated discussion |
|
||||||
| 1:30 | 30 min | Hands-on Practice (Part 2) | Solo/pairs coding |
|
|
||||||
| 2:00 | 10 min | Show & Tell | 2-3 volunteers |
|
|
||||||
| 2:10 | 10 min | Retrospective Discussion | Facilitated |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Segment-by-Segment Guide
|
## Segment-by-Segment Guide
|
||||||
|
|
||||||
### Segment 1: Welcome & Setup (0:00-0:10) — 10 minutes
|
### Segment 1: Welcome & Setup (0:00-0:05) — 5 minutes
|
||||||
|
|
||||||
**Objectives:**
|
**Objectives:**
|
||||||
- Ensure everyone can run tests
|
- Ensure everyone can run tests
|
||||||
|
|
@ -118,7 +117,7 @@ Let's see this in action.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Segment 2: Live Demo - FizzBuzz (0:10-0:25) — 15 minutes
|
### Segment 2: Live Demo - FizzBuzz (0:05-0:20) — 15 minutes
|
||||||
|
|
||||||
**Objectives:**
|
**Objectives:**
|
||||||
- Demonstrate RED-GREEN-REFACTOR in real-time
|
- Demonstrate RED-GREEN-REFACTOR in real-time
|
||||||
|
|
@ -144,16 +143,16 @@ Let's see this in action.
|
||||||
- Participants see the rhythm clearly
|
- Participants see the rhythm clearly
|
||||||
- At least one "aha!" moment visible (nodding, note-taking)
|
- At least one "aha!" moment visible (nodding, note-taking)
|
||||||
|
|
||||||
**Time Check:** Should finish by 0:25
|
**Time Check:** Should finish by 0:20
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Segment 3: Exercise Introduction (0:25-0:30) — 5 minutes
|
### Segment 3: Exercise Introduction (0:20-0:25) — 5 minutes
|
||||||
|
|
||||||
**Objectives:**
|
**Objectives:**
|
||||||
- Explain the two kata choices
|
- Explain the two kata choices
|
||||||
- Clarify workflow
|
- Introduce 3-Way Ping-Pong workflow
|
||||||
- Get people started
|
- Get the team started on a single shared screen
|
||||||
|
|
||||||
**Script:**
|
**Script:**
|
||||||
```
|
```
|
||||||
|
|
@ -169,61 +168,67 @@ SHOPPING CART:
|
||||||
- You'll discover when to use Map vs List
|
- You'll discover when to use Map vs List
|
||||||
- Good for practicing: Value Objects, data structure decisions
|
- Good for practicing: Value Objects, data structure decisions
|
||||||
|
|
||||||
Both are good. Pick what sounds interesting.
|
Pick what sounds interesting to your team.
|
||||||
|
|
||||||
Workflow:
|
Today we're using "3-Way Ping-Pong" - a mob programming format that maps perfectly to RED-GREEN-REFACTOR:
|
||||||
1. Open the test file
|
|
||||||
2. Uncomment ONE test group
|
|
||||||
3. Run dart test - see it RED
|
|
||||||
4. Write code to make it GREEN
|
|
||||||
5. Refactor if needed
|
|
||||||
6. Next test
|
|
||||||
|
|
||||||
Pairing optional - if you pair, try "ping-pong":
|
1. Developer A (RED): Uncomment ONE test, run it, watch it fail. Pass the keyboard.
|
||||||
- Person A writes test
|
2. Developer B (GREEN): Write minimal code to make it pass. Pass the keyboard.
|
||||||
- Person B makes it pass
|
3. Developer C (REFACTOR): Clean up the code if needed. Then write the next test (uncomment it). Pass the keyboard back to Developer A.
|
||||||
- Switch roles
|
|
||||||
|
|
||||||
Questions? Good. Choose your kata and start with Step 1!
|
Rotate roles with each cycle. Everyone gets to experience all three phases.
|
||||||
|
|
||||||
|
Use one shared screen. The person with the keyboard is the "driver" - everyone else navigates.
|
||||||
|
|
||||||
|
Questions? Good. Choose your kata and let's start with Step 1!
|
||||||
```
|
```
|
||||||
|
|
||||||
**Actions:**
|
**Actions:**
|
||||||
- [ ] Show README files on screen briefly
|
- [ ] Show README files on screen briefly
|
||||||
- [ ] Start 45-minute timer
|
- [ ] Ensure team has chosen one kata
|
||||||
- [ ] Let people move to pair up if desired
|
- [ ] Confirm they understand the rotation: A (Red) → B (Green) → C (Refactor + next Red) → A (Green) → ...
|
||||||
|
- [ ] Start 40-minute timer
|
||||||
|
|
||||||
**What Success Looks Like:**
|
**What Success Looks Like:**
|
||||||
- Everyone has chosen a kata
|
- Team has chosen a kata
|
||||||
- Files are open
|
- Files are open on shared screen
|
||||||
- First test is being uncommented
|
- Roles are assigned (A, B, C)
|
||||||
|
- First developer is ready to uncomment first test
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Segment 4: Hands-on Practice Part 1 (0:30-1:15) — 45 minutes
|
### Segment 4: Hands-on Practice (0:25-1:05) — 40 minutes
|
||||||
|
|
||||||
**Objectives:**
|
**Objectives:**
|
||||||
- Build RED-GREEN-REFACTOR muscle memory
|
- Build RED-GREEN-REFACTOR muscle memory
|
||||||
- Experience how tests drive design
|
- Experience how tests drive design
|
||||||
|
- Practice mob programming rotation
|
||||||
- Encounter and resolve "stuck" moments
|
- Encounter and resolve "stuck" moments
|
||||||
|
|
||||||
**Your Role:** Circulate, observe, nudge with questions (don't give solutions)
|
**Your Role:** Participate as the 3rd person in the rotation. When it's your turn, model good TDD practices. When it's not your turn, gently guide with questions.
|
||||||
|
|
||||||
**Good Signs:**
|
**Good Signs:**
|
||||||
- Tests running frequently
|
- Tests running frequently
|
||||||
- Seeing RED before touching lib/
|
- Seeing RED before touching lib/
|
||||||
- Small commits or at least incremental changes
|
- Smooth keyboard passing between roles
|
||||||
- Discussing design (if pairing)
|
- Team discussing design together
|
||||||
|
- Each person respecting their role (Red only writes test, Green only makes it pass, Refactor cleans up)
|
||||||
|
|
||||||
**Interventions (Use these phrases):**
|
**When It's Your Turn at the Keyboard:**
|
||||||
|
- Model the behavior: run tests frequently, write minimal code, think aloud
|
||||||
|
- Show vulnerability: "Hmm, what's the simplest thing here?"
|
||||||
|
- Narrate your thinking: "I'm going to run the test first to see it fail..."
|
||||||
|
|
||||||
|
**When Others Have the Keyboard (Gentle Guidance):**
|
||||||
|
|
||||||
| Situation | What to Say |
|
| Situation | What to Say |
|
||||||
|-----------|-------------|
|
|-----------|-------------|
|
||||||
| Writing code without test | "Have you run the test yet? What did it say?" |
|
| Writing code without test | "Have you run the test yet? What did it say?" |
|
||||||
| Multiple tests at once | "Let's focus on making just ONE test pass first" |
|
| Developer doing too much in their role | "Remember, you're on GREEN - just make it pass. We'll clean up in REFACTOR." |
|
||||||
| Overthinking | "What's the simplest thing that could work?" |
|
| Not refactoring | "See any duplication? What could we extract?" |
|
||||||
| Not refactoring | "See any duplication? What could you extract?" |
|
|
||||||
| Stuck on data structure | "What makes 'find by name' really easy?" (Shopping Cart) |
|
| Stuck on data structure | "What makes 'find by name' really easy?" (Shopping Cart) |
|
||||||
| Hardcoding too long | "Try another test with different input. What happens?" |
|
| Keyboard not being passed | "It's time to pass the keyboard! Let's rotate." |
|
||||||
|
| Overthinking | "What's the simplest thing that could work?" |
|
||||||
|
|
||||||
**Guiding Questions:**
|
**Guiding Questions:**
|
||||||
- "What's the next simplest test?"
|
- "What's the next simplest test?"
|
||||||
|
|
@ -233,161 +238,49 @@ Questions? Good. Choose your kata and start with Step 1!
|
||||||
- "Why List vs Map here?" (Shopping Cart)
|
- "Why List vs Map here?" (Shopping Cart)
|
||||||
|
|
||||||
**Time Checks:**
|
**Time Checks:**
|
||||||
- **At 15 min (0:45):** Most should be on Step 2-3
|
- **At 15 min (0:40):** Team should be on Step 2-3
|
||||||
- **At 30 min (1:00):** Most should be on Step 3-4
|
- **At 25 min (0:50):** Team should be on Step 3-4
|
||||||
- **At 40 min (1:10):** Some on Step 5-6, some still on 3-4 (both fine)
|
- **At 35 min (1:00):** Give heads up: "5 minutes left - finish your current test"
|
||||||
|
|
||||||
**What Success Looks Like:**
|
**What Success Looks Like:**
|
||||||
- Steady typing and test running
|
- Steady rotation and test running
|
||||||
- Some "aha!" moments (facial expressions, discussions)
|
- Team experiencing "aha!" moments together
|
||||||
- A few stuck moments that resolve with nudging
|
- A few stuck moments that resolve with nudging
|
||||||
- Energy remains positive
|
- Energy remains positive and collaborative
|
||||||
|
- Team completes at least Steps 1-4
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Segment 5: Mid-Point Check-in (1:15-1:30) — 15 minutes
|
### Segment 5: Combined Retrospective (1:05-1:20) — 15 minutes
|
||||||
|
|
||||||
**Objectives:**
|
|
||||||
- Surface common struggles
|
|
||||||
- Share discoveries
|
|
||||||
- Re-energize
|
|
||||||
|
|
||||||
**Script:**
|
|
||||||
```
|
|
||||||
Alright, pause for a moment. Stretch if you need to.
|
|
||||||
|
|
||||||
Quick progress check:
|
|
||||||
- Who's completed Steps 1-2? [Show of hands]
|
|
||||||
- Step 3-4? [Hands]
|
|
||||||
- Step 5 or beyond? [Hands]
|
|
||||||
|
|
||||||
Good mix!
|
|
||||||
|
|
||||||
What surprised you so far?
|
|
||||||
[Let 2-3 people share - listen for:]
|
|
||||||
- How often tests run
|
|
||||||
- Refactor step insights
|
|
||||||
- Data structure discoveries (Map vs List)
|
|
||||||
|
|
||||||
Anyone get stuck? What helped you get unstuck?
|
|
||||||
[Usually: reading test carefully, trying minimal solution]
|
|
||||||
|
|
||||||
Any design insights?
|
|
||||||
[Password folks might mention: rules as functions
|
|
||||||
Shopping Cart folks might mention: Value Objects]
|
|
||||||
|
|
||||||
Okay, 30 more minutes. Options:
|
|
||||||
- Continue your current kata
|
|
||||||
- If finished, try the other one
|
|
||||||
- If deep in refactoring, keep going
|
|
||||||
|
|
||||||
The TDD_REFERENCE_CARD.md is available if you need a reminder on anything.
|
|
||||||
|
|
||||||
Let's go!
|
|
||||||
```
|
|
||||||
|
|
||||||
**Actions:**
|
|
||||||
- [ ] Capture themes on whiteboard
|
|
||||||
- [ ] Validate their experiences ("Yes! That's exactly what TDD teaches")
|
|
||||||
- [ ] Note time remaining
|
|
||||||
|
|
||||||
**What Success Looks Like:**
|
|
||||||
- Participants feel heard and validated
|
|
||||||
- Common struggles are normalized
|
|
||||||
- Energy is refreshed for part 2
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Segment 6: Hands-on Practice Part 2 (1:30-2:00) — 30 minutes
|
|
||||||
|
|
||||||
**Objectives:**
|
|
||||||
- Complete current kata OR start second one
|
|
||||||
- Experience full cycle including refactoring
|
|
||||||
|
|
||||||
**Your Role:**
|
|
||||||
- Continue circulating
|
|
||||||
- Focus attention on people doing Step 6 (Password) or Value Objects (Cart)
|
|
||||||
- Encourage those who finished to try the other kata
|
|
||||||
|
|
||||||
**Deep Dive Conversations:**
|
|
||||||
|
|
||||||
**Password Validator (Step 6):**
|
|
||||||
```
|
|
||||||
Them: "I have 5 if-blocks. Works but feels repetitive."
|
|
||||||
You: "What if each rule was a function? What would that look like?"
|
|
||||||
[Pause - let them think]
|
|
||||||
You: "Each rule is: String → Optional Error, right? Could you make a list?"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Shopping Cart (Value Object):**
|
|
||||||
```
|
|
||||||
Them: "Should I validate price in ShoppingCart.addItem()?"
|
|
||||||
You: "What if someone creates CartItem outside the cart? Who validates?"
|
|
||||||
[Pause - let them think]
|
|
||||||
You: "What if CartItem couldn't be created invalid? Where would validation go?"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Time Checks:**
|
|
||||||
- **At 10 min (1:40):** Give heads up: "20 minutes left"
|
|
||||||
- **At 20 min (1:50):** Give heads up: "10 minutes - finish your current test"
|
|
||||||
- **At 28 min (1:58):** "2 minutes - get to a stopping point"
|
|
||||||
|
|
||||||
**What Success Looks Like:**
|
|
||||||
- Most finish Step 4-5 or beyond
|
|
||||||
- Some discover refactoring patterns
|
|
||||||
- Code is messy but working (that's fine!)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Segment 7: Show & Tell (2:00-2:10) — 10 minutes
|
|
||||||
|
|
||||||
**Objectives:**
|
|
||||||
- See different approaches
|
|
||||||
- Celebrate progress
|
|
||||||
- Learn from peers
|
|
||||||
|
|
||||||
**Script:**
|
|
||||||
```
|
|
||||||
Time's up! Let's see what you built.
|
|
||||||
|
|
||||||
I'd like 2-3 volunteers to share briefly:
|
|
||||||
1. Which tests are passing?
|
|
||||||
2. Show your implementation
|
|
||||||
3. ONE interesting decision you made
|
|
||||||
|
|
||||||
Who wants to go first?
|
|
||||||
```
|
|
||||||
|
|
||||||
**What to Highlight:**
|
|
||||||
- Test organization and naming
|
|
||||||
- Simple → complex progression
|
|
||||||
- Refactorings (if they reached Step 6)
|
|
||||||
- Different valid approaches (e.g., replace vs accumulate in cart)
|
|
||||||
|
|
||||||
**Feedback Template:**
|
|
||||||
```
|
|
||||||
"I love that you [specific good practice].
|
|
||||||
Look at how tests tell a story: [point to names].
|
|
||||||
This design [refactoring] is the [pattern name] pattern."
|
|
||||||
```
|
|
||||||
|
|
||||||
**What Success Looks Like:**
|
|
||||||
- 2-3 people share
|
|
||||||
- Variety in progress levels shown
|
|
||||||
- Participants see multiple valid approaches
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Segment 8: Retrospective (2:10-2:20) — 10 minutes
|
|
||||||
|
|
||||||
**Objectives:**
|
**Objectives:**
|
||||||
- Solidify learning
|
- Solidify learning
|
||||||
|
- Reflect on the 3-way rotation experience
|
||||||
- Connect to daily work
|
- Connect to daily work
|
||||||
- Inspire continued practice
|
- Inspire continued practice
|
||||||
|
|
||||||
|
**Script:**
|
||||||
|
```
|
||||||
|
Great work! Let's reflect on what we just experienced.
|
||||||
|
|
||||||
|
Since you all worked on the same code together, let's discuss:
|
||||||
|
|
||||||
|
1. Which tests are passing now?
|
||||||
|
2. Show your implementation - what does it look like?
|
||||||
|
3. What interesting decisions did you make as a team?
|
||||||
|
```
|
||||||
|
|
||||||
**Discussion Questions:**
|
**Discussion Questions:**
|
||||||
|
|
||||||
**1. "What was different about writing tests FIRST vs AFTER?"**
|
**1. "How did the 3-way ping-pong rotation help enforce TDD discipline?"**
|
||||||
|
|
||||||
|
Expected answers:
|
||||||
|
- Forced us to stay in role (couldn't skip ahead)
|
||||||
|
- Made the cycle explicit and visible
|
||||||
|
- Kept us from writing too much code at once
|
||||||
|
- Everyone had to understand what was happening
|
||||||
|
|
||||||
|
**2. "What was different about writing tests FIRST vs AFTER?"**
|
||||||
|
|
||||||
Expected answers:
|
Expected answers:
|
||||||
- Tests were easier to write
|
- Tests were easier to write
|
||||||
|
|
@ -395,25 +288,26 @@ Expected answers:
|
||||||
- Caught edge cases earlier
|
- Caught edge cases earlier
|
||||||
- Felt slower at first, faster later
|
- Felt slower at first, faster later
|
||||||
|
|
||||||
**2. "Did you feel tempted to skip the RED step?"**
|
**3. "Did you feel tempted to skip the RED step?"**
|
||||||
|
|
||||||
Expected: "Yes!"
|
Expected: "Yes!"
|
||||||
|
|
||||||
Your response: "That's the discipline. RED proves the test can fail."
|
Your response: "That's the discipline. RED proves the test can fail."
|
||||||
|
|
||||||
**3. "What design insight surprised you?"**
|
**4. "What design insight surprised you?"**
|
||||||
|
|
||||||
Reinforce:
|
Reinforce:
|
||||||
- Password: Rules as data (Open-Closed Principle)
|
- Password: Rules as data (Open-Closed Principle)
|
||||||
- Shopping Cart: Map for lookup, Value Objects for validation
|
- Shopping Cart: Map for lookup, Value Objects for validation
|
||||||
- General: Small steps → emergent design
|
- General: Small steps → emergent design
|
||||||
|
|
||||||
**4. "How would you apply this at work?"**
|
**5. "How would you apply this at work?"**
|
||||||
|
|
||||||
Let them think out loud:
|
Let them think out loud:
|
||||||
- "That pricing calculation module..."
|
- "That pricing calculation module..."
|
||||||
- "Our validation logic could be structured like this..."
|
- "Our validation logic could be structured like this..."
|
||||||
- "Next util function, test first"
|
- "Next util function, test first"
|
||||||
|
- "We could do mob programming for complex features"
|
||||||
|
|
||||||
**Closing Message:**
|
**Closing Message:**
|
||||||
```
|
```
|
||||||
|
|
@ -426,6 +320,9 @@ Three takeaways:
|
||||||
2. Small steps - one test at a time
|
2. Small steps - one test at a time
|
||||||
3. Refactor - don't skip this
|
3. Refactor - don't skip this
|
||||||
|
|
||||||
|
The 3-way rotation you just experienced is a great way to learn TDD as a team.
|
||||||
|
You can use this format for any new feature or complex problem.
|
||||||
|
|
||||||
Want more practice? The tdd-katas repo has 5 complete examples with commit history showing every step:
|
Want more practice? The tdd-katas repo has 5 complete examples with commit history showing every step:
|
||||||
[Show link on screen]
|
[Show link on screen]
|
||||||
|
|
||||||
|
|
@ -433,12 +330,14 @@ Thanks for participating! Questions?
|
||||||
```
|
```
|
||||||
|
|
||||||
**Actions:**
|
**Actions:**
|
||||||
|
- [ ] Capture key insights on whiteboard
|
||||||
- [ ] Share link to tdd-katas repo
|
- [ ] Share link to tdd-katas repo
|
||||||
- [ ] Share TDD_REFERENCE_CARD.md
|
- [ ] Share TDD_REFERENCE_CARD.md
|
||||||
- [ ] Mention optional office hours if offering them
|
- [ ] Mention optional office hours if offering them
|
||||||
|
|
||||||
**What Success Looks Like:**
|
**What Success Looks Like:**
|
||||||
- Clear takeaways articulated
|
- Clear takeaways articulated
|
||||||
|
- Team reflects on both TDD and mob programming benefits
|
||||||
- Participants know where to practice next
|
- Participants know where to practice next
|
||||||
- Positive energy to continue learning
|
- Positive energy to continue learning
|
||||||
|
|
||||||
|
|
@ -450,13 +349,11 @@ Thanks for participating! Questions?
|
||||||
|
|
||||||
| Time | Energy Level | Your Role |
|
| Time | Energy Level | Your Role |
|
||||||
|------|-------------|-----------|
|
|------|-------------|-----------|
|
||||||
| 0-10 min | High | Welcoming, enthusiastic |
|
| 0-5 min | High | Welcoming, enthusiastic |
|
||||||
| 10-25 min | Focused | Demonstrating, teaching |
|
| 5-20 min | Focused | Demonstrating, teaching |
|
||||||
| 25-30 min | Enthusiastic | Clarifying, motivating |
|
| 20-25 min | Enthusiastic | Clarifying, motivating |
|
||||||
| 30-1:15 | Calm | Supportive, circulating |
|
| 25-1:05 | Collaborative | Participating in rotation, modeling TDD, gentle coaching |
|
||||||
| 1:15-1:30 | Re-energize | Celebrating, validating |
|
| 1:05-1:20 | Reflective | Celebrating, connecting dots, inspiring |
|
||||||
| 1:30-2:00 | Encouraging | Nudging toward completion |
|
|
||||||
| 2:00-2:20 | Reflective | Connecting dots, inspiring |
|
|
||||||
|
|
||||||
### Common Questions and Answers
|
### Common Questions and Answers
|
||||||
|
|
||||||
|
|
@ -499,21 +396,22 @@ At workshop end, participants should:
|
||||||
|
|
||||||
### Running Behind Schedule
|
### Running Behind Schedule
|
||||||
|
|
||||||
- **Skip:** Detailed mid-point discussion (keep it to 5 min)
|
- **Skip:** Detailed retrospective discussion (keep it to 10 min)
|
||||||
- **Skip:** Show & tell (or reduce to 1 person, 3 min)
|
- **Reduce:** Live demo to 12 minutes (skip one cycle)
|
||||||
- **Keep:** Live demo and hands-on time
|
- **Keep:** Hands-on practice time (this is the core learning)
|
||||||
|
|
||||||
### Running Ahead of Schedule
|
### Running Ahead of Schedule
|
||||||
|
|
||||||
- **Add:** 6th test to FizzBuzz demo (input 6 → "Fizz")
|
- **Add:** 6th test to FizzBuzz demo (input 6 → "Fizz")
|
||||||
- **Add:** Bonus questions in retrospective
|
- **Add:** Bonus questions in retrospective
|
||||||
- **Add:** Quick discussion of when NOT to use TDD
|
- **Add:** Quick discussion of when NOT to use TDD
|
||||||
|
- **Extend:** Hands-on practice time
|
||||||
|
|
||||||
### Technical Difficulties
|
### Technical Difficulties
|
||||||
|
|
||||||
- **Network down:** Use USB backup of repo
|
- **Network down:** Use USB backup of repo
|
||||||
- **Projector fails:** Do demo at participant's screen, others gather
|
- **Projector fails:** Do demo at team's screen, gather around
|
||||||
- **Participant computer issues:** Pair them with someone or use backup laptop
|
- **Computer issues:** Use backup laptop or share one machine
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
909
slides.html
Normal file
909
slides.html
Normal file
|
|
@ -0,0 +1,909 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>TDD Workshop — 80 Minutes</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700;800&family=Archivo+Black&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--red: #FF3B30;
|
||||||
|
--green: #34C759;
|
||||||
|
--refactor: #007AFF;
|
||||||
|
--bg-dark: #0A0A0A;
|
||||||
|
--bg-light: #F5F5F0;
|
||||||
|
--text-dark: #1A1A1A;
|
||||||
|
--text-light: #FAFAFA;
|
||||||
|
--accent: #FFD60A;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
background: var(--bg-dark);
|
||||||
|
color: var(--text-light);
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Cursor */
|
||||||
|
#cursor {
|
||||||
|
position: fixed;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid var(--accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 10000;
|
||||||
|
transition: transform 0.15s ease, border-color 0.2s;
|
||||||
|
mix-blend-mode: difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cursor.click {
|
||||||
|
transform: scale(0.7);
|
||||||
|
border-color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slide Container */
|
||||||
|
.slides {
|
||||||
|
position: relative;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4rem;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide.active {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide.prev {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slide Backgrounds */
|
||||||
|
.slide-title { background: linear-gradient(135deg, var(--bg-dark) 0%, #1a1a2e 100%); }
|
||||||
|
.slide-red { background: linear-gradient(135deg, #2D0A0A 0%, var(--bg-dark) 100%); }
|
||||||
|
.slide-green { background: linear-gradient(135deg, #0A2D0F 0%, var(--bg-dark) 100%); }
|
||||||
|
.slide-refactor { background: linear-gradient(135deg, #0A1A2D 0%, var(--bg-dark) 100%); }
|
||||||
|
.slide-neutral { background: var(--bg-dark); }
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
h1 {
|
||||||
|
font-family: 'Archivo Black', sans-serif;
|
||||||
|
font-size: clamp(3rem, 8vw, 7rem);
|
||||||
|
line-height: 0.95;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 .highlight {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.1em 0.3em;
|
||||||
|
background: var(--accent);
|
||||||
|
color: var(--bg-dark);
|
||||||
|
transform: skewX(-5deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: clamp(2rem, 5vw, 4rem);
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding: 0.5em 0.2em;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: clamp(1.5rem, 3vw, 2.5rem);
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
p, li {
|
||||||
|
font-size: clamp(1rem, 2vw, 1.5rem);
|
||||||
|
line-height: 1.6;
|
||||||
|
opacity: 0.85;
|
||||||
|
max-width: 50ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: clamp(1.2rem, 2.5vw, 2rem);
|
||||||
|
opacity: 0.7;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cycle Visualization */
|
||||||
|
.cycle {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
margin: 3rem 0;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cycle-step {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
animation: fadeInUp 0.6s ease forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cycle-step:nth-child(1) { animation-delay: 0.2s; }
|
||||||
|
.cycle-step:nth-child(2) { animation-delay: 0.4s; }
|
||||||
|
.cycle-step:nth-child(3) { animation-delay: 0.6s; }
|
||||||
|
.cycle-step:nth-child(4) { animation-delay: 0.8s; }
|
||||||
|
|
||||||
|
.cycle-icon {
|
||||||
|
width: 140px;
|
||||||
|
height: 140px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 3.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
border: 4px solid;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cycle-icon::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
transform: skewX(-20deg);
|
||||||
|
animation: shine 3s infinite;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-step .cycle-icon {
|
||||||
|
background: var(--red);
|
||||||
|
border-color: var(--red);
|
||||||
|
color: white;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green-step .cycle-icon {
|
||||||
|
background: var(--green);
|
||||||
|
border-color: var(--green);
|
||||||
|
color: white;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refactor-step .cycle-icon {
|
||||||
|
background: var(--refactor);
|
||||||
|
border-color: var(--refactor);
|
||||||
|
color: white;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repeat-step .cycle-icon {
|
||||||
|
background: transparent;
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cycle-label {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cycle-desc {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 20ch;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
font-size: 2rem;
|
||||||
|
opacity: 0.4;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Timeline */
|
||||||
|
.timeline {
|
||||||
|
display: grid;
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin: 2rem 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 900px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 100px 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-left: 4px solid var(--accent);
|
||||||
|
animation: slideInLeft 0.5s ease forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item:nth-child(1) { animation-delay: 0.1s; }
|
||||||
|
.timeline-item:nth-child(2) { animation-delay: 0.2s; }
|
||||||
|
.timeline-item:nth-child(3) { animation-delay: 0.3s; }
|
||||||
|
.timeline-item:nth-child(4) { animation-delay: 0.4s; }
|
||||||
|
.timeline-item:nth-child(5) { animation-delay: 0.5s; }
|
||||||
|
|
||||||
|
.timeline-time {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-content h3 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-content p {
|
||||||
|
font-size: 1rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ping Pong Diagram */
|
||||||
|
.ping-pong {
|
||||||
|
display: flex;
|
||||||
|
gap: 3rem;
|
||||||
|
margin: 3rem 0;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dev-role {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
animation: fadeInUp 0.6s ease forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dev-role:nth-child(1) { animation-delay: 0.2s; }
|
||||||
|
.dev-role:nth-child(2) { animation-delay: 0.5s; }
|
||||||
|
.dev-role:nth-child(3) { animation-delay: 0.8s; }
|
||||||
|
|
||||||
|
.dev-avatar {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
border: 4px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dev-role:nth-child(1) .dev-avatar {
|
||||||
|
background: var(--red);
|
||||||
|
border-color: var(--red);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dev-role:nth-child(2) .dev-avatar {
|
||||||
|
background: var(--green);
|
||||||
|
border-color: var(--green);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dev-role:nth-child(3) .dev-avatar {
|
||||||
|
background: var(--refactor);
|
||||||
|
border-color: var(--refactor);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dev-name {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dev-task {
|
||||||
|
font-size: 1rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 20ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-arrow {
|
||||||
|
font-size: 3rem;
|
||||||
|
opacity: 0.5;
|
||||||
|
animation: slideRight 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lists */
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding-left: 2rem;
|
||||||
|
position: relative;
|
||||||
|
animation: fadeInUp 0.5s ease forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:nth-child(1) { animation-delay: 0.1s; }
|
||||||
|
li:nth-child(2) { animation-delay: 0.2s; }
|
||||||
|
li:nth-child(3) { animation-delay: 0.3s; }
|
||||||
|
li:nth-child(4) { animation-delay: 0.4s; }
|
||||||
|
li:nth-child(5) { animation-delay: 0.5s; }
|
||||||
|
|
||||||
|
li::before {
|
||||||
|
content: '▸';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress Bar */
|
||||||
|
.progress {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--accent);
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slide Counter */
|
||||||
|
.counter {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
right: 2rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
opacity: 0.5;
|
||||||
|
z-index: 1000;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation Hint */
|
||||||
|
.nav-hint {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
left: 2rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
opacity: 0.4;
|
||||||
|
z-index: 1000;
|
||||||
|
animation: fadeIn 1s ease 2s forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInLeft {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
to { opacity: 0.4; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 0.4; }
|
||||||
|
50% { opacity: 0.8; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideRight {
|
||||||
|
0%, 100% { transform: translateX(0); }
|
||||||
|
50% { transform: translateX(10px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shine {
|
||||||
|
to { left: 100%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code Block */
|
||||||
|
.code-block {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2rem;
|
||||||
|
margin: 2rem 0;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
max-width: 600px;
|
||||||
|
animation: fadeInUp 0.6s ease 0.3s forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-comment {
|
||||||
|
color: #6A9955;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-keyword {
|
||||||
|
color: #569CD6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-string {
|
||||||
|
color: #CE9178;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Big Number */
|
||||||
|
.big-number {
|
||||||
|
font-size: clamp(8rem, 20vw, 15rem);
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--accent);
|
||||||
|
opacity: 0.2;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-over {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
max-width: 90%;
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Phase indicator circles */
|
||||||
|
.phase-indicator {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 0.3em;
|
||||||
|
vertical-align: middle;
|
||||||
|
position: relative;
|
||||||
|
top: -0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-red {
|
||||||
|
background: var(--red);
|
||||||
|
box-shadow: 0 0 20px rgba(255, 59, 48, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-green {
|
||||||
|
background: var(--green);
|
||||||
|
box-shadow: 0 0 20px rgba(52, 199, 89, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-blue {
|
||||||
|
background: var(--refactor);
|
||||||
|
box-shadow: 0 0 20px rgba(0, 122, 255, 0.5);
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cycle {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ping-pong {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow, .flow-arrow {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="cursor"></div>
|
||||||
|
|
||||||
|
<div class="slides">
|
||||||
|
<!-- Slide 1: Title -->
|
||||||
|
<div class="slide active slide-title">
|
||||||
|
<div class="content-over">
|
||||||
|
<h1>
|
||||||
|
<span class="highlight">TDD</span><br>
|
||||||
|
WORKSHOP
|
||||||
|
</h1>
|
||||||
|
<p class="subtitle">80 minutes · 3-Way Ping-Pong · Hands-on Practice</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide 2: The Cycle -->
|
||||||
|
<div class="slide slide-neutral">
|
||||||
|
<div class="content-over">
|
||||||
|
<h2>The TDD Rhythm</h2>
|
||||||
|
<div class="cycle">
|
||||||
|
<div class="cycle-step red-step">
|
||||||
|
<div class="cycle-icon">1</div>
|
||||||
|
<div class="cycle-label">Red</div>
|
||||||
|
<div class="cycle-desc">Write failing test</div>
|
||||||
|
</div>
|
||||||
|
<div class="arrow">→</div>
|
||||||
|
<div class="cycle-step green-step">
|
||||||
|
<div class="cycle-icon">2</div>
|
||||||
|
<div class="cycle-label">Green</div>
|
||||||
|
<div class="cycle-desc">Make it pass</div>
|
||||||
|
</div>
|
||||||
|
<div class="arrow">→</div>
|
||||||
|
<div class="cycle-step refactor-step">
|
||||||
|
<div class="cycle-icon">3</div>
|
||||||
|
<div class="cycle-label">Refactor</div>
|
||||||
|
<div class="cycle-desc">Clean up code</div>
|
||||||
|
</div>
|
||||||
|
<div class="arrow">→</div>
|
||||||
|
<div class="cycle-step repeat-step">
|
||||||
|
<div class="cycle-icon">↻</div>
|
||||||
|
<div class="cycle-label">Repeat</div>
|
||||||
|
<div class="cycle-desc">Next test</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide 3: Schedule -->
|
||||||
|
<div class="slide slide-neutral">
|
||||||
|
<div class="big-number">80</div>
|
||||||
|
<div class="content-over">
|
||||||
|
<h2>Today's Schedule</h2>
|
||||||
|
<div class="timeline">
|
||||||
|
<div class="timeline-item">
|
||||||
|
<div class="timeline-time">0:00</div>
|
||||||
|
<div class="timeline-content">
|
||||||
|
<h3>Welcome & Setup</h3>
|
||||||
|
<p>5 minutes · Verify environment</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-item">
|
||||||
|
<div class="timeline-time">0:05</div>
|
||||||
|
<div class="timeline-content">
|
||||||
|
<h3>Live Demo: FizzBuzz</h3>
|
||||||
|
<p>15 minutes · Watch TDD in action</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-item">
|
||||||
|
<div class="timeline-time">0:20</div>
|
||||||
|
<div class="timeline-content">
|
||||||
|
<h3>Exercise Intro</h3>
|
||||||
|
<p>5 minutes · 3-Way Ping-Pong explained</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-item">
|
||||||
|
<div class="timeline-time">0:25</div>
|
||||||
|
<div class="timeline-content">
|
||||||
|
<h3>Hands-on Practice</h3>
|
||||||
|
<p>40 minutes · Mob programming</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-item">
|
||||||
|
<div class="timeline-time">1:05</div>
|
||||||
|
<div class="timeline-content">
|
||||||
|
<h3>Retrospective</h3>
|
||||||
|
<p>15 minutes · Reflect & discuss</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide 4: 3-Way Ping-Pong -->
|
||||||
|
<div class="slide slide-neutral">
|
||||||
|
<div class="content-over">
|
||||||
|
<h2>3-Way Ping-Pong</h2>
|
||||||
|
<div class="ping-pong">
|
||||||
|
<div class="dev-role">
|
||||||
|
<div class="dev-avatar">A</div>
|
||||||
|
<div class="dev-name">Red</div>
|
||||||
|
<div class="dev-task">Uncomment test<br>Watch it fail</div>
|
||||||
|
</div>
|
||||||
|
<div class="flow-arrow">→</div>
|
||||||
|
<div class="dev-role">
|
||||||
|
<div class="dev-avatar">B</div>
|
||||||
|
<div class="dev-name">Green</div>
|
||||||
|
<div class="dev-task">Write minimal code<br>Make it pass</div>
|
||||||
|
</div>
|
||||||
|
<div class="flow-arrow">→</div>
|
||||||
|
<div class="dev-role">
|
||||||
|
<div class="dev-avatar">C</div>
|
||||||
|
<div class="dev-name">Refactor</div>
|
||||||
|
<div class="dev-task">Clean up<br>Next test</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p style="margin-top: 3rem; text-align: center; opacity: 0.7;">
|
||||||
|
Rotate roles with each cycle · One shared screen
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide 5: RED Phase -->
|
||||||
|
<div class="slide slide-red">
|
||||||
|
<div class="content-over">
|
||||||
|
<h2 style="color: var(--red);"><span class="phase-indicator phase-red"></span>RED Phase</h2>
|
||||||
|
<h3>Write a failing test</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Uncomment ONE test</li>
|
||||||
|
<li>Run <code>dart test</code></li>
|
||||||
|
<li>Watch it fail (proves it can fail)</li>
|
||||||
|
<li>Pass the keyboard</li>
|
||||||
|
</ul>
|
||||||
|
<div class="code-block">
|
||||||
|
<span class="code-comment">// Uncomment this test</span><br>
|
||||||
|
<span class="code-keyword">test</span>(<span class="code-string">'returns 1 for input 1'</span>, () {<br>
|
||||||
|
<span class="code-keyword">expect</span>(fizzbuzz(<span class="code-string">1</span>), <span class="code-string">'1'</span>);<br>
|
||||||
|
});
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide 6: GREEN Phase -->
|
||||||
|
<div class="slide slide-green">
|
||||||
|
<div class="content-over">
|
||||||
|
<h2 style="color: var(--green);"><span class="phase-indicator phase-green"></span>GREEN Phase</h2>
|
||||||
|
<h3>Make it pass (minimal code)</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Write the simplest code that works</li>
|
||||||
|
<li>Don't worry about perfection</li>
|
||||||
|
<li>Run tests to confirm GREEN</li>
|
||||||
|
<li>Pass the keyboard</li>
|
||||||
|
</ul>
|
||||||
|
<div class="code-block">
|
||||||
|
<span class="code-keyword">String</span> fizzbuzz(<span class="code-keyword">int</span> n) {<br>
|
||||||
|
<span class="code-keyword">return</span> <span class="code-string">'1'</span>; <span class="code-comment">// Hardcode is OK!</span><br>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide 7: REFACTOR Phase -->
|
||||||
|
<div class="slide slide-refactor">
|
||||||
|
<div class="content-over">
|
||||||
|
<h2 style="color: var(--refactor);"><span class="phase-indicator phase-blue"></span>REFACTOR Phase</h2>
|
||||||
|
<h3>Clean up the code</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Remove duplication</li>
|
||||||
|
<li>Improve names</li>
|
||||||
|
<li>Tests stay GREEN</li>
|
||||||
|
<li>Uncomment next test (back to RED)</li>
|
||||||
|
<li>Pass the keyboard</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide 8: Kata Choices -->
|
||||||
|
<div class="slide slide-neutral">
|
||||||
|
<div class="content-over">
|
||||||
|
<h2>Choose Your Kata</h2>
|
||||||
|
<div style="display: grid; gap: 2rem; margin-top: 2rem;">
|
||||||
|
<div style="padding: 2rem; background: rgba(255, 255, 255, 0.05); border-left: 4px solid var(--accent);">
|
||||||
|
<h3>Password Validator</h3>
|
||||||
|
<p>Rules-based validation · Refactoring challenge · Open-Closed Principle</p>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 2rem; background: rgba(255, 255, 255, 0.05); border-left: 4px solid var(--green);">
|
||||||
|
<h3>Shopping Cart</h3>
|
||||||
|
<p>Stateful domain object · Data structures · Value Objects</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide 9: Key Principles -->
|
||||||
|
<div class="slide slide-neutral">
|
||||||
|
<div class="content-over">
|
||||||
|
<h2>TDD Principles</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Tests first</strong> — Even for "obvious" code</li>
|
||||||
|
<li><strong>Small steps</strong> — One test at a time</li>
|
||||||
|
<li><strong>See RED</strong> — Proves the test can fail</li>
|
||||||
|
<li><strong>Minimal GREEN</strong> — Hardcoding is OK initially</li>
|
||||||
|
<li><strong>Refactor fearlessly</strong> — Tests protect you</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide 10: Questions -->
|
||||||
|
<div class="slide slide-title">
|
||||||
|
<div class="content-over">
|
||||||
|
<h1>
|
||||||
|
Ready to<br>
|
||||||
|
<span class="highlight">Practice?</span>
|
||||||
|
</h1>
|
||||||
|
<p class="subtitle">Let's build muscle memory for TDD</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress Bar -->
|
||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide Counter -->
|
||||||
|
<div class="counter">
|
||||||
|
<span id="current">1</span> / <span id="total">10</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Hint -->
|
||||||
|
<div class="nav-hint">
|
||||||
|
← → arrows or click to navigate
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Custom cursor
|
||||||
|
const cursor = document.getElementById('cursor');
|
||||||
|
document.addEventListener('mousemove', (e) => {
|
||||||
|
cursor.style.left = e.clientX + 'px';
|
||||||
|
cursor.style.top = e.clientY + 'px';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', () => {
|
||||||
|
cursor.classList.add('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', () => {
|
||||||
|
cursor.classList.remove('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Slide navigation
|
||||||
|
const slides = document.querySelectorAll('.slide');
|
||||||
|
const progressBar = document.querySelector('.progress-bar');
|
||||||
|
const currentCounter = document.getElementById('current');
|
||||||
|
const totalCounter = document.getElementById('total');
|
||||||
|
|
||||||
|
let currentSlide = 0;
|
||||||
|
const totalSlides = slides.length;
|
||||||
|
|
||||||
|
totalCounter.textContent = totalSlides;
|
||||||
|
|
||||||
|
function updateSlide() {
|
||||||
|
slides.forEach((slide, index) => {
|
||||||
|
slide.classList.remove('active', 'prev');
|
||||||
|
if (index === currentSlide) {
|
||||||
|
slide.classList.add('active');
|
||||||
|
} else if (index < currentSlide) {
|
||||||
|
slide.classList.add('prev');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update progress bar
|
||||||
|
const progress = ((currentSlide + 1) / totalSlides) * 100;
|
||||||
|
progressBar.style.width = progress + '%';
|
||||||
|
|
||||||
|
// Update counter
|
||||||
|
currentCounter.textContent = currentSlide + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextSlide() {
|
||||||
|
if (currentSlide < totalSlides - 1) {
|
||||||
|
currentSlide++;
|
||||||
|
updateSlide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevSlide() {
|
||||||
|
if (currentSlide > 0) {
|
||||||
|
currentSlide--;
|
||||||
|
updateSlide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keyboard navigation
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'ArrowRight' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
nextSlide();
|
||||||
|
} else if (e.key === 'ArrowLeft') {
|
||||||
|
e.preventDefault();
|
||||||
|
prevSlide();
|
||||||
|
} else if (e.key === 'Home') {
|
||||||
|
currentSlide = 0;
|
||||||
|
updateSlide();
|
||||||
|
} else if (e.key === 'End') {
|
||||||
|
currentSlide = totalSlides - 1;
|
||||||
|
updateSlide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click navigation
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
if (e.clientX > window.innerWidth / 2) {
|
||||||
|
nextSlide();
|
||||||
|
} else {
|
||||||
|
prevSlide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Touch navigation
|
||||||
|
let touchStartX = 0;
|
||||||
|
document.addEventListener('touchstart', (e) => {
|
||||||
|
touchStartX = e.touches[0].clientX;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('touchend', (e) => {
|
||||||
|
const touchEndX = e.changedTouches[0].clientX;
|
||||||
|
const diff = touchStartX - touchEndX;
|
||||||
|
|
||||||
|
if (Math.abs(diff) > 50) {
|
||||||
|
if (diff > 0) {
|
||||||
|
nextSlide();
|
||||||
|
} else {
|
||||||
|
prevSlide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
updateSlide();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue