Merge branch 'feature/manager-guide'

This commit is contained in:
Bastian Wagner
2026-08-02 09:25:09 +02:00
43 changed files with 1002 additions and 6 deletions

View File

@@ -10,6 +10,15 @@
</div>
</header>
<app-context-help
title="Buchungen verstehen"
[hints]="[
'Mitgliederbuchungen verändern persönliche Salden; Teambuchungen erfassen direkte Einnahmen oder Ausgaben der Kasse.',
'Bei mehreren Mitgliedern bestimmt die Verteilungsoption, ob der Gesamtbetrag geteilt oder je Person gebucht wird.',
]"
sectionId="bookings"
/>
@if (canBook()) {
<section class="booking-grid">
<mat-card data-testid="player-booking">

View File

@@ -139,6 +139,7 @@ describe('Cashbox', () => {
expect(fixture.nativeElement.textContent).toContain('Bea Test');
expect(fixture.nativeElement.textContent).toContain('-12,00');
expect(fixture.nativeElement.querySelector('[data-testid="player-booking"]')).not.toBeNull();
expect(fixture.nativeElement.textContent).toContain('Buchungen verstehen');
});
it('submits cent-preserving split transactions for selected players', async () => {

View File

@@ -26,6 +26,7 @@ import {
import { ConfirmDialog, ConfirmDialogData } from '../../../shared/confirm-dialog/confirm-dialog';
import { splitAmounts } from './transaction-calculation';
import { signedTransactionAmount } from '../../../models/transaction-amount';
import { ContextHelp } from '../../../shared/context-help/context-help';
registerLocaleData(localeDe);
@@ -46,6 +47,7 @@ const HIGH_AMOUNT_CONFIRM_THRESHOLD = 300;
MatProgressSpinnerModule,
MatSelectModule,
MatSnackBarModule,
ContextHelp,
],
providers: [{ provide: LOCALE_ID, useValue: 'de-DE' }],
templateUrl: './cashbox.html',

View File

@@ -10,6 +10,14 @@
</button>
}
</header>
<app-context-help
title="Mitglieder verwalten"
[hints]="[
'Ein Mitglied wird zuerst angelegt und erst danach über eine Einladung mit einem Benutzerkonto verbunden.',
'Ausgetretene Mitglieder deaktivieren, damit ihre Buchungshistorie erhalten bleibt.',
]"
sectionId="members"
/>
@if (showCreateForm()) {
<mat-card class="create-card"
><mat-card-content

View File

@@ -52,6 +52,7 @@ describe('Members', () => {
const fixture = TestBed.createComponent(Members);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Bea Test');
expect(fixture.nativeElement.textContent).toContain('Mitglieder verwalten');
fixture.componentInstance['showCreateForm'].set(true);
fixture.componentInstance['createForm'].setValue({

View File

@@ -12,6 +12,7 @@ import { MatSelectModule } from '@angular/material/select';
import { AuthStore } from '../../../core/auth/auth-store';
import { TeamStore } from '../../../core/team/team-store';
import { TeamsApi } from '../../../core/team/teams-api';
import { ContextHelp } from '../../../shared/context-help/context-help';
registerLocaleData(localeDe);
@@ -27,6 +28,7 @@ registerLocaleData(localeDe);
MatIconModule,
MatInputModule,
MatSelectModule,
ContextHelp,
],
providers: [{ provide: LOCALE_ID, useValue: 'de-DE' }],
templateUrl: './members.html',

View File

@@ -0,0 +1,61 @@
@if (canOpenGuide()) {
<a mat-button routerLink="../"><mat-icon>arrow_back</mat-icon>Mehr</a>
<header class="guide-header">
<p class="eyebrow">Team verwalten</p>
<h1>Anleitung für Verantwortliche</h1>
<p>Ein praktischer Leitfaden für Kassenwarte, Kapitäne, Trainer und ihre Vertretung.</p>
</header>
<mat-card class="handover-card">
<mat-card-content>
<p class="eyebrow">Schnellstart</p>
<h2>Neu in der Verantwortung?</h2>
<p>Gehe diese Punkte gemeinsam mit der bisherigen verantwortlichen Person durch.</p>
<ol>
@for (item of handoverChecklist; track item) {
<li>{{ item }}</li>
}
</ol>
</mat-card-content>
</mat-card>
<nav class="guide-nav" aria-label="Inhalt der Anleitung">
<strong>Direkt zu einem Thema</strong>
<div>
@for (section of sections; track section.id) {
<a [routerLink]="[]" [fragment]="section.id">{{ section.title }}</a>
}
</div>
</nav>
<div class="guide-sections">
@for (section of sections; track section.id) {
<section [id]="section.id" class="guide-section">
<p class="eyebrow">{{ $index + 1 | number: '2.0' }}</p>
<h2>{{ section.title }}</h2>
<p class="guide-section__intro">{{ section.intro }}</p>
@if (section.id === 'roles') {
<div class="role-grid">
@for (role of roleGuide; track role.role) {
<article>
<strong>{{ role.role }}</strong>
<span>{{ role.description }}</span>
</article>
}
</div>
}
<ul>
@for (point of section.points; track point) {
<li>{{ point }}</li>
}
</ul>
@if (section.note) {
<p class="guide-note"><strong>Wichtig:</strong> {{ section.note }}</p>
}
</section>
}
</div>
}

View File

@@ -0,0 +1,132 @@
:host {
display: block;
max-width: 960px;
margin: 0 auto;
padding: 28px;
}
.guide-header {
margin: 1rem 0 2rem;
}
h1,
h2,
p {
margin-top: 0;
}
h1 {
max-width: 18ch;
font-size: clamp(2rem, 5vw, 3.5rem);
line-height: 1.05;
margin-bottom: 0.75rem;
}
.eyebrow {
color: var(--mat-sys-primary);
font-size: 0.75rem;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 0.4rem;
}
.handover-card {
border-radius: 1.25rem;
background: var(--mat-sys-primary-container);
color: var(--mat-sys-on-primary-container);
}
.handover-card mat-card-content {
padding: clamp(1.25rem, 4vw, 2rem);
}
.handover-card ol {
display: grid;
gap: 0.75rem;
padding-left: 1.4rem;
margin-bottom: 0;
}
.guide-nav {
margin: 2rem 0;
padding: 1.25rem;
border: 1px solid var(--mat-sys-outline-variant);
border-radius: 1rem;
}
.guide-nav div {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.8rem;
}
.guide-nav a {
padding: 0.45rem 0.7rem;
border-radius: 999px;
background: var(--mat-sys-surface-container);
color: var(--mat-sys-on-surface);
text-decoration: none;
}
.guide-sections {
display: grid;
gap: 1rem;
}
.guide-section {
scroll-margin-top: 1.5rem;
padding: clamp(1.25rem, 4vw, 2rem);
border: 1px solid var(--mat-sys-outline-variant);
border-radius: 1.25rem;
background: var(--mat-sys-surface-container-lowest);
}
.guide-section__intro {
max-width: 72ch;
color: var(--mat-sys-on-surface-variant);
font-size: 1.05rem;
}
.guide-section ul {
display: grid;
gap: 0.55rem;
padding-left: 1.2rem;
}
.role-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.75rem;
margin: 1.25rem 0;
}
.role-grid article {
display: grid;
gap: 0.35rem;
padding: 1rem;
border-radius: 0.9rem;
background: var(--mat-sys-surface-container);
}
.role-grid span {
color: var(--mat-sys-on-surface-variant);
}
.guide-note {
margin: 1rem 0 0;
padding: 1rem;
border-left: 4px solid var(--mat-sys-tertiary);
background: var(--mat-sys-tertiary-container);
}
@media (max-width: 600px) {
:host {
padding: 20px 16px;
}
.role-grid {
grid-template-columns: 1fr;
}
}

View File

@@ -0,0 +1,53 @@
import { Component, signal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { provideRouter, Router } from '@angular/router';
import { HelpAccessService } from '../../../../core/help/help-access';
import { TeamStore } from '../../../../core/team/team-store';
import { Guide } from './guide';
@Component({ template: '' })
class EmptyRoute {}
describe('Guide', () => {
it('renders the handover path and all task-oriented chapters for responsible users', async () => {
const fixture = await createFixture(true);
const text = fixture.nativeElement.textContent;
expect(text).toContain('Neu in der Verantwortung?');
expect(text).toContain('Amtsübergabe prüfen');
expect(text).toContain('Buchungen sicher erfassen');
expect(text).toContain('Verantwortung weitergeben');
expect(fixture.nativeElement.querySelector('#bookings')).not.toBeNull();
expect(fixture.nativeElement.querySelector('#faq')).not.toBeNull();
});
it('redirects regular players back to the More page after team loading', async () => {
const fixture = await createFixture(false);
const router = TestBed.inject(Router);
await fixture.whenStable();
expect(router.url).toBe('/team/5/more');
expect(fixture.nativeElement.textContent).not.toContain('Amtsübergabe prüfen');
});
});
async function createFixture(canOpen: boolean) {
await TestBed.configureTestingModule({
imports: [Guide],
providers: [
provideRouter([{ path: 'team/5/more', component: EmptyRoute }]),
{ provide: HelpAccessService, useValue: { canOpenGuide: signal(canOpen) } },
{
provide: TeamStore,
useValue: {
team: signal({ id: 5 }),
loading: signal(false),
},
},
],
}).compileComponents();
const fixture = TestBed.createComponent(Guide);
fixture.detectChanges();
await fixture.whenStable();
return fixture;
}

View File

@@ -0,0 +1,39 @@
import { Component, effect, inject } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { Router, RouterLink } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import {
GUIDE_SECTIONS,
HANDOVER_CHECKLIST,
ROLE_GUIDE,
} from '../../../../core/help/guide-content';
import { HelpAccessService } from '../../../../core/help/help-access';
import { TeamStore } from '../../../../core/team/team-store';
@Component({
selector: 'app-guide',
imports: [DecimalPipe, RouterLink, MatButtonModule, MatCardModule, MatIconModule],
templateUrl: './guide.html',
styleUrl: './guide.scss',
})
export class Guide {
private readonly helpAccess = inject(HelpAccessService);
private readonly router = inject(Router);
private readonly teamStore = inject(TeamStore);
protected readonly sections = GUIDE_SECTIONS;
protected readonly handoverChecklist = HANDOVER_CHECKLIST;
protected readonly roleGuide = ROLE_GUIDE;
protected readonly canOpenGuide = this.helpAccess.canOpenGuide;
constructor() {
effect(() => {
const team = this.teamStore.team();
if (!this.teamStore.loading() && team && !this.helpAccess.canOpenGuide()) {
void this.router.navigateByUrl(`/team/${team.id}/more`);
}
});
}
}

View File

@@ -4,6 +4,15 @@
<h1>Einladen</h1>
<p>Der Link ist personalisiert und darf nur an die ausgewählte Person gehen.</p>
</header>
<app-context-help
title="Einladungen richtig zuordnen"
[hints]="[
'Der persönliche Link verbindet genau das ausgewählte Mitglied mit einem Benutzerkonto.',
'Sende den Link nur an die Person, deren Mitgliedseintrag ausgewählt wurde.',
]"
sectionId="sharing"
/>
@if (!canInvite()) {
<mat-card class="notice"
><mat-icon>lock</mat-icon

View File

@@ -46,6 +46,7 @@ describe('Invite', () => {
}).compileComponents();
const fixture = TestBed.createComponent(Invite);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Einladungen richtig zuordnen');
fixture.componentInstance['form'].setValue({ playerId: 7 });
fixture.componentInstance['generateLink']();

View File

@@ -11,6 +11,7 @@ import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { AuthApi } from '../../../../core/auth/auth-api';
import { AuthStore } from '../../../../core/auth/auth-store';
import { TeamStore } from '../../../../core/team/team-store';
import { ContextHelp } from '../../../../shared/context-help/context-help';
@Component({
selector: 'app-invite',
@@ -24,6 +25,7 @@ import { TeamStore } from '../../../../core/team/team-store';
MatInputModule,
MatSelectModule,
MatSnackBarModule,
ContextHelp,
],
templateUrl: './invite.html',
styleUrl: './invite.scss',

View File

@@ -5,6 +5,18 @@
</header>
<section class="link-grid">
@if (canOpenGuide()) {
<a routerLink="guide"
><mat-card
><mat-icon>menu_book</mat-icon>
<div>
<strong>Anleitung für Verantwortliche</strong
><span>Amtsübergabe, Kasse und Teamverwaltung verstehen</span>
</div>
<mat-icon>chevron_right</mat-icon></mat-card
></a
>
}
<a routerLink="/users"
><mat-card
><mat-icon>group</mat-icon>

View File

@@ -2,6 +2,7 @@ import { Component, signal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { provideRouter, Router } from '@angular/router';
import { AuthStore } from '../../../core/auth/auth-store';
import { HelpAccessService } from '../../../core/help/help-access';
import { More } from './more';
@Component({ template: '' })
@@ -21,6 +22,7 @@ describe('More', () => {
clearSession,
},
},
{ provide: HelpAccessService, useValue: { canOpenGuide: signal(true) } },
],
}).compileComponents();
const fixture = TestBed.createComponent(More);
@@ -30,6 +32,7 @@ describe('More', () => {
expect(fixture.nativeElement.textContent).toContain('Einladen');
expect(fixture.nativeElement.textContent).toContain('Profil');
expect(fixture.nativeElement.textContent).toContain('Öffentliche Freigabe');
expect(fixture.nativeElement.textContent).toContain('Anleitung für Verantwortliche');
expect(fixture.nativeElement.textContent).toContain('Benutzer');
expect(fixture.nativeElement.querySelector('a[href="/users"]')).not.toBeNull();
@@ -39,4 +42,25 @@ describe('More', () => {
expect(clearSession).toHaveBeenCalled();
expect(TestBed.inject(Router).url).toBe('/auth/login');
});
it('hides the guide link from regular players', async () => {
await TestBed.configureTestingModule({
imports: [More],
providers: [
provideRouter([]),
{
provide: AuthStore,
useValue: {
currentUser: signal({ firstName: 'Alex', lastName: 'Muster', email: 'a@b.de' }),
clearSession: vi.fn(),
},
},
{ provide: HelpAccessService, useValue: { canOpenGuide: signal(false) } },
],
}).compileComponents();
const fixture = TestBed.createComponent(More);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).not.toContain('Anleitung für Verantwortliche');
});
});

View File

@@ -4,6 +4,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { AuthStore } from '../../../core/auth/auth-store';
import { HelpAccessService } from '../../../core/help/help-access';
@Component({
selector: 'app-more',
@@ -13,8 +14,10 @@ import { AuthStore } from '../../../core/auth/auth-store';
})
export class More {
private readonly authStore = inject(AuthStore);
private readonly helpAccess = inject(HelpAccessService);
private readonly router = inject(Router);
protected readonly user = this.authStore.currentUser;
protected readonly canOpenGuide = this.helpAccess.canOpenGuide;
protected logout(): void {
this.authStore.clearSession();

View File

@@ -5,6 +5,15 @@
<p>Klare Regeln, transparent für das ganze Team.</p>
</header>
<app-context-help
title="Strafenkatalog richtig nutzen"
[hints]="[
'Ein Katalogeintrag ist eine Vorlage und noch keine Forderung gegen ein Mitglied.',
'Bestehende Buchungen ändern sich nicht, wenn ein Katalogeintrag später bearbeitet wird.',
]"
sectionId="penalties"
/>
@if (canManage()) {
<mat-card class="create-card">
<form [formGroup]="form" (ngSubmit)="createPenalty()" aria-label="Katalogeintrag anlegen">

View File

@@ -101,6 +101,7 @@ describe('Penalties', () => {
.mockReturnValueOnce(of([first, { id: 2, description: 'Handy', amount: 3 }]));
create();
fixture.componentInstance['form'].setValue({ description: 'Handy', amount: 3 });
expect(text()).toContain('Strafenkatalog richtig nutzen');
fixture.componentInstance['createPenalty']();
fixture.detectChanges();

View File

@@ -18,6 +18,7 @@ import { PenaltyApi } from '../../../../core/team/penalty-api';
import { TeamStore } from '../../../../core/team/team-store';
import { Penalty } from '../../../../models/penalty.model';
import { ConfirmDialog } from '../../../../shared/confirm-dialog/confirm-dialog';
import { ContextHelp } from '../../../../shared/context-help/context-help';
registerLocaleData(localeDe);
@@ -33,6 +34,7 @@ registerLocaleData(localeDe);
MatIconModule,
MatInputModule,
MatProgressSpinnerModule,
ContextHelp,
],
providers: [{ provide: LOCALE_ID, useValue: 'de-DE' }],
templateUrl: './penalties.html',

View File

@@ -25,6 +25,17 @@
><input matInput type="password" formControlName="password"
/></mat-form-field>
</div>
@if (canOpenGuide()) {
<section class="help-settings">
<h2>Hilfe</h2>
<p class="hint">
Blende kurze Erklärungen auf Kasse, Mitglieder und Verwaltungsseiten ein.
</p>
<mat-slide-toggle formControlName="helpTextsEnabled">
Hilfetexte in der App anzeigen
</mat-slide-toggle>
</section>
}
<button mat-flat-button type="submit" [disabled]="form.invalid || saving()">
<mat-icon>save</mat-icon>Speichern
</button>

View File

@@ -44,6 +44,19 @@ h2 {
color: var(--mat-sys-on-surface-variant);
margin-bottom: 2px;
}
.help-settings {
display: grid;
gap: 0.75rem;
margin: 0.5rem 0;
padding: 1rem;
border-radius: 1rem;
background: var(--mat-sys-surface-container);
}
.help-settings h2,
.help-settings p {
margin: 0;
}
form button {
justify-self: end;
}

View File

@@ -1,9 +1,12 @@
import { signal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { throwError } from 'rxjs';
import { provideRouter } from '@angular/router';
import { MatSnackBar } from '@angular/material/snack-bar';
import { AuthApi } from '../../../../core/auth/auth-api';
import { AuthStore } from '../../../../core/auth/auth-store';
import { HelpAccessService } from '../../../../core/help/help-access';
import { Profile } from './profile';
describe('Profile', () => {
@@ -13,6 +16,7 @@ describe('Profile', () => {
email: 'alex@example.com',
firstName: 'Alexander',
lastName: 'Neu',
helpTextsEnabled: false,
};
const updateProfile = vi.fn(() => of(updated));
const updateUser = vi.fn();
@@ -29,10 +33,12 @@ describe('Profile', () => {
email: 'alex@example.com',
firstName: 'Alex',
lastName: 'Muster',
helpTextsEnabled: true,
}),
updateUser,
},
},
{ provide: HelpAccessService, useValue: { canOpenGuide: signal(true) } },
],
}).compileComponents();
const fixture = TestBed.createComponent(Profile);
@@ -42,11 +48,56 @@ describe('Profile', () => {
lastName: 'Neu',
oldPassword: '',
password: '',
});
helpTextsEnabled: false,
} as any);
fixture.componentInstance['save']();
expect(updateProfile).toHaveBeenCalledWith({ firstName: 'Alexander', lastName: 'Neu' });
expect(updateProfile).toHaveBeenCalledWith({
firstName: 'Alexander',
lastName: 'Neu',
helpTextsEnabled: false,
});
expect(updateUser).toHaveBeenCalledWith(updated);
});
it('keeps the stored preference and reports an error when saving fails', async () => {
const updateUser = vi.fn();
await TestBed.configureTestingModule({
imports: [Profile],
providers: [
provideRouter([]),
{
provide: AuthApi,
useValue: { updateProfile: vi.fn(() => throwError(() => new Error('failed'))) },
},
{
provide: AuthStore,
useValue: {
currentUser: signal({
id: 42,
email: 'alex@example.com',
firstName: 'Alex',
lastName: 'Muster',
helpTextsEnabled: true,
}),
updateUser,
},
},
{ provide: HelpAccessService, useValue: { canOpenGuide: signal(true) } },
],
}).compileComponents();
const fixture = TestBed.createComponent(Profile);
const snackBar = fixture.debugElement.injector.get(MatSnackBar);
const open = vi.spyOn(snackBar, 'open');
fixture.detectChanges();
fixture.componentInstance['form'].patchValue({ helpTextsEnabled: false } as any);
fixture.componentInstance['save']();
expect(updateUser).not.toHaveBeenCalled();
expect(open).toHaveBeenCalledWith('Profil konnte nicht gespeichert werden.', undefined, {
duration: 5000,
});
});
});

View File

@@ -7,8 +7,10 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { AuthApi, UpdateProfileRequest } from '../../../../core/auth/auth-api';
import { AuthStore } from '../../../../core/auth/auth-store';
import { HelpAccessService } from '../../../../core/help/help-access';
@Component({
selector: 'app-profile',
@@ -21,6 +23,7 @@ import { AuthStore } from '../../../../core/auth/auth-store';
MatIconModule,
MatInputModule,
MatSnackBarModule,
MatSlideToggleModule,
],
templateUrl: './profile.html',
styleUrl: './profile.scss',
@@ -28,13 +31,16 @@ import { AuthStore } from '../../../../core/auth/auth-store';
export class Profile {
private readonly authApi = inject(AuthApi);
private readonly authStore = inject(AuthStore);
private readonly helpAccess = inject(HelpAccessService);
private readonly snackBar = inject(MatSnackBar);
protected readonly saving = signal(false);
protected readonly canOpenGuide = this.helpAccess.canOpenGuide;
protected readonly form = inject(FormBuilder).nonNullable.group({
firstName: [this.authStore.currentUser()?.firstName ?? '', Validators.required],
lastName: [this.authStore.currentUser()?.lastName ?? '', Validators.required],
oldPassword: [''],
password: ['', Validators.minLength(6)],
helpTextsEnabled: [this.authStore.currentUser()?.helpTextsEnabled !== false],
});
protected save(): void {
@@ -47,6 +53,7 @@ export class Profile {
const request: UpdateProfileRequest = {
firstName: value.firstName.trim(),
lastName: value.lastName.trim(),
helpTextsEnabled: value.helpTextsEnabled,
};
if (value.password) {
request.oldPassword = value.oldPassword;
@@ -60,7 +67,12 @@ export class Profile {
this.saving.set(false);
this.snackBar.open('Profil wurde gespeichert.', undefined, { duration: 4000 });
},
error: () => this.saving.set(false),
error: () => {
this.saving.set(false);
this.snackBar.open('Profil konnte nicht gespeichert werden.', undefined, {
duration: 5000,
});
},
});
}
}

View File

@@ -5,6 +5,15 @@
<p>Teile Kassenstand, Mitgliedersalden, Strafenkatalog und Buchungsverläufe.</p>
</header>
<app-context-help
title="Öffentliche Freigabe bewusst nutzen"
[hints]="[
'Jede Person mit dem Link kann Teamstand, Salden, Strafenkatalog und Buchungsverläufe sehen.',
'Erneuere den Link, wenn er unerwünscht weitergegeben wurde.',
]"
sectionId="sharing"
/>
@if (loading()) {
<div class="state"><mat-spinner diameter="38" /><span>Freigabe wird geladen …</span></div>
} @else if (loadFailed()) {

View File

@@ -79,6 +79,8 @@ describe('PublicAccess', () => {
it('allows a captain to activate sharing', async () => {
const { fixture, setEnabled } = await setup(3, false);
expect(fixture.nativeElement.textContent).toContain('Öffentliche Freigabe bewusst nutzen');
fixture.componentInstance['toggleAccess']();
expect(setEnabled).toHaveBeenCalledWith(7, true);

View File

@@ -14,6 +14,7 @@ import { PublicAccessApi } from '../../../../core/team/public-access-api';
import { TeamStore } from '../../../../core/team/team-store';
import { PublicAccessStatus } from '../../../../models/public-access.model';
import { ConfirmDialog, ConfirmDialogData } from '../../../../shared/confirm-dialog/confirm-dialog';
import { ContextHelp } from '../../../../shared/context-help/context-help';
@Component({
selector: 'app-public-access',
@@ -25,6 +26,7 @@ import { ConfirmDialog, ConfirmDialogData } from '../../../../shared/confirm-dia
MatIconModule,
MatInputModule,
MatProgressSpinnerModule,
ContextHelp,
],
templateUrl: './public-access.html',
styleUrl: './public-access.scss',