Team of 3 Guide
In this section, you and your partners will work on three different features simultaneously. This will simulate a real-world scenario where multiple developers contribute to the same codebase, leading to potential merge conflicts.
9.1 Divide Roles
Decide who will be Member A, Member B, and Member C.
- Member A: Will implement the Average Calculation feature.
- Member B: Will implement the Reset Button feature.
- Member C: Will implement the Dark Mode Toggle feature.
9.2 Create Issues
- Member A: Find Issue #6 ("Average Calculation Form") on the Project Board and move it to "In Progress".
- Member B: Create a new issue titled "Add Reset Functionality".
- Description:
Add a button to clear the input field and reset the result label. - Add it to the Project Board and move it to "In Progress".
- Description:
- Member C: Create a new issue titled "Add Dark Mode Toggle".
- Description:
Add a switch to toggle dark mode on and off. - Add it to the Project Board and move it to "In Progress".
- Description:
9.3 Member Inputs (Parallel Work)
All members should execute their tasks simultaneously on their own machines.
CRITICAL STEP: Before starting, make sure your local repository is up to date with the merged PR from Part 1.
git pull origin main
Member A: Implement Average Calculation
- Sync your repository: ensure you are on
mainand have the latest changes. - Create a new branch named
feature/average-calcfrommain. - Open
app/main.py. - Add
from statistics import meanto imports (at the top of the file). -
Add the
parse_and_averagefunction (before thehomefunction):def parse_and_average(raw: str | None) -> str: raw = raw or "" tokens = [t for part in raw.split(",") for t in part.strip().split()] if raw else [] values = [float(t) for t in tokens if t] return f"Average: {mean(values):.4f}" -
Update the
home()function to include the form:@ui.page('/') def home() -> None: ui.label("CSE120 GitHub Workshop").classes("text-2xl font-bold") ui.label("Average Calculator").classes("text-lg text-gray-600 mb-4") numbers_input = ui.textarea(label="Numbers (comma or space separated)").classes("w-full h-32") result_label = ui.label("Enter numbers and click Compute").classes("mt-4 text-lg") def compute() -> None: # Connect the UI to the logic function result_label.text = parse_and_average(numbers_input.value) ui.button("Compute Average", on_click=compute).classes("mt-2") ui.separator() -
Add a Unit Test: To ensure your logic helps catch bugs, add a test case. Create
tests/test_average.py:from app.main import parse_and_average def test_average_simple(): assert parse_and_average("1, 2, 3") == "Average: 2.0000"Run the test:
uv run pytest -
Verify locally. Commit:
feat: add average calculation. Push.
Member B: Implement Reset Functionality
- Create a new branch named
feature/reset-buttonfrommain. -
Open
app/main.py. Updatehome()with a reset button (ignoring A's code for now):@ui.page('/') def home() -> None: ui.label("CSE120 GitHub Workshop").classes("text-2xl font-bold") ui.label("Average Calculator").classes("text-lg text-gray-600 mb-4") numbers_input = ui.textarea(label="Numbers").classes("w-full h-32") result_label = ui.label("Enter numbers and click Compute").classes("mt-4 text-lg") def reset() -> None: numbers_input.value = "" result_label.text = "Enter numbers..." ui.button("Reset", on_click=reset).classes("mt-2 ml-4 bg-red-500 text-white") -
Verify locally. Commit:
feat: add reset button. Push.
Member C: Implement Dark Mode Toggle
- Create a new branch named
feature/dark-modefrommain. -
Open
app/main.py. Updatehome()with a dark mode switch:@ui.page('/') def home() -> None: # Header with Dark Mode Toggle with ui.row().classes('w-full items-center justify-between'): ui.label("CSE120 GitHub Workshop").classes("text-2xl font-bold") ui.switch("Dark Mode", on_change=lambda e: ui.dark_mode().enable() if e.value else ui.dark_mode().disable()) ui.label("Average Calculator").classes("text-lg text-gray-600 mb-4") # Inputs (placeholders) ui.textarea(label="Numbers").classes("w-full h-32") ui.label("Enter numbers...").classes("mt-4 text-lg") -
Add a Unit Test: Verify that the Dark Mode toggle is rendered. Create
tests/test_dark_mode.py:from fastapi.testclient import TestClient from app.main import app client = TestClient(app) def test_dark_mode_present(): resp = client.get("/") assert resp.status_code == 200 assert "Dark Mode" in resp.textRun the test:
uv run pytest -
Verify locally (
uv run fastapi dev). - Commit changes:
feat: add dark mode toggle. - Push branch to GitHub.
9.4 Merge and Resolve Conflicts
Step 1: Merge Member A 1. Member A creates PR. 2. Member B reviews/approves. 3. Member A merges PR.
Step 2: Merge Member B (First Conflict)
1. Member B creates PR.
2. GitHub warns of conflict.
3. Member B pulls main into their branch: git pull origin main.
4. Member B resolves conflict. Choose ONE method:
**Option A: Using VS Code (Recommended)**
* Open `app/main.py`.
* Use the "Merge Editor" or inline markers.
* Combine A's logic (Compute) and B's logic (Reset).
* **Verify:** `uv run pytest`.
**Option B: Using GitHub UI**
* Go to the PR page.
* Click "Resolve conflicts".
* Manually edit to keep both features. *Watch your indentation!*
* Commit merge.
- Member B commits merge and pushes (if using Option A).
- Member A approves PR.
- Member B merges PR.
Step 3: Merge Member C (Second Conflict)
1. Member C creates PR.
2. GitHub warns of conflict (with both A and B changes).
3. Member C pulls main locally: git pull origin main.
4. Member C resolves conflict. Choose ONE method:
**Option A: Using VS Code (Recommended)**
* Open `app/main.py`.
* Use the "Merge Editor" or inline markers.
* You need to verify that:
- The `parse_and_average` function exists.
- The `home` function has:
- Dark Mode logic (top row).
- Inputs and Labels.
- Compute and Reset functions.
- Buttons for both.
* **Verify:** `uv run pytest` (Ensure average test still passes!).
**Option B: Using GitHub UI**
* Go to the PR page.
* Click "Resolve conflicts".
* Manually edit to combine all 3 features. *Watch your indentation!*
* Commit merge.
- Member C commits merge and pushes (if using Option A).
- Member A/B approves PR.
- Member C merges PR.
🆘 Stuck? Click here to see the Solution Code
**Final code structure roughly:**def parse_and_average(raw: str | None) -> str:
# Logic...
@ui.page('/')
def home() -> None:
# C's Header
with ui.row().classes('w-full items-center justify-between'):
ui.label("CSE120 GitHub Workshop").classes("text-2xl font-bold")
ui.switch("Dark Mode", on_change=lambda e: ui.dark_mode().enable() if e.value else ui.dark_mode().disable())
ui.label("Average Calculator").classes("text-lg text-gray-600 mb-4")
# A's Inputs
numbers_input = ui.textarea(label="Numbers").classes("w-full h-32")
result_label = ui.label("Enter numbers and click Compute").classes("mt-4 text-lg")
# A + B Functions
def compute() -> None:
result_label.text = parse_and_average(numbers_input.value)
def reset() -> None:
numbers_input.value = ""
result_label.text = "Enter numbers..."
# A + B Buttons
with ui.row():
ui.button("Compute Average", on_click=compute).classes("mt-2")
ui.button("Reset", on_click=reset).classes("mt-2 ml-4 bg-red-500 text-white")
ui.separator()
9.5 Bug Discovery and Fix
- Pull latest
main. - Test the combined app.
- Member B: Discover "Non-numeric input crash".
- Member B: Create Issue & Branch.
- Member B: Implement
try/exceptfix inparse_and_average. - Member B: Create PR.
- Member C: Review & Merge.
9.6 Conclusion
Proceed to Step 10: Deploy to Render.