Compare commits
3 Commits
f9516bbc4d
...
firebase-u
| Author | SHA1 | Date | |
|---|---|---|---|
| ecb25ee322 | |||
| 2173ad6abf | |||
| 3bd359ee9e |
15
angular.json
15
angular.json
@@ -64,7 +64,7 @@
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kB",
|
||||
"maximumError": "1MB"
|
||||
"maximumError": "10MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
@@ -95,9 +95,20 @@
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular/build:unit-test"
|
||||
"builder": "@angular/build:unit-test",
|
||||
"options": {
|
||||
"runner": "vitest",
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"setupFiles": [
|
||||
"src/test-vitest.ts"
|
||||
],
|
||||
"runnerConfig": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"analytics": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/wgenerator'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
||||
3527
package-lock.json
generated
3527
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -47,22 +47,15 @@
|
||||
"@angular/cli": "^21.2.1",
|
||||
"@angular/compiler-cli": "^21.2.2",
|
||||
"@angular/language-service": "^21.2.2",
|
||||
"@types/jasmine": "~6.0.0",
|
||||
"@types/jasminewd2": "~2.0.13",
|
||||
"@typescript-eslint/eslint-plugin": "^8.57.0",
|
||||
"@typescript-eslint/parser": "^8.57.0",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"firebase-tools": "^15.9.1",
|
||||
"jasmine-core": "~6.1.0",
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
"karma": "~6.4.4",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage-istanbul-reporter": "~3.0.3",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "^2.2.0",
|
||||
"jsdom": "^29.0.0",
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "~5.9.3"
|
||||
"typescript": "~5.9.3",
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<app-navigation></app-navigation>
|
||||
|
||||
<div [@fader]="o.isActivated ? o.activatedRoute : ''" class="content">
|
||||
<router-outlet #o="outlet"></router-outlet>
|
||||
<div class="content">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {AppComponent} from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule, AppComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {BrandComponent} from './brand.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('BrandComponent', () => {
|
||||
let component: BrandComponent;
|
||||
let fixture: ComponentFixture<BrandComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [BrandComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BrandComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {NewUserComponent} from './new-user.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('NewUserComponent', () => {
|
||||
let component: NewUserComponent;
|
||||
let fixture: ComponentFixture<NewUserComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NewUserComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NewUserComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {GuestComponent} from './guest.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('GuestComponent', () => {
|
||||
let component: GuestComponent;
|
||||
let fixture: ComponentFixture<GuestComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GuestComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GuestComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {LegalComponent} from './legal.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('LegalComponent', () => {
|
||||
let component: LegalComponent;
|
||||
let fixture: ComponentFixture<LegalComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LegalComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LegalComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {LogoComponent} from './logo.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('LogoComponent', () => {
|
||||
let component: LogoComponent;
|
||||
let fixture: ComponentFixture<LogoComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LogoComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LogoComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {MonitorComponent} from './monitor.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('MonitorComponent', () => {
|
||||
let component: MonitorComponent;
|
||||
let fixture: ComponentFixture<MonitorComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MonitorComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MonitorComponent);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@if (show) {
|
||||
<div @fade>
|
||||
<app-card [closeIcon]="faIcon" [heading]="show.showType | showType" [subheading]="show.date.toDate() | date:'dd.MM.yyyy'" closeLink="/presentation/select">
|
||||
@if (!progress) {
|
||||
<div class="song">
|
||||
@@ -79,5 +78,4 @@
|
||||
<app-add-song [addedLive]="true" [showSongs]="showSongs" [show]="show" [songs]="songs$|async"></app-add-song>
|
||||
} }
|
||||
</app-card>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {RemoteComponent} from './remote.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('RemoteComponent', () => {
|
||||
let component: RemoteComponent;
|
||||
let fixture: ComponentFixture<RemoteComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [RemoteComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RemoteComponent);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {Router} from '@angular/router';
|
||||
import {of} from 'rxjs';
|
||||
import {firstValueFrom, of} from 'rxjs';
|
||||
import {GlobalSettingsService} from '../../../services/global-settings.service';
|
||||
import {ShowService} from '../../shows/services/show.service';
|
||||
import {SelectComponent} from './select.component';
|
||||
@@ -52,12 +52,11 @@ describe('SelectComponent', () => {
|
||||
expect(component.visible).toBeTrue();
|
||||
});
|
||||
|
||||
it('should expose recent shows sorted descending by date', done => {
|
||||
component.shows$.subscribe(shows => {
|
||||
it('should expose recent shows sorted descending by date', async () => {
|
||||
const shows = await firstValueFrom(component.shows$);
|
||||
|
||||
expect(showServiceSpy.list$).toHaveBeenCalledWith(true);
|
||||
expect(shows.map(show => show.id)).toEqual(['recent-a', 'recent-b']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should persist the selected show, trigger presentation reset and navigate', async () => {
|
||||
|
||||
@@ -5,8 +5,8 @@ import {PresentationService} from './presentation.service';
|
||||
describe('PresentationService', () => {
|
||||
let service: PresentationService;
|
||||
|
||||
beforeEach(() => {
|
||||
void TestBed.configureTestingModule({});
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(PresentationService);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,4 +2,5 @@ export interface FilterValues {
|
||||
time: number;
|
||||
owner: string;
|
||||
showType: string;
|
||||
archived: boolean;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
</mat-optgroup>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-checkbox formControlName="archived">Archiviert</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<i>Anzahl der Suchergebnisse: {{ shows?.length ?? 0 }}</i>
|
||||
<!-- <i>Anzahl der Suchergebnisse: {{ shows?.length ?? 0 }}</i>-->
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
.third {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
column-gap: 20px;
|
||||
.third,
|
||||
div[formGroup] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.third {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-mdc-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Component, DestroyRef, Input, inject} from '@angular/core';
|
||||
import {Component, DestroyRef, inject, Input} from '@angular/core';
|
||||
import {KeyValue} from '@angular/common';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
|
||||
@@ -14,28 +14,23 @@ import {MatFormField, MatLabel} from '@angular/material/form-field';
|
||||
import {MatSelect} from '@angular/material/select';
|
||||
import {MatOptgroup, MatOption} from '@angular/material/core';
|
||||
import {ShowTypePipe} from '../../../../widget-modules/pipes/show-type-translater/show-type.pipe';
|
||||
import {MatCheckbox} from '@angular/material/checkbox';
|
||||
|
||||
@Component({
|
||||
selector: 'app-filter',
|
||||
templateUrl: './filter.component.html',
|
||||
styleUrls: ['./filter.component.less'],
|
||||
imports: [ReactiveFormsModule, MatFormField, MatLabel, MatSelect, MatOption, MatOptgroup, ShowTypePipe],
|
||||
imports: [ReactiveFormsModule, MatFormField, MatLabel, MatSelect, MatOption, MatOptgroup, ShowTypePipe, MatCheckbox],
|
||||
})
|
||||
export class FilterComponent {
|
||||
private showService = inject(ShowService);
|
||||
private userService = inject(UserService);
|
||||
private filterStore = inject(FilterStoreService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
@Input() public shows: Show[] = [];
|
||||
|
||||
public showTypePublic = ShowService.SHOW_TYPE_PUBLIC;
|
||||
public showTypePrivate = ShowService.SHOW_TYPE_PRIVATE;
|
||||
|
||||
public filterFormGroup: FormGroup<{
|
||||
time: FormControl<number>;
|
||||
owner: FormControl<string | null>;
|
||||
showType: FormControl<string | null>;
|
||||
archived: FormControl<boolean>;
|
||||
}>;
|
||||
public times: KeyValue<number, string>[] = [
|
||||
{key: 1, value: 'letzter Monat'},
|
||||
@@ -43,8 +38,11 @@ export class FilterComponent {
|
||||
{key: 12, value: 'letztes Jahr'},
|
||||
{key: 99999, value: 'alle'},
|
||||
];
|
||||
|
||||
public owners: {key: string; value: string}[] = [];
|
||||
private showService = inject(ShowService);
|
||||
private userService = inject(UserService);
|
||||
private filterStore = inject(FilterStoreService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
public constructor() {
|
||||
const fb = inject(FormBuilder);
|
||||
@@ -53,6 +51,7 @@ export class FilterComponent {
|
||||
time: fb.nonNullable.control(1),
|
||||
owner: fb.control<string | null>(null),
|
||||
showType: fb.control<string | null>(null),
|
||||
archived: fb.nonNullable.control(false),
|
||||
});
|
||||
|
||||
this.filterStore.showFilter$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(filterValues => {
|
||||
@@ -61,6 +60,7 @@ export class FilterComponent {
|
||||
time: filterValues.time,
|
||||
owner: filterValues.owner || null,
|
||||
showType: filterValues.showType || null,
|
||||
archived: !!filterValues.archived,
|
||||
},
|
||||
{emitEvent: false}
|
||||
);
|
||||
@@ -69,6 +69,7 @@ export class FilterComponent {
|
||||
this.filterFormGroup.controls.time.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('time', value));
|
||||
this.filterFormGroup.controls.owner.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('owner', value ?? ''));
|
||||
this.filterFormGroup.controls.showType.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('showType', value ?? ''));
|
||||
this.filterFormGroup.controls.archived.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('archived', value));
|
||||
|
||||
this.owners$()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
|
||||
@@ -1,31 +1,42 @@
|
||||
<div>
|
||||
<!-- <app-list-header *appRole="['leader']"></app-list-header>-->
|
||||
<app-list-header *appRole="['leader']">
|
||||
@if (shows$ | async; as shows) {
|
||||
<app-filter [shows]="publicShows$ | async"></app-filter>
|
||||
}
|
||||
</app-list-header>
|
||||
|
||||
<ng-container *appRole="['leader']">
|
||||
@if (privateShows$ | async; as shows) { @if (shows.length > 0) {
|
||||
@if (showSidebar$ | async) {
|
||||
<app-sidebar>
|
||||
<div class="sidebar-content" sidebar>
|
||||
<app-filter [shows]="(publicShows$ | async) ?? []"></app-filter>
|
||||
</div>
|
||||
<div content>
|
||||
@if (privateShows$ | async; as privateShows) {
|
||||
<app-card [padding]="false" heading="Meine Veranstaltungen">
|
||||
@for (show of shows | sortBy: 'desc':'date'; track trackBy($index, show)) {
|
||||
@for (show of privateShows; track trackBy($index, show)) {
|
||||
<app-list-item
|
||||
[routerLink]="show.id"
|
||||
[showStatusBadge]="show.published ? 'nicht gemeldet' : 'unveröffentlicht'"
|
||||
[showStatusBadgeType]="show.published ? 'error' : 'none'"
|
||||
[showStatusBadgeType]="show.archived ? 'warn' : show.published ? 'error' : 'none'"
|
||||
[showStatusBadge]="show.archived ? 'archiviert' : show.published ? 'nicht gemeldet' : 'unveröffentlicht'"
|
||||
[show]="show"
|
||||
></app-list-item>
|
||||
}
|
||||
<div *appRole="['leader']" class="list-action">
|
||||
<app-button [fullWidth]="true" [icon]="faNewShow" routerLink="new">Neue Veranstaltung anlegen </app-button>
|
||||
</div>
|
||||
</app-card>
|
||||
} }
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
@if (publicShows$ | async; as shows) { @if (shows.length > 0) {
|
||||
<app-card [padding]="false" heading="Veröffentlichte Veranstaltungen">
|
||||
@for (show of shows | sortBy: 'desc':'date'; track trackBy($index, show)) {
|
||||
@for (show of shows; track trackBy($index, show)) {
|
||||
<app-list-item [routerLink]="show.id" [show]="show"></app-list-item>
|
||||
}
|
||||
</app-card>
|
||||
} }
|
||||
</div>
|
||||
</app-sidebar>
|
||||
} @else {
|
||||
<div>
|
||||
@if (publicShows$ | async; as shows) { @if (shows.length > 0) {
|
||||
<app-card [padding]="false" heading="Veröffentlichte Veranstaltungen">
|
||||
@for (show of shows; track trackBy($index, show)) {
|
||||
<app-list-item [routerLink]="show.id" [show]="show"></app-list-item>
|
||||
}
|
||||
</app-card>
|
||||
} }
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.sidebar-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {BehaviorSubject, of} from 'rxjs';
|
||||
import {BehaviorSubject, firstValueFrom, of} from 'rxjs';
|
||||
import {skip, take} from 'rxjs/operators';
|
||||
import {ListComponent} from './list.component';
|
||||
import {ShowService} from '../services/show.service';
|
||||
import {UserService} from '../../../services/user/user.service';
|
||||
@@ -50,7 +51,7 @@ describe('ListComponent', () => {
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should list own drafts and pending published shows in my shows', done => {
|
||||
it('should list own drafts and pending published shows in my shows', async () => {
|
||||
shows$.next([
|
||||
createShow({id: 'draft-own', owner: 'user-1', published: false, reportedType: null}),
|
||||
createShow({id: 'pending-own', owner: 'user-1', published: true, reportedType: 'pending', date: {toDate: () => new Date('2026-03-02')}}),
|
||||
@@ -58,13 +59,11 @@ describe('ListComponent', () => {
|
||||
createShow({id: 'draft-other', owner: 'user-2', published: false, reportedType: null, date: {toDate: () => new Date('2026-03-04')}}),
|
||||
] as never);
|
||||
|
||||
component.privateShows$.subscribe(shows => {
|
||||
expect(shows.map(show => show.id)).toEqual(['draft-own', 'pending-own']);
|
||||
done();
|
||||
});
|
||||
const shows = await firstValueFrom(component.privateShows$);
|
||||
expect(shows.map(show => show.id)).toEqual(['pending-own', 'draft-own']);
|
||||
});
|
||||
|
||||
it('should ignore show filters for my shows', done => {
|
||||
it('should ignore show filters for my shows', async () => {
|
||||
const filterStore = TestBed.inject(FilterStoreService);
|
||||
filterStore.updateShowFilter({time: 0, showType: 'service-worship'});
|
||||
shows$.next([
|
||||
@@ -72,9 +71,36 @@ describe('ListComponent', () => {
|
||||
createShow({id: 'pending-own', owner: 'user-1', published: true, reportedType: 'pending', showType: 'home-group', date: {toDate: () => new Date('2026-03-05')}}),
|
||||
] as never);
|
||||
|
||||
component.privateShows$.subscribe(shows => {
|
||||
expect(shows.map(show => show.id)).toEqual(['older-draft', 'pending-own']);
|
||||
done();
|
||||
});
|
||||
const shows = await firstValueFrom(component.privateShows$);
|
||||
expect(shows.map(show => show.id)).toEqual(['pending-own', 'older-draft']);
|
||||
});
|
||||
|
||||
it('should hide archived own shows until archived filter is enabled', async () => {
|
||||
const filterStore = TestBed.inject(FilterStoreService);
|
||||
shows$.next([
|
||||
createShow({id: 'draft-own', owner: 'user-1', published: false, reportedType: null, date: {toDate: () => new Date('2026-03-02')}}),
|
||||
createShow({id: 'archived-own', owner: 'user-1', published: true, archived: true, reportedType: 'reported', date: {toDate: () => new Date('2026-03-03')}}),
|
||||
] as never);
|
||||
|
||||
const initialShows = await firstValueFrom(component.privateShows$.pipe(take(1)));
|
||||
expect(initialShows.map(show => show.id)).toEqual(['draft-own']);
|
||||
|
||||
const updatedShowsPromise = firstValueFrom(component.privateShows$.pipe(skip(1), take(1)));
|
||||
filterStore.updateShowFilter({archived: true});
|
||||
const updatedShows = await updatedShowsPromise;
|
||||
expect(updatedShows.map(show => show.id)).toEqual(['archived-own', 'draft-own']);
|
||||
});
|
||||
|
||||
it('should sort public shows by date descending', async () => {
|
||||
const filterStore = TestBed.inject(FilterStoreService);
|
||||
filterStore.updateShowFilter({time: 99999});
|
||||
shows$.next([
|
||||
createShow({id: 'old-public', owner: 'user-2', published: true, archived: false, date: {toDate: () => new Date('2026-01-01')}}),
|
||||
createShow({id: 'new-public', owner: 'user-3', published: true, archived: false, date: {toDate: () => new Date('2026-03-10')}}),
|
||||
createShow({id: 'mid-public', owner: 'user-4', published: true, archived: false, date: {toDate: () => new Date('2026-02-05')}}),
|
||||
] as never);
|
||||
|
||||
const shows = await firstValueFrom(component.publicShows$.pipe(take(1)));
|
||||
expect(shows.map(show => show.id)).toEqual(['new-public', 'mid-public', 'old-public']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,23 +7,25 @@ import {FilterValues} from './filter/filter-values';
|
||||
import {RouterLink} from '@angular/router';
|
||||
import {map, switchMap} from 'rxjs/operators';
|
||||
import {FilterStoreService} from '../../../services/filter-store.service';
|
||||
import {RoleDirective} from '../../../services/user/role.directive';
|
||||
import {ListHeaderComponent} from '../../../widget-modules/components/list-header/list-header.component';
|
||||
import {AsyncPipe} from '@angular/common';
|
||||
import {FilterComponent} from './filter/filter.component';
|
||||
import {CardComponent} from '../../../widget-modules/components/card/card.component';
|
||||
import {ListItemComponent} from './list-item/list-item.component';
|
||||
import {SortByPipe} from '../../../widget-modules/pipes/sort-by/sort-by.pipe';
|
||||
import {UserService} from '../../../services/user/user.service';
|
||||
import {SidebarComponent} from '../../../widget-modules/components/sidebar/sidebar.component';
|
||||
import {ButtonComponent} from '../../../widget-modules/components/button/button.component';
|
||||
import {faPlus} from '@fortawesome/free-solid-svg-icons';
|
||||
import {RoleDirective} from '../../../services/user/role.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list',
|
||||
templateUrl: './list.component.html',
|
||||
styleUrls: ['./list.component.less'],
|
||||
animations: [fade],
|
||||
imports: [RoleDirective, ListHeaderComponent, FilterComponent, CardComponent, ListItemComponent, RouterLink, AsyncPipe, SortByPipe],
|
||||
imports: [FilterComponent, CardComponent, ListItemComponent, RouterLink, AsyncPipe, SidebarComponent, ButtonComponent, RoleDirective],
|
||||
})
|
||||
export class ListComponent {
|
||||
public faNewShow = faPlus;
|
||||
private showService = inject(ShowService);
|
||||
private filterStore = inject(FilterStoreService);
|
||||
private userService = inject(UserService);
|
||||
@@ -32,9 +34,24 @@ export class ListComponent {
|
||||
public lastMonths$ = this.filter$.pipe(map((filterValues: FilterValues) => filterValues.time || 1));
|
||||
public owner$ = this.filter$.pipe(map((filterValues: FilterValues) => filterValues.owner));
|
||||
public showType$ = this.filter$.pipe(map((filterValues: FilterValues) => filterValues.showType));
|
||||
public archived$ = this.filter$.pipe(map((filterValues: FilterValues) => !!filterValues.archived));
|
||||
public shows$ = this.showService.list$();
|
||||
public privateShows$ = combineLatest([this.shows$, this.userService.user$]).pipe(
|
||||
map(([shows, user]) => shows.filter(show => show.owner === user?.id).filter(show => !show.published || show.reportedType === 'pending'))
|
||||
public ownShows$ = this.showService.list$(false, true);
|
||||
public privateShows$ = combineLatest([this.ownShows$, this.userService.user$, this.archived$]).pipe(
|
||||
map(([shows, user, showArchived]) =>
|
||||
shows.filter(show => {
|
||||
if (show.owner !== user?.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (show.archived) {
|
||||
return showArchived;
|
||||
}
|
||||
|
||||
return !show.published || show.reportedType === 'pending';
|
||||
})
|
||||
),
|
||||
map(shows => this.sortShowsByDateDesc(shows))
|
||||
);
|
||||
public queriedPublicShows$ = this.lastMonths$.pipe(switchMap(lastMonths => this.showService.listPublicSince$(lastMonths)));
|
||||
public fallbackPublicShows$ = combineLatest([this.shows$, this.lastMonths$]).pipe(
|
||||
@@ -46,9 +63,10 @@ export class ListComponent {
|
||||
map(([queriedShows, fallbackShows, owner, showType]) => {
|
||||
const shows = queriedShows.length > 0 || fallbackShows.length === 0 ? queriedShows : fallbackShows;
|
||||
|
||||
return shows.filter(show => !owner || show.owner === owner).filter(show => !showType || show.showType === showType);
|
||||
return this.sortShowsByDateDesc(shows.filter(show => !owner || show.owner === owner).filter(show => !showType || show.showType === showType));
|
||||
})
|
||||
);
|
||||
public showSidebar$ = this.userService.user$.pipe(map(user => this.hasSidebarAccess(user?.role)));
|
||||
|
||||
public trackBy = (index: number, show: unknown) => (show as Show).id;
|
||||
|
||||
@@ -58,4 +76,17 @@ export class ListComponent {
|
||||
startDate.setDate(startDate.getDate() - lastMonths * 30);
|
||||
return show.date.toDate() >= startDate;
|
||||
}
|
||||
|
||||
private sortShowsByDateDesc(shows: Show[]): Show[] {
|
||||
return [...shows].sort((left, right) => right.date.toDate().getTime() - left.date.toDate().getTime());
|
||||
}
|
||||
|
||||
private hasSidebarAccess(role: string | null | undefined): boolean {
|
||||
if (!role) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const roles = role.split(';').map(item => item.trim());
|
||||
return roles.includes('admin') || roles.includes('leader');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {NewComponent} from './new.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('NewComponent', () => {
|
||||
let component: NewComponent;
|
||||
let fixture: ComponentFixture<NewComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NewComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NewComponent);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {BehaviorSubject, of} from 'rxjs';
|
||||
import {BehaviorSubject, firstValueFrom, of} from 'rxjs';
|
||||
import {ShowDataService} from './show-data.service';
|
||||
import {ShowService} from './show.service';
|
||||
import {UserService} from '../../../services/user/user.service';
|
||||
@@ -8,6 +8,7 @@ describe('ShowService', () => {
|
||||
let service: ShowService;
|
||||
let showDataServiceSpy: jasmine.SpyObj<ShowDataService>;
|
||||
let user$: BehaviorSubject<unknown>;
|
||||
let shows$: BehaviorSubject<unknown[]>;
|
||||
const shows = [
|
||||
{id: 'show-1', owner: 'user-1', published: false, archived: false},
|
||||
{id: 'show-2', owner: 'other-user', published: true, archived: false},
|
||||
@@ -16,8 +17,9 @@ describe('ShowService', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
user$ = new BehaviorSubject<unknown>({id: 'user-1'});
|
||||
shows$ = new BehaviorSubject<unknown[]>(shows as unknown[]);
|
||||
showDataServiceSpy = jasmine.createSpyObj<ShowDataService>('ShowDataService', ['read$', 'listPublicSince$', 'update', 'add'], {
|
||||
list$: of(shows) as unknown as ShowDataService['list$'],
|
||||
list$: shows$.asObservable() as unknown as ShowDataService['list$'],
|
||||
});
|
||||
showDataServiceSpy.read$.and.returnValue(of(shows[0]));
|
||||
showDataServiceSpy.listPublicSince$.and.returnValue(of([shows[1]]));
|
||||
@@ -38,34 +40,39 @@ describe('ShowService', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should list published shows and own drafts, but exclude archived ones', done => {
|
||||
service.list$().subscribe(result => {
|
||||
it('should list published shows and own drafts, but exclude archived ones', async () => {
|
||||
const result = await firstValueFrom(service.list$());
|
||||
expect(result.map(show => show.id)).toEqual(['show-1', 'show-2']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter out private drafts when publishedOnly is true', done => {
|
||||
service.list$(true).subscribe(result => {
|
||||
it('should filter out private drafts when publishedOnly is true', async () => {
|
||||
const result = await firstValueFrom(service.list$(true));
|
||||
expect(result.map(show => show.id)).toEqual(['show-2']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should delegate public listing to the data service', done => {
|
||||
service.listPublicSince$(6).subscribe(result => {
|
||||
expect(result).toEqual([shows[1]]);
|
||||
it('should include own archived shows when requested', async () => {
|
||||
const result = await firstValueFrom(service.list$(false, true));
|
||||
expect(result.map(show => show.id)).toEqual(['show-1', 'show-2', 'show-3']);
|
||||
});
|
||||
|
||||
it('should not include archived shows from other users when requested', async () => {
|
||||
shows$.next([
|
||||
...(shows as unknown as unknown[]),
|
||||
{id: 'show-4', owner: 'other-user', published: true, archived: true},
|
||||
]);
|
||||
|
||||
const result = await firstValueFrom(service.list$(false, true));
|
||||
expect(result.map(show => show.id)).toEqual(['show-1', 'show-2', 'show-3']);
|
||||
});
|
||||
|
||||
it('should delegate public listing to the data service', async () => {
|
||||
await expectAsync(firstValueFrom(service.listPublicSince$(6))).toBeResolvedTo([shows[1]]);
|
||||
expect(showDataServiceSpy.listPublicSince$).toHaveBeenCalledWith(6);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should delegate reads to the data service', done => {
|
||||
service.read$('show-1').subscribe(result => {
|
||||
expect(result).toEqual(shows[0]);
|
||||
it('should delegate reads to the data service', async () => {
|
||||
await expectAsync(firstValueFrom(service.read$('show-1'))).toBeResolvedTo(shows[0]);
|
||||
expect(showDataServiceSpy.read$).toHaveBeenCalledWith('show-1');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should delegate updates to the data service', async () => {
|
||||
|
||||
@@ -20,13 +20,17 @@ export class ShowService {
|
||||
public read$ = (showId: string): Observable<Show | null> => this.showDataService.read$(showId);
|
||||
public listPublicSince$ = (lastMonths: number): Observable<Show[]> => this.showDataService.listPublicSince$(lastMonths);
|
||||
|
||||
public list$(publishedOnly = false): Observable<Show[]> {
|
||||
public list$(publishedOnly = false, includeOwnArchived = false): Observable<Show[]> {
|
||||
return this.userService.user$.pipe(
|
||||
switchMap(
|
||||
() => this.showDataService.list$,
|
||||
(user: User | null, shows: Show[]) => ({user, shows})
|
||||
),
|
||||
map(s => s.shows.filter(show => !show.archived).filter(show => show.published || (show.owner === s.user?.id && !publishedOnly)))
|
||||
map(s =>
|
||||
s.shows
|
||||
.filter(show => !show.archived || (includeOwnArchived && show.owner === s.user?.id))
|
||||
.filter(show => show.published || (show.owner === s.user?.id && !publishedOnly))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {SongComponent} from './song.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('SongComponent', () => {
|
||||
let component: SongComponent;
|
||||
let fixture: ComponentFixture<SongComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SongComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SongComponent);
|
||||
|
||||
@@ -4,6 +4,7 @@ import {NewComponent} from './new/new.component';
|
||||
import {ListComponent} from './list/list.component';
|
||||
import {ShowComponent} from './show/show.component';
|
||||
import {EditComponent} from './edit/edit.component';
|
||||
import {RoleGuard} from '../../widget-modules/guards/role.guard';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -14,6 +15,10 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'new',
|
||||
component: NewComponent,
|
||||
canActivate: [RoleGuard],
|
||||
data: {
|
||||
requiredRoles: ['leader'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ':showId/edit',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {of} from 'rxjs';
|
||||
import {firstValueFrom, of} from 'rxjs';
|
||||
import {SongService} from './song.service';
|
||||
import {SongListResolver} from './song-list.resolver';
|
||||
|
||||
@@ -22,11 +22,8 @@ describe('SongListResolver', () => {
|
||||
expect(resolver).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should resolve the first emitted song list from the service', done => {
|
||||
resolver.resolve().subscribe(songs => {
|
||||
it('should resolve the first emitted song list from the service', async () => {
|
||||
await expectAsync(firstValueFrom(resolver.resolve())).toBeResolvedTo([{id: 'song-1', title: 'Amazing Grace'}] as never);
|
||||
expect(songServiceSpy.listLoaded$).toHaveBeenCalled();
|
||||
expect(songs).toEqual([{id: 'song-1', title: 'Amazing Grace'}] as never);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {of} from 'rxjs';
|
||||
import {firstValueFrom, of} from 'rxjs';
|
||||
import {SongDataService} from './song-data.service';
|
||||
import {SongService} from './song.service';
|
||||
import {UserService} from '../../../services/user/user.service';
|
||||
@@ -41,11 +41,8 @@ describe('SongService', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should list songs from the data service', done => {
|
||||
service.list$().subscribe(songs => {
|
||||
expect(songs).toEqual([song]);
|
||||
done();
|
||||
});
|
||||
it('should list songs from the data service', async () => {
|
||||
await expectAsync(firstValueFrom(service.list$())).toBeResolvedTo([song]);
|
||||
});
|
||||
|
||||
it('should delegate reads to the data service', async () => {
|
||||
|
||||
@@ -38,7 +38,7 @@ Bridge
|
||||
Cool bridge without any chords
|
||||
`;
|
||||
|
||||
beforeEach(() => void TestBed.configureTestingModule({}));
|
||||
beforeEach(async () => await TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: TextRenderingService = TestBed.inject(TextRenderingService);
|
||||
@@ -478,13 +478,13 @@ Text`;
|
||||
const service: TextRenderingService = TestBed.inject(TextRenderingService);
|
||||
const text = 'Strophe\nC\tG\ta\nText';
|
||||
|
||||
void expect(service.validateChordNotation(text)).toContain(
|
||||
void expect(service.validateChordNotation(text)).toEqual(expect.arrayContaining([
|
||||
jasmine.objectContaining({
|
||||
lineNumber: 2,
|
||||
token: '\t',
|
||||
reason: 'tab_character',
|
||||
})
|
||||
);
|
||||
}),
|
||||
]));
|
||||
});
|
||||
|
||||
it('should not flag tabs on non chord lines', () => {
|
||||
|
||||
@@ -7,8 +7,8 @@ import {Line} from './line';
|
||||
describe('TransposeService', () => {
|
||||
let service: TransposeService;
|
||||
|
||||
beforeEach(() => {
|
||||
void TestBed.configureTestingModule({});
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(TransposeService);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
.third {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
column-gap: 20px;
|
||||
.third,
|
||||
:host ::ng-deep form,
|
||||
div[formGroup] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.third {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-mdc-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {FilterComponent} from './filter.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('FilterComponent', () => {
|
||||
let component: FilterComponent;
|
||||
let fixture: ComponentFixture<FilterComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [FilterComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FilterComponent);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
@if (songs$ | async; as songs) {
|
||||
<div>
|
||||
<app-list-header [anyFilterActive]="anyFilterActive">
|
||||
<app-sidebar>
|
||||
<div sidebar class="sidebar-content">
|
||||
<app-filter [songs]="songs"></app-filter>
|
||||
</app-list-header>
|
||||
</div>
|
||||
<div content>
|
||||
<app-card [padding]="false">
|
||||
@for (song of songs; track trackBy($index, song)) {
|
||||
<div [routerLink]="song.id" class="list-item">
|
||||
@@ -38,6 +39,10 @@
|
||||
<div>{{ song.key }}</div>
|
||||
</div>
|
||||
}
|
||||
<div *appRole="['contributor']" class="list-action">
|
||||
<app-button [fullWidth]="true" [icon]="faNewSong" routerLink="new">Neuen Song anlegen</app-button>
|
||||
</div>
|
||||
</app-card>
|
||||
</div>
|
||||
</app-sidebar>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
.sidebar-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
padding: 5px 20px;
|
||||
display: grid;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {SongListComponent} from './song-list.component';
|
||||
import {of} from 'rxjs';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
@@ -12,8 +12,8 @@ describe('SongListComponent', () => {
|
||||
|
||||
const songs = [{id: 'song-1', title: 'title1', number: 1, text: '', flags: ''}];
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SongListComponent],
|
||||
providers: [
|
||||
{provide: ActivatedRoute, useValue: {data: of({songs})}},
|
||||
@@ -22,7 +22,7 @@ describe('SongListComponent', () => {
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SongListComponent);
|
||||
|
||||
@@ -6,15 +6,16 @@ import {fade} from '../../../animations';
|
||||
import {ActivatedRoute, RouterLink} from '@angular/router';
|
||||
import {filterSong} from '../../../services/filter.helper';
|
||||
import {FilterValues} from './filter/filter-values';
|
||||
import {faBalanceScaleRight, faCheck, faPencilRuler} from '@fortawesome/free-solid-svg-icons';
|
||||
import {faBalanceScaleRight, faCheck, faPencilRuler, faPlus} from '@fortawesome/free-solid-svg-icons';
|
||||
import {TextRenderingService} from '../services/text-rendering.service';
|
||||
import {FilterStoreService} from '../../../services/filter-store.service';
|
||||
import {AsyncPipe} from '@angular/common';
|
||||
import {ListHeaderComponent} from '../../../widget-modules/components/list-header/list-header.component';
|
||||
import {FilterComponent} from './filter/filter.component';
|
||||
import {CardComponent} from '../../../widget-modules/components/card/card.component';
|
||||
import {RoleDirective} from '../../../services/user/role.directive';
|
||||
import {FaIconComponent} from '@fortawesome/angular-fontawesome';
|
||||
import {SidebarComponent} from '../../../widget-modules/components/sidebar/sidebar.component';
|
||||
import {ButtonComponent} from '../../../widget-modules/components/button/button.component';
|
||||
|
||||
interface SongListItem extends Song {
|
||||
hasChordValidationIssues: boolean;
|
||||
@@ -26,20 +27,21 @@ interface SongListItem extends Song {
|
||||
styleUrls: ['./song-list.component.less'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: [fade],
|
||||
imports: [ListHeaderComponent, FilterComponent, CardComponent, RouterLink, RoleDirective, FaIconComponent, AsyncPipe],
|
||||
imports: [FilterComponent, CardComponent, RouterLink, RoleDirective, FaIconComponent, AsyncPipe, SidebarComponent, ButtonComponent],
|
||||
})
|
||||
export class SongListComponent {
|
||||
public faLegal = faBalanceScaleRight;
|
||||
public faDraft = faPencilRuler;
|
||||
public faFinal = faCheck;
|
||||
public faNewSong = faPlus;
|
||||
private route = inject(ActivatedRoute);
|
||||
private textRenderingService = inject(TextRenderingService);
|
||||
private filterStore = inject(FilterStoreService);
|
||||
|
||||
public anyFilterActive = false;
|
||||
public songs$: Observable<SongListItem[]> = combineLatest([
|
||||
this.filterStore.songFilter$,
|
||||
this.route.data.pipe(map(data => (data['songs'] as Song[]).slice().sort((a, b) => a.number - b.number))),
|
||||
]).pipe(
|
||||
map(([filter, songs]) => {
|
||||
this.anyFilterActive = this.checkIfFilterActive(filter);
|
||||
return songs
|
||||
.filter(song => this.filter(song, filter))
|
||||
.map(song => ({
|
||||
@@ -49,9 +51,6 @@ export class SongListComponent {
|
||||
.sort((a, b) => a.title?.localeCompare(b.title));
|
||||
})
|
||||
);
|
||||
public faLegal = faBalanceScaleRight;
|
||||
public faDraft = faPencilRuler;
|
||||
public faFinal = faCheck;
|
||||
|
||||
public trackBy = (index: number, show: SongListItem) => show.id;
|
||||
|
||||
@@ -65,10 +64,6 @@ export class SongListComponent {
|
||||
return baseFilter;
|
||||
}
|
||||
|
||||
private checkIfFilterActive(filter: FilterValues): boolean {
|
||||
return !!filter.q || !!filter.type || !!filter.key || !!filter.legalType || !!filter.flag;
|
||||
}
|
||||
|
||||
private checkFlag(flag: string, flags: string) {
|
||||
if (!flags) {
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {EditFileComponent} from './edit-file.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('EditFileComponent', () => {
|
||||
let component: EditFileComponent;
|
||||
let fixture: ComponentFixture<EditFileComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [EditFileComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EditFileComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {FileComponent} from './file.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('FileComponent', () => {
|
||||
let component: FileComponent;
|
||||
let fixture: ComponentFixture<FileComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [FileComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FileComponent);
|
||||
|
||||
@@ -4,8 +4,8 @@ import {EditSongGuard} from './edit-song.guard';
|
||||
describe('EditSongGuard', () => {
|
||||
let guard: EditSongGuard;
|
||||
|
||||
beforeEach(() => {
|
||||
void TestBed.configureTestingModule({});
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({});
|
||||
guard = TestBed.inject(EditSongGuard);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {EditSongComponent} from './edit-song.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('EditSongComponent', () => {
|
||||
let component: EditSongComponent;
|
||||
let fixture: ComponentFixture<EditSongComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [EditSongComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EditSongComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {SaveDialogComponent} from './save-dialog.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('SaveDialogComponent', () => {
|
||||
let component: SaveDialogComponent;
|
||||
let fixture: ComponentFixture<SaveDialogComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SaveDialogComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SaveDialogComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {EditComponent} from './edit.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('EditComponent', () => {
|
||||
let component: EditComponent;
|
||||
let fixture: ComponentFixture<EditComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [EditComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EditComponent);
|
||||
|
||||
@@ -4,8 +4,8 @@ import {EditService} from './edit.service';
|
||||
describe('EditService', () => {
|
||||
let service: EditService;
|
||||
|
||||
beforeEach(() => {
|
||||
void TestBed.configureTestingModule({});
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(EditService);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {HistoryComponent} from './history.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('HistoryComponent', () => {
|
||||
let component: HistoryComponent;
|
||||
let fixture: ComponentFixture<HistoryComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HistoryComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HistoryComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {Storage} from '@angular/fire/storage';
|
||||
|
||||
import {FileComponent} from './file.component';
|
||||
@@ -7,12 +7,12 @@ describe('FileComponent', () => {
|
||||
let component: FileComponent;
|
||||
let fixture: ComponentFixture<FileComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [FileComponent],
|
||||
providers: [{provide: Storage, useValue: {}}],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FileComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {NewComponent} from './new.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('NewComponent', () => {
|
||||
let component: NewComponent;
|
||||
let fixture: ComponentFixture<NewComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NewComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NewComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {SongComponent} from './song.component';
|
||||
import {of} from 'rxjs';
|
||||
@@ -17,7 +17,7 @@ describe('SongComponent', () => {
|
||||
params: of({songId: '4711'}),
|
||||
};
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
beforeEach(async () => {
|
||||
const songServiceSpy = jasmine.createSpyObj<SongService>('SongService', ['read$']);
|
||||
const fileDataServiceSpy = jasmine.createSpyObj<FileDataService>('FileDataService', ['read$']);
|
||||
const userServiceSpy = jasmine.createSpyObj<UserService>('UserService', ['incSongCount', 'decSongCount'], {
|
||||
@@ -33,7 +33,7 @@ describe('SongComponent', () => {
|
||||
userServiceSpy.loggedIn$ = jasmine.createSpy('loggedIn$').and.returnValue(of(true));
|
||||
userServiceSpy.getUserbyId$ = jasmine.createSpy('getUserbyId$').and.returnValue(of({name: 'Benjamin'}));
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SongComponent],
|
||||
providers: [
|
||||
{provide: ActivatedRoute, useValue: mockActivatedRoute},
|
||||
@@ -44,7 +44,7 @@ describe('SongComponent', () => {
|
||||
{provide: ShowSongService, useValue: showSongServiceSpy},
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SongComponent);
|
||||
|
||||
@@ -6,6 +6,7 @@ import {EditComponent} from './song/edit/edit.component';
|
||||
import {NewComponent} from './song/new/new.component';
|
||||
import {EditSongGuard} from './song/edit/edit-song.guard';
|
||||
import {SongListResolver} from './services/song-list.resolver';
|
||||
import {RoleGuard} from '../../widget-modules/guards/role.guard';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -19,6 +20,10 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'new',
|
||||
component: NewComponent,
|
||||
canActivate: [RoleGuard],
|
||||
data: {
|
||||
requiredRoles: ['contributor'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ':songId/edit',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {InfoComponent} from './info.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('InfoComponent', () => {
|
||||
let component: InfoComponent;
|
||||
let fixture: ComponentFixture<InfoComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [InfoComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InfoComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {UserComponent} from './user.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('UserComponent', () => {
|
||||
let component: UserComponent;
|
||||
let fixture: ComponentFixture<UserComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [UserComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(UserComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {UsersComponent} from './users.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('UsersComponent', () => {
|
||||
let component: UsersComponent;
|
||||
let fixture: ComponentFixture<UsersComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [UsersComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(UsersComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {LoginComponent} from './login.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LoginComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {LogoutComponent} from './logout.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('LogoutComponent', () => {
|
||||
let component: LogoutComponent;
|
||||
let fixture: ComponentFixture<LogoutComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LogoutComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LogoutComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {NewComponent} from './new.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('NewComponent', () => {
|
||||
let component: NewComponent;
|
||||
let fixture: ComponentFixture<NewComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NewComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NewComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {PasswordSendComponent} from './password-send.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('PasswordSendComponent', () => {
|
||||
let component: PasswordSendComponent;
|
||||
let fixture: ComponentFixture<PasswordSendComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PasswordSendComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PasswordSendComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {PasswordComponent} from './password.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('PasswordComponent', () => {
|
||||
let component: PasswordComponent;
|
||||
let fixture: ComponentFixture<PasswordComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PasswordComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PasswordComponent);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {of} from 'rxjs';
|
||||
import {firstValueFrom, of} from 'rxjs';
|
||||
import {DbService} from './db.service';
|
||||
import {ConfigService} from './config.service';
|
||||
|
||||
@@ -27,11 +27,8 @@ describe('ConfigService', () => {
|
||||
expect(dbServiceSpy.doc$).toHaveBeenCalledWith('global/config');
|
||||
});
|
||||
|
||||
it('should expose the shared config stream via get$', done => {
|
||||
service.get$().subscribe(config => {
|
||||
expect(config).toEqual({copyright: 'CCLI'} as never);
|
||||
done();
|
||||
});
|
||||
it('should expose the shared config stream via get$', async () => {
|
||||
await expectAsync(firstValueFrom(service.get$())).toBeResolvedTo({copyright: 'CCLI'} as never);
|
||||
});
|
||||
|
||||
it('should resolve the current config via get()', async () => {
|
||||
|
||||
@@ -4,12 +4,11 @@ import {Firestore} from '@angular/fire/firestore';
|
||||
import {DbService} from './db.service';
|
||||
|
||||
describe('DbService', () => {
|
||||
beforeEach(
|
||||
() =>
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [{provide: Firestore, useValue: {}}],
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
const service: DbService = TestBed.inject(DbService);
|
||||
|
||||
@@ -15,6 +15,7 @@ const DEFAULT_SHOW_FILTER: ShowFilterValues = {
|
||||
time: 1,
|
||||
owner: '',
|
||||
showType: '',
|
||||
archived: false,
|
||||
};
|
||||
|
||||
@Injectable({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {of} from 'rxjs';
|
||||
import {firstValueFrom, of} from 'rxjs';
|
||||
import {DbService} from './db.service';
|
||||
import {GlobalSettingsService} from './global-settings.service';
|
||||
|
||||
@@ -30,11 +30,8 @@ describe('GlobalSettingsService', () => {
|
||||
expect(dbServiceSpy.doc$).toHaveBeenCalledWith('global/static');
|
||||
});
|
||||
|
||||
it('should expose the shared settings stream via the getter', done => {
|
||||
service.get$.subscribe(settings => {
|
||||
expect(settings).toEqual({churchName: 'ICF'} as never);
|
||||
done();
|
||||
});
|
||||
it('should expose the shared settings stream via the getter', async () => {
|
||||
await expectAsync(firstValueFrom(service.get$)).toBeResolvedTo({churchName: 'ICF'} as never);
|
||||
});
|
||||
|
||||
it('should update the static global settings document', async () => {
|
||||
|
||||
@@ -5,8 +5,8 @@ import {ScrollService} from './scroll.service';
|
||||
describe('ScrollService', () => {
|
||||
let service: ScrollService;
|
||||
|
||||
beforeEach(() => {
|
||||
void TestBed.configureTestingModule({});
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(ScrollService);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {of} from 'rxjs';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {firstValueFrom, of} from 'rxjs';
|
||||
import {UserService} from '../user.service';
|
||||
import {UserNameComponent} from './user-name.component';
|
||||
|
||||
@@ -8,15 +8,15 @@ describe('UserNameComponent', () => {
|
||||
let fixture: ComponentFixture<UserNameComponent>;
|
||||
let userServiceSpy: jasmine.SpyObj<UserService>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
beforeEach(async () => {
|
||||
userServiceSpy = jasmine.createSpyObj<UserService>('UserService', ['getUserbyId$']);
|
||||
userServiceSpy.getUserbyId$.and.returnValue(of({name: 'Benjamin'} as never));
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [UserNameComponent],
|
||||
providers: [{provide: UserService, useValue: userServiceSpy}],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(UserNameComponent);
|
||||
@@ -28,24 +28,18 @@ describe('UserNameComponent', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should resolve the user name when the userId input changes', done => {
|
||||
it('should resolve the user name when the userId input changes', async () => {
|
||||
component.userId = 'user-1';
|
||||
|
||||
component.name$?.subscribe(name => {
|
||||
expect(userServiceSpy.getUserbyId$).toHaveBeenCalledWith('user-1');
|
||||
expect(name).toBe('Benjamin');
|
||||
done();
|
||||
});
|
||||
await expectAsync(firstValueFrom(component.name$!)).toBeResolvedTo('Benjamin');
|
||||
});
|
||||
|
||||
it('should map missing users to null names', done => {
|
||||
it('should map missing users to null names', async () => {
|
||||
userServiceSpy.getUserbyId$.and.returnValue(of(null));
|
||||
|
||||
component.userId = 'missing-user';
|
||||
|
||||
component.name$?.subscribe(name => {
|
||||
expect(name).toBeNull();
|
||||
done();
|
||||
});
|
||||
await expectAsync(firstValueFrom(component.name$!)).toBeResolvedTo(null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {of} from 'rxjs';
|
||||
import {firstValueFrom, of} from 'rxjs';
|
||||
import {UserService} from './user.service';
|
||||
import {UserSessionService} from './user-session.service';
|
||||
import {UserSongUsageService} from './user-song-usage.service';
|
||||
@@ -54,17 +54,10 @@ describe('UserService', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should expose the session streams directly', done => {
|
||||
service.userId$.subscribe(userId => {
|
||||
expect(userId).toBe('user-1');
|
||||
service.user$.subscribe(user => {
|
||||
expect(user).toEqual({id: 'user-1'} as never);
|
||||
service.users$.subscribe(users => {
|
||||
expect(users).toEqual([{id: 'user-1'}] as never);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should expose the session streams directly', async () => {
|
||||
await expectAsync(firstValueFrom(service.userId$)).toBeResolvedTo('user-1');
|
||||
await expectAsync(firstValueFrom(service.user$)).toBeResolvedTo({id: 'user-1'} as never);
|
||||
await expectAsync(firstValueFrom(service.users$)).toBeResolvedTo([{id: 'user-1'}] as never);
|
||||
});
|
||||
|
||||
it('should delegate session operations to UserSessionService', async () => {
|
||||
@@ -85,20 +78,13 @@ describe('UserService', () => {
|
||||
expect(sessionSpy.createNewUser).toHaveBeenCalledWith('mail', 'Benjamin', 'secret');
|
||||
});
|
||||
|
||||
it('should delegate user lookup and loggedIn/list streams to UserSessionService', done => {
|
||||
service.getUserbyId$('user-2').subscribe(user => {
|
||||
expect(user).toEqual({id: 'user-2'} as never);
|
||||
service.loggedIn$().subscribe(loggedIn => {
|
||||
expect(loggedIn).toBeTrue();
|
||||
service.list$().subscribe(users => {
|
||||
expect(users).toEqual([{id: 'user-1'}] as never);
|
||||
it('should delegate user lookup and loggedIn/list streams to UserSessionService', async () => {
|
||||
await expectAsync(firstValueFrom(service.getUserbyId$('user-2'))).toBeResolvedTo({id: 'user-2'} as never);
|
||||
await expectAsync(firstValueFrom(service.loggedIn$())).toBeResolvedTo(true);
|
||||
await expectAsync(firstValueFrom(service.list$())).toBeResolvedTo([{id: 'user-1'}] as never);
|
||||
expect(sessionSpy.getUserbyId$).toHaveBeenCalledWith('user-2');
|
||||
expect(sessionSpy.loggedIn$).toHaveBeenCalled();
|
||||
expect(sessionSpy.list$).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should delegate song usage operations to UserSongUsageService', async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {AddSongComponent} from './add-song.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('AddSongComponent', () => {
|
||||
let component: AddSongComponent;
|
||||
let fixture: ComponentFixture<AddSongComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AddSongComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddSongComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {BrandComponent} from './brand.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('BrandComponent', () => {
|
||||
let component: BrandComponent;
|
||||
let fixture: ComponentFixture<BrandComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [BrandComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BrandComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {FilterComponent} from './filter.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('FilterComponent', () => {
|
||||
let component: FilterComponent;
|
||||
let fixture: ComponentFixture<FilterComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [FilterComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FilterComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {LinkComponent} from './link.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('LinkComponent', () => {
|
||||
let component: LinkComponent;
|
||||
let fixture: ComponentFixture<LinkComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LinkComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LinkComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {NavigationComponent} from './navigation.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('NavigationComponent', () => {
|
||||
let component: NavigationComponent;
|
||||
let fixture: ComponentFixture<NavigationComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NavigationComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NavigationComponent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {ButtonRowComponent} from './button-row.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('ButtonRowComponent', () => {
|
||||
let component: ButtonRowComponent;
|
||||
let fixture: ComponentFixture<ButtonRowComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ButtonRowComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ButtonRowComponent);
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
:host {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
:host(.full-width) {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
color: var(--text);
|
||||
transition: var(--transition);
|
||||
|
||||
:host(.full-width) & {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-active);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {ButtonComponent} from './button.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('ButtonComponent', () => {
|
||||
let component: ButtonComponent;
|
||||
let fixture: ComponentFixture<ButtonComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ButtonComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ButtonComponent);
|
||||
|
||||
@@ -9,8 +9,12 @@ import {FaIconComponent} from '@fortawesome/angular-fontawesome';
|
||||
templateUrl: './button.component.html',
|
||||
styleUrls: ['./button.component.less'],
|
||||
imports: [MatButton, FaIconComponent],
|
||||
host: {
|
||||
'[class.full-width]': 'fullWidth',
|
||||
},
|
||||
})
|
||||
export class ButtonComponent {
|
||||
@Input() public disabled = false;
|
||||
@Input() public fullWidth = false;
|
||||
@Input() public icon: IconProp | null = null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div [class.fullscreen]="fullscreen" [class.padding]="padding" class="card">
|
||||
<div @fade [class.fullscreen]="fullscreen" [class.padding]="padding" class="card">
|
||||
@if (closeLink && !fullscreen) {
|
||||
<button [routerLink]="closeLink" class="btn-close" mat-icon-button>
|
||||
<fa-icon [icon]="closeIcon"></fa-icon>
|
||||
|
||||
@@ -70,9 +70,10 @@
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
--icon-button-color: var(--text-soft);
|
||||
--icon-button-hover-color: var(--text);
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 15px;
|
||||
opacity: 0.7;
|
||||
color: var(--text-soft);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {CardComponent} from './card.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('CardComponent', () => {
|
||||
let component: CardComponent;
|
||||
let fixture: ComponentFixture<CardComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CardComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardComponent);
|
||||
|
||||
@@ -4,12 +4,14 @@ import {faTimes} from '@fortawesome/free-solid-svg-icons';
|
||||
import {MatIconButton} from '@angular/material/button';
|
||||
import {RouterLink} from '@angular/router';
|
||||
import {FaIconComponent} from '@fortawesome/angular-fontawesome';
|
||||
import {fade} from '../../../animations';
|
||||
|
||||
@Component({
|
||||
selector: 'app-card',
|
||||
templateUrl: './card.component.html',
|
||||
styleUrls: ['./card.component.less'],
|
||||
imports: [MatIconButton, RouterLink, FaIconComponent],
|
||||
animations: [fade],
|
||||
})
|
||||
export class CardComponent {
|
||||
@Input() public padding = true;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<div class="header">
|
||||
@if (showFilterButton) {
|
||||
<button (click)="onFilterClick()" [class.filter-active]="anyFilterActive" mat-icon-button>
|
||||
<fa-icon [icon]="faFilter"></fa-icon>
|
||||
</button>
|
||||
}
|
||||
<button mat-icon-button routerLink="new">
|
||||
<fa-icon [icon]="faNew"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (filterVisible || anyFilterActive) {
|
||||
@if (showFilterButton && (filterVisible || anyFilterActive)) {
|
||||
<div @fade>
|
||||
<app-card>
|
||||
<ng-content></ng-content>
|
||||
|
||||
@@ -10,11 +10,15 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
color: var(--primary-hover);
|
||||
|
||||
}
|
||||
|
||||
.filter-active {
|
||||
color: var(--danger);
|
||||
.header .mat-mdc-icon-button {
|
||||
--icon-button-color: var(--primary-hover);
|
||||
--icon-button-hover-color: var(--primary-active);
|
||||
}
|
||||
|
||||
.header .mat-mdc-icon-button.filter-active {
|
||||
--icon-button-color: var(--danger);
|
||||
--icon-button-hover-color: var(--danger);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {ListHeaderComponent} from './list-header.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('ListHeaderComponent', () => {
|
||||
let component: ListHeaderComponent;
|
||||
let fixture: ComponentFixture<ListHeaderComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ListHeaderComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ListHeaderComponent);
|
||||
|
||||
@@ -20,6 +20,7 @@ export class ListHeaderComponent {
|
||||
public filterVisible = false;
|
||||
@Output() public filterVisibleChanged = new EventEmitter<boolean>();
|
||||
@Input() public anyFilterActive = false;
|
||||
@Input() public showFilterButton = true;
|
||||
|
||||
public onFilterClick(): void {
|
||||
this.filterVisible = !this.filterVisible || this.anyFilterActive;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {LogoComponent} from './logo.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('LogoComponent', () => {
|
||||
let component: LogoComponent;
|
||||
let fixture: ComponentFixture<LogoComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LogoComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LogoComponent);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
button {
|
||||
.mat-mdc-button {
|
||||
--icon-button-color: var(--primary-color);
|
||||
--icon-button-hover-color: var(--primary-active);
|
||||
min-width: 0;
|
||||
padding: 0 var(--button-padding, 5px);
|
||||
font-size: var(--button-font-size, 1em);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {MenuButtonComponent} from './menu-button.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('MenuButtonComponent', () => {
|
||||
let component: MenuButtonComponent;
|
||||
let fixture: ComponentFixture<MenuButtonComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MenuButtonComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MenuButtonComponent);
|
||||
|
||||
@@ -1 +1,12 @@
|
||||
<aside></aside>
|
||||
<button class="sidebar-toggle" (click)="toggle()" [attr.aria-expanded]="!collapsed" aria-label="Sidebar umschalten" mat-icon-button type="button">
|
||||
<fa-icon [icon]="collapsed ? closedIcon : openIcon"></fa-icon>
|
||||
</button>
|
||||
<aside [class.collapsed]="collapsed">
|
||||
<div class="sidebar-toggle-placeholder" aria-hidden="true"></div>
|
||||
<div class="sidebar-body">
|
||||
<ng-content select="[sidebar]"></ng-content>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="content">
|
||||
<ng-content select="[content]"></ng-content>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,48 @@
|
||||
:host {
|
||||
--sidebar-width: 300px;
|
||||
--sidebar-toggle-size: 48px;
|
||||
--sidebar-toggle-offset: 12px;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
|
||||
align-items: start;
|
||||
box-sizing: border-box;
|
||||
transition: grid-template-columns 200ms ease;
|
||||
}
|
||||
|
||||
:host.collapsed {
|
||||
grid-template-columns: 0 minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
--icon-button-color: var(--primary-hover);
|
||||
--icon-button-hover-color: var(--primary-active);
|
||||
position: fixed;
|
||||
top: calc(50px + var(--sidebar-toggle-offset));
|
||||
left: var(--sidebar-toggle-offset);
|
||||
z-index: 11;
|
||||
color: var(--icon-button-color);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:host.collapsed .sidebar-toggle {
|
||||
--icon-button-color: var(--text-inverse);
|
||||
--icon-button-hover-color: var(--text-inverse);
|
||||
}
|
||||
|
||||
.sidebar-toggle fa-icon {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.sidebar-toggle:hover {
|
||||
color: var(--icon-button-hover-color);
|
||||
}
|
||||
|
||||
aside {
|
||||
width: 200px;
|
||||
width: var(--sidebar-width);
|
||||
height: calc(100vh - 50px);
|
||||
|
||||
position: fixed;
|
||||
@@ -8,4 +51,28 @@ aside {
|
||||
bottom: 0;
|
||||
|
||||
background: var(--surface);
|
||||
box-shadow: var(--shadow-card-2);
|
||||
overflow: hidden;
|
||||
transform: translateX(0);
|
||||
transition: transform 200ms ease;
|
||||
}
|
||||
|
||||
aside.collapsed {
|
||||
transform: translateX(calc(-1 * var(--sidebar-width)));
|
||||
}
|
||||
|
||||
.sidebar-toggle-placeholder {
|
||||
height: calc(var(--sidebar-toggle-size) + var(--sidebar-toggle-offset));
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.sidebar-body {
|
||||
height: calc(100% - var(--sidebar-toggle-size) - var(--sidebar-toggle-offset));
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-column: 2;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {MatIconButton} from '@angular/material/button';
|
||||
import {FaIconComponent} from '@fortawesome/angular-fontawesome';
|
||||
import {faBars, faChevronLeft} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sidebar',
|
||||
imports: [],
|
||||
imports: [MatIconButton, FaIconComponent],
|
||||
templateUrl: './sidebar.component.html',
|
||||
styleUrl: './sidebar.component.less',
|
||||
host: {
|
||||
'[class.collapsed]': 'collapsed',
|
||||
},
|
||||
})
|
||||
export class SidebarComponent {}
|
||||
export class SidebarComponent {
|
||||
public collapsed = true;
|
||||
public openIcon = faChevronLeft;
|
||||
public closedIcon = faBars;
|
||||
|
||||
public toggle(): void {
|
||||
this.collapsed = !this.collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {SongTextComponent} from './song-text.component';
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('SongTextComponent', () => {
|
||||
let component: SongTextComponent;
|
||||
let fixture: ComponentFixture<SongTextComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SongTextComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SongTextComponent);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {Router} from '@angular/router';
|
||||
import {of} from 'rxjs';
|
||||
import {firstValueFrom, of} from 'rxjs';
|
||||
import {UserService} from '../../services/user/user.service';
|
||||
import {RoleGuard} from './role.guard';
|
||||
|
||||
@@ -30,14 +30,13 @@ describe('RoleGuard', () => {
|
||||
expect(() => guard.canActivate({data: {}} as never)).toThrowError('requiredRoles is not defined!');
|
||||
});
|
||||
|
||||
it('should deny access when there is no current user', done => {
|
||||
guard.canActivate({data: {requiredRoles: ['leader']}} as never).subscribe(result => {
|
||||
expect(result).toEqual({commands: ['brand', 'new-user']} as never);
|
||||
done();
|
||||
});
|
||||
it('should deny access when there is no current user', async () => {
|
||||
await expectAsync(firstValueFrom(guard.canActivate({data: {requiredRoles: ['leader']}} as never))).toBeResolvedTo(
|
||||
{commands: ['brand', 'new-user']} as never
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow admins regardless of requiredRoles', done => {
|
||||
it('should allow admins regardless of requiredRoles', async () => {
|
||||
TestBed.resetTestingModule();
|
||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||
TestBed.configureTestingModule({
|
||||
@@ -48,13 +47,10 @@ describe('RoleGuard', () => {
|
||||
});
|
||||
guard = TestBed.inject(RoleGuard);
|
||||
|
||||
guard.canActivate({data: {requiredRoles: ['leader']}} as never).subscribe(result => {
|
||||
expect(result).toBeTrue();
|
||||
done();
|
||||
});
|
||||
await expectAsync(firstValueFrom(guard.canActivate({data: {requiredRoles: ['leader']}} as never))).toBeResolvedTo(true);
|
||||
});
|
||||
|
||||
it('should allow users with a matching required role', done => {
|
||||
it('should allow users with a matching required role', async () => {
|
||||
TestBed.resetTestingModule();
|
||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||
TestBed.configureTestingModule({
|
||||
@@ -65,13 +61,10 @@ describe('RoleGuard', () => {
|
||||
});
|
||||
guard = TestBed.inject(RoleGuard);
|
||||
|
||||
guard.canActivate({data: {requiredRoles: ['leader']}} as never).subscribe(result => {
|
||||
expect(result).toBeTrue();
|
||||
done();
|
||||
});
|
||||
await expectAsync(firstValueFrom(guard.canActivate({data: {requiredRoles: ['leader']}} as never))).toBeResolvedTo(true);
|
||||
});
|
||||
|
||||
it('should redirect users without the required role to their role default route', done => {
|
||||
it('should redirect users without the required role to their role default route', async () => {
|
||||
TestBed.resetTestingModule();
|
||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||
routerSpy.createUrlTree.and.returnValue({redirect: ['presentation']} as never);
|
||||
@@ -83,14 +76,12 @@ describe('RoleGuard', () => {
|
||||
});
|
||||
guard = TestBed.inject(RoleGuard);
|
||||
|
||||
guard.canActivate({data: {requiredRoles: ['leader']}} as never).subscribe(result => {
|
||||
const result = await firstValueFrom(guard.canActivate({data: {requiredRoles: ['leader']}} as never));
|
||||
expect(routerSpy.createUrlTree).toHaveBeenCalledWith(['presentation']);
|
||||
expect(result).toEqual({redirect: ['presentation']} as never);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should redirect members to shows instead of new-user', done => {
|
||||
it('should redirect members to shows instead of new-user', async () => {
|
||||
TestBed.resetTestingModule();
|
||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||
routerSpy.createUrlTree.and.returnValue({redirect: ['shows']} as never);
|
||||
@@ -102,14 +93,12 @@ describe('RoleGuard', () => {
|
||||
});
|
||||
guard = TestBed.inject(RoleGuard);
|
||||
|
||||
guard.canActivate({data: {requiredRoles: ['user']}} as never).subscribe(result => {
|
||||
const result = await firstValueFrom(guard.canActivate({data: {requiredRoles: ['user']}} as never));
|
||||
expect(routerSpy.createUrlTree).toHaveBeenCalledWith(['shows']);
|
||||
expect(result).toEqual({redirect: ['shows']} as never);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should choose a matching default route from all assigned roles', done => {
|
||||
it('should choose a matching default route from all assigned roles', async () => {
|
||||
TestBed.resetTestingModule();
|
||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||
routerSpy.createUrlTree.and.callFake(commands => ({redirect: commands}) as never);
|
||||
@@ -121,10 +110,8 @@ describe('RoleGuard', () => {
|
||||
});
|
||||
guard = TestBed.inject(RoleGuard);
|
||||
|
||||
guard.canActivate({data: {requiredRoles: ['presenter']}} as never).subscribe(result => {
|
||||
const result = await firstValueFrom(guard.canActivate({data: {requiredRoles: ['presenter']}} as never));
|
||||
expect(routerSpy.createUrlTree).toHaveBeenCalledWith(['shows']);
|
||||
expect(result).toEqual({redirect: ['shows']} as never);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ $wgenerator-theme: mat.m2-define-light-theme((
|
||||
warn: $wgenerator-warn,
|
||||
),
|
||||
typography: mat.m2-define-typography-config(),
|
||||
density: 0,
|
||||
density: -2,
|
||||
));
|
||||
|
||||
@include mat.all-component-themes($wgenerator-theme);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
z-index: 100;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
--focus-ring: 0 0 0 2px rgba(111, 143, 149, 0.28);
|
||||
--transition: all 300ms ease-in-out;
|
||||
--transition-fast: all 150ms ease-in-out;
|
||||
--icon-button-color: var(--primary-color);
|
||||
--icon-button-hover-color: var(--primary-active);
|
||||
--icon-button-opacity: 1;
|
||||
--icon-button-hover-opacity: 1;
|
||||
|
||||
|
||||
--mat-dialog-supporting-text-color: var(--text);
|
||||
@@ -78,11 +82,11 @@ a {
|
||||
}
|
||||
|
||||
.mat-mdc-icon-button {
|
||||
color: var(--primary-color) !important;
|
||||
color: var(--icon-button-color, var(--primary-color));
|
||||
transition: var(--transition);
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-active) !important;
|
||||
color: var(--icon-button-hover-color, var(--primary-active));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,11 +107,11 @@ body .cdk-overlay-transparent-backdrop.cdk-overlay-backdrop-showing {
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
opacity: 0.2;
|
||||
opacity: var(--icon-button-opacity);
|
||||
transition: var(--transition);
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
opacity: var(--icon-button-hover-opacity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +130,5 @@ body {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
--mat-form-field-container-text-line-height: 16px;
|
||||
--mat-form-field-container-text-size: 16px;
|
||||
|
||||
}
|
||||
|
||||
228
src/test-vitest.ts
Normal file
228
src/test-vitest.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
import {expect, vi} from 'vitest';
|
||||
|
||||
import 'zone.js/testing';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {provideNoopAnimations} from '@angular/platform-browser/animations';
|
||||
import {ActivatedRoute, provideRouter} from '@angular/router';
|
||||
import {BehaviorSubject, Observable, of} from 'rxjs';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
|
||||
import {provideNativeDateAdapter} from '@angular/material/core';
|
||||
import {getApp, getApps, initializeApp, provideFirebaseApp} from '@angular/fire/app';
|
||||
import {getAuth, provideAuth} from '@angular/fire/auth';
|
||||
import {initializeFirestore, provideFirestore} from '@angular/fire/firestore';
|
||||
import {getStorage, provideStorage} from '@angular/fire/storage';
|
||||
import {environment} from './environments/environment';
|
||||
import {DbService} from './app/services/db.service';
|
||||
|
||||
type TestingModuleDefinition = Parameters<typeof TestBed.configureTestingModule>[0];
|
||||
type TestingProviderList = NonNullable<NonNullable<TestingModuleDefinition>['providers']>;
|
||||
type CollectionStub = {
|
||||
valueChanges: () => Observable<unknown[]>;
|
||||
add: () => Promise<{id: string}>;
|
||||
};
|
||||
type DocumentStub = {
|
||||
set: () => Promise<void>;
|
||||
update: () => Promise<void>;
|
||||
delete: () => Promise<void>;
|
||||
collection: () => CollectionStub;
|
||||
};
|
||||
|
||||
type MockFunction = ReturnType<typeof vi.fn> & {
|
||||
and: {
|
||||
returnValue: (value: unknown) => MockFunction;
|
||||
resolveTo: (value: unknown) => MockFunction;
|
||||
rejectWith: (value: unknown) => MockFunction;
|
||||
callFake: (fn: (...args: unknown[]) => unknown) => MockFunction;
|
||||
callThrough: () => MockFunction;
|
||||
};
|
||||
calls: {
|
||||
argsFor: (index: number) => unknown[];
|
||||
mostRecent: () => {args: unknown[]};
|
||||
};
|
||||
};
|
||||
|
||||
const routeParams$ = new BehaviorSubject<Record<string, unknown>>({});
|
||||
const queryParams$ = new BehaviorSubject<Record<string, unknown>>({});
|
||||
const defaultFirebaseApp = getApps().length > 0 ? getApp() : initializeApp(environment.firebase);
|
||||
|
||||
const defaultTestingProviders: TestingProviderList = [
|
||||
provideNoopAnimations(),
|
||||
provideNativeDateAdapter(),
|
||||
provideRouter([]),
|
||||
provideFirebaseApp(() => defaultFirebaseApp),
|
||||
provideAuth(() => getAuth(defaultFirebaseApp)),
|
||||
provideFirestore(() => initializeFirestore(defaultFirebaseApp, {})),
|
||||
provideStorage(() => getStorage(defaultFirebaseApp)),
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
snapshot: {params: {}, queryParams: {}, data: {}},
|
||||
params: routeParams$.asObservable(),
|
||||
queryParams: queryParams$.asObservable(),
|
||||
data: of({}),
|
||||
fragment: of(null),
|
||||
},
|
||||
},
|
||||
{provide: MAT_DIALOG_DATA, useValue: {}},
|
||||
{provide: MatDialogRef, useValue: {close: () => void 0}},
|
||||
{
|
||||
provide: DbService,
|
||||
useValue: {
|
||||
col$: () => of([]),
|
||||
doc$: () => of(null),
|
||||
col: (): CollectionStub => ({
|
||||
valueChanges: () => of([]),
|
||||
add: () => Promise.resolve({id: 'test-id'}),
|
||||
}),
|
||||
doc: (): DocumentStub => ({
|
||||
set: () => Promise.resolve(),
|
||||
update: () => Promise.resolve(),
|
||||
delete: () => Promise.resolve(),
|
||||
collection: (): CollectionStub => ({
|
||||
valueChanges: () => of([]),
|
||||
add: () => Promise.resolve({id: 'test-id'}),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const originalConfigureTestingModule = TestBed.configureTestingModule.bind(TestBed);
|
||||
const configureTestingModule: typeof TestBed.configureTestingModule = moduleDef => {
|
||||
const extraProviders: TestingProviderList = moduleDef?.providers ?? [];
|
||||
const mergedModuleDef: TestingModuleDefinition = moduleDef ? {...moduleDef} : {};
|
||||
mergedModuleDef.providers = defaultTestingProviders.concat(extraProviders);
|
||||
|
||||
return originalConfigureTestingModule(mergedModuleDef);
|
||||
};
|
||||
TestBed.configureTestingModule = configureTestingModule;
|
||||
|
||||
function decorateMock<T extends ReturnType<typeof vi.fn>>(mock: T): T & MockFunction {
|
||||
const decorated = mock as T & MockFunction;
|
||||
|
||||
Object.defineProperty(decorated, 'and', {
|
||||
configurable: true,
|
||||
get: () => ({
|
||||
returnValue(value: unknown) {
|
||||
decorated.mockReturnValue(value);
|
||||
return decorated;
|
||||
},
|
||||
resolveTo(value: unknown) {
|
||||
decorated.mockResolvedValue(value);
|
||||
return decorated;
|
||||
},
|
||||
rejectWith(value: unknown) {
|
||||
decorated.mockRejectedValue(value);
|
||||
return decorated;
|
||||
},
|
||||
callFake(fn: (...args: unknown[]) => unknown) {
|
||||
decorated.mockImplementation(fn);
|
||||
return decorated;
|
||||
},
|
||||
callThrough() {
|
||||
return decorated;
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
Object.defineProperty(decorated, 'calls', {
|
||||
configurable: true,
|
||||
get: () => ({
|
||||
argsFor(index: number) {
|
||||
return decorated.mock.calls[index] ?? [];
|
||||
},
|
||||
mostRecent() {
|
||||
const args = decorated.mock.lastCall ?? [];
|
||||
return {args};
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return decorated;
|
||||
}
|
||||
|
||||
function createSpy(name?: string): MockFunction {
|
||||
const spy = decorateMock(vi.fn());
|
||||
if (name) {
|
||||
spy.mockName(name);
|
||||
}
|
||||
|
||||
return spy;
|
||||
}
|
||||
|
||||
function createSpyObj<T>(
|
||||
baseName: string,
|
||||
methodNames: string[] | Record<string, unknown>,
|
||||
propertyValues?: Record<string, unknown>
|
||||
): T {
|
||||
const result: Record<string, unknown> = {};
|
||||
const methods = Array.isArray(methodNames) ? methodNames : Object.keys(methodNames);
|
||||
|
||||
for (const methodName of methods) {
|
||||
result[methodName] = createSpy(`${baseName}.${methodName}`);
|
||||
}
|
||||
|
||||
if (!Array.isArray(methodNames)) {
|
||||
for (const [key, value] of Object.entries(methodNames)) {
|
||||
(result[key] as MockFunction).and.returnValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (propertyValues) {
|
||||
for (const [key, value] of Object.entries(propertyValues)) {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
function spyOnCompat<T extends object, K extends keyof T>(object: T, methodName: K): MockFunction {
|
||||
const spy = vi.spyOn(object as Record<PropertyKey, (...args: unknown[]) => unknown>, methodName as PropertyKey);
|
||||
return decorateMock(spy as unknown as ReturnType<typeof vi.fn>);
|
||||
}
|
||||
|
||||
function expectAsyncCompat<T>(value: Promise<T>) {
|
||||
return {
|
||||
async toBeResolvedTo(expected: T) {
|
||||
await expect(value).resolves.toEqual(expected);
|
||||
},
|
||||
async toBeRejectedWithError(expected?: string | RegExp | Error) {
|
||||
if (expected instanceof Error) {
|
||||
await expect(value).rejects.toThrowError(expected.message);
|
||||
} else if (expected !== undefined) {
|
||||
await expect(value).rejects.toThrowError(expected);
|
||||
} else {
|
||||
await expect(value).rejects.toThrowError();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
expect.extend({
|
||||
toBeTrue(received: unknown) {
|
||||
return {
|
||||
pass: received === true,
|
||||
message: () => `expected ${String(received)} to be true`,
|
||||
};
|
||||
},
|
||||
toBeFalse(received: unknown) {
|
||||
return {
|
||||
pass: received === false,
|
||||
message: () => `expected ${String(received)} to be false`,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
Object.assign(globalThis, {
|
||||
spyOn: spyOnCompat,
|
||||
expectAsync: expectAsyncCompat,
|
||||
jasmine: {
|
||||
createSpy,
|
||||
createSpyObj,
|
||||
any: expect.any,
|
||||
anything: expect.anything,
|
||||
objectContaining: expect.objectContaining,
|
||||
stringMatching: expect.stringMatching,
|
||||
},
|
||||
});
|
||||
90
src/test.ts
90
src/test.ts
@@ -1,90 +0,0 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/testing';
|
||||
import {getTestBed, TestBed} from '@angular/core/testing';
|
||||
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
|
||||
import {provideNoopAnimations} from '@angular/platform-browser/animations';
|
||||
import {ActivatedRoute, provideRouter} from '@angular/router';
|
||||
import {BehaviorSubject, of} from 'rxjs';
|
||||
import {Observable} from 'rxjs';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
|
||||
import {provideNativeDateAdapter} from '@angular/material/core';
|
||||
import {provideFirebaseApp, initializeApp} from '@angular/fire/app';
|
||||
import {getApp, getApps} from '@angular/fire/app';
|
||||
import {getAuth, provideAuth} from '@angular/fire/auth';
|
||||
import {initializeFirestore, provideFirestore} from '@angular/fire/firestore';
|
||||
import {getStorage, provideStorage} from '@angular/fire/storage';
|
||||
import {environment} from './environments/environment';
|
||||
import {DbService} from './app/services/db.service';
|
||||
|
||||
type req = {keys: () => {map: (context: req) => void}};
|
||||
type TestingModuleDefinition = Parameters<typeof TestBed.configureTestingModule>[0];
|
||||
type TestingProviderList = NonNullable<NonNullable<TestingModuleDefinition>['providers']>;
|
||||
type CollectionStub = {
|
||||
valueChanges: () => Observable<unknown[]>;
|
||||
add: () => Promise<{id: string}>;
|
||||
};
|
||||
type DocumentStub = {
|
||||
set: () => Promise<void>;
|
||||
update: () => Promise<void>;
|
||||
delete: () => Promise<void>;
|
||||
collection: () => CollectionStub;
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
|
||||
const routeParams$ = new BehaviorSubject<Record<string, unknown>>({});
|
||||
const queryParams$ = new BehaviorSubject<Record<string, unknown>>({});
|
||||
|
||||
const defaultTestingProviders: TestingProviderList = [
|
||||
provideNoopAnimations(),
|
||||
provideNativeDateAdapter(),
|
||||
provideRouter([]),
|
||||
provideFirebaseApp(() => (getApps().some(app => app.name === 'wgenerator-tests') ? getApp('wgenerator-tests') : initializeApp(environment.firebase, 'wgenerator-tests'))),
|
||||
provideAuth(() => getAuth(getApp('wgenerator-tests'))),
|
||||
provideFirestore(() => initializeFirestore(getApp('wgenerator-tests'), {})),
|
||||
provideStorage(() => getStorage(getApp('wgenerator-tests'))),
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
snapshot: {params: {}, queryParams: {}, data: {}},
|
||||
params: routeParams$.asObservable(),
|
||||
queryParams: queryParams$.asObservable(),
|
||||
data: of({}),
|
||||
fragment: of(null),
|
||||
},
|
||||
},
|
||||
{provide: MAT_DIALOG_DATA, useValue: {}},
|
||||
{provide: MatDialogRef, useValue: {close: () => void 0}},
|
||||
{
|
||||
provide: DbService,
|
||||
useValue: {
|
||||
col$: () => of([]),
|
||||
doc$: () => of(null),
|
||||
col: (): CollectionStub => ({
|
||||
valueChanges: () => of([]),
|
||||
add: () => Promise.resolve({id: 'test-id'}),
|
||||
}),
|
||||
doc: (): DocumentStub => ({
|
||||
set: () => Promise.resolve(),
|
||||
update: () => Promise.resolve(),
|
||||
delete: () => Promise.resolve(),
|
||||
collection: (): CollectionStub => ({
|
||||
valueChanges: () => of([]),
|
||||
add: () => Promise.resolve({id: 'test-id'}),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const originalConfigureTestingModule = TestBed.configureTestingModule.bind(TestBed);
|
||||
const configureTestingModule: typeof TestBed.configureTestingModule = moduleDef => {
|
||||
const extraProviders: TestingProviderList = moduleDef?.providers ?? [];
|
||||
const mergedModuleDef: TestingModuleDefinition = moduleDef ? {...moduleDef} : {};
|
||||
mergedModuleDef.providers = defaultTestingProviders.concat(extraProviders);
|
||||
|
||||
return originalConfigureTestingModule(mergedModuleDef);
|
||||
};
|
||||
TestBed.configureTestingModule = configureTestingModule;
|
||||
44
src/types/jasmine-compat.d.ts
vendored
Normal file
44
src/types/jasmine-compat.d.ts
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
type SpyAnd = {
|
||||
returnValue(value?: unknown): jasmine.Spy;
|
||||
resolveTo(value?: unknown): jasmine.Spy;
|
||||
rejectWith(value?: unknown): jasmine.Spy;
|
||||
callFake(fn: (...args: any[]) => unknown): jasmine.Spy;
|
||||
callThrough(): jasmine.Spy;
|
||||
};
|
||||
|
||||
type SpyCalls = {
|
||||
argsFor(index: number): any[];
|
||||
mostRecent(): {args: any[]};
|
||||
};
|
||||
|
||||
declare global {
|
||||
function spyOn<T = any>(object: T, methodName: any): jasmine.Spy;
|
||||
function expectAsync<T>(value: Promise<T>): {
|
||||
toBeResolvedTo(expected: T): Promise<void>;
|
||||
toBeRejectedWithError(expected?: string | RegExp | Error): Promise<void>;
|
||||
};
|
||||
|
||||
namespace jasmine {
|
||||
type Spy<T extends (...args: any[]) => any = (...args: any[]) => any> = T & ReturnType<typeof import('vitest')['vi']['fn']> & {
|
||||
and: SpyAnd;
|
||||
calls: SpyCalls;
|
||||
};
|
||||
|
||||
type SpyObj<T> = {
|
||||
[K in keyof T]: T[K] extends (...args: any[]) => any ? Spy<T[K]> : T[K];
|
||||
};
|
||||
|
||||
function createSpy(name?: string): Spy;
|
||||
function createSpyObj<T>(
|
||||
baseName: string,
|
||||
methodNames: string[] | Record<string, unknown>,
|
||||
propertyValues?: Record<string, unknown>
|
||||
): SpyObj<T>;
|
||||
function any(expectedClass: unknown): unknown;
|
||||
function anything(): unknown;
|
||||
function objectContaining<T>(value: Partial<T>): unknown;
|
||||
function stringMatching(value: string | RegExp): unknown;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
1
src/types/vitest-globals.d.ts
vendored
Normal file
1
src/types/vitest-globals.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vitest/globals" />
|
||||
13
src/types/vitest-matchers.d.ts
vendored
Normal file
13
src/types/vitest-matchers.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'vitest';
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Assertion<T = any> {
|
||||
toBeTrue(): T;
|
||||
toBeFalse(): T;
|
||||
}
|
||||
|
||||
interface AsymmetricMatchersContaining {
|
||||
toBeTrue(): void;
|
||||
toBeFalse(): void;
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,12 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/test-vitest.ts",
|
||||
"src/polyfills.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user