global filter
This commit is contained in:
@@ -54,6 +54,21 @@
|
|||||||
"plugin:@angular-eslint/template/recommended"
|
"plugin:@angular-eslint/template/recommended"
|
||||||
],
|
],
|
||||||
"rules": {}
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"*.spec.ts"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/await-thenable": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-floating-promises": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-argument": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-call": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-return": "off"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ describe('GuestShowDataService', () => {
|
|||||||
let colSpy: jasmine.Spy;
|
let colSpy: jasmine.Spy;
|
||||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
docUpdateSpy = jasmine.createSpy('update').and.resolveTo();
|
docUpdateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||||
docDeleteSpy = jasmine.createSpy('delete').and.resolveTo();
|
docDeleteSpy = jasmine.createSpy('delete').and.resolveTo();
|
||||||
docSpy = jasmine.createSpy('doc').and.returnValue({
|
docSpy = jasmine.createSpy('doc').and.returnValue({
|
||||||
@@ -27,7 +27,7 @@ describe('GuestShowDataService', () => {
|
|||||||
dbServiceSpy.doc.and.callFake(docSpy);
|
dbServiceSpy.doc.and.callFake(docSpy);
|
||||||
dbServiceSpy.col.and.callFake(colSpy);
|
dbServiceSpy.col.and.callFake(colSpy);
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,22 @@ import {TestBed} from '@angular/core/testing';
|
|||||||
import {GuestShowDataService} from './guest-show-data.service';
|
import {GuestShowDataService} from './guest-show-data.service';
|
||||||
import {GuestShowService} from './guest-show.service';
|
import {GuestShowService} from './guest-show.service';
|
||||||
import {ShowService} from '../shows/services/show.service';
|
import {ShowService} from '../shows/services/show.service';
|
||||||
|
import {Show} from '../shows/services/show';
|
||||||
|
import {Song} from '../songs/services/song';
|
||||||
|
|
||||||
describe('GuestShowService', () => {
|
describe('GuestShowService', () => {
|
||||||
let service: GuestShowService;
|
let service: GuestShowService;
|
||||||
let guestShowDataServiceSpy: jasmine.SpyObj<GuestShowDataService>;
|
let guestShowDataServiceSpy: jasmine.SpyObj<GuestShowDataService>;
|
||||||
let showServiceSpy: jasmine.SpyObj<ShowService>;
|
let showServiceSpy: jasmine.SpyObj<ShowService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
guestShowDataServiceSpy = jasmine.createSpyObj<GuestShowDataService>('GuestShowDataService', ['add', 'update$']);
|
guestShowDataServiceSpy = jasmine.createSpyObj<GuestShowDataService>('GuestShowDataService', ['add', 'update$']);
|
||||||
showServiceSpy = jasmine.createSpyObj<ShowService>('ShowService', ['update$']);
|
showServiceSpy = jasmine.createSpyObj<ShowService>('ShowService', ['update$']);
|
||||||
guestShowDataServiceSpy.add.and.resolveTo('share-1');
|
guestShowDataServiceSpy.add.and.resolveTo('share-1');
|
||||||
guestShowDataServiceSpy.update$.and.resolveTo();
|
guestShowDataServiceSpy.update$.and.resolveTo();
|
||||||
showServiceSpy.update$.and.resolveTo();
|
showServiceSpy.update$.and.resolveTo();
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: GuestShowDataService, useValue: guestShowDataServiceSpy},
|
{provide: GuestShowDataService, useValue: guestShowDataServiceSpy},
|
||||||
{provide: ShowService, useValue: showServiceSpy},
|
{provide: ShowService, useValue: showServiceSpy},
|
||||||
@@ -30,8 +32,8 @@ describe('GuestShowService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should create a new guest share, persist the generated shareId on the show and return the share url', async () => {
|
it('should create a new guest share, persist the generated shareId on the show and return the share url', async () => {
|
||||||
const show = {id: 'show-1', showType: 'service-worship', date: new Date(), shareId: ''} as any;
|
const show = {id: 'show-1', showType: 'service-worship', date: new Date(), shareId: ''} as unknown as Show;
|
||||||
const songs = [{id: 'song-1'}] as any;
|
const songs = [{id: 'song-1'}] as unknown as Song[];
|
||||||
const expectedUrl = window.location.protocol + '//' + window.location.host + '/guest/share-1';
|
const expectedUrl = window.location.protocol + '//' + window.location.host + '/guest/share-1';
|
||||||
|
|
||||||
await expectAsync(service.share(show, songs)).toBeResolvedTo(expectedUrl);
|
await expectAsync(service.share(show, songs)).toBeResolvedTo(expectedUrl);
|
||||||
@@ -45,8 +47,8 @@ describe('GuestShowService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update an existing share and reuse its id in the returned url', async () => {
|
it('should update an existing share and reuse its id in the returned url', async () => {
|
||||||
const show = {id: 'show-1', showType: 'service-worship', date: new Date(), shareId: 'share-9'} as any;
|
const show = {id: 'show-1', showType: 'service-worship', date: new Date(), shareId: 'share-9'} as unknown as Show;
|
||||||
const songs = [{id: 'song-1'}] as any;
|
const songs = [{id: 'song-1'}] as unknown as Song[];
|
||||||
const expectedUrl = window.location.protocol + '//' + window.location.host + '/guest/share-9';
|
const expectedUrl = window.location.protocol + '//' + window.location.host + '/guest/share-9';
|
||||||
|
|
||||||
await expectAsync(service.share(show, songs)).toBeResolvedTo(expectedUrl);
|
await expectAsync(service.share(show, songs)).toBeResolvedTo(expectedUrl);
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import {combineLatest} from 'rxjs';
|
|||||||
import {Show} from '../services/show';
|
import {Show} from '../services/show';
|
||||||
import {fade} from '../../../animations';
|
import {fade} from '../../../animations';
|
||||||
import {ShowService} from '../services/show.service';
|
import {ShowService} from '../services/show.service';
|
||||||
import {FilterValues} from './filter/filter-values'import {RouterLink} from '@angular/router';
|
import {FilterValues} from './filter/filter-values';
|
||||||
|
import {RouterLink} from '@angular/router';
|
||||||
import {map, switchMap} from 'rxjs/operators';
|
import {map, switchMap} from 'rxjs/operators';
|
||||||
import {FilterStoreService} from '../../../services/filter-store.service';
|
import {FilterStoreService} from '../../../services/filter-store.service';
|
||||||
import {RoleDirective} from '../../../services/user/role.directive';
|
import {RoleDirective} from '../../../services/user/role.directive';
|
||||||
@@ -49,10 +50,6 @@ export class ListComponent {
|
|||||||
|
|
||||||
public trackBy = (index: number, show: unknown) => (show as Show).id;
|
public trackBy = (index: number, show: unknown) => (show as Show).id;
|
||||||
|
|
||||||
private matchesFilter(show: Show, filter: FilterValues): boolean {
|
|
||||||
return this.matchesTimeFilter(show, filter.time || 1) && (!filter.owner || show.owner === filter.owner) && (!filter.showType || show.showType === filter.showType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private matchesPrivateFilter(show: Show, filter: FilterValues): boolean {
|
private matchesPrivateFilter(show: Show, filter: FilterValues): boolean {
|
||||||
return this.matchesTimeFilter(show, filter.time || 1) && (!filter.showType || show.showType === filter.showType);
|
return this.matchesTimeFilter(show, filter.time || 1) && (!filter.showType || show.showType === filter.showType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,14 @@ import {DocxService} from './docx.service';
|
|||||||
|
|
||||||
describe('DocxService', () => {
|
describe('DocxService', () => {
|
||||||
let service: DocxService;
|
let service: DocxService;
|
||||||
|
type DocxServiceInternals = DocxService & {
|
||||||
|
prepareData: (showId: string) => Promise<unknown>;
|
||||||
|
prepareNewDocument: (data: unknown, options?: unknown) => unknown;
|
||||||
|
saveAs: (blob: Blob, name: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
void TestBed.configureTestingModule({});
|
await TestBed.configureTestingModule({});
|
||||||
service = TestBed.inject(DocxService);
|
service = TestBed.inject(DocxService);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -15,8 +20,9 @@ describe('DocxService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not try to save a document when the required data cannot be prepared', async () => {
|
it('should not try to save a document when the required data cannot be prepared', async () => {
|
||||||
const prepareDataSpy = spyOn<any>(service, 'prepareData').and.resolveTo(null);
|
const serviceInternals = service as DocxServiceInternals;
|
||||||
const saveAsSpy = spyOn<any>(service, 'saveAs');
|
const prepareDataSpy = spyOn(serviceInternals, 'prepareData').and.resolveTo(null);
|
||||||
|
const saveAsSpy = spyOn(serviceInternals, 'saveAs');
|
||||||
|
|
||||||
await service.create('show-1');
|
await service.create('show-1');
|
||||||
|
|
||||||
@@ -26,7 +32,8 @@ describe('DocxService', () => {
|
|||||||
|
|
||||||
it('should build and save a docx file when all data is available', async () => {
|
it('should build and save a docx file when all data is available', async () => {
|
||||||
const blob = new Blob(['docx']);
|
const blob = new Blob(['docx']);
|
||||||
const prepareDataSpy = spyOn<any>(service, 'prepareData').and.resolveTo({
|
const serviceInternals = service as DocxServiceInternals;
|
||||||
|
const prepareDataSpy = spyOn(serviceInternals, 'prepareData').and.resolveTo({
|
||||||
show: {
|
show: {
|
||||||
showType: 'service-worship',
|
showType: 'service-worship',
|
||||||
date: {toDate: () => new Date('2026-03-10T00:00:00Z')},
|
date: {toDate: () => new Date('2026-03-10T00:00:00Z')},
|
||||||
@@ -35,8 +42,8 @@ describe('DocxService', () => {
|
|||||||
user: {name: 'Benjamin'},
|
user: {name: 'Benjamin'},
|
||||||
config: {ccliLicenseId: '12345'},
|
config: {ccliLicenseId: '12345'},
|
||||||
});
|
});
|
||||||
const prepareNewDocumentSpy = spyOn<any>(service, 'prepareNewDocument').and.returnValue({doc: true});
|
const prepareNewDocumentSpy = spyOn(serviceInternals, 'prepareNewDocument').and.returnValue({doc: true});
|
||||||
const saveAsSpy = spyOn<any>(service, 'saveAs');
|
const saveAsSpy = spyOn(serviceInternals, 'saveAs');
|
||||||
spyOn(Packer, 'toBlob').and.resolveTo(blob);
|
spyOn(Packer, 'toBlob').and.resolveTo(blob);
|
||||||
|
|
||||||
await service.create('show-1', {copyright: true});
|
await service.create('show-1', {copyright: true});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {firstValueFrom, of, Subject} from 'rxjs';
|
import {firstValueFrom, of, Subject} from 'rxjs';
|
||||||
import {skip, take} from 'rxjs/operators';
|
import {take} from 'rxjs/operators';
|
||||||
import {DbService} from '../../../services/db.service';
|
import {DbService} from '../../../services/db.service';
|
||||||
import {ShowDataService} from './show-data.service';
|
import {ShowDataService} from './show-data.service';
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ describe('ShowDataService', () => {
|
|||||||
let colSpy: jasmine.Spy;
|
let colSpy: jasmine.Spy;
|
||||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
shows$ = new Subject<Array<{id: string; date: {toMillis: () => number}; archived?: boolean}>>();
|
shows$ = new Subject<Array<{id: string; date: {toMillis: () => number}; archived?: boolean}>>();
|
||||||
docUpdateSpy = jasmine.createSpy('update').and.resolveTo();
|
docUpdateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||||
docSpy = jasmine.createSpy('doc').and.returnValue({update: docUpdateSpy});
|
docSpy = jasmine.createSpy('doc').and.returnValue({update: docUpdateSpy});
|
||||||
@@ -25,7 +25,7 @@ describe('ShowDataService', () => {
|
|||||||
dbServiceSpy.doc.and.callFake(docSpy);
|
dbServiceSpy.doc.and.callFake(docSpy);
|
||||||
dbServiceSpy.col.and.callFake(colSpy);
|
dbServiceSpy.col.and.callFake(colSpy);
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,11 +76,7 @@ describe('ShowDataService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should request only published recent shows and filter archived entries', async () => {
|
it('should request only published recent shows and filter archived entries', async () => {
|
||||||
const publicShows$ = of([
|
const publicShows$ = of([{id: 'show-1', archived: false}, {id: 'show-2', archived: true}, {id: 'show-3'}]);
|
||||||
{id: 'show-1', archived: false},
|
|
||||||
{id: 'show-2', archived: true},
|
|
||||||
{id: 'show-3'},
|
|
||||||
]);
|
|
||||||
dbServiceSpy.col$.and.returnValue(publicShows$ as never);
|
dbServiceSpy.col$.and.returnValue(publicShows$ as never);
|
||||||
|
|
||||||
const result = await firstValueFrom(service.listPublicSince$(3));
|
const result = await firstValueFrom(service.listPublicSince$(3));
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ describe('ShowSongDataService', () => {
|
|||||||
let colSpy: jasmine.Spy;
|
let colSpy: jasmine.Spy;
|
||||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
docUpdateSpy = jasmine.createSpy('update').and.resolveTo();
|
docUpdateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||||
docDeleteSpy = jasmine.createSpy('delete').and.resolveTo();
|
docDeleteSpy = jasmine.createSpy('delete').and.resolveTo();
|
||||||
docSpy = jasmine.createSpy('doc').and.returnValue({
|
docSpy = jasmine.createSpy('doc').and.returnValue({
|
||||||
@@ -27,7 +27,7 @@ describe('ShowSongDataService', () => {
|
|||||||
dbServiceSpy.doc.and.callFake(docSpy);
|
dbServiceSpy.doc.and.callFake(docSpy);
|
||||||
dbServiceSpy.col.and.callFake(colSpy);
|
dbServiceSpy.col.and.callFake(colSpy);
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import {UserService} from '../../../services/user/user.service';
|
|||||||
import {ShowService} from './show.service';
|
import {ShowService} from './show.service';
|
||||||
import {ShowSongDataService} from './show-song-data.service';
|
import {ShowSongDataService} from './show-song-data.service';
|
||||||
import {ShowSongService} from './show-song.service';
|
import {ShowSongService} from './show-song.service';
|
||||||
|
import {ShowSong} from './show-song';
|
||||||
|
import {Song} from '../../songs/services/song';
|
||||||
|
import {Show} from './show';
|
||||||
|
import {User} from '../../../services/user/user';
|
||||||
|
|
||||||
describe('ShowSongService', () => {
|
describe('ShowSongService', () => {
|
||||||
let service: ShowSongService;
|
let service: ShowSongService;
|
||||||
@@ -12,13 +16,13 @@ describe('ShowSongService', () => {
|
|||||||
let songDataServiceSpy: jasmine.SpyObj<SongDataService>;
|
let songDataServiceSpy: jasmine.SpyObj<SongDataService>;
|
||||||
let userServiceSpy: jasmine.SpyObj<UserService>;
|
let userServiceSpy: jasmine.SpyObj<UserService>;
|
||||||
let showServiceSpy: jasmine.SpyObj<ShowService>;
|
let showServiceSpy: jasmine.SpyObj<ShowService>;
|
||||||
let user$: BehaviorSubject<any>;
|
let user$: BehaviorSubject<User | null>;
|
||||||
const song = {id: 'song-1', key: 'G', title: 'Amazing Grace'} as any;
|
const song = {id: 'song-1', key: 'G', title: 'Amazing Grace'} as unknown as Song;
|
||||||
const showSong = {id: 'show-song-1', songId: 'song-1'} as any;
|
const showSong = {id: 'show-song-1', songId: 'song-1'} as unknown as ShowSong;
|
||||||
const show = {id: 'show-1', order: ['show-song-1', 'show-song-2']} as any;
|
const show = {id: 'show-1', order: ['show-song-1', 'show-song-2']} as unknown as Show;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
user$ = new BehaviorSubject<any>({id: 'user-1', name: 'Benjamin', role: 'editor', chordMode: 'letters', songUsage: {}});
|
user$ = new BehaviorSubject<User | null>({id: 'user-1', name: 'Benjamin', role: 'editor', chordMode: 'letters', songUsage: {}});
|
||||||
showSongDataServiceSpy = jasmine.createSpyObj<ShowSongDataService>('ShowSongDataService', ['add', 'read$', 'list$', 'delete', 'update$']);
|
showSongDataServiceSpy = jasmine.createSpyObj<ShowSongDataService>('ShowSongDataService', ['add', 'read$', 'list$', 'delete', 'update$']);
|
||||||
songDataServiceSpy = jasmine.createSpyObj<SongDataService>('SongDataService', ['read$']);
|
songDataServiceSpy = jasmine.createSpyObj<SongDataService>('SongDataService', ['read$']);
|
||||||
userServiceSpy = jasmine.createSpyObj<UserService>('UserService', ['incSongCount', 'decSongCount'], {
|
userServiceSpy = jasmine.createSpyObj<UserService>('UserService', ['incSongCount', 'decSongCount'], {
|
||||||
@@ -37,7 +41,7 @@ describe('ShowSongService', () => {
|
|||||||
showServiceSpy.read$.and.returnValue(of(show));
|
showServiceSpy.read$.and.returnValue(of(show));
|
||||||
showServiceSpy.update$.and.resolveTo();
|
showServiceSpy.update$.and.resolveTo();
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: ShowSongDataService, useValue: showSongDataServiceSpy},
|
{provide: ShowSongDataService, useValue: showSongDataServiceSpy},
|
||||||
{provide: SongDataService, useValue: songDataServiceSpy},
|
{provide: SongDataService, useValue: songDataServiceSpy},
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ describe('ShowService', () => {
|
|||||||
{id: 'show-3', owner: 'user-1', published: true, archived: true},
|
{id: 'show-3', owner: 'user-1', published: true, archived: true},
|
||||||
] as never;
|
] as never;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
user$ = new BehaviorSubject<unknown>({id: 'user-1'});
|
user$ = new BehaviorSubject<unknown>({id: 'user-1'});
|
||||||
showDataServiceSpy = jasmine.createSpyObj<ShowDataService>('ShowDataService', ['read$', 'listPublicSince$', 'update', 'add'], {
|
showDataServiceSpy = jasmine.createSpyObj<ShowDataService>('ShowDataService', ['read$', 'listPublicSince$', 'update', 'add'], {
|
||||||
list$: of(shows),
|
list$: of(shows),
|
||||||
@@ -24,7 +24,7 @@ describe('ShowService', () => {
|
|||||||
showDataServiceSpy.update.and.resolveTo();
|
showDataServiceSpy.update.and.resolveTo();
|
||||||
showDataServiceSpy.add.and.resolveTo('new-show-id');
|
showDataServiceSpy.add.and.resolveTo('new-show-id');
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: ShowDataService, useValue: showDataServiceSpy},
|
{provide: ShowDataService, useValue: showDataServiceSpy},
|
||||||
{provide: UserService, useValue: {user$: user$.asObservable()}},
|
{provide: UserService, useValue: {user$: user$.asObservable()}},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ describe('FileDataService', () => {
|
|||||||
let fileDeleteSpy: jasmine.Spy;
|
let fileDeleteSpy: jasmine.Spy;
|
||||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
filesCollectionValueChangesSpy = jasmine.createSpy('valueChanges').and.returnValue(of([{id: 'file-1', name: 'plan.pdf'}]));
|
filesCollectionValueChangesSpy = jasmine.createSpy('valueChanges').and.returnValue(of([{id: 'file-1', name: 'plan.pdf'}]));
|
||||||
filesCollectionAddSpy = jasmine.createSpy('add').and.resolveTo({id: 'file-2'});
|
filesCollectionAddSpy = jasmine.createSpy('add').and.resolveTo({id: 'file-2'});
|
||||||
songDocCollectionSpy = jasmine.createSpy('collection').and.returnValue({
|
songDocCollectionSpy = jasmine.createSpy('collection').and.returnValue({
|
||||||
@@ -30,7 +30,7 @@ describe('FileDataService', () => {
|
|||||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc']);
|
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc']);
|
||||||
dbServiceSpy.doc.and.callFake(songDocSpy);
|
dbServiceSpy.doc.and.callFake(songDocSpy);
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,16 @@ import {FileService} from './file.service';
|
|||||||
describe('FileService', () => {
|
describe('FileService', () => {
|
||||||
let service: FileService;
|
let service: FileService;
|
||||||
let fileDataServiceSpy: jasmine.SpyObj<FileDataService>;
|
let fileDataServiceSpy: jasmine.SpyObj<FileDataService>;
|
||||||
|
type FileServiceInternals = FileService & {
|
||||||
|
resolveDownloadUrl: (path: string) => Promise<string>;
|
||||||
|
deleteFromStorage: (path: string) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
fileDataServiceSpy = jasmine.createSpyObj<FileDataService>('FileDataService', ['delete']);
|
fileDataServiceSpy = jasmine.createSpyObj<FileDataService>('FileDataService', ['delete']);
|
||||||
fileDataServiceSpy.delete.and.resolveTo();
|
fileDataServiceSpy.delete.and.resolveTo();
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: Storage, useValue: {app: 'test-storage'}},
|
{provide: Storage, useValue: {app: 'test-storage'}},
|
||||||
{provide: FileDataService, useValue: fileDataServiceSpy},
|
{provide: FileDataService, useValue: fileDataServiceSpy},
|
||||||
@@ -26,7 +30,7 @@ describe('FileService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve download urls via AngularFire storage helpers', async () => {
|
it('should resolve download urls via AngularFire storage helpers', async () => {
|
||||||
const resolveSpy = spyOn<any>(service, 'resolveDownloadUrl').and.resolveTo('https://cdn.example/file.pdf');
|
const resolveSpy = spyOn(service as FileServiceInternals, 'resolveDownloadUrl').and.resolveTo('https://cdn.example/file.pdf');
|
||||||
|
|
||||||
await expectAsync(service.getDownloadUrl('songs/song-1/file.pdf').toPromise()).toBeResolvedTo('https://cdn.example/file.pdf');
|
await expectAsync(service.getDownloadUrl('songs/song-1/file.pdf').toPromise()).toBeResolvedTo('https://cdn.example/file.pdf');
|
||||||
|
|
||||||
@@ -34,10 +38,9 @@ describe('FileService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should delete the file from storage and metadata from firestore', async () => {
|
it('should delete the file from storage and metadata from firestore', async () => {
|
||||||
const deleteFromStorageSpy = spyOn<any>(service, 'deleteFromStorage').and.resolveTo();
|
const deleteFromStorageSpy = spyOn(service as FileServiceInternals, 'deleteFromStorage').and.resolveTo();
|
||||||
|
|
||||||
service.delete('songs/song-1/file.pdf', 'song-1', 'file-1');
|
await service.delete('songs/song-1/file.pdf', 'song-1', 'file-1');
|
||||||
await Promise.resolve();
|
|
||||||
|
|
||||||
expect(deleteFromStorageSpy).toHaveBeenCalledWith('songs/song-1/file.pdf');
|
expect(deleteFromStorageSpy).toHaveBeenCalledWith('songs/song-1/file.pdf');
|
||||||
expect(fileDataServiceSpy.delete).toHaveBeenCalledWith('song-1', 'file-1');
|
expect(fileDataServiceSpy.delete).toHaveBeenCalledWith('song-1', 'file-1');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {firstValueFrom, Subject} from 'rxjs';
|
import {firstValueFrom, Subject} from 'rxjs';
|
||||||
import {skip, take, toArray} from 'rxjs/operators';
|
import {take, toArray} from 'rxjs/operators';
|
||||||
import {DbService} from '../../../services/db.service';
|
import {DbService} from '../../../services/db.service';
|
||||||
import {SongDataService} from './song-data.service';
|
import {SongDataService} from './song-data.service';
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ describe('SongDataService', () => {
|
|||||||
let colSpy: jasmine.Spy;
|
let colSpy: jasmine.Spy;
|
||||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
songs$ = new Subject<Array<{id: string; title: string}>>();
|
songs$ = new Subject<Array<{id: string; title: string}>>();
|
||||||
docUpdateSpy = jasmine.createSpy('update').and.resolveTo();
|
docUpdateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||||
docDeleteSpy = jasmine.createSpy('delete').and.resolveTo();
|
docDeleteSpy = jasmine.createSpy('delete').and.resolveTo();
|
||||||
@@ -32,7 +32,7 @@ describe('SongDataService', () => {
|
|||||||
dbServiceSpy.doc.and.callFake(docSpy);
|
dbServiceSpy.doc.and.callFake(docSpy);
|
||||||
dbServiceSpy.col.and.callFake(colSpy);
|
dbServiceSpy.col.and.callFake(colSpy);
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ describe('SongListResolver', () => {
|
|||||||
let resolver: SongListResolver;
|
let resolver: SongListResolver;
|
||||||
let songServiceSpy: jasmine.SpyObj<SongService>;
|
let songServiceSpy: jasmine.SpyObj<SongService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
songServiceSpy = jasmine.createSpyObj<SongService>('SongService', ['list$']);
|
songServiceSpy = jasmine.createSpyObj<SongService>('SongService', ['list$']);
|
||||||
songServiceSpy.list$.and.returnValue(of([{id: 'song-1', title: 'Amazing Grace'}] as never));
|
songServiceSpy.list$.and.returnValue(of([{id: 'song-1', title: 'Amazing Grace'}] as never));
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [{provide: SongService, useValue: songServiceSpy}],
|
providers: [{provide: SongService, useValue: songServiceSpy}],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ describe('SongService', () => {
|
|||||||
edits: [],
|
edits: [],
|
||||||
} as never;
|
} as never;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
songDataServiceSpy = jasmine.createSpyObj<SongDataService>('SongDataService', ['read$', 'update$', 'add', 'delete'], {
|
songDataServiceSpy = jasmine.createSpyObj<SongDataService>('SongDataService', ['read$', 'update$', 'add', 'delete'], {
|
||||||
list$: of([song]),
|
list$: of([song]),
|
||||||
});
|
});
|
||||||
@@ -27,7 +27,7 @@ describe('SongService', () => {
|
|||||||
songDataServiceSpy.delete.and.resolveTo();
|
songDataServiceSpy.delete.and.resolveTo();
|
||||||
userServiceSpy.currentUser.and.resolveTo({name: 'Benjamin'} as never);
|
userServiceSpy.currentUser.and.resolveTo({name: 'Benjamin'} as never);
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: SongDataService, useValue: songDataServiceSpy},
|
{provide: SongDataService, useValue: songDataServiceSpy},
|
||||||
{provide: UserService, useValue: userServiceSpy},
|
{provide: UserService, useValue: userServiceSpy},
|
||||||
|
|||||||
@@ -333,11 +333,18 @@ Text`;
|
|||||||
|
|
||||||
void expect(sections[0].lines[0].text).toBe('Cmaj7(add9)');
|
void expect(sections[0].lines[0].text).toBe('Cmaj7(add9)');
|
||||||
void expect(sections[0].lines[0].chords).toEqual([
|
void expect(sections[0].lines[0].chords).toEqual([
|
||||||
{chord: 'C', length: 11, position: 0, add: 'maj7(add9)', slashChord: null, addDescriptor: descriptor('maj7(add9)', {
|
{
|
||||||
quality: 'major',
|
chord: 'C',
|
||||||
extensions: ['7'],
|
length: 11,
|
||||||
modifiers: ['(add9)'],
|
position: 0,
|
||||||
})},
|
add: 'maj7(add9)',
|
||||||
|
slashChord: null,
|
||||||
|
addDescriptor: descriptor('maj7(add9)', {
|
||||||
|
quality: 'major',
|
||||||
|
extensions: ['7'],
|
||||||
|
modifiers: ['(add9)'],
|
||||||
|
}),
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
void expect(service.validateChordNotation(text)).toEqual([]);
|
void expect(service.validateChordNotation(text)).toEqual([]);
|
||||||
});
|
});
|
||||||
@@ -459,9 +466,7 @@ Text`;
|
|||||||
|
|
||||||
void expect(sections[0].lines[0].type).toBe(LineType.chord);
|
void expect(sections[0].lines[0].type).toBe(LineType.chord);
|
||||||
void expect(sections[0].lines[0].text).toBe('C Es G');
|
void expect(sections[0].lines[0].text).toBe('C Es G');
|
||||||
void expect(service.validateChordNotation(text)).toEqual([
|
void expect(service.validateChordNotation(text)).toEqual([jasmine.objectContaining({lineNumber: 2, token: 'Es', reason: 'alias', suggestion: 'Eb'})]);
|
||||||
jasmine.objectContaining({lineNumber: 2, token: 'Es', reason: 'alias', suggestion: 'Eb'}),
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should flag unknown tokens on mostly chord lines', () => {
|
it('should flag unknown tokens on mostly chord lines', () => {
|
||||||
@@ -470,9 +475,7 @@ Text`;
|
|||||||
C Foo G a
|
C Foo G a
|
||||||
Text`;
|
Text`;
|
||||||
|
|
||||||
void expect(service.validateChordNotation(text)).toEqual([
|
void expect(service.validateChordNotation(text)).toEqual([jasmine.objectContaining({lineNumber: 2, token: 'Foo', reason: 'unknown_token', suggestion: null})]);
|
||||||
jasmine.objectContaining({lineNumber: 2, token: 'Foo', reason: 'unknown_token', suggestion: null}),
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject tabs on chord lines', () => {
|
it('should reject tabs on chord lines', () => {
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ import {LineType} from './line-type';
|
|||||||
import {Chord, ChordAddDescriptor, ChordValidationIssue} from './chord';
|
import {Chord, ChordAddDescriptor, ChordValidationIssue} from './chord';
|
||||||
import {Line} from './line';
|
import {Line} from './line';
|
||||||
|
|
||||||
const CHORD_ROOT_DEFINITIONS = [
|
type ChordRootDefinition = {
|
||||||
|
canonical: string;
|
||||||
|
aliases: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const CHORD_ROOT_DEFINITIONS: readonly ChordRootDefinition[] = [
|
||||||
{canonical: 'C#', aliases: ['Cis']},
|
{canonical: 'C#', aliases: ['Cis']},
|
||||||
{canonical: 'Db', aliases: ['Des']},
|
{canonical: 'Db', aliases: ['Des']},
|
||||||
{canonical: 'D#', aliases: ['Dis']},
|
{canonical: 'D#', aliases: ['Dis']},
|
||||||
@@ -45,9 +50,12 @@ const CHORD_ROOT_DEFINITIONS = [
|
|||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const CANONICAL_CHORD_ROOTS = CHORD_ROOT_DEFINITIONS.map(entry => entry.canonical);
|
const CANONICAL_CHORD_ROOTS = CHORD_ROOT_DEFINITIONS.map(entry => entry.canonical);
|
||||||
const ALTERNATIVE_CHORD_ROOTS = Object.fromEntries(
|
const ALTERNATIVE_CHORD_ROOTS = CHORD_ROOT_DEFINITIONS.reduce<Record<string, string>>((aliases, entry) => {
|
||||||
CHORD_ROOT_DEFINITIONS.flatMap(entry => entry.aliases.map(alias => [alias, entry.canonical]))
|
entry.aliases.forEach(alias => {
|
||||||
) as Record<string, string>;
|
aliases[alias] = entry.canonical;
|
||||||
|
});
|
||||||
|
return aliases;
|
||||||
|
}, {});
|
||||||
|
|
||||||
interface ParsedValidationToken {
|
interface ParsedValidationToken {
|
||||||
prefix: string;
|
prefix: string;
|
||||||
@@ -66,6 +74,11 @@ interface ChordLineValidationResult {
|
|||||||
isChordLike: boolean;
|
isChordLike: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ParsedTokenCandidate {
|
||||||
|
token: string;
|
||||||
|
parsed: ParsedValidationToken | null;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
@@ -141,9 +154,7 @@ export class TextRenderingService {
|
|||||||
private getLineOfLineText(text: string, transpose: TransposeMode | null, lineNumber?: number): Line | null {
|
private getLineOfLineText(text: string, transpose: TransposeMode | null, lineNumber?: number): Line | null {
|
||||||
if (!text) return null;
|
if (!text) return null;
|
||||||
|
|
||||||
const validationResult = lineNumber
|
const validationResult = lineNumber ? this.getChordLineValidationResult(text, lineNumber) : {chords: [], issues: [], isStrictChordLine: false, isChordLike: false};
|
||||||
? this.getChordLineValidationResult(text, lineNumber)
|
|
||||||
: {chords: [], issues: [], isStrictChordLine: false, isChordLike: false};
|
|
||||||
const validationIssues = validationResult.issues;
|
const validationIssues = validationResult.issues;
|
||||||
const hasMatches = validationResult.isStrictChordLine;
|
const hasMatches = validationResult.isStrictChordLine;
|
||||||
const isChordLikeLine = hasMatches || validationResult.isChordLike;
|
const isChordLikeLine = hasMatches || validationResult.isChordLike;
|
||||||
@@ -194,13 +205,12 @@ export class TextRenderingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getChordLineValidationResult(line: string, lineNumber: number): ChordLineValidationResult {
|
private getChordLineValidationResult(line: string, lineNumber: number): ChordLineValidationResult {
|
||||||
const tokens = line.match(/\S+/g) ?? [];
|
const tokens: string[] = line.match(/\S+/g) ?? [];
|
||||||
const chords = this.getParsedChords(line);
|
const chords = this.getParsedChords(line);
|
||||||
const parsedTokens = tokens
|
const parsedTokens: ParsedTokenCandidate[] = tokens.map(token => ({
|
||||||
.map(token => ({
|
token,
|
||||||
token,
|
parsed: this.parseValidationToken(token),
|
||||||
parsed: this.parseValidationToken(token),
|
}));
|
||||||
}));
|
|
||||||
|
|
||||||
const recognizedTokens = parsedTokens.filter((entry): entry is {token: string; parsed: ParsedValidationToken} => entry.parsed !== null);
|
const recognizedTokens = parsedTokens.filter((entry): entry is {token: string; parsed: ParsedValidationToken} => entry.parsed !== null);
|
||||||
|
|
||||||
@@ -227,12 +237,12 @@ export class TextRenderingService {
|
|||||||
...issues,
|
...issues,
|
||||||
...parsedTokens
|
...parsedTokens
|
||||||
.map(entry => {
|
.map(entry => {
|
||||||
if (!entry.parsed) {
|
if (!entry.parsed) {
|
||||||
return this.createUnknownTokenIssue(line, lineNumber, entry.token);
|
return this.createUnknownTokenIssue(line, lineNumber, entry.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.createValidationIssue(line, lineNumber, entry.token, entry.parsed);
|
return this.createValidationIssue(line, lineNumber, entry.token, entry.parsed);
|
||||||
})
|
})
|
||||||
.filter((issue): issue is ChordValidationIssue => issue !== null),
|
.filter((issue): issue is ChordValidationIssue => issue !== null),
|
||||||
],
|
],
|
||||||
isStrictChordLine,
|
isStrictChordLine,
|
||||||
@@ -482,9 +492,7 @@ export class TextRenderingService {
|
|||||||
suffix = this.stripLeadingDurMarker(normalizedSuffix);
|
suffix = this.stripLeadingDurMarker(normalizedSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
const slashChord = parsed.slashChord
|
const slashChord = parsed.slashChord ? this.toMajorRoot(parsed.slashChord) : null;
|
||||||
? this.toMajorRoot(parsed.slashChord)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return parsed.prefix + root + suffix + (slashChord ? `/${slashChord}` : '') + parsed.tokenSuffix;
|
return parsed.prefix + root + suffix + (slashChord ? `/${slashChord}` : '') + parsed.tokenSuffix;
|
||||||
}
|
}
|
||||||
@@ -671,7 +679,7 @@ export class TextRenderingService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
while (rest.length > 0) {
|
while (rest.length > 0) {
|
||||||
const additionMatch = rest.match(/^add([#b+\-]?\d+)/);
|
const additionMatch = rest.match(/^add([#b+-]?\d+)/);
|
||||||
if (additionMatch) {
|
if (additionMatch) {
|
||||||
descriptor.additions.push(additionMatch[1]);
|
descriptor.additions.push(additionMatch[1]);
|
||||||
rest = rest.slice(additionMatch[0].length);
|
rest = rest.slice(additionMatch[0].length);
|
||||||
@@ -692,7 +700,7 @@ export class TextRenderingService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const alterationMatch = rest.match(/^[#b+\-]\d+/);
|
const alterationMatch = rest.match(/^[#b+-]\d+/);
|
||||||
if (alterationMatch) {
|
if (alterationMatch) {
|
||||||
descriptor.alterations.push(alterationMatch[0]);
|
descriptor.alterations.push(alterationMatch[0]);
|
||||||
rest = rest.slice(alterationMatch[0].length);
|
rest = rest.slice(alterationMatch[0].length);
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ describe('TransposeService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should create map upwards', () => {
|
it('should create map upwards', () => {
|
||||||
const distance = service.getDistance('D', 'G');
|
|
||||||
const map = service.getMap('D', 'G');
|
const map = service.getMap('D', 'G');
|
||||||
|
|
||||||
if (map) {
|
if (map) {
|
||||||
@@ -22,7 +21,6 @@ describe('TransposeService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should create map downwards', () => {
|
it('should create map downwards', () => {
|
||||||
const distance = service.getDistance('G', 'D');
|
|
||||||
const map = service.getMap('G', 'D');
|
const map = service.getMap('G', 'D');
|
||||||
|
|
||||||
if (map) {
|
if (map) {
|
||||||
|
|||||||
@@ -7,12 +7,18 @@ import {UploadService} from './upload.service';
|
|||||||
describe('UploadService', () => {
|
describe('UploadService', () => {
|
||||||
let service: UploadService;
|
let service: UploadService;
|
||||||
let fileDataServiceSpy: jasmine.SpyObj<FileDataService>;
|
let fileDataServiceSpy: jasmine.SpyObj<FileDataService>;
|
||||||
|
type UploadTaskLike = {
|
||||||
|
on: (event: string, progress: (snapshot: {bytesTransferred: number; totalBytes: number}) => void, error: () => void, success: () => void) => void;
|
||||||
|
};
|
||||||
|
type UploadServiceInternals = UploadService & {
|
||||||
|
startUpload: (path: string, file: File) => UploadTaskLike;
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
fileDataServiceSpy = jasmine.createSpyObj<FileDataService>('FileDataService', ['set']);
|
fileDataServiceSpy = jasmine.createSpyObj<FileDataService>('FileDataService', ['set']);
|
||||||
fileDataServiceSpy.set.and.resolveTo('file-1');
|
fileDataServiceSpy.set.and.resolveTo('file-1');
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: Storage, useValue: {app: 'test-storage'}},
|
{provide: Storage, useValue: {app: 'test-storage'}},
|
||||||
{provide: FileDataService, useValue: fileDataServiceSpy},
|
{provide: FileDataService, useValue: fileDataServiceSpy},
|
||||||
@@ -27,17 +33,16 @@ describe('UploadService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should upload the file, update progress and persist file metadata on success', async () => {
|
it('should upload the file, update progress and persist file metadata on success', async () => {
|
||||||
const task = {
|
const task: UploadTaskLike = {
|
||||||
on: (event: string, progress: (snapshot: {bytesTransferred: number; totalBytes: number}) => void, error: () => void, success: () => void) => {
|
on: (event: string, progress: (snapshot: {bytesTransferred: number; totalBytes: number}) => void, error: () => void, success: () => void) => {
|
||||||
progress({bytesTransferred: 50, totalBytes: 100});
|
progress({bytesTransferred: 50, totalBytes: 100});
|
||||||
success();
|
success();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const uploadSpy = spyOn<any>(service, 'startUpload').and.returnValue(task as never);
|
const uploadSpy = spyOn(service as UploadServiceInternals, 'startUpload').and.returnValue(task);
|
||||||
const upload = new Upload(new File(['content'], 'test.pdf', {type: 'application/pdf'}));
|
const upload = new Upload(new File(['content'], 'test.pdf', {type: 'application/pdf'}));
|
||||||
|
|
||||||
service.pushUpload('song-1', upload);
|
await service.pushUpload('song-1', upload);
|
||||||
await Promise.resolve();
|
|
||||||
|
|
||||||
expect(uploadSpy).toHaveBeenCalledWith('/attachments/song-1/test.pdf', upload.file);
|
expect(uploadSpy).toHaveBeenCalledWith('/attachments/song-1/test.pdf', upload.file);
|
||||||
expect(upload.progress).toBe(50);
|
expect(upload.progress).toBe(50);
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ describe('ConfigService', () => {
|
|||||||
let service: ConfigService;
|
let service: ConfigService;
|
||||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc$']);
|
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc$']);
|
||||||
dbServiceSpy.doc$.and.returnValue(of({copyright: 'CCLI'}) as never);
|
dbServiceSpy.doc$.and.returnValue(of({copyright: 'CCLI'}) as never);
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ describe('GlobalSettingsService', () => {
|
|||||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||||
let updateSpy: jasmine.Spy;
|
let updateSpy: jasmine.Spy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
updateSpy = jasmine.createSpy('update').and.resolveTo();
|
updateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc$', 'doc']);
|
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc$', 'doc']);
|
||||||
dbServiceSpy.doc$.and.returnValue(of({churchName: 'ICF'}) as never);
|
dbServiceSpy.doc$.and.returnValue(of({churchName: 'ICF'}) as never);
|
||||||
dbServiceSpy.doc.and.returnValue({update: updateSpy} as never);
|
dbServiceSpy.doc.and.returnValue({update: updateSpy} as never);
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ describe('UserSessionService', () => {
|
|||||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||||
let routerSpy: jasmine.SpyObj<Router>;
|
let routerSpy: jasmine.SpyObj<Router>;
|
||||||
let authStateSubject: BehaviorSubject<unknown>;
|
let authStateSubject: BehaviorSubject<unknown>;
|
||||||
let createAuthStateSpy: jasmine.Spy;
|
let createAuthStateSpy: jasmine.Spy<() => ReturnType<UserSessionService['createAuthState$']>>;
|
||||||
let runInFirebaseContextSpy: jasmine.Spy;
|
let runInFirebaseContextSpy: jasmine.Spy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
authStateSubject = new BehaviorSubject<unknown>(null);
|
authStateSubject = new BehaviorSubject<unknown>(null);
|
||||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['col$', 'doc$', 'doc']);
|
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['col$', 'doc$', 'doc']);
|
||||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['navigateByUrl']);
|
routerSpy = jasmine.createSpyObj<Router>('Router', ['navigateByUrl']);
|
||||||
@@ -34,9 +34,9 @@ describe('UserSessionService', () => {
|
|||||||
} as never);
|
} as never);
|
||||||
routerSpy.navigateByUrl.and.resolveTo(true);
|
routerSpy.navigateByUrl.and.resolveTo(true);
|
||||||
|
|
||||||
createAuthStateSpy = spyOn<any>(UserSessionService.prototype, 'createAuthState$').and.returnValue(authStateSubject.asObservable() as never);
|
createAuthStateSpy = spyOn(UserSessionService.prototype, 'createAuthState$').and.returnValue(authStateSubject.asObservable() as never);
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: DbService, useValue: dbServiceSpy},
|
{provide: DbService, useValue: dbServiceSpy},
|
||||||
{provide: Router, useValue: routerSpy},
|
{provide: Router, useValue: routerSpy},
|
||||||
@@ -45,7 +45,7 @@ describe('UserSessionService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
service = TestBed.inject(UserSessionService);
|
service = TestBed.inject(UserSessionService);
|
||||||
runInFirebaseContextSpy = spyOn<any>(service, 'runInFirebaseContext');
|
runInFirebaseContextSpy = spyOn(service as UserSessionService & {runInFirebaseContext: (...args: unknown[]) => Promise<unknown>}, 'runInFirebaseContext');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be created', () => {
|
it('should be created', () => {
|
||||||
@@ -63,9 +63,7 @@ describe('UserSessionService', () => {
|
|||||||
it('should resolve the current user document from auth state', async () => {
|
it('should resolve the current user document from auth state', async () => {
|
||||||
authStateSubject.next({uid: 'user-1'});
|
authStateSubject.next({uid: 'user-1'});
|
||||||
|
|
||||||
await expectAsync(firstValueFrom(service.user$)).toBeResolvedTo(
|
await expectAsync(firstValueFrom(service.user$)).toBeResolvedTo(jasmine.objectContaining({id: 'user-1', name: 'Benjamin'}) as never);
|
||||||
jasmine.objectContaining({id: 'user-1', name: 'Benjamin'}) as never
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cache user lookups by id', async () => {
|
it('should cache user lookups by id', async () => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe('UserSongUsageService', () => {
|
|||||||
let showDataServiceSpy: jasmine.SpyObj<ShowDataService>;
|
let showDataServiceSpy: jasmine.SpyObj<ShowDataService>;
|
||||||
let showSongDataServiceSpy: jasmine.SpyObj<ShowSongDataService>;
|
let showSongDataServiceSpy: jasmine.SpyObj<ShowSongDataService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc']);
|
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc']);
|
||||||
sessionSpy = jasmine.createSpyObj<UserSessionService>('UserSessionService', ['update$'], {
|
sessionSpy = jasmine.createSpyObj<UserSessionService>('UserSessionService', ['update$'], {
|
||||||
user$: of({id: 'user-1', role: 'admin', songUsage: {}}) as never,
|
user$: of({id: 'user-1', role: 'admin', songUsage: {}}) as never,
|
||||||
@@ -23,13 +23,18 @@ describe('UserSongUsageService', () => {
|
|||||||
showSongDataServiceSpy = jasmine.createSpyObj<ShowSongDataService>('ShowSongDataService', ['list$']);
|
showSongDataServiceSpy = jasmine.createSpyObj<ShowSongDataService>('ShowSongDataService', ['list$']);
|
||||||
|
|
||||||
sessionSpy.update$.and.resolveTo();
|
sessionSpy.update$.and.resolveTo();
|
||||||
showDataServiceSpy.listRaw$.and.returnValue(of([{id: 'show-1', owner: 'user-1'}, {id: 'show-2', owner: 'user-2'}] as never));
|
showDataServiceSpy.listRaw$.and.returnValue(
|
||||||
|
of([
|
||||||
|
{id: 'show-1', owner: 'user-1'},
|
||||||
|
{id: 'show-2', owner: 'user-2'},
|
||||||
|
] as never)
|
||||||
|
);
|
||||||
showSongDataServiceSpy.list$.and.callFake((showId: string) =>
|
showSongDataServiceSpy.list$.and.callFake((showId: string) =>
|
||||||
of(showId === 'show-1' ? ([{songId: 'song-1'}, {songId: 'song-1'}, {songId: 'song-2'}] as never) : ([{songId: 'song-3'}] as never))
|
of(showId === 'show-1' ? ([{songId: 'song-1'}, {songId: 'song-1'}, {songId: 'song-2'}] as never) : ([{songId: 'song-3'}] as never))
|
||||||
);
|
);
|
||||||
dbServiceSpy.doc.and.returnValue({update: jasmine.createSpy('update').and.resolveTo()} as never);
|
dbServiceSpy.doc.and.returnValue({update: jasmine.createSpy('update').and.resolveTo()} as never);
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: DbService, useValue: dbServiceSpy},
|
{provide: DbService, useValue: dbServiceSpy},
|
||||||
{provide: UserSessionService, useValue: sessionSpy},
|
{provide: UserSessionService, useValue: sessionSpy},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ describe('UserService', () => {
|
|||||||
let sessionSpy: jasmine.SpyObj<UserSessionService>;
|
let sessionSpy: jasmine.SpyObj<UserSessionService>;
|
||||||
let songUsageSpy: jasmine.SpyObj<UserSongUsageService>;
|
let songUsageSpy: jasmine.SpyObj<UserSongUsageService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
sessionSpy = jasmine.createSpyObj<UserSessionService>(
|
sessionSpy = jasmine.createSpyObj<UserSessionService>(
|
||||||
'UserSessionService',
|
'UserSessionService',
|
||||||
['currentUser', 'getUserbyId', 'getUserbyId$', 'login', 'loggedIn$', 'list$', 'logout', 'update$', 'changePassword', 'createNewUser'],
|
['currentUser', 'getUserbyId', 'getUserbyId$', 'login', 'loggedIn$', 'list$', 'logout', 'update$', 'changePassword', 'createNewUser'],
|
||||||
@@ -35,7 +35,7 @@ describe('UserService', () => {
|
|||||||
songUsageSpy.decSongCount.and.resolveTo();
|
songUsageSpy.decSongCount.and.resolveTo();
|
||||||
songUsageSpy.rebuildSongUsage.and.resolveTo({usersProcessed: 1, showsProcessed: 2, showSongsProcessed: 3});
|
songUsageSpy.rebuildSongUsage.and.resolveTo({usersProcessed: 1, showsProcessed: 2, showSongsProcessed: 3});
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: UserSessionService, useValue: sessionSpy},
|
{provide: UserSessionService, useValue: sessionSpy},
|
||||||
{provide: UserSongUsageService, useValue: songUsageSpy},
|
{provide: UserSongUsageService, useValue: songUsageSpy},
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ describe('RoleGuard', () => {
|
|||||||
let guard: RoleGuard;
|
let guard: RoleGuard;
|
||||||
let routerSpy: jasmine.SpyObj<Router>;
|
let routerSpy: jasmine.SpyObj<Router>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||||
routerSpy.createUrlTree.and.callFake(commands => ({commands}) as never);
|
routerSpy.createUrlTree.and.callFake(commands => ({commands}) as never);
|
||||||
|
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: Router, useValue: routerSpy},
|
{provide: Router, useValue: routerSpy},
|
||||||
{provide: UserService, useValue: {user$: of(null)}},
|
{provide: UserService, useValue: {user$: of(null)}},
|
||||||
@@ -37,10 +37,10 @@ describe('RoleGuard', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow admins regardless of requiredRoles', done => {
|
it('should allow admins regardless of requiredRoles', async done => {
|
||||||
TestBed.resetTestingModule();
|
TestBed.resetTestingModule();
|
||||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: Router, useValue: routerSpy},
|
{provide: Router, useValue: routerSpy},
|
||||||
{provide: UserService, useValue: {user$: of({role: 'user;admin'})}},
|
{provide: UserService, useValue: {user$: of({role: 'user;admin'})}},
|
||||||
@@ -54,10 +54,10 @@ describe('RoleGuard', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow users with a matching required role', done => {
|
it('should allow users with a matching required role', async done => {
|
||||||
TestBed.resetTestingModule();
|
TestBed.resetTestingModule();
|
||||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: Router, useValue: routerSpy},
|
{provide: Router, useValue: routerSpy},
|
||||||
{provide: UserService, useValue: {user$: of({role: 'leader;user'})}},
|
{provide: UserService, useValue: {user$: of({role: 'leader;user'})}},
|
||||||
@@ -71,11 +71,11 @@ describe('RoleGuard', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
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 done => {
|
||||||
TestBed.resetTestingModule();
|
TestBed.resetTestingModule();
|
||||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||||
routerSpy.createUrlTree.and.returnValue({redirect: ['presentation']} as never);
|
routerSpy.createUrlTree.and.returnValue({redirect: ['presentation']} as never);
|
||||||
void TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: Router, useValue: routerSpy},
|
{provide: Router, useValue: routerSpy},
|
||||||
{provide: UserService, useValue: {user$: of({role: 'presenter'})}},
|
{provide: UserService, useValue: {user$: of({role: 'presenter'})}},
|
||||||
|
|||||||
45
src/test.ts
45
src/test.ts
@@ -17,6 +17,18 @@ import {environment} from './environments/environment';
|
|||||||
import {DbService} from './app/services/db.service';
|
import {DbService} from './app/services/db.service';
|
||||||
|
|
||||||
type req = {keys: () => {map: (context: req) => void}};
|
type req = {keys: () => {map: (context: req) => void}};
|
||||||
|
type TestingModuleDefinition = Parameters<typeof TestBed.configureTestingModule>[0];
|
||||||
|
type TestingProviderList = NonNullable<NonNullable<TestingModuleDefinition>['providers']>;
|
||||||
|
type CollectionStub = {
|
||||||
|
valueChanges: () => ReturnType<typeof of>;
|
||||||
|
add: () => Promise<{id: string}>;
|
||||||
|
};
|
||||||
|
type DocumentStub = {
|
||||||
|
set: () => Promise<void>;
|
||||||
|
update: () => Promise<void>;
|
||||||
|
delete: () => Promise<void>;
|
||||||
|
collection: () => CollectionStub;
|
||||||
|
};
|
||||||
|
|
||||||
// First, initialize the Angular testing environment.
|
// First, initialize the Angular testing environment.
|
||||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||||
@@ -24,7 +36,7 @@ getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDyn
|
|||||||
const routeParams$ = new BehaviorSubject<Record<string, unknown>>({});
|
const routeParams$ = new BehaviorSubject<Record<string, unknown>>({});
|
||||||
const queryParams$ = new BehaviorSubject<Record<string, unknown>>({});
|
const queryParams$ = new BehaviorSubject<Record<string, unknown>>({});
|
||||||
|
|
||||||
const defaultTestingProviders = [
|
const defaultTestingProviders: TestingProviderList = [
|
||||||
provideNoopAnimations(),
|
provideNoopAnimations(),
|
||||||
provideNativeDateAdapter(),
|
provideNativeDateAdapter(),
|
||||||
provideRouter([]),
|
provideRouter([]),
|
||||||
@@ -49,26 +61,29 @@ const defaultTestingProviders = [
|
|||||||
useValue: {
|
useValue: {
|
||||||
col$: () => of([]),
|
col$: () => of([]),
|
||||||
doc$: () => of(null),
|
doc$: () => of(null),
|
||||||
col: () => ({
|
col: (): CollectionStub => ({
|
||||||
valueChanges: () => of([]),
|
valueChanges: () => of([]),
|
||||||
add: async () => ({id: 'test-id'}),
|
add: () => Promise.resolve({id: 'test-id'}),
|
||||||
}),
|
}),
|
||||||
doc: () => ({
|
doc: (): DocumentStub => ({
|
||||||
set: async () => void 0,
|
set: () => Promise.resolve(),
|
||||||
update: async () => void 0,
|
update: () => Promise.resolve(),
|
||||||
delete: async () => void 0,
|
delete: () => Promise.resolve(),
|
||||||
collection: () => ({
|
collection: (): CollectionStub => ({
|
||||||
valueChanges: () => of([]),
|
valueChanges: () => of([]),
|
||||||
add: async () => ({id: 'test-id'}),
|
add: () => Promise.resolve({id: 'test-id'}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const originalConfigureTestingModule = TestBed.configureTestingModule.bind(TestBed);
|
const originalConfigureTestingModule = TestBed.configureTestingModule.bind(TestBed) as typeof TestBed.configureTestingModule;
|
||||||
TestBed.configureTestingModule = ((moduleDef?: Parameters<typeof TestBed.configureTestingModule>[0]) =>
|
const configureTestingModule: typeof TestBed.configureTestingModule = moduleDef => {
|
||||||
originalConfigureTestingModule({
|
const extraProviders: TestingProviderList = moduleDef?.providers ?? [];
|
||||||
...moduleDef,
|
const mergedModuleDef: TestingModuleDefinition = moduleDef ? {...moduleDef} : {};
|
||||||
providers: [...defaultTestingProviders, ...(moduleDef?.providers ?? [])],
|
mergedModuleDef.providers = defaultTestingProviders.concat(extraProviders);
|
||||||
})) as typeof TestBed.configureTestingModule;
|
|
||||||
|
return originalConfigureTestingModule(mergedModuleDef);
|
||||||
|
};
|
||||||
|
TestBed.configureTestingModule = configureTestingModule;
|
||||||
|
|||||||
Reference in New Issue
Block a user