Skip to content

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

  1. Member A: Find Issue #6 ("Average Calculation Form") on the Project Board and move it to "In Progress".
  2. 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".
  3. 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".

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

  1. Sync your repository: ensure you are on main and have the latest changes.
  2. Create a new branch named feature/average-calc from main.
  3. Open app/main.py.
  4. Add from statistics import mean to imports (at the top of the file).
  5. Add the parse_and_average function (before the home function):

    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}"
    
  6. 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()
    
  7. 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
    

  8. Verify locally. Commit: feat: add average calculation. Push.

Member B: Implement Reset Functionality

  1. Create a new branch named feature/reset-button from main.
  2. Open app/main.py. Update home() 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")
    
  3. Verify locally. Commit: feat: add reset button. Push.

Member C: Implement Dark Mode Toggle

  1. Create a new branch named feature/dark-mode from main.
  2. Open app/main.py. Update home() 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")
    
  3. 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.text
    

    Run the test:

    uv run pytest
    

  4. Verify locally (uv run fastapi dev).

  5. Commit changes: feat: add dark mode toggle.
  6. 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.
  1. Member B commits merge and pushes (if using Option A).
  2. Member A approves PR.
  3. 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.
  1. Member C commits merge and pushes (if using Option A).
  2. Member A/B approves PR.
  3. 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

  1. Pull latest main.
  2. Test the combined app.
  3. Member B: Discover "Non-numeric input crash".
  4. Member B: Create Issue & Branch.
  5. Member B: Implement try/except fix in parse_and_average.
  6. Member B: Create PR.
  7. Member C: Review & Merge.

9.6 Conclusion

Proceed to Step 10: Deploy to Render.