Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ If your change spans directories (e.g., API + Web + Tests), read the `AGENTS.md`
- `tests/TechHub.Infrastructure.Tests/` — Infrastructure test conventions
- `tests/TechHub.E2E.Tests/` — E2E test conventions
- `tests/TechHub.TestUtilities/` — Test utilities conventions
- `tests/javascript/` — JavaScript test conventions
- `tests/powershell/` — PowerShell test conventions
- `scripts/` — Script conventions
- `docs/` — Documentation rules
Expand Down
2 changes: 1 addition & 1 deletion .github/prompts/address-pr-reviews.prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ Repeat steps 6a–6e for every unresolved thread before moving on.
Run:

```pwsh
Run -SkipE2E
Run -Clean
```

If there are build or test failures caused by changes made in Step 6, fix them before proceeding.
Expand Down
33 changes: 31 additions & 2 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,26 @@ jobs:
path: TestResults/pester-results.xml
retention-days: 7

test-javascript:
name: JavaScript Tests
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run Vitest
run: npm test

lint:
name: Lint & Format Check
runs-on: ubuntu-latest
Expand Down Expand Up @@ -333,7 +353,7 @@ jobs:
quality-gate:
name: Quality Gate
runs-on: ubuntu-latest
needs: [build, test-unit, test-integration, test-powershell, lint, security, codeql]
needs: [build, test-unit, test-integration, test-powershell, test-javascript, lint, security, codeql]
if: always()

steps:
Expand All @@ -347,6 +367,7 @@ jobs:
UNIT_STATUS="${{ needs.test-unit.result }}"
INTEGRATION_STATUS="${{ needs.test-integration.result }}"
POWERSHELL_STATUS="${{ needs.test-powershell.result }}"
JAVASCRIPT_STATUS="${{ needs.test-javascript.result }}"
LINT_STATUS="${{ needs.lint.result }}"
SECURITY_STATUS="${{ needs.security.result }}"
CODEQL_STATUS="${{ needs.codeql.result }}"
Expand Down Expand Up @@ -382,6 +403,13 @@ jobs:
echo "| 🔵 PowerShell Tests | ❌ Failed |" >> $GITHUB_STEP_SUMMARY
fi

# JavaScript Tests
if [ "$JAVASCRIPT_STATUS" = "success" ]; then
echo "| 🟡 JavaScript Tests | ✅ Passed |" >> $GITHUB_STEP_SUMMARY
else
echo "| 🟡 JavaScript Tests | ❌ Failed |" >> $GITHUB_STEP_SUMMARY
fi

# Lint
if [ "$LINT_STATUS" = "success" ]; then
echo "| 📝 Linting & Formatting | ✅ Passed |" >> $GITHUB_STEP_SUMMARY
Expand Down Expand Up @@ -410,6 +438,7 @@ jobs:
[ "$UNIT_STATUS" = "success" ] && \
[ "$INTEGRATION_STATUS" = "success" ] && \
[ "$POWERSHELL_STATUS" = "success" ] && \
[ "$JAVASCRIPT_STATUS" = "success" ] && \
[ "$LINT_STATUS" = "success" ] && \
[ "$SECURITY_STATUS" = "success" ] && \
[ "$CODEQL_STATUS" = "success" ]; then
Expand All @@ -423,7 +452,7 @@ jobs:
if [ "$BUILD_STATUS" != "success" ]; then
echo "- 🏗️ Build failed - check compilation errors" >> $GITHUB_STEP_SUMMARY
fi
if [ "$UNIT_STATUS" != "success" ] || [ "$INTEGRATION_STATUS" != "success" ] || [ "$POWERSHELL_STATUS" != "success" ]; then
if [ "$UNIT_STATUS" != "success" ] || [ "$INTEGRATION_STATUS" != "success" ] || [ "$POWERSHELL_STATUS" != "success" ] || [ "$JAVASCRIPT_STATUS" != "success" ]; then
echo "- 🧪 Tests failed - see test results in job logs" >> $GITHUB_STEP_SUMMARY
fi
if [ "$LINT_STATUS" != "success" ]; then
Expand Down
34 changes: 32 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,27 @@ jobs:
path: TestResults/pester-results.xml
retention-days: 7

test-javascript:
name: JavaScript Tests
runs-on: ubuntu-latest
if: github.event.action != 'closed'

steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run Vitest
run: npm test

lint:
name: Lint & Format Check
runs-on: ubuntu-latest
Expand Down Expand Up @@ -340,7 +361,7 @@ jobs:
quality-gate:
name: Quality Gate
runs-on: ubuntu-latest
needs: [build, test-unit, test-integration, test-powershell, lint, security, codeql]
needs: [build, test-unit, test-integration, test-powershell, test-javascript, lint, security, codeql]
if: always() && github.event.action != 'closed'

steps:
Expand All @@ -354,6 +375,7 @@ jobs:
UNIT_STATUS="${{ needs.test-unit.result }}"
INTEGRATION_STATUS="${{ needs.test-integration.result }}"
POWERSHELL_STATUS="${{ needs.test-powershell.result }}"
JAVASCRIPT_STATUS="${{ needs.test-javascript.result }}"
LINT_STATUS="${{ needs.lint.result }}"
SECURITY_STATUS="${{ needs.security.result }}"
CODEQL_STATUS="${{ needs.codeql.result }}"
Expand Down Expand Up @@ -389,6 +411,13 @@ jobs:
echo "| 🔵 PowerShell Tests | ❌ Failed |" >> $GITHUB_STEP_SUMMARY
fi

# JavaScript Tests
if [ "$JAVASCRIPT_STATUS" = "success" ]; then
echo "| 🟡 JavaScript Tests | ✅ Passed |" >> $GITHUB_STEP_SUMMARY
else
echo "| 🟡 JavaScript Tests | ❌ Failed |" >> $GITHUB_STEP_SUMMARY
fi

# Lint
if [ "$LINT_STATUS" = "success" ]; then
echo "| 📝 Linting & Formatting | ✅ Passed |" >> $GITHUB_STEP_SUMMARY
Expand Down Expand Up @@ -417,6 +446,7 @@ jobs:
[ "$UNIT_STATUS" = "success" ] && \
[ "$INTEGRATION_STATUS" = "success" ] && \
[ "$POWERSHELL_STATUS" = "success" ] && \
[ "$JAVASCRIPT_STATUS" = "success" ] && \
[ "$LINT_STATUS" = "success" ] && \
[ "$SECURITY_STATUS" = "success" ] && \
[ "$CODEQL_STATUS" = "success" ]; then
Expand All @@ -439,7 +469,7 @@ jobs:
if [ "$BUILD_STATUS" != "success" ]; then
echo "- 🏗️ Build failed - check compilation errors" >> $GITHUB_STEP_SUMMARY
fi
if [ "$UNIT_STATUS" != "success" ] || [ "$INTEGRATION_STATUS" != "success" ] || [ "$POWERSHELL_STATUS" != "success" ]; then
if [ "$UNIT_STATUS" != "success" ] || [ "$INTEGRATION_STATUS" != "success" ] || [ "$POWERSHELL_STATUS" != "success" ] || [ "$JAVASCRIPT_STATUS" != "success" ]; then
echo "- 🧪 Tests failed - see test results in job logs" >> $GITHUB_STEP_SUMMARY
fi
if [ "$LINT_STATUS" != "success" ]; then
Expand Down
41 changes: 39 additions & 2 deletions docs/javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,15 @@ Files in `wwwroot/js/`:

| File | Purpose | Loading | Format |
|------|---------|---------|--------|
| `nav-helpers.js` | Back to top, back to previous buttons | Static (every page) | IIFE |
| `nav-helpers.js` | Back to top, back to previous buttons, keyboard nav detection | Static (every page) | IIFE |
| `sidebar-toggle.js` | Desktop sidebar collapse/expand with cookie persistence | Static (every page) | Script |
| `mobile-nav.js` | Mobile menu scroll lock and Escape key handler | Static (via Blazor JS interop) | Script |
| `hero-banner.js` | Hero banner collapse/expand with cookie persistence | Static (via Blazor JS interop) | IIFE |
| `infinite-scroll.js` | Scroll-based infinite loading trigger with position memory | Dynamic (via Blazor JS interop) | ES Module |
| `toc-scroll-spy.js` | TOC scroll highlighting, history management | Dynamic (pages with TOC) | ES Module |
| `custom-pages.js` | Collapsible sections for SDLC/DX pages | Dynamic (pages with `[data-collapsible]`) | ES Module |
| `custom-pages.js` | Collapsible sections for SDLC/DX pages, feature filters | Dynamic (pages with `[data-collapsible]`) | ES Module |
| `date-range-slider.js` | Client-side slider clamping (prevents handles crossing) | Dynamic (via Blazor JS interop) | ES Module |
| `page-scripts.js` | Orchestrator for CDN loading (Highlight.js, Mermaid) and page init | Static (every page) | ES Module |

Special file in `wwwroot/`:

Expand Down Expand Up @@ -152,3 +157,35 @@ history.replaceState(null, '', newUrl);
- CDN library versions: [src/TechHub.Web/Configuration/CdnLibraries.cs](../src/TechHub.Web/Configuration/CdnLibraries.cs)
- Navigation helpers: [src/TechHub.Web/wwwroot/js/nav-helpers.js](../src/TechHub.Web/wwwroot/js/nav-helpers.js)
- TOC scroll-spy: [src/TechHub.Web/wwwroot/js/toc-scroll-spy.js](../src/TechHub.Web/wwwroot/js/toc-scroll-spy.js)

## Testing

All client-side JavaScript is unit-tested with **Vitest** + **jsdom** in `tests/javascript/`.

### Running JavaScript Tests

```powershell
# Via the standard Run command
Run -TestProject javascript

# Direct npm commands
npm test # Single run (CI mode)
npm run test:watch # Watch mode (development)
```

### Test Coverage

Every file in `wwwroot/js/` has a corresponding `*.test.js` file. Tests verify:

- Exported function behavior and return values
- DOM manipulation (class toggling, element creation)
- Event listener registration and cleanup
- Cookie persistence (value only — jsdom limitation)
- Module lifecycle (init/dispose patterns)
- Page navigation guards (URL change detection)

### CI/CD Integration

JavaScript tests run as a dedicated `test-javascript` job in both CI and CD pipelines. They are part of the quality gate — failures block PR merge and deployment.

See [tests/javascript/AGENTS.md](../tests/javascript/AGENTS.md) for patterns and conventions.
3 changes: 3 additions & 0 deletions docs/repository-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ See [src/AGENTS.md](../src/AGENTS.md) for general .NET development patterns and
- **`powershell/`** - Pester tests for PowerShell scripts
- Tests automation scripts in `scripts/`
- See [tests/powershell/AGENTS.md](../tests/powershell/AGENTS.md)
- **`javascript/`** - Vitest tests for client-side JavaScript
- Tests all JS files in `src/TechHub.Web/wwwroot/js/`
- See [tests/javascript/AGENTS.md](../tests/javascript/AGENTS.md)

### Test Utilities & Test Collections

Expand Down
6 changes: 4 additions & 2 deletions docs/running-and-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ There are many parameters you can give to tweak the behavior. You can combine al
| `Run -TestProject Api -TestName Auth` | Run only "Auth" tests within the "Api" project (combines filters). |
| `Run -TestProject Web.Tests` | Run only the **Web** component tests. |
| `Run -TestProject Api` | Run tests with "Api" in the project name. |
| `Run -TestProject javascript` | Run only the **JavaScript** (Vitest) tests. |
| `Run -TestName Filter` | Run only individual test methods containing "Filter". |
| `Run -Docker` | Run ALL services (API + Web + PostgreSQL) via docker compose containers (production-like). |
| `Run -BuildOnly` | Build only, then exit (no tests, no servers). |
Expand All @@ -54,8 +55,9 @@ Run
**Test execution order**:

1. PowerShell/Pester tests (if any)
2. Unit and integration tests (fast, no servers needed)
3. E2E tests (starts servers automatically)
2. JavaScript/Vitest tests (fast, no build needed)
3. Unit and integration tests (fast, no servers needed)
4. E2E tests (starts servers automatically)

**Performance note**: The `Run` command is optimized to only start/restart servers when actually needed (E2E tests or `-WithoutTests` mode). Running unit/integration tests alone will NOT touch running servers.

Expand Down
2 changes: 2 additions & 0 deletions docs/testing-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Tech Hub uses a **testing diamond** approach that prioritizes integration tests
| **Unit** (narrower) | Edge cases, boundary conditions, complex business logic | High |
| **E2E** (focused) | Critical user journeys, complete workflows | High |
| **Component** | UI component behavior, rendering, interactions | Medium |
| **JavaScript** | Client-side DOM interactions, scroll/navigation logic | Medium |

**Key Principle**: If a code path is NEVER exposed via the API, its test priority is lower. Focus testing effort on what users can actually trigger through the API.

Expand Down Expand Up @@ -147,6 +148,7 @@ Testcontainers spins up a throwaway `postgres:17-alpine` container per test fixt
| **Unit** | xUnit v3 + Stubs | Core, Infrastructure | NEVER | NEVER |
| **E2E** | Playwright .NET | E2E | Real (local or deployed) | Real |
| **Component** | bUnit | Web | Stub/Mock | Stub/Mock |
| **JavaScript** | Vitest + jsdom | javascript/ | N/A | Mock (Blazor interop, CDN) |
| **PowerShell** | Pester | powershell/ | Mock | Real (test files) |

## Test Doubles Terminology
Expand Down
2 changes: 1 addition & 1 deletion infra/modules/web.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ resource web 'Microsoft.App/containerApps@2025-07-01' = {
external: true
allowInsecure: false
targetPort: 8080
transport: 'http'
transport: 'auto'
stickySessions: {
affinity: 'sticky'
}
Expand Down
Loading
Loading