global filter
This commit is contained in:
@@ -54,6 +54,21 @@
|
||||
"plugin:@angular-eslint/template/recommended"
|
||||
],
|
||||
"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 dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
docUpdateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||
docDeleteSpy = jasmine.createSpy('delete').and.resolveTo();
|
||||
docSpy = jasmine.createSpy('doc').and.returnValue({
|
||||
@@ -27,7 +27,7 @@ describe('GuestShowDataService', () => {
|
||||
dbServiceSpy.doc.and.callFake(docSpy);
|
||||
dbServiceSpy.col.and.callFake(colSpy);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||
});
|
||||
|
||||
|
||||
@@ -2,20 +2,22 @@ import {TestBed} from '@angular/core/testing';
|
||||
import {GuestShowDataService} from './guest-show-data.service';
|
||||
import {GuestShowService} from './guest-show.service';
|
||||
import {ShowService} from '../shows/services/show.service';
|
||||
import {Show} from '../shows/services/show';
|
||||
import {Song} from '../songs/services/song';
|
||||
|
||||
describe('GuestShowService', () => {
|
||||
let service: GuestShowService;
|
||||
let guestShowDataServiceSpy: jasmine.SpyObj<GuestShowDataService>;
|
||||
let showServiceSpy: jasmine.SpyObj<ShowService>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
guestShowDataServiceSpy = jasmine.createSpyObj<GuestShowDataService>('GuestShowDataService', ['add', 'update$']);
|
||||
showServiceSpy = jasmine.createSpyObj<ShowService>('ShowService', ['update$']);
|
||||
guestShowDataServiceSpy.add.and.resolveTo('share-1');
|
||||
guestShowDataServiceSpy.update$.and.resolveTo();
|
||||
showServiceSpy.update$.and.resolveTo();
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: GuestShowDataService, useValue: guestShowDataServiceSpy},
|
||||
{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 () => {
|
||||
const show = {id: 'show-1', showType: 'service-worship', date: new Date(), shareId: ''} as any;
|
||||
const songs = [{id: 'song-1'}] as any;
|
||||
const show = {id: 'show-1', showType: 'service-worship', date: new Date(), shareId: ''} as unknown as Show;
|
||||
const songs = [{id: 'song-1'}] as unknown as Song[];
|
||||
const expectedUrl = window.location.protocol + '//' + window.location.host + '/guest/share-1';
|
||||
|
||||
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 () => {
|
||||
const show = {id: 'show-1', showType: 'service-worship', date: new Date(), shareId: 'share-9'} as any;
|
||||
const songs = [{id: 'song-1'}] 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 unknown as Song[];
|
||||
const expectedUrl = window.location.protocol + '//' + window.location.host + '/guest/share-9';
|
||||
|
||||
await expectAsync(service.share(show, songs)).toBeResolvedTo(expectedUrl);
|
||||
|
||||
@@ -3,7 +3,8 @@ import {combineLatest} from 'rxjs';
|
||||
import {Show} from '../services/show';
|
||||
import {fade} from '../../../animations';
|
||||
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 {FilterStoreService} from '../../../services/filter-store.service';
|
||||
import {RoleDirective} from '../../../services/user/role.directive';
|
||||
@@ -49,10 +50,6 @@ export class ListComponent {
|
||||
|
||||
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 {
|
||||
return this.matchesTimeFilter(show, filter.time || 1) && (!filter.showType || show.showType === filter.showType);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,14 @@ import {DocxService} from './docx.service';
|
||||
|
||||
describe('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(() => {
|
||||
void TestBed.configureTestingModule({});
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({});
|
||||
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 () => {
|
||||
const prepareDataSpy = spyOn<any>(service, 'prepareData').and.resolveTo(null);
|
||||
const saveAsSpy = spyOn<any>(service, 'saveAs');
|
||||
const serviceInternals = service as DocxServiceInternals;
|
||||
const prepareDataSpy = spyOn(serviceInternals, 'prepareData').and.resolveTo(null);
|
||||
const saveAsSpy = spyOn(serviceInternals, 'saveAs');
|
||||
|
||||
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 () => {
|
||||
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: {
|
||||
showType: 'service-worship',
|
||||
date: {toDate: () => new Date('2026-03-10T00:00:00Z')},
|
||||
@@ -35,8 +42,8 @@ describe('DocxService', () => {
|
||||
user: {name: 'Benjamin'},
|
||||
config: {ccliLicenseId: '12345'},
|
||||
});
|
||||
const prepareNewDocumentSpy = spyOn<any>(service, 'prepareNewDocument').and.returnValue({doc: true});
|
||||
const saveAsSpy = spyOn<any>(service, 'saveAs');
|
||||
const prepareNewDocumentSpy = spyOn(serviceInternals, 'prepareNewDocument').and.returnValue({doc: true});
|
||||
const saveAsSpy = spyOn(serviceInternals, 'saveAs');
|
||||
spyOn(Packer, 'toBlob').and.resolveTo(blob);
|
||||
|
||||
await service.create('show-1', {copyright: true});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {firstValueFrom, of, Subject} from 'rxjs';
|
||||
import {skip, take} from 'rxjs/operators';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {DbService} from '../../../services/db.service';
|
||||
import {ShowDataService} from './show-data.service';
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('ShowDataService', () => {
|
||||
let colSpy: jasmine.Spy;
|
||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
shows$ = new Subject<Array<{id: string; date: {toMillis: () => number}; archived?: boolean}>>();
|
||||
docUpdateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||
docSpy = jasmine.createSpy('doc').and.returnValue({update: docUpdateSpy});
|
||||
@@ -25,7 +25,7 @@ describe('ShowDataService', () => {
|
||||
dbServiceSpy.doc.and.callFake(docSpy);
|
||||
dbServiceSpy.col.and.callFake(colSpy);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||
});
|
||||
|
||||
@@ -76,11 +76,7 @@ describe('ShowDataService', () => {
|
||||
});
|
||||
|
||||
it('should request only published recent shows and filter archived entries', async () => {
|
||||
const publicShows$ = of([
|
||||
{id: 'show-1', archived: false},
|
||||
{id: 'show-2', archived: true},
|
||||
{id: 'show-3'},
|
||||
]);
|
||||
const publicShows$ = of([{id: 'show-1', archived: false}, {id: 'show-2', archived: true}, {id: 'show-3'}]);
|
||||
dbServiceSpy.col$.and.returnValue(publicShows$ as never);
|
||||
|
||||
const result = await firstValueFrom(service.listPublicSince$(3));
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('ShowSongDataService', () => {
|
||||
let colSpy: jasmine.Spy;
|
||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
docUpdateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||
docDeleteSpy = jasmine.createSpy('delete').and.resolveTo();
|
||||
docSpy = jasmine.createSpy('doc').and.returnValue({
|
||||
@@ -27,7 +27,7 @@ describe('ShowSongDataService', () => {
|
||||
dbServiceSpy.doc.and.callFake(docSpy);
|
||||
dbServiceSpy.col.and.callFake(colSpy);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@ import {UserService} from '../../../services/user/user.service';
|
||||
import {ShowService} from './show.service';
|
||||
import {ShowSongDataService} from './show-song-data.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', () => {
|
||||
let service: ShowSongService;
|
||||
@@ -12,13 +16,13 @@ describe('ShowSongService', () => {
|
||||
let songDataServiceSpy: jasmine.SpyObj<SongDataService>;
|
||||
let userServiceSpy: jasmine.SpyObj<UserService>;
|
||||
let showServiceSpy: jasmine.SpyObj<ShowService>;
|
||||
let user$: BehaviorSubject<any>;
|
||||
const song = {id: 'song-1', key: 'G', title: 'Amazing Grace'} as any;
|
||||
const showSong = {id: 'show-song-1', songId: 'song-1'} as any;
|
||||
const show = {id: 'show-1', order: ['show-song-1', 'show-song-2']} as any;
|
||||
let user$: BehaviorSubject<User | null>;
|
||||
const song = {id: 'song-1', key: 'G', title: 'Amazing Grace'} as unknown as Song;
|
||||
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 unknown as Show;
|
||||
|
||||
beforeEach(() => {
|
||||
user$ = new BehaviorSubject<any>({id: 'user-1', name: 'Benjamin', role: 'editor', chordMode: 'letters', songUsage: {}});
|
||||
beforeEach(async () => {
|
||||
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$']);
|
||||
songDataServiceSpy = jasmine.createSpyObj<SongDataService>('SongDataService', ['read$']);
|
||||
userServiceSpy = jasmine.createSpyObj<UserService>('UserService', ['incSongCount', 'decSongCount'], {
|
||||
@@ -37,7 +41,7 @@ describe('ShowSongService', () => {
|
||||
showServiceSpy.read$.and.returnValue(of(show));
|
||||
showServiceSpy.update$.and.resolveTo();
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: ShowSongDataService, useValue: showSongDataServiceSpy},
|
||||
{provide: SongDataService, useValue: songDataServiceSpy},
|
||||
|
||||
@@ -14,7 +14,7 @@ describe('ShowService', () => {
|
||||
{id: 'show-3', owner: 'user-1', published: true, archived: true},
|
||||
] as never;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
user$ = new BehaviorSubject<unknown>({id: 'user-1'});
|
||||
showDataServiceSpy = jasmine.createSpyObj<ShowDataService>('ShowDataService', ['read$', 'listPublicSince$', 'update', 'add'], {
|
||||
list$: of(shows),
|
||||
@@ -24,7 +24,7 @@ describe('ShowService', () => {
|
||||
showDataServiceSpy.update.and.resolveTo();
|
||||
showDataServiceSpy.add.and.resolveTo('new-show-id');
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: ShowDataService, useValue: showDataServiceSpy},
|
||||
{provide: UserService, useValue: {user$: user$.asObservable()}},
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('FileDataService', () => {
|
||||
let fileDeleteSpy: jasmine.Spy;
|
||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
filesCollectionValueChangesSpy = jasmine.createSpy('valueChanges').and.returnValue(of([{id: 'file-1', name: 'plan.pdf'}]));
|
||||
filesCollectionAddSpy = jasmine.createSpy('add').and.resolveTo({id: 'file-2'});
|
||||
songDocCollectionSpy = jasmine.createSpy('collection').and.returnValue({
|
||||
@@ -30,7 +30,7 @@ describe('FileDataService', () => {
|
||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc']);
|
||||
dbServiceSpy.doc.and.callFake(songDocSpy);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||
});
|
||||
|
||||
|
||||
@@ -6,12 +6,16 @@ import {FileService} from './file.service';
|
||||
describe('FileService', () => {
|
||||
let service: FileService;
|
||||
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.delete.and.resolveTo();
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: Storage, useValue: {app: 'test-storage'}},
|
||||
{provide: FileDataService, useValue: fileDataServiceSpy},
|
||||
@@ -26,7 +30,7 @@ describe('FileService', () => {
|
||||
});
|
||||
|
||||
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');
|
||||
|
||||
@@ -34,10 +38,9 @@ describe('FileService', () => {
|
||||
});
|
||||
|
||||
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 Promise.resolve();
|
||||
await service.delete('songs/song-1/file.pdf', 'song-1', 'file-1');
|
||||
|
||||
expect(deleteFromStorageSpy).toHaveBeenCalledWith('songs/song-1/file.pdf');
|
||||
expect(fileDataServiceSpy.delete).toHaveBeenCalledWith('song-1', 'file-1');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
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 {SongDataService} from './song-data.service';
|
||||
|
||||
@@ -14,7 +14,7 @@ describe('SongDataService', () => {
|
||||
let colSpy: jasmine.Spy;
|
||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
songs$ = new Subject<Array<{id: string; title: string}>>();
|
||||
docUpdateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||
docDeleteSpy = jasmine.createSpy('delete').and.resolveTo();
|
||||
@@ -32,7 +32,7 @@ describe('SongDataService', () => {
|
||||
dbServiceSpy.doc.and.callFake(docSpy);
|
||||
dbServiceSpy.col.and.callFake(colSpy);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||
});
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ describe('SongListResolver', () => {
|
||||
let resolver: SongListResolver;
|
||||
let songServiceSpy: jasmine.SpyObj<SongService>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
songServiceSpy = jasmine.createSpyObj<SongService>('SongService', ['list$']);
|
||||
songServiceSpy.list$.and.returnValue(of([{id: 'song-1', title: 'Amazing Grace'}] as never));
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [{provide: SongService, useValue: songServiceSpy}],
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('SongService', () => {
|
||||
edits: [],
|
||||
} as never;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
songDataServiceSpy = jasmine.createSpyObj<SongDataService>('SongDataService', ['read$', 'update$', 'add', 'delete'], {
|
||||
list$: of([song]),
|
||||
});
|
||||
@@ -27,7 +27,7 @@ describe('SongService', () => {
|
||||
songDataServiceSpy.delete.and.resolveTo();
|
||||
userServiceSpy.currentUser.and.resolveTo({name: 'Benjamin'} as never);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: SongDataService, useValue: songDataServiceSpy},
|
||||
{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].chords).toEqual([
|
||||
{chord: 'C', length: 11, position: 0, add: 'maj7(add9)', slashChord: null, addDescriptor: descriptor('maj7(add9)', {
|
||||
{
|
||||
chord: 'C',
|
||||
length: 11,
|
||||
position: 0,
|
||||
add: 'maj7(add9)',
|
||||
slashChord: null,
|
||||
addDescriptor: descriptor('maj7(add9)', {
|
||||
quality: 'major',
|
||||
extensions: ['7'],
|
||||
modifiers: ['(add9)'],
|
||||
})},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
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].text).toBe('C Es G');
|
||||
void expect(service.validateChordNotation(text)).toEqual([
|
||||
jasmine.objectContaining({lineNumber: 2, token: 'Es', reason: 'alias', suggestion: 'Eb'}),
|
||||
]);
|
||||
void expect(service.validateChordNotation(text)).toEqual([jasmine.objectContaining({lineNumber: 2, token: 'Es', reason: 'alias', suggestion: 'Eb'})]);
|
||||
});
|
||||
|
||||
it('should flag unknown tokens on mostly chord lines', () => {
|
||||
@@ -470,9 +475,7 @@ Text`;
|
||||
C Foo G a
|
||||
Text`;
|
||||
|
||||
void expect(service.validateChordNotation(text)).toEqual([
|
||||
jasmine.objectContaining({lineNumber: 2, token: 'Foo', reason: 'unknown_token', suggestion: null}),
|
||||
]);
|
||||
void expect(service.validateChordNotation(text)).toEqual([jasmine.objectContaining({lineNumber: 2, token: 'Foo', reason: 'unknown_token', suggestion: null})]);
|
||||
});
|
||||
|
||||
it('should reject tabs on chord lines', () => {
|
||||
|
||||
@@ -7,7 +7,12 @@ import {LineType} from './line-type';
|
||||
import {Chord, ChordAddDescriptor, ChordValidationIssue} from './chord';
|
||||
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: 'Db', aliases: ['Des']},
|
||||
{canonical: 'D#', aliases: ['Dis']},
|
||||
@@ -45,9 +50,12 @@ const CHORD_ROOT_DEFINITIONS = [
|
||||
] as const;
|
||||
|
||||
const CANONICAL_CHORD_ROOTS = CHORD_ROOT_DEFINITIONS.map(entry => entry.canonical);
|
||||
const ALTERNATIVE_CHORD_ROOTS = Object.fromEntries(
|
||||
CHORD_ROOT_DEFINITIONS.flatMap(entry => entry.aliases.map(alias => [alias, entry.canonical]))
|
||||
) as Record<string, string>;
|
||||
const ALTERNATIVE_CHORD_ROOTS = CHORD_ROOT_DEFINITIONS.reduce<Record<string, string>>((aliases, entry) => {
|
||||
entry.aliases.forEach(alias => {
|
||||
aliases[alias] = entry.canonical;
|
||||
});
|
||||
return aliases;
|
||||
}, {});
|
||||
|
||||
interface ParsedValidationToken {
|
||||
prefix: string;
|
||||
@@ -66,6 +74,11 @@ interface ChordLineValidationResult {
|
||||
isChordLike: boolean;
|
||||
}
|
||||
|
||||
interface ParsedTokenCandidate {
|
||||
token: string;
|
||||
parsed: ParsedValidationToken | null;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
@@ -141,9 +154,7 @@ export class TextRenderingService {
|
||||
private getLineOfLineText(text: string, transpose: TransposeMode | null, lineNumber?: number): Line | null {
|
||||
if (!text) return null;
|
||||
|
||||
const validationResult = lineNumber
|
||||
? this.getChordLineValidationResult(text, lineNumber)
|
||||
: {chords: [], issues: [], isStrictChordLine: false, isChordLike: false};
|
||||
const validationResult = lineNumber ? this.getChordLineValidationResult(text, lineNumber) : {chords: [], issues: [], isStrictChordLine: false, isChordLike: false};
|
||||
const validationIssues = validationResult.issues;
|
||||
const hasMatches = validationResult.isStrictChordLine;
|
||||
const isChordLikeLine = hasMatches || validationResult.isChordLike;
|
||||
@@ -194,10 +205,9 @@ export class TextRenderingService {
|
||||
}
|
||||
|
||||
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 parsedTokens = tokens
|
||||
.map(token => ({
|
||||
const parsedTokens: ParsedTokenCandidate[] = tokens.map(token => ({
|
||||
token,
|
||||
parsed: this.parseValidationToken(token),
|
||||
}));
|
||||
@@ -482,9 +492,7 @@ export class TextRenderingService {
|
||||
suffix = this.stripLeadingDurMarker(normalizedSuffix);
|
||||
}
|
||||
|
||||
const slashChord = parsed.slashChord
|
||||
? this.toMajorRoot(parsed.slashChord)
|
||||
: null;
|
||||
const slashChord = parsed.slashChord ? this.toMajorRoot(parsed.slashChord) : null;
|
||||
|
||||
return parsed.prefix + root + suffix + (slashChord ? `/${slashChord}` : '') + parsed.tokenSuffix;
|
||||
}
|
||||
@@ -671,7 +679,7 @@ export class TextRenderingService {
|
||||
};
|
||||
|
||||
while (rest.length > 0) {
|
||||
const additionMatch = rest.match(/^add([#b+\-]?\d+)/);
|
||||
const additionMatch = rest.match(/^add([#b+-]?\d+)/);
|
||||
if (additionMatch) {
|
||||
descriptor.additions.push(additionMatch[1]);
|
||||
rest = rest.slice(additionMatch[0].length);
|
||||
@@ -692,7 +700,7 @@ export class TextRenderingService {
|
||||
continue;
|
||||
}
|
||||
|
||||
const alterationMatch = rest.match(/^[#b+\-]\d+/);
|
||||
const alterationMatch = rest.match(/^[#b+-]\d+/);
|
||||
if (alterationMatch) {
|
||||
descriptor.alterations.push(alterationMatch[0]);
|
||||
rest = rest.slice(alterationMatch[0].length);
|
||||
|
||||
@@ -13,7 +13,6 @@ describe('TransposeService', () => {
|
||||
});
|
||||
|
||||
it('should create map upwards', () => {
|
||||
const distance = service.getDistance('D', 'G');
|
||||
const map = service.getMap('D', 'G');
|
||||
|
||||
if (map) {
|
||||
@@ -22,7 +21,6 @@ describe('TransposeService', () => {
|
||||
});
|
||||
|
||||
it('should create map downwards', () => {
|
||||
const distance = service.getDistance('G', 'D');
|
||||
const map = service.getMap('G', 'D');
|
||||
|
||||
if (map) {
|
||||
|
||||
@@ -7,12 +7,18 @@ import {UploadService} from './upload.service';
|
||||
describe('UploadService', () => {
|
||||
let service: UploadService;
|
||||
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.set.and.resolveTo('file-1');
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: Storage, useValue: {app: 'test-storage'}},
|
||||
{provide: FileDataService, useValue: fileDataServiceSpy},
|
||||
@@ -27,17 +33,16 @@ describe('UploadService', () => {
|
||||
});
|
||||
|
||||
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) => {
|
||||
progress({bytesTransferred: 50, totalBytes: 100});
|
||||
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'}));
|
||||
|
||||
service.pushUpload('song-1', upload);
|
||||
await Promise.resolve();
|
||||
await service.pushUpload('song-1', upload);
|
||||
|
||||
expect(uploadSpy).toHaveBeenCalledWith('/attachments/song-1/test.pdf', upload.file);
|
||||
expect(upload.progress).toBe(50);
|
||||
|
||||
@@ -7,11 +7,11 @@ describe('ConfigService', () => {
|
||||
let service: ConfigService;
|
||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc$']);
|
||||
dbServiceSpy.doc$.and.returnValue(of({copyright: 'CCLI'}) as never);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||
});
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@ describe('GlobalSettingsService', () => {
|
||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||
let updateSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
updateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc$', 'doc']);
|
||||
dbServiceSpy.doc$.and.returnValue(of({churchName: 'ICF'}) as never);
|
||||
dbServiceSpy.doc.and.returnValue({update: updateSpy} as never);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||
});
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ describe('UserSessionService', () => {
|
||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||
let routerSpy: jasmine.SpyObj<Router>;
|
||||
let authStateSubject: BehaviorSubject<unknown>;
|
||||
let createAuthStateSpy: jasmine.Spy;
|
||||
let createAuthStateSpy: jasmine.Spy<() => ReturnType<UserSessionService['createAuthState$']>>;
|
||||
let runInFirebaseContextSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
authStateSubject = new BehaviorSubject<unknown>(null);
|
||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['col$', 'doc$', 'doc']);
|
||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['navigateByUrl']);
|
||||
@@ -34,9 +34,9 @@ describe('UserSessionService', () => {
|
||||
} as never);
|
||||
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: [
|
||||
{provide: DbService, useValue: dbServiceSpy},
|
||||
{provide: Router, useValue: routerSpy},
|
||||
@@ -45,7 +45,7 @@ describe('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', () => {
|
||||
@@ -63,9 +63,7 @@ describe('UserSessionService', () => {
|
||||
it('should resolve the current user document from auth state', async () => {
|
||||
authStateSubject.next({uid: 'user-1'});
|
||||
|
||||
await expectAsync(firstValueFrom(service.user$)).toBeResolvedTo(
|
||||
jasmine.objectContaining({id: 'user-1', name: 'Benjamin'}) as never
|
||||
);
|
||||
await expectAsync(firstValueFrom(service.user$)).toBeResolvedTo(jasmine.objectContaining({id: 'user-1', name: 'Benjamin'}) as never);
|
||||
});
|
||||
|
||||
it('should cache user lookups by id', async () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('UserSongUsageService', () => {
|
||||
let showDataServiceSpy: jasmine.SpyObj<ShowDataService>;
|
||||
let showSongDataServiceSpy: jasmine.SpyObj<ShowSongDataService>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc']);
|
||||
sessionSpy = jasmine.createSpyObj<UserSessionService>('UserSessionService', ['update$'], {
|
||||
user$: of({id: 'user-1', role: 'admin', songUsage: {}}) as never,
|
||||
@@ -23,13 +23,18 @@ describe('UserSongUsageService', () => {
|
||||
showSongDataServiceSpy = jasmine.createSpyObj<ShowSongDataService>('ShowSongDataService', ['list$']);
|
||||
|
||||
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) =>
|
||||
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);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: DbService, useValue: dbServiceSpy},
|
||||
{provide: UserSessionService, useValue: sessionSpy},
|
||||
|
||||
@@ -9,7 +9,7 @@ describe('UserService', () => {
|
||||
let sessionSpy: jasmine.SpyObj<UserSessionService>;
|
||||
let songUsageSpy: jasmine.SpyObj<UserSongUsageService>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
sessionSpy = jasmine.createSpyObj<UserSessionService>(
|
||||
'UserSessionService',
|
||||
['currentUser', 'getUserbyId', 'getUserbyId$', 'login', 'loggedIn$', 'list$', 'logout', 'update$', 'changePassword', 'createNewUser'],
|
||||
@@ -35,7 +35,7 @@ describe('UserService', () => {
|
||||
songUsageSpy.decSongCount.and.resolveTo();
|
||||
songUsageSpy.rebuildSongUsage.and.resolveTo({usersProcessed: 1, showsProcessed: 2, showSongsProcessed: 3});
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: UserSessionService, useValue: sessionSpy},
|
||||
{provide: UserSongUsageService, useValue: songUsageSpy},
|
||||
|
||||
@@ -8,11 +8,11 @@ describe('RoleGuard', () => {
|
||||
let guard: RoleGuard;
|
||||
let routerSpy: jasmine.SpyObj<Router>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||
routerSpy.createUrlTree.and.callFake(commands => ({commands}) as never);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: Router, useValue: routerSpy},
|
||||
{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();
|
||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: Router, useValue: routerSpy},
|
||||
{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();
|
||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: Router, useValue: routerSpy},
|
||||
{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();
|
||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||
routerSpy.createUrlTree.and.returnValue({redirect: ['presentation']} as never);
|
||||
void TestBed.configureTestingModule({
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: Router, useValue: routerSpy},
|
||||
{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';
|
||||
|
||||
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.
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
@@ -24,7 +36,7 @@ getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDyn
|
||||
const routeParams$ = new BehaviorSubject<Record<string, unknown>>({});
|
||||
const queryParams$ = new BehaviorSubject<Record<string, unknown>>({});
|
||||
|
||||
const defaultTestingProviders = [
|
||||
const defaultTestingProviders: TestingProviderList = [
|
||||
provideNoopAnimations(),
|
||||
provideNativeDateAdapter(),
|
||||
provideRouter([]),
|
||||
@@ -49,26 +61,29 @@ const defaultTestingProviders = [
|
||||
useValue: {
|
||||
col$: () => of([]),
|
||||
doc$: () => of(null),
|
||||
col: () => ({
|
||||
col: (): CollectionStub => ({
|
||||
valueChanges: () => of([]),
|
||||
add: async () => ({id: 'test-id'}),
|
||||
add: () => Promise.resolve({id: 'test-id'}),
|
||||
}),
|
||||
doc: () => ({
|
||||
set: async () => void 0,
|
||||
update: async () => void 0,
|
||||
delete: async () => void 0,
|
||||
collection: () => ({
|
||||
doc: (): DocumentStub => ({
|
||||
set: () => Promise.resolve(),
|
||||
update: () => Promise.resolve(),
|
||||
delete: () => Promise.resolve(),
|
||||
collection: (): CollectionStub => ({
|
||||
valueChanges: () => of([]),
|
||||
add: async () => ({id: 'test-id'}),
|
||||
add: () => Promise.resolve({id: 'test-id'}),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const originalConfigureTestingModule = TestBed.configureTestingModule.bind(TestBed);
|
||||
TestBed.configureTestingModule = ((moduleDef?: Parameters<typeof TestBed.configureTestingModule>[0]) =>
|
||||
originalConfigureTestingModule({
|
||||
...moduleDef,
|
||||
providers: [...defaultTestingProviders, ...(moduleDef?.providers ?? [])],
|
||||
})) as typeof TestBed.configureTestingModule;
|
||||
const originalConfigureTestingModule = TestBed.configureTestingModule.bind(TestBed) as typeof TestBed.configureTestingModule;
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user