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
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ take up to 60 seconds once the docker build finishes.
### First steps

- Install git commit template: [Commit Template](docs/commit.template.md).
- Volta: [Volta](#volta)
- [Volta](#volta)

### Recommended

- Compodoc: [Compodoc Conventions](docs/compodoc.md).
- Docker Commands: [Docker Commands](docs/docker.md).
- Git Conventions: [Git Conventions](docs/git-convention.md).
- NGXS: [NGXS Conventions](docs/ngxs.md).
- [Compodoc Conventions](docs/compodoc.md).
- [Docker Commands](docs/docker.md).
- [ESLint Strategy](docs/eslint.md).
- [Git Conventions](docs/git-convention.md).
- [NGXS Conventions](docs/ngxs.md).
- [Testing Strategy](docs/testing.md).

### Optional

Expand Down
105 changes: 105 additions & 0 deletions docs/eslint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Linting Strategy – OSF Angular

---

## Overview

This project uses a **unified, modern ESLint flat config** approach to enforce consistent coding styles, accessibility standards, and TypeScript best practices. Linting runs on:

- TypeScript (`*.ts`)
- HTML templates (`*.html`)
- Specs (`*.spec.ts`)

It also integrates into the **Git workflow** via `pre-commit` hooks to ensure clean, compliant code before every commit.

---

## Linting Commands

```bash
# Run full project lint
npm run lint
```

---

## ESLint Config Structure

### 1. TypeScript Files (`**/*.ts`)

**Extends**:

- `@eslint/js` → base ESLint config
- `typescript-eslint` recommended & stylistic configs
- `angular-eslint` TypeScript rules
- `eslint-plugin-prettier/recommended`

**Plugins**:

- `eslint-plugin-import`
- `eslint-plugin-simple-import-sort`
- `eslint-plugin-unused-imports`

**Key Rules**:

- Enforces Angular selector styles:
- Directives → `osfFoo` (camelCase)
- Components → `<osf-bar>` (kebab-case)
- Enforces import sorting and duplicate prevention
- Removes unused imports automatically
- Allows unused variables named `_` to support convention (e.g., destructuring)

---

### 2. HTML Templates (`**/*.html`)

**Extends**:

- `angular-eslint/templateRecommended`
- `angular-eslint/templateAccessibility`

**Parser**:

- `@angular-eslint/template-parser`

**Key A11y Rules** (All Set to `"error"`):

- `alt-text`
- `valid-aria`
- `click-events-have-key-events`
- `role-has-required-aria`
- `interactive-supports-focus`
- `no-distracting-elements`
- `no-autofocus`
- `label-has-associated-control`

> This enforces **WCAG accessibility compliance** directly in Angular templates.

---

### 3. Test Files (`**/*.spec.ts`)

Loosened restrictions for developer convenience:

```ts
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off',
```

---

## Pre-Commit Hook

The `pre-commit` file includes:

- `npx lint-staged` → Lint only **staged files** before every commit
- Ensures **TypeScript and template linting** run automatically

## Summary

| File Type | Purpose | Key Rules |
| ------------ | ------------------------------------- | --------------------------------------- |
| `*.ts` | Lint Angular + TS logic | Unused imports, selector rules, sorting |
| `*.html` | Enforce a11y & Angular best practices | All template a11y rules enforced |
| `*.spec.ts` | Relaxed for test convenience | Some TS rules turned off |
| `pre-commit` | Git hook to lint before committing | Ensures consistent PR quality |
247 changes: 247 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
# OSF Angular Testing Strategy

## Overview

The OSF Angular project uses a modular and mock-driven testing strategy. A shared `testing/` folder provides reusable mocks, mock data, and testing module configuration to support consistent and maintainable unit tests across the codebase.

---

## Index

- [Best Practices](#best-practices)
- [Summary Table](#summary-table)
- [Test Coverage Enforcement (100%)](#test-coverage-enforcement-100)
- [Key Structure](#key-structure)
- [Testing Angular Services (with HTTP)](#testing-angular-services-with-http)
- [Testing Angular Components](#testing-angular-components)
- [Testing Angular Pipes](#testing-angular-pipes)
- [Testing Angular Directives](#testing-angular-directives)
- [Testing Angular NGXS](#testing-ngxs)

--

## Best Practices

- Always import `OsfTestingModule` or `OsfTestingStoreModule` to minimize boilerplate and get consistent mock behavior.
- Use mocks and mock-data from `testing/` to avoid repeating test setup.
- Avoid real HTTP, translation, or store dependencies in unit tests by default.
- Co-locate unit tests with components using `*.spec.ts`.

---

## Summary Table

| Location | Purpose |
| ----------------------- | -------------------------------------- |
| `osf.testing.module.ts` | Unified test module for shared imports |
| `mocks/*.mock.ts` | Mock services and tokens |
| `data/*.data.ts` | Static mock data for test cases |

---

## Test Coverage Enforcement (100%)

This project **strictly enforces 100% test coverage** through the following mechanisms:

### Husky Pre-Push Hook

Before pushing any code, Husky runs a **pre-push hook** that executes:

```bash
npm run test:coverage
```

This command:

- Runs the full test suite with `--coverage`.
- Fails the push if **coverage drops below 100%**.
- Ensures developers never bypass test coverage enforcement locally.

> Pro Tip: Use `npm run test:watch` during development to maintain coverage incrementally.

---

### GitHub Actions CI

Every pull request and push runs GitHub Actions that:

- Run `npm run test:coverage`.
- Verify test suite passes with **100% code coverage**.
- Fail the build if even **1 uncovered branch/line/function** exists.

This guarantees **test integrity in CI** and **prevents regressions**.

---

### Coverage Expectations

| File Type | Coverage Requirement |
| ----------- | ------------------------------------------ |
| `*.ts` | 100% line & branch |
| `*.spec.ts` | Required per file |
| Services | Must mock HTTP via `HttpTestingController` |
| Components | DOM + Input + Output event coverage |
| Pipes/Utils | All edge cases tested |

---

### Summary

- **Zero exceptions** for test coverage.
- **Push blocked** without passing 100% tests.
- GitHub CI double-checks every PR.

## Key Structure

### `testing/osf.testing.module.ts`

This module centralizes commonly used providers, declarations, and test utilities. It's intended to be imported into any `*.spec.ts` test file to avoid repetitive boilerplate.

Example usage:

```ts
import { TestBed } from '@angular/core/testing';
import { OsfTestingModule } from 'testing/osf.testing.module';

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [OsfTestingModule],
}).compileComponents();
});
```

### OSFTestingModule

**Imports:**

- `NoopAnimationsModule` – disables Angular animations for clean, predictable unit tests.
- `BrowserModule` – required for bootstrapping Angular features.
- `CommonModule` – provides core Angular directives (e.g., `ngIf`, `ngFor`).
- `TranslateModule.forRoot()` – sets up the translation layer for template-based testing with `@ngx-translate`.

**Providers:**

- `provideNoopAnimations()` – disables animation via the new standalone provider API.
- `provideRouter([])` – injects an empty router config for component-level testing.
- `provideHttpClient(withInterceptorsFromDi())` – ensures DI-compatible HTTP interceptors are respected in tests.
- `provideHttpClientTesting()` – injects `HttpTestingController` for mocking HTTP requests in unit tests.
- `TranslationServiceMock` – mocks i18n service methods.
- `EnvironmentTokenMock` – mocks environment config values.

---

### OSFTestingStoreModule

**Imports:**

- `OSFTestingModule` – reuses core mocks and modules.

**Providers:**

- `StoreMock` – mocks NgRx Store for selector and dispatch testing.
- `ToastServiceMock` – injects a mock version of the UI toast service.

### `testing/mocks/`

Provides common service and token mocks to isolate unit tests from real implementations.

**examples**

- `environment.token.mock.ts` – Mocks environment tokens like base API URLs.
- `store.mock.ts` – NGXS or other store-related mocks.
- `translation.service.mock.ts` – Prevents needing actual i18n setup during testing.
- `toast.service.mock.ts` – Mocks user feedback services to track invocations without UI.

---

### `testing/data/`

Includes fake/mock data used by tests to simulate external API responses or internal state.

Only use data from the `testing/data` data mocks to ensure that all data is the centralized.

**examples**

- `addons.authorized-storage.data.ts`
- `addons.external-storage.data.ts`
- `addons.configured.data.ts`
- `addons.operation-invocation.data.ts`

---

---

## Testing Angular Services (with HTTP)

All OSF Angular services that make HTTP requests must be tested using `HttpClientTestingModule` and `HttpTestingController`.

### Setup

```ts
import { HttpTestingController } from '@angular/common/http/testing';
import { OSFTestingModule } from '@testing/osf.testing.module';

let service: YourService;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [OSFTestingModule],
providers: [YourService],
});

service = TestBed.inject(YourService);
});
```

### Example Test

```ts
it('should call correct endpoint and return expected data', inject(
[HttpTestingController],
(httpMock: HttpTestingController) => {
service.getSomething().subscribe((data) => {
expect(data).toEqual(mockData);
});

const req = httpMock.expectOne('/api/endpoint');
expect(req.request.method).toBe('GET');
req.flush(getMockDataFromTestingData());

httpMock.verify(); // Verify no outstanding HTTP calls
}
));
```

### Key Rules

- Use `OSFTestingModule` to isolate the service
- Inject and use `HttpTestingController`
- Always call `httpMock.expectOne()` to verify the URL and method
- Always call `req.flush()` to simulate the backend response
- Add `httpMock.verify()` in each `it` to catch unflushed requests

---

## Testing Angular Components

- coming soon

---

## Testing Angular Pipes

- coming soon

---

## Testing Angular Directives

- coming soon

---

## Testing NGXS

- coming soon

---
Loading