global filter
This commit is contained in:
@@ -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)', {
|
||||
quality: 'major',
|
||||
extensions: ['7'],
|
||||
modifiers: ['(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,13 +205,12 @@ 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 => ({
|
||||
token,
|
||||
parsed: this.parseValidationToken(token),
|
||||
}));
|
||||
const parsedTokens: ParsedTokenCandidate[] = tokens.map(token => ({
|
||||
token,
|
||||
parsed: this.parseValidationToken(token),
|
||||
}));
|
||||
|
||||
const recognizedTokens = parsedTokens.filter((entry): entry is {token: string; parsed: ParsedValidationToken} => entry.parsed !== null);
|
||||
|
||||
@@ -227,12 +237,12 @@ export class TextRenderingService {
|
||||
...issues,
|
||||
...parsedTokens
|
||||
.map(entry => {
|
||||
if (!entry.parsed) {
|
||||
return this.createUnknownTokenIssue(line, lineNumber, entry.token);
|
||||
}
|
||||
if (!entry.parsed) {
|
||||
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),
|
||||
],
|
||||
isStrictChordLine,
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user