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
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: TeachLink Community
url: https://t.me/teachlinkOD
about: Ask questions and discuss changes with maintainers.
39 changes: 39 additions & 0 deletions .github/ISSUE_TEMPLATE/quality-gates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: "PR Quality Gates / Governance"
description: "Work related to CI, branch protection, PR process, governance"
title: "[Governance] <short description>"
labels: ["frontend", "devops", "ci", "governance", "priority-high"]
body:
- type: markdown
attributes:
value: |
Use this template for branch protection, CI, and PR process improvements.

- type: input
id: background
attributes:
label: Background
description: Why is this needed?
validations:
required: true

- type: textarea
id: acceptance
attributes:
label: Acceptance Criteria
description: List the conditions that must be true to close this issue.
value: |
- [ ] PR cannot merge without passing CI
- [ ] PR cannot merge without approval
- [ ] Direct push to main/develop is blocked
- [ ] All PRs must reference an issue
- [ ] All conversations must be resolved
validations:
required: true

- type: textarea
id: implementation
attributes:
label: Implementation notes
description: Optional implementation plan / links.
validations:
required: false
49 changes: 49 additions & 0 deletions .github/branch-protection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Branch protection (repository settings)

These are **GitHub repository settings** (not enforced by code). Configure them under:
**Settings → Branches → Branch protection rules**.

## Protected branches
Create rules for:
- `main`
- `develop`

## Required settings
Enable the following options:

### блок direct pushes
- ✅ **Restrict who can push to matching branches** (recommended)
- ✅ **Do not allow force pushes**
- ✅ **Do not allow deletions**

### Require PRs
- ✅ **Require a pull request before merging**
- ✅ **Require approvals**: at least **1** (or **2** if desired)
- ✅ **Dismiss stale approvals when new commits are pushed**
- ✅ **Require conversation resolution before merging**

### Require checks
- ✅ **Require status checks to pass before merging**
- ✅ Select required checks from `Frontend CI`:
- `type-check`
- `lint`
- `build`
- `test`

### Keep branch up to date
- ✅ **Require branches to be up to date before merging**

## PR must reference an issue
GitHub does not have a single built-in "require issue link" toggle for all repos.
Recommended options:

1. **Process enforcement (lightweight)**
- Use `.github/pull_request_template.md` and require `Closes #<issue>` in the PR.

2. **Stronger enforcement (recommended)**
- Add a dedicated GitHub Action that fails if the PR body does not contain `Closes #<number>`.
- If you want this, we can add a small workflow using `actions/github-script`.

## Notes
- Repository admins can optionally be included or excluded from these requirements.
- Configure the same rule set for both `main` and `develop` to avoid bypass paths.
29 changes: 29 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Summary
<!-- What does this PR change and why? -->

## Related Issue
<!-- REQUIRED: link the issue this PR addresses -->
Closes #

## Type of change
- [ ] Feature
- [ ] Bug fix
- [ ] Chore / Refactor
- [ ] Docs

## Screenshots / Recording (if UI)
<!-- Optional -->

## Testing
<!-- REQUIRED: describe how you tested this change -->
- [ ] `npm run type-check`
- [ ] `npm run lint`
- [ ] `npm run test`
- [ ] `npm run build`

## Quality gate checklist
- [ ] CI checks pass (Frontend CI)
- [ ] At least 1–2 approvals (per branch protection rules)
- [ ] Branch is up-to-date with the base branch
- [ ] All conversations resolved
- [ ] PR description includes `Closes #<issue-number>`
26 changes: 26 additions & 0 deletions .github/workflows/pr-quality-gates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: PR Quality Gates

on:
pull_request:
types: [opened, edited, synchronize, reopened]
branches: [main, develop]

permissions:
pull-requests: read

jobs:
require-linked-issue:
name: Require linked issue in PR body
runs-on: ubuntu-latest
steps:
- name: Validate PR body contains issue closing keyword
uses: actions/github-script@v7
with:
script: |
const body = context.payload.pull_request?.body || '';
// Accept common keywords: close/closes/closed/fix/fixes/fixed/resolve/resolves/resolved
// Require a github issue reference like: "Closes #123"
const re = /(close[sd]?|fix(e[sd])?|resolve[sd]?)\s+#\d+/i;
if (!re.test(body)) {
core.setFailed('PR description must reference an issue using e.g. "Closes #123".');
}
53 changes: 53 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Contributing to TeachLink Frontend

Thanks for contributing to TeachLink.

## Branching & workflow
- **Do not push directly** to protected branches (`main`, `develop`).
- Create a feature branch from `develop` (preferred) or `main`:
- `feat/<short-description>`
- `fix/<short-description>`
- `chore/<short-description>`

## Assignment required
Before opening a PR, ensure the issue is assigned to you.

## Pull request requirements (quality gates)
Your PR will be blocked from merging unless it meets the following:

1. **CI must pass**
- Required checks: `type-check`, `lint`, `build`, `test` (GitHub Actions: **Frontend CI**)

2. **Approvals required**
- Minimum **1–2 approvals** (as configured in branch protection rules).

3. **Branch must be up to date**
- Update your branch with the target branch before merge (no stale merge).

4. **Conversations resolved**
- All review conversations must be resolved before merge.

5. **Issue must be referenced**
- PR description must reference a GitHub issue and include one of:
- `Close #<issue-number>` / `Closes #<issue-number>` / `Fixes #<issue-number>`

## Local checks (run before pushing)
- `npm run type-check`
- `npm run lint`
- `npm run test`
- `npm run build`

## PR description format
Use the PR template (auto-applied). Ensure it includes:
- Summary of changes
- Testing notes
- `Close #<issue-number>`

## Code standards
- Keep changes small and focused.
- No console errors.
- Use `lucide-react` icons for UI.
- Keep components accessible and responsive.

## Security
Do not commit secrets. Use `.env.local` for local environment variables.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,16 @@ npm run dev

For detailed tasks, see GitHub Issues

🤝 Contributing
## 🤝 Contributing
We welcome community contributions!

Guidelines:
Fork the repo and make your changes in a feature branch
- Read **`CONTRIBUTING.md`** before opening a PR.
- All PRs must include an issue reference in the description (e.g. `Closes #68`).
- Merges to protected branches require passing CI + approvals.

Before submitting a PR, read the CONTRIBUTING.md file
Guidelines:
- Fork the repo and make your changes in a feature branch
- Before submitting a PR, read the **`CONTRIBUTING.md`** file

## 📬 Join the Community

Expand Down Expand Up @@ -144,5 +147,3 @@ let make our code clean, maintainable and scallable. Keep to Standard

📜 License
MIT © 2025 TeachLink DAO

```
28 changes: 14 additions & 14 deletions src/form-management/state/form-state-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Unit tests for Form State Manager
*/

import { describe, it, expect, beforeEach } from 'vitest';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { FormStateManager } from './form-state-manager';
import { ValidationResult, StateChangeEvent } from '../types/core';

Expand Down Expand Up @@ -214,7 +214,7 @@ describe('FormStateManager', () => {

describe('state change subscriptions', () => {
it('should notify subscribers of field changes', () => {
const callback = jest.fn();
const callback = vi.fn();
const subscription = stateManager.subscribeToChanges(callback);

stateManager.updateField('email', 'test@example.com');
Expand All @@ -232,7 +232,7 @@ describe('FormStateManager', () => {
});

it('should notify subscribers of validation changes', () => {
const callback = jest.fn();
const callback = vi.fn();
stateManager.subscribeToChanges(callback);

const validationResult: ValidationResult = {
Expand All @@ -252,10 +252,10 @@ describe('FormStateManager', () => {
});

it('should handle subscription errors gracefully', () => {
const errorCallback = jest.fn(() => {
throw new Error('Callback error');
const errorCallback = vi.fn(() => {
throw new Error('subscription error');
});
const normalCallback = jest.fn();
const normalCallback = vi.fn();

stateManager.subscribeToChanges(errorCallback);
stateManager.subscribeToChanges(normalCallback);
Expand All @@ -269,7 +269,7 @@ describe('FormStateManager', () => {
});

it('should unsubscribe correctly', () => {
const callback = jest.fn();
const callback = vi.fn();
const subscription = stateManager.subscribeToChanges(callback);

stateManager.updateField('email', 'test1@example.com');
Expand Down Expand Up @@ -333,7 +333,7 @@ describe('FormStateManager', () => {
describe('programmatic state control methods', () => {
describe('silent field updates', () => {
it('should set field value without triggering change events', () => {
const callback = jest.fn();
const callback = vi.fn();
stateManager.subscribeToChanges(callback);

stateManager.setFieldValueSilently('email', 'test@example.com');
Expand All @@ -345,7 +345,7 @@ describe('FormStateManager', () => {

describe('batch operations', () => {
it('should set multiple validation states at once', () => {
const callback = jest.fn();
const callback = vi.fn();
stateManager.subscribeToChanges(callback);

const validationStates = {
Expand All @@ -364,7 +364,7 @@ describe('FormStateManager', () => {
});

it('should set multiple field values in batch', () => {
const callback = jest.fn();
const callback = vi.fn();
stateManager.subscribeToChanges(callback);

const values = {
Expand Down Expand Up @@ -471,15 +471,15 @@ describe('FormStateManager', () => {

describe('submission control', () => {
it('should start submission with callback', () => {
const callback = jest.fn();
const callback = vi.fn();
stateManager.startSubmission(callback);

expect(stateManager.getState().isSubmitting).toBe(true);
expect(callback).toHaveBeenCalled();
});

it('should complete submission successfully', () => {
const callback = jest.fn();
const callback = vi.fn();

// Set up dirty state
stateManager.updateField('email', 'test@example.com');
Expand All @@ -493,7 +493,7 @@ describe('FormStateManager', () => {
});

it('should complete submission with failure', () => {
const callback = jest.fn();
const callback = vi.fn();

// Set up dirty state
stateManager.updateField('email', 'test@example.com');
Expand Down Expand Up @@ -686,7 +686,7 @@ describe('FormStateManager', () => {
});

it('should trigger cascading updates manually', () => {
const callback = jest.fn();
const callback = vi.fn();
stateManager.subscribeToChanges(callback);

stateManager.updateField('trigger', 'show');
Expand Down
Loading
Loading