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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,33 @@ <h3 class="text-body-12 lists__title">основное</h3>
</div>

<ul class="lists__list">
@if (project()?.partnerProgram; as program) {
<li class="lists__item lists__item--program" lang="ru">
<i
appIcon
icon="program"
class="lists__icon"
appWidth="10"
appHeight="10"
appViewBox="0 0 21 16"
aria-hidden="true"
></i>
<div class="lists__item-content">
<span class="text-body-10 lists__label">Программа</span>
@if (program.programId > 0) {
<a
class="text-body-10 lists__value lists__value--link"
[routerLink]="AppRoutes.program.detail(program.programId)"
>{{ program.name?.trim() || "Название не указано" }}</a
>
} @else {
<p class="text-body-10 lists__value">
{{ program.name?.trim() || "Название не указано" }}
</p>
}
</div>
</li>
}
<li class="lists__item">
<i appIcon icon="label" class="lists__icon" appSquare="10"></i>
<div class="lists__item-content">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,23 @@
align-items: flex-start;
min-width: 0;

&--program {
display: grid;
grid-template-columns: 22px minmax(0, 1fr);
gap: 4px 6px;
align-items: center;

.lists__item-content {
display: contents;
}

.lists__value {
grid-column: 1 / -1;
hyphens: auto;
word-break: normal;
}
}

&--status {
padding: 8px;
border: 0.5px solid var(--medium-grey-for-outline);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
/** @format */

import { ComponentFixture, TestBed } from "@angular/core/testing";
import { signal } from "@angular/core";
import { DesktopLayoutService } from "@uilib";
import { provideRouter } from "@angular/router";
import { IndustryRepositoryPort } from "@domain/industry/ports/industry.repository.port";
import { ProjectsLeftSideComponent } from "./projects-left-side.component";

describe("ProjectsLeftSideComponent", () => {
let fixture: ComponentFixture<ProjectsLeftSideComponent>;
const desktop = signal(false);

beforeEach(async () => {
desktop.set(false);
await TestBed.configureTestingModule({
imports: [ProjectsLeftSideComponent],
providers: [
{ provide: DesktopLayoutService, useValue: { desktop } },
provideRouter([]),
{
provide: IndustryRepositoryPort,
Expand Down Expand Up @@ -53,7 +58,8 @@ describe("ProjectsLeftSideComponent", () => {
expect(text).not.toContain(">0<");
});

it("показывает полное имя и открывает именно программу, а не связь или проект", () => {
it.each([true, false])("desktop=%s: полное имя и маршрут программы", isDesktop => {
desktop.set(isDesktop);
fixture.componentRef.setInput("project", {
industry: 1,
leader: 42,
Expand All @@ -72,7 +78,9 @@ describe("ProjectsLeftSideComponent", () => {
expect(field.querySelector("a").getAttribute("href")).toBe("/office/program/12");
});

it("не добавляет программу без связи и явно обозначает отсутствующее legacy имя", () => {
it.each([true, false])("desktop=%s: нет связи или отсутствует legacy имя", isDesktop => {
desktop.set(isDesktop);
fixture.detectChanges();
expect(fixture.nativeElement.querySelector(".lists__item--program")).toBeNull();
fixture.componentRef.setInput("project", {
industry: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@
<a
class="card__project-link"
[routerLink]="AppRoutes.projects.detail(info()?.id)"
[attr.aria-label]="'Открыть проект «' + info()?.name + '»'"
[attr.aria-label]="
'Открыть проект «' +
info()?.name +
'»' +
(programName() ? '. Программа: ' + programName() : '')
"
>
<ng-container *ngTemplateOutlet="cardContent"></ng-container>
</a>
Expand All @@ -58,6 +63,25 @@
<div class="card__content" [class.card__content--empty]="appereance() === 'empty'">
@if (myProjectPresentation(); as presentation) {
<p [class]="'card__context card__status card__status--' + presentation.lifecycle">
@if (presentation.lifecycle === "program") {
<i
appIcon
icon="program"
appWidth="12"
appHeight="12"
appViewBox="0 0 21 16"
aria-hidden="true"
></i>
} @else if (presentation.lifecycle === "submitted") {
<i
appIcon
icon="lock"
appWidth="12"
appHeight="12"
appViewBox="0 0 11 12"
aria-hidden="true"
></i>
}
{{ presentation.statusLabel }}
</p>
}
Expand All @@ -68,6 +92,9 @@
} @else {
<ng-container *ngTemplateOutlet="regularCardHeader"></ng-container>
}
@if (programName(); as name) {
<p class="card__program-name" [title]="'Программа: ' + name">Программа: {{ name }}</p>
}
</div>
</div>

Expand Down Expand Up @@ -137,6 +164,7 @@
<div
class="card__description"
[class.card__description--project]="type() === 'projects'"
[class.card__description--with-program]="programName() !== null"
[style.max-height]="
isProjectCard() || type() === 'invite' ? null : type() === 'members' ? '50px' : '24px'
"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,26 @@
line-height: 12px;
-webkit-line-clamp: 2;
}

&--with-program {
flex-basis: 12px;
height: 12px;
min-height: 12px;

p {
-webkit-line-clamp: 1;
}
}
}

.card__program-name {
width: 100%;
overflow: hidden;
font-size: 9px;
line-height: 12px;
color: var(--grey-for-text);
text-overflow: ellipsis;
white-space: nowrap;
}

.card__footer {
Expand Down Expand Up @@ -470,6 +490,15 @@
}

&__status {
gap: 4px;
padding-inline: 5px;
color: var(--black);

i {
display: flex;
flex-shrink: 0;
}

&::before {
flex: 0 0 6px;
width: 6px;
Expand All @@ -485,13 +514,18 @@
}

&--program {
color: var(--accent-dark);
background: var(--accent-light);
&::before {
display: none;
}
}

&--submitted {
color: var(--gold-dark);
background: var(--gold-light);
color: var(--green-dark);
background: var(--green-light);

&::before {
display: none;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { OverlayContainer } from "@angular/cdk/overlay";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { signal } from "@angular/core";
import { By } from "@angular/platform-browser";
import { provideRouter, Router, RouterLink, UrlTree } from "@angular/router";
import { of } from "rxjs";
Expand All @@ -14,18 +15,22 @@ import { AvatarComponent } from "@ui/primitives/avatar/avatar.component";
import { IconComponent } from "@ui/primitives";
import { InfoCardComponent } from "./info-card.component";
import { myProjectCardFixtures, projectCardFixture, projectCardProgram } from "./info-card.fixture";
import { DesktopLayoutService } from "../../../../../../ui/src/lib/services/desktop-layout.service";

describe("InfoCardComponent: статусы моих проектов", () => {
let fixture: ComponentFixture<InfoCardComponent>;
let component: InfoCardComponent;
const desktop = signal(false);
const addSubscription = { execute: vi.fn(() => of(ok(undefined))) };
const deleteSubscription = { execute: vi.fn(() => of(ok(undefined))) };

beforeEach(async () => {
vi.clearAllMocks();
desktop.set(false);
await TestBed.configureTestingModule({
imports: [InfoCardComponent],
providers: [
{ provide: DesktopLayoutService, useValue: { desktop } },
{
provide: IndustryRepositoryPort,
useValue: { industries: () => [], getOne: () => ({ id: 1, name: "Образование" }) },
Expand All @@ -50,6 +55,35 @@ describe("InfoCardComponent: статусы моих проектов", () => {
});
const card = (): HTMLElement => fixture.nativeElement.querySelector(".card__body");

it.each([true, false])(
"desktop=%s: статусы и программа совпадают в обеих разметках",
isDesktop => {
desktop.set(isDesktop);
for (const item of myProjectCardFixtures) {
fixture.componentRef.setInput("info", item.project);
fixture.detectChanges();
expect(card().querySelector(".card__status")?.textContent?.trim()).toBe(item.label);
expect(card().querySelector(".card__access-label")?.textContent?.trim()).toBe(item.access);
const name = item.project.partnerProgram?.name;
const program = card().querySelector<HTMLElement>(".card__program-name");
if (name) {
expect(program?.textContent?.trim()).toBe("Программа: " + name);
expect(program?.title).toBe("Программа: " + name);
expect(card().querySelector("a")?.getAttribute("aria-label")).toContain(name);
} else {
expect(program).toBeNull();
}
const icons = fixture.debugElement.queryAll(By.directive(IconComponent));
const expectedIcon =
item.key === "submitted" ? "lock" : item.key === "program" ? "program" : null;
expect(icons).toHaveLength(expectedIcon ? 1 : 0);
if (expectedIcon) expect(icons[0].componentInstance.icon()).toBe(expectedIcon);
expect(card().querySelectorAll("a")).toHaveLength(1);
expect(card().querySelector("a a, a button")).toBeNull();
}
},
);

it.each(myProjectCardFixtures)(
"$key / $role: статус, доступ и единственный корректный маршрут",
async item => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,7 @@ import { Project } from "@domain/project/project.model";

interface MyProjectPresentation {
lifecycle: "submitted" | "draft" | "program" | "published";
statusLabel:
| "Черновик"
| "Опубликован"
| "В программе"
| "Привязан к программе"
| "Сдан в программу";
statusLabel: "Черновик" | "Опубликован" | "Привязан к программе" | "Сдан в программу";
role: "leader" | "participant";
roleLabel: "Лидер" | "Участник";
accessLabel: "можно редактировать" | "только просмотр";
Expand Down Expand Up @@ -123,7 +118,7 @@ export class InfoCardComponent {
{
submitted: "Сдан в программу",
draft: "Черновик",
program: this.releaseDesktop() ? "В программе" : "Привязан к программе",
program: "Привязан к программе",
published: "Опубликован",
};
const userId = this.loggedUserId();
Expand Down