first commit

This commit is contained in:
2026-10-03 13:40:26 +02:00
parent 732a5b5a16
commit 566f415fa7
555 changed files with 62704 additions and 0 deletions

View File

@@ -0,0 +1,159 @@
import { Component, inject, signal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { Events } from '@ngrx/signals/events';
import { AuthFacade } from '@task-stream/web/identity/application';
import {
ActivityGateway,
CommentsGateway,
IssuesGateway,
IssueStatus,
Project,
ProjectRole,
ProjectsGateway,
ReleasesGateway,
TrackerRealtimeGateway,
UsersGateway,
} from '@task-stream/web/tracker/domain';
import { NEVER, of } from 'rxjs';
import { boardPageEvents } from '../events/board.events';
import { projectPageEvents } from '../events/project.events';
import { Notifications } from '../notifications';
import { BoardFacade, provideBoardFacade } from './board.facade';
import { IssueFacade, provideIssueFacade } from './issue.facade';
import { ProjectFacade, provideProjectFacade } from './project.facade';
const ok = { id: 'x', key: null, aggregateVersion: 1 };
const project = { id: 'p1', key: 'WEB', members: [] } as unknown as Project;
const issue = { id: 'i1', key: 'WEB-1', links: [] };
/** Every command succeeds, every query answers once, nothing arrives in real time. */
function gateways() {
return {
projects: {
project: vi.fn(() => of(project)),
stats: vi.fn(() => NEVER),
addMember: vi.fn(() => of(ok)),
},
issues: {
board: vi.fn(() => of([])),
issue: vi.fn(() => of(issue)),
changeStatus: vi.fn(() => of(ok)),
},
list: { list: vi.fn(() => of([])) },
realtime: {
issueChanges: () => NEVER,
commentChanges: () => NEVER,
activity: () => NEVER,
releaseChanges: () => NEVER,
projectChanges: () => NEVER,
},
};
}
/** Facades are scoped to a route component, like in the app. */
@Component({
template: '',
providers: [
provideProjectFacade(),
provideBoardFacade(),
provideIssueFacade(),
],
})
class Host {
readonly project = inject(ProjectFacade);
readonly board = inject(BoardFacade);
readonly issue = inject(IssueFacade);
}
function setup() {
const fakes = gateways();
TestBed.configureTestingModule({
providers: [
{ provide: ProjectsGateway, useValue: fakes.projects },
{ provide: IssuesGateway, useValue: fakes.issues },
{ provide: ReleasesGateway, useValue: fakes.list },
{ provide: ActivityGateway, useValue: fakes.list },
{ provide: CommentsGateway, useValue: fakes.list },
{ provide: UsersGateway, useValue: { search: () => of([]) } },
{ provide: TrackerRealtimeGateway, useValue: fakes.realtime },
{
provide: Notifications,
useValue: { success: vi.fn(), error: vi.fn() },
},
{ provide: AuthFacade, useValue: { user: signal({ id: 'me' }) } },
],
});
const host = TestBed.createComponent(Host).componentInstance;
return { ...host, fakes };
}
describe('BoardFacade', () => {
it('opens the board for a new project and refilters the same one', () => {
const { board, fakes } = setup();
const seen: string[] = [];
TestBed.inject(Events)
.on(boardPageEvents.opened, boardPageEvents.filterChanged)
.subscribe((event) => seen.push(event.type));
board.show({ projectId: 'p1' });
board.show({ projectId: 'p1', assigneeId: 'u2' });
board.show({ projectId: 'p2' });
expect(seen).toEqual([
boardPageEvents.opened.type,
boardPageEvents.filterChanged.type,
boardPageEvents.opened.type,
]);
expect(fakes.issues.board).toHaveBeenLastCalledWith({ projectId: 'p2' });
expect(board.filter()).toEqual({ projectId: 'p2' });
});
});
describe('ProjectFacade', () => {
it('ignores commands until a project is open, then sends its id', () => {
const { project, fakes } = setup();
project.addMember('u2');
expect(fakes.projects.addMember).not.toHaveBeenCalled();
project.open('WEB');
expect(project.project()?.id).toBe('p1');
project.addMember('u2', ProjectRole.VIEWER);
expect(fakes.projects.addMember).toHaveBeenCalledWith({
projectId: 'p1',
userId: 'u2',
role: ProjectRole.VIEWER,
});
});
it('searches the directory from two characters', () => {
const { project } = setup();
const seen: string[] = [];
TestBed.inject(Events)
.on(projectPageEvents.userSearchRequested)
.subscribe(({ payload }) => seen.push(payload));
project.searchUsers(' a ');
project.searchUsers(' an ');
expect(seen).toEqual(['an']);
});
});
describe('IssueFacade', () => {
it('acts on the open ticket', () => {
const { issue, fakes } = setup();
issue.changeStatus(IssueStatus.IN_PROGRESS);
expect(fakes.issues.changeStatus).not.toHaveBeenCalled();
issue.open('WEB-1');
issue.changeStatus(IssueStatus.QA_FAILED, 'Falla en Safari');
expect(fakes.issues.changeStatus).toHaveBeenCalledWith({
issueId: 'i1',
status: IssueStatus.QA_FAILED,
reason: 'Falla en Safari',
});
});
});