vitest implementation

This commit is contained in:
2026-03-16 18:38:15 +01:00
parent 2173ad6abf
commit ecb25ee322
70 changed files with 3560 additions and 1067 deletions

View File

@@ -95,35 +95,14 @@
"defaultConfiguration": "development"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"builder": "@angular/build:unit-test",
"options": {
"main": "src/test.ts",
"polyfills": [
"src/polyfills.ts",
"zone.js/testing"
],
"runner": "vitest",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "less",
"assets": [
"src/browserconfig.xml",
"src/android-chrome-192x192.png",
"src/apple-touch-icon.png",
"src/apple-touch-icon-precomposed.png",
"src/safari-pinned-tab.svg",
"src/favicon.ico",
"src/favicon-16x16.png",
"src/favicon-32x32.png",
"src/mstile-150x150.png",
"src/assets",
"src/manifest.webmanifest"
"setupFiles": [
"src/test-vitest.ts"
],
"styles": [
"src/custom-theme.scss",
"src/styles/styles.less",
"src/styles/shadow.less"
],
"scripts": []
"runnerConfig": true
}
}
}

View File

@@ -1,32 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/wgenerator'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

3527
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,22 +47,15 @@
"@angular/cli": "^21.2.1",
"@angular/compiler-cli": "^21.2.2",
"@angular/language-service": "^21.2.2",
"@types/jasmine": "~6.0.0",
"@types/jasminewd2": "~2.0.13",
"@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^8.57.0",
"eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5",
"firebase-tools": "^15.9.1",
"jasmine-core": "~6.1.0",
"jasmine-spec-reporter": "~7.0.0",
"karma": "~6.4.4",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage-istanbul-reporter": "~3.0.3",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "^2.2.0",
"jsdom": "^29.0.0",
"prettier": "^3.8.1",
"typescript": "~5.9.3"
"typescript": "~5.9.3",
"vitest": "^4.1.0"
}
}

View File

@@ -1,13 +1,13 @@
import {TestBed, waitForAsync} from '@angular/core/testing';
import {TestBed} from '@angular/core/testing';
import {RouterTestingModule} from '@angular/router/testing';
import {AppComponent} from './app.component';
describe('AppComponent', () => {
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RouterTestingModule, AppComponent],
}).compileComponents();
}));
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {BrandComponent} from './brand.component';
@@ -6,11 +6,11 @@ describe('BrandComponent', () => {
let component: BrandComponent;
let fixture: ComponentFixture<BrandComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [BrandComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(BrandComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {NewUserComponent} from './new-user.component';
@@ -6,11 +6,11 @@ describe('NewUserComponent', () => {
let component: NewUserComponent;
let fixture: ComponentFixture<NewUserComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NewUserComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(NewUserComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {GuestComponent} from './guest.component';
@@ -6,11 +6,11 @@ describe('GuestComponent', () => {
let component: GuestComponent;
let fixture: ComponentFixture<GuestComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GuestComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(GuestComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {LegalComponent} from './legal.component';
@@ -6,11 +6,11 @@ describe('LegalComponent', () => {
let component: LegalComponent;
let fixture: ComponentFixture<LegalComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LegalComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(LegalComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {LogoComponent} from './logo.component';
@@ -6,11 +6,11 @@ describe('LogoComponent', () => {
let component: LogoComponent;
let fixture: ComponentFixture<LogoComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LogoComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(LogoComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {MonitorComponent} from './monitor.component';
@@ -6,11 +6,11 @@ describe('MonitorComponent', () => {
let component: MonitorComponent;
let fixture: ComponentFixture<MonitorComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MonitorComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(MonitorComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {RemoteComponent} from './remote.component';
@@ -6,11 +6,11 @@ describe('RemoteComponent', () => {
let component: RemoteComponent;
let fixture: ComponentFixture<RemoteComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RemoteComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(RemoteComponent);

View File

@@ -1,6 +1,6 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {Router} from '@angular/router';
import {of} from 'rxjs';
import {firstValueFrom, of} from 'rxjs';
import {GlobalSettingsService} from '../../../services/global-settings.service';
import {ShowService} from '../../shows/services/show.service';
import {SelectComponent} from './select.component';
@@ -52,12 +52,11 @@ describe('SelectComponent', () => {
expect(component.visible).toBeTrue();
});
it('should expose recent shows sorted descending by date', done => {
component.shows$.subscribe(shows => {
expect(showServiceSpy.list$).toHaveBeenCalledWith(true);
expect(shows.map(show => show.id)).toEqual(['recent-a', 'recent-b']);
done();
});
it('should expose recent shows sorted descending by date', async () => {
const shows = await firstValueFrom(component.shows$);
expect(showServiceSpy.list$).toHaveBeenCalledWith(true);
expect(shows.map(show => show.id)).toEqual(['recent-a', 'recent-b']);
});
it('should persist the selected show, trigger presentation reset and navigate', async () => {

View File

@@ -5,8 +5,8 @@ import {PresentationService} from './presentation.service';
describe('PresentationService', () => {
let service: PresentationService;
beforeEach(() => {
void TestBed.configureTestingModule({});
beforeEach(async () => {
await TestBed.configureTestingModule({});
service = TestBed.inject(PresentationService);
});

View File

@@ -1,5 +1,5 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {BehaviorSubject, of} from 'rxjs';
import {BehaviorSubject, firstValueFrom, of} from 'rxjs';
import {skip, take} from 'rxjs/operators';
import {ListComponent} from './list.component';
import {ShowService} from '../services/show.service';
@@ -51,7 +51,7 @@ describe('ListComponent', () => {
void expect(component).toBeTruthy();
});
it('should list own drafts and pending published shows in my shows', done => {
it('should list own drafts and pending published shows in my shows', async () => {
shows$.next([
createShow({id: 'draft-own', owner: 'user-1', published: false, reportedType: null}),
createShow({id: 'pending-own', owner: 'user-1', published: true, reportedType: 'pending', date: {toDate: () => new Date('2026-03-02')}}),
@@ -59,13 +59,11 @@ describe('ListComponent', () => {
createShow({id: 'draft-other', owner: 'user-2', published: false, reportedType: null, date: {toDate: () => new Date('2026-03-04')}}),
] as never);
component.privateShows$.subscribe(shows => {
expect(shows.map(show => show.id)).toEqual(['pending-own', 'draft-own']);
done();
});
const shows = await firstValueFrom(component.privateShows$);
expect(shows.map(show => show.id)).toEqual(['pending-own', 'draft-own']);
});
it('should ignore show filters for my shows', done => {
it('should ignore show filters for my shows', async () => {
const filterStore = TestBed.inject(FilterStoreService);
filterStore.updateShowFilter({time: 0, showType: 'service-worship'});
shows$.next([
@@ -73,32 +71,27 @@ describe('ListComponent', () => {
createShow({id: 'pending-own', owner: 'user-1', published: true, reportedType: 'pending', showType: 'home-group', date: {toDate: () => new Date('2026-03-05')}}),
] as never);
component.privateShows$.subscribe(shows => {
expect(shows.map(show => show.id)).toEqual(['pending-own', 'older-draft']);
done();
});
const shows = await firstValueFrom(component.privateShows$);
expect(shows.map(show => show.id)).toEqual(['pending-own', 'older-draft']);
});
it('should hide archived own shows until archived filter is enabled', done => {
it('should hide archived own shows until archived filter is enabled', async () => {
const filterStore = TestBed.inject(FilterStoreService);
shows$.next([
createShow({id: 'draft-own', owner: 'user-1', published: false, reportedType: null, date: {toDate: () => new Date('2026-03-02')}}),
createShow({id: 'archived-own', owner: 'user-1', published: true, archived: true, reportedType: 'reported', date: {toDate: () => new Date('2026-03-03')}}),
] as never);
component.privateShows$.pipe(take(1)).subscribe(shows => {
expect(shows.map(show => show.id)).toEqual(['draft-own']);
const initialShows = await firstValueFrom(component.privateShows$.pipe(take(1)));
expect(initialShows.map(show => show.id)).toEqual(['draft-own']);
component.privateShows$.pipe(skip(1), take(1)).subscribe(updatedShows => {
expect(updatedShows.map(show => show.id)).toEqual(['archived-own', 'draft-own']);
done();
});
filterStore.updateShowFilter({archived: true});
});
const updatedShowsPromise = firstValueFrom(component.privateShows$.pipe(skip(1), take(1)));
filterStore.updateShowFilter({archived: true});
const updatedShows = await updatedShowsPromise;
expect(updatedShows.map(show => show.id)).toEqual(['archived-own', 'draft-own']);
});
it('should sort public shows by date descending', done => {
it('should sort public shows by date descending', async () => {
const filterStore = TestBed.inject(FilterStoreService);
filterStore.updateShowFilter({time: 99999});
shows$.next([
@@ -107,9 +100,7 @@ describe('ListComponent', () => {
createShow({id: 'mid-public', owner: 'user-4', published: true, archived: false, date: {toDate: () => new Date('2026-02-05')}}),
] as never);
component.publicShows$.pipe(take(1)).subscribe(shows => {
expect(shows.map(show => show.id)).toEqual(['new-public', 'mid-public', 'old-public']);
done();
});
const shows = await firstValueFrom(component.publicShows$.pipe(take(1)));
expect(shows.map(show => show.id)).toEqual(['new-public', 'mid-public', 'old-public']);
});
});

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {NewComponent} from './new.component';
@@ -6,11 +6,11 @@ describe('NewComponent', () => {
let component: NewComponent;
let fixture: ComponentFixture<NewComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NewComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(NewComponent);

View File

@@ -1,5 +1,5 @@
import {TestBed} from '@angular/core/testing';
import {BehaviorSubject, of} from 'rxjs';
import {BehaviorSubject, firstValueFrom, of} from 'rxjs';
import {ShowDataService} from './show-data.service';
import {ShowService} from './show.service';
import {UserService} from '../../../services/user/user.service';
@@ -40,53 +40,39 @@ describe('ShowService', () => {
expect(service).toBeTruthy();
});
it('should list published shows and own drafts, but exclude archived ones', done => {
service.list$().subscribe(result => {
expect(result.map(show => show.id)).toEqual(['show-1', 'show-2']);
done();
});
it('should list published shows and own drafts, but exclude archived ones', async () => {
const result = await firstValueFrom(service.list$());
expect(result.map(show => show.id)).toEqual(['show-1', 'show-2']);
});
it('should filter out private drafts when publishedOnly is true', done => {
service.list$(true).subscribe(result => {
expect(result.map(show => show.id)).toEqual(['show-2']);
done();
});
it('should filter out private drafts when publishedOnly is true', async () => {
const result = await firstValueFrom(service.list$(true));
expect(result.map(show => show.id)).toEqual(['show-2']);
});
it('should include own archived shows when requested', done => {
service.list$(false, true).subscribe(result => {
expect(result.map(show => show.id)).toEqual(['show-1', 'show-2', 'show-3']);
done();
});
it('should include own archived shows when requested', async () => {
const result = await firstValueFrom(service.list$(false, true));
expect(result.map(show => show.id)).toEqual(['show-1', 'show-2', 'show-3']);
});
it('should not include archived shows from other users when requested', done => {
it('should not include archived shows from other users when requested', async () => {
shows$.next([
...(shows as unknown as unknown[]),
{id: 'show-4', owner: 'other-user', published: true, archived: true},
]);
service.list$(false, true).subscribe(result => {
expect(result.map(show => show.id)).toEqual(['show-1', 'show-2', 'show-3']);
done();
});
const result = await firstValueFrom(service.list$(false, true));
expect(result.map(show => show.id)).toEqual(['show-1', 'show-2', 'show-3']);
});
it('should delegate public listing to the data service', done => {
service.listPublicSince$(6).subscribe(result => {
expect(result).toEqual([shows[1]]);
expect(showDataServiceSpy.listPublicSince$).toHaveBeenCalledWith(6);
done();
});
it('should delegate public listing to the data service', async () => {
await expectAsync(firstValueFrom(service.listPublicSince$(6))).toBeResolvedTo([shows[1]]);
expect(showDataServiceSpy.listPublicSince$).toHaveBeenCalledWith(6);
});
it('should delegate reads to the data service', done => {
service.read$('show-1').subscribe(result => {
expect(result).toEqual(shows[0]);
expect(showDataServiceSpy.read$).toHaveBeenCalledWith('show-1');
done();
});
it('should delegate reads to the data service', async () => {
await expectAsync(firstValueFrom(service.read$('show-1'))).toBeResolvedTo(shows[0]);
expect(showDataServiceSpy.read$).toHaveBeenCalledWith('show-1');
});
it('should delegate updates to the data service', async () => {

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {SongComponent} from './song.component';
@@ -6,11 +6,11 @@ describe('SongComponent', () => {
let component: SongComponent;
let fixture: ComponentFixture<SongComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SongComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(SongComponent);

View File

@@ -1,5 +1,5 @@
import {TestBed} from '@angular/core/testing';
import {of} from 'rxjs';
import {firstValueFrom, of} from 'rxjs';
import {SongService} from './song.service';
import {SongListResolver} from './song-list.resolver';
@@ -22,11 +22,8 @@ describe('SongListResolver', () => {
expect(resolver).toBeTruthy();
});
it('should resolve the first emitted song list from the service', done => {
resolver.resolve().subscribe(songs => {
expect(songServiceSpy.listLoaded$).toHaveBeenCalled();
expect(songs).toEqual([{id: 'song-1', title: 'Amazing Grace'}] as never);
done();
});
it('should resolve the first emitted song list from the service', async () => {
await expectAsync(firstValueFrom(resolver.resolve())).toBeResolvedTo([{id: 'song-1', title: 'Amazing Grace'}] as never);
expect(songServiceSpy.listLoaded$).toHaveBeenCalled();
});
});

View File

@@ -1,5 +1,5 @@
import {TestBed} from '@angular/core/testing';
import {of} from 'rxjs';
import {firstValueFrom, of} from 'rxjs';
import {SongDataService} from './song-data.service';
import {SongService} from './song.service';
import {UserService} from '../../../services/user/user.service';
@@ -41,11 +41,8 @@ describe('SongService', () => {
expect(service).toBeTruthy();
});
it('should list songs from the data service', done => {
service.list$().subscribe(songs => {
expect(songs).toEqual([song]);
done();
});
it('should list songs from the data service', async () => {
await expectAsync(firstValueFrom(service.list$())).toBeResolvedTo([song]);
});
it('should delegate reads to the data service', async () => {

View File

@@ -38,7 +38,7 @@ Bridge
Cool bridge without any chords
`;
beforeEach(() => void TestBed.configureTestingModule({}));
beforeEach(async () => await TestBed.configureTestingModule({}));
it('should be created', () => {
const service: TextRenderingService = TestBed.inject(TextRenderingService);
@@ -478,13 +478,13 @@ Text`;
const service: TextRenderingService = TestBed.inject(TextRenderingService);
const text = 'Strophe\nC\tG\ta\nText';
void expect(service.validateChordNotation(text)).toContain(
void expect(service.validateChordNotation(text)).toEqual(expect.arrayContaining([
jasmine.objectContaining({
lineNumber: 2,
token: '\t',
reason: 'tab_character',
})
);
}),
]));
});
it('should not flag tabs on non chord lines', () => {

View File

@@ -7,8 +7,8 @@ import {Line} from './line';
describe('TransposeService', () => {
let service: TransposeService;
beforeEach(() => {
void TestBed.configureTestingModule({});
beforeEach(async () => {
await TestBed.configureTestingModule({});
service = TestBed.inject(TransposeService);
});

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {FilterComponent} from './filter.component';
@@ -6,11 +6,11 @@ describe('FilterComponent', () => {
let component: FilterComponent;
let fixture: ComponentFixture<FilterComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FilterComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(FilterComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {SongListComponent} from './song-list.component';
import {of} from 'rxjs';
import {ActivatedRoute} from '@angular/router';
@@ -12,8 +12,8 @@ describe('SongListComponent', () => {
const songs = [{id: 'song-1', title: 'title1', number: 1, text: '', flags: ''}];
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SongListComponent],
providers: [
{provide: ActivatedRoute, useValue: {data: of({songs})}},
@@ -22,7 +22,7 @@ describe('SongListComponent', () => {
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(SongListComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {EditFileComponent} from './edit-file.component';
@@ -6,11 +6,11 @@ describe('EditFileComponent', () => {
let component: EditFileComponent;
let fixture: ComponentFixture<EditFileComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [EditFileComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(EditFileComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {FileComponent} from './file.component';
@@ -6,11 +6,11 @@ describe('FileComponent', () => {
let component: FileComponent;
let fixture: ComponentFixture<FileComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FileComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(FileComponent);

View File

@@ -4,8 +4,8 @@ import {EditSongGuard} from './edit-song.guard';
describe('EditSongGuard', () => {
let guard: EditSongGuard;
beforeEach(() => {
void TestBed.configureTestingModule({});
beforeEach(async () => {
await TestBed.configureTestingModule({});
guard = TestBed.inject(EditSongGuard);
});

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {EditSongComponent} from './edit-song.component';
@@ -6,11 +6,11 @@ describe('EditSongComponent', () => {
let component: EditSongComponent;
let fixture: ComponentFixture<EditSongComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [EditSongComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(EditSongComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {SaveDialogComponent} from './save-dialog.component';
@@ -6,11 +6,11 @@ describe('SaveDialogComponent', () => {
let component: SaveDialogComponent;
let fixture: ComponentFixture<SaveDialogComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SaveDialogComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(SaveDialogComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {EditComponent} from './edit.component';
@@ -6,11 +6,11 @@ describe('EditComponent', () => {
let component: EditComponent;
let fixture: ComponentFixture<EditComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [EditComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(EditComponent);

View File

@@ -4,8 +4,8 @@ import {EditService} from './edit.service';
describe('EditService', () => {
let service: EditService;
beforeEach(() => {
void TestBed.configureTestingModule({});
beforeEach(async () => {
await TestBed.configureTestingModule({});
service = TestBed.inject(EditService);
});

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HistoryComponent} from './history.component';
@@ -6,11 +6,11 @@ describe('HistoryComponent', () => {
let component: HistoryComponent;
let fixture: ComponentFixture<HistoryComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HistoryComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(HistoryComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {Storage} from '@angular/fire/storage';
import {FileComponent} from './file.component';
@@ -7,12 +7,12 @@ describe('FileComponent', () => {
let component: FileComponent;
let fixture: ComponentFixture<FileComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FileComponent],
providers: [{provide: Storage, useValue: {}}],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(FileComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {NewComponent} from './new.component';
@@ -6,11 +6,11 @@ describe('NewComponent', () => {
let component: NewComponent;
let fixture: ComponentFixture<NewComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NewComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(NewComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {SongComponent} from './song.component';
import {of} from 'rxjs';
@@ -17,7 +17,7 @@ describe('SongComponent', () => {
params: of({songId: '4711'}),
};
beforeEach(waitForAsync(() => {
beforeEach(async () => {
const songServiceSpy = jasmine.createSpyObj<SongService>('SongService', ['read$']);
const fileDataServiceSpy = jasmine.createSpyObj<FileDataService>('FileDataService', ['read$']);
const userServiceSpy = jasmine.createSpyObj<UserService>('UserService', ['incSongCount', 'decSongCount'], {
@@ -33,7 +33,7 @@ describe('SongComponent', () => {
userServiceSpy.loggedIn$ = jasmine.createSpy('loggedIn$').and.returnValue(of(true));
userServiceSpy.getUserbyId$ = jasmine.createSpy('getUserbyId$').and.returnValue(of({name: 'Benjamin'}));
void TestBed.configureTestingModule({
await TestBed.configureTestingModule({
imports: [SongComponent],
providers: [
{provide: ActivatedRoute, useValue: mockActivatedRoute},
@@ -44,7 +44,7 @@ describe('SongComponent', () => {
{provide: ShowSongService, useValue: showSongServiceSpy},
],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(SongComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {InfoComponent} from './info.component';
@@ -6,11 +6,11 @@ describe('InfoComponent', () => {
let component: InfoComponent;
let fixture: ComponentFixture<InfoComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [InfoComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(InfoComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {UserComponent} from './user.component';
@@ -6,11 +6,11 @@ describe('UserComponent', () => {
let component: UserComponent;
let fixture: ComponentFixture<UserComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(UserComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {UsersComponent} from './users.component';
@@ -6,11 +6,11 @@ describe('UsersComponent', () => {
let component: UsersComponent;
let fixture: ComponentFixture<UsersComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UsersComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(UsersComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {LoginComponent} from './login.component';
@@ -6,11 +6,11 @@ describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LoginComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {LogoutComponent} from './logout.component';
@@ -6,11 +6,11 @@ describe('LogoutComponent', () => {
let component: LogoutComponent;
let fixture: ComponentFixture<LogoutComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LogoutComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(LogoutComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {NewComponent} from './new.component';
@@ -6,11 +6,11 @@ describe('NewComponent', () => {
let component: NewComponent;
let fixture: ComponentFixture<NewComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NewComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(NewComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {PasswordSendComponent} from './password-send.component';
@@ -6,11 +6,11 @@ describe('PasswordSendComponent', () => {
let component: PasswordSendComponent;
let fixture: ComponentFixture<PasswordSendComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PasswordSendComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(PasswordSendComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {PasswordComponent} from './password.component';
@@ -6,11 +6,11 @@ describe('PasswordComponent', () => {
let component: PasswordComponent;
let fixture: ComponentFixture<PasswordComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PasswordComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(PasswordComponent);

View File

@@ -1,5 +1,5 @@
import {TestBed} from '@angular/core/testing';
import {of} from 'rxjs';
import {firstValueFrom, of} from 'rxjs';
import {DbService} from './db.service';
import {ConfigService} from './config.service';
@@ -27,11 +27,8 @@ describe('ConfigService', () => {
expect(dbServiceSpy.doc$).toHaveBeenCalledWith('global/config');
});
it('should expose the shared config stream via get$', done => {
service.get$().subscribe(config => {
expect(config).toEqual({copyright: 'CCLI'} as never);
done();
});
it('should expose the shared config stream via get$', async () => {
await expectAsync(firstValueFrom(service.get$())).toBeResolvedTo({copyright: 'CCLI'} as never);
});
it('should resolve the current config via get()', async () => {

View File

@@ -4,12 +4,11 @@ import {Firestore} from '@angular/fire/firestore';
import {DbService} from './db.service';
describe('DbService', () => {
beforeEach(
() =>
void TestBed.configureTestingModule({
providers: [{provide: Firestore, useValue: {}}],
})
);
beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [{provide: Firestore, useValue: {}}],
});
});
it('should be created', () => {
const service: DbService = TestBed.inject(DbService);

View File

@@ -1,5 +1,5 @@
import {TestBed} from '@angular/core/testing';
import {of} from 'rxjs';
import {firstValueFrom, of} from 'rxjs';
import {DbService} from './db.service';
import {GlobalSettingsService} from './global-settings.service';
@@ -30,11 +30,8 @@ describe('GlobalSettingsService', () => {
expect(dbServiceSpy.doc$).toHaveBeenCalledWith('global/static');
});
it('should expose the shared settings stream via the getter', done => {
service.get$.subscribe(settings => {
expect(settings).toEqual({churchName: 'ICF'} as never);
done();
});
it('should expose the shared settings stream via the getter', async () => {
await expectAsync(firstValueFrom(service.get$)).toBeResolvedTo({churchName: 'ICF'} as never);
});
it('should update the static global settings document', async () => {

View File

@@ -5,8 +5,8 @@ import {ScrollService} from './scroll.service';
describe('ScrollService', () => {
let service: ScrollService;
beforeEach(() => {
void TestBed.configureTestingModule({});
beforeEach(async () => {
await TestBed.configureTestingModule({});
service = TestBed.inject(ScrollService);
});

View File

@@ -1,5 +1,5 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {of} from 'rxjs';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {firstValueFrom, of} from 'rxjs';
import {UserService} from '../user.service';
import {UserNameComponent} from './user-name.component';
@@ -8,15 +8,15 @@ describe('UserNameComponent', () => {
let fixture: ComponentFixture<UserNameComponent>;
let userServiceSpy: jasmine.SpyObj<UserService>;
beforeEach(waitForAsync(() => {
beforeEach(async () => {
userServiceSpy = jasmine.createSpyObj<UserService>('UserService', ['getUserbyId$']);
userServiceSpy.getUserbyId$.and.returnValue(of({name: 'Benjamin'} as never));
void TestBed.configureTestingModule({
await TestBed.configureTestingModule({
imports: [UserNameComponent],
providers: [{provide: UserService, useValue: userServiceSpy}],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(UserNameComponent);
@@ -28,24 +28,18 @@ describe('UserNameComponent', () => {
expect(component).toBeTruthy();
});
it('should resolve the user name when the userId input changes', done => {
it('should resolve the user name when the userId input changes', async () => {
component.userId = 'user-1';
component.name$?.subscribe(name => {
expect(userServiceSpy.getUserbyId$).toHaveBeenCalledWith('user-1');
expect(name).toBe('Benjamin');
done();
});
expect(userServiceSpy.getUserbyId$).toHaveBeenCalledWith('user-1');
await expectAsync(firstValueFrom(component.name$!)).toBeResolvedTo('Benjamin');
});
it('should map missing users to null names', done => {
it('should map missing users to null names', async () => {
userServiceSpy.getUserbyId$.and.returnValue(of(null));
component.userId = 'missing-user';
component.name$?.subscribe(name => {
expect(name).toBeNull();
done();
});
await expectAsync(firstValueFrom(component.name$!)).toBeResolvedTo(null);
});
});

View File

@@ -1,5 +1,5 @@
import {TestBed} from '@angular/core/testing';
import {of} from 'rxjs';
import {firstValueFrom, of} from 'rxjs';
import {UserService} from './user.service';
import {UserSessionService} from './user-session.service';
import {UserSongUsageService} from './user-song-usage.service';
@@ -54,17 +54,10 @@ describe('UserService', () => {
expect(service).toBeTruthy();
});
it('should expose the session streams directly', done => {
service.userId$.subscribe(userId => {
expect(userId).toBe('user-1');
service.user$.subscribe(user => {
expect(user).toEqual({id: 'user-1'} as never);
service.users$.subscribe(users => {
expect(users).toEqual([{id: 'user-1'}] as never);
done();
});
});
});
it('should expose the session streams directly', async () => {
await expectAsync(firstValueFrom(service.userId$)).toBeResolvedTo('user-1');
await expectAsync(firstValueFrom(service.user$)).toBeResolvedTo({id: 'user-1'} as never);
await expectAsync(firstValueFrom(service.users$)).toBeResolvedTo([{id: 'user-1'}] as never);
});
it('should delegate session operations to UserSessionService', async () => {
@@ -85,20 +78,13 @@ describe('UserService', () => {
expect(sessionSpy.createNewUser).toHaveBeenCalledWith('mail', 'Benjamin', 'secret');
});
it('should delegate user lookup and loggedIn/list streams to UserSessionService', done => {
service.getUserbyId$('user-2').subscribe(user => {
expect(user).toEqual({id: 'user-2'} as never);
service.loggedIn$().subscribe(loggedIn => {
expect(loggedIn).toBeTrue();
service.list$().subscribe(users => {
expect(users).toEqual([{id: 'user-1'}] as never);
expect(sessionSpy.getUserbyId$).toHaveBeenCalledWith('user-2');
expect(sessionSpy.loggedIn$).toHaveBeenCalled();
expect(sessionSpy.list$).toHaveBeenCalled();
done();
});
});
});
it('should delegate user lookup and loggedIn/list streams to UserSessionService', async () => {
await expectAsync(firstValueFrom(service.getUserbyId$('user-2'))).toBeResolvedTo({id: 'user-2'} as never);
await expectAsync(firstValueFrom(service.loggedIn$())).toBeResolvedTo(true);
await expectAsync(firstValueFrom(service.list$())).toBeResolvedTo([{id: 'user-1'}] as never);
expect(sessionSpy.getUserbyId$).toHaveBeenCalledWith('user-2');
expect(sessionSpy.loggedIn$).toHaveBeenCalled();
expect(sessionSpy.list$).toHaveBeenCalled();
});
it('should delegate song usage operations to UserSongUsageService', async () => {

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {AddSongComponent} from './add-song.component';
@@ -6,11 +6,11 @@ describe('AddSongComponent', () => {
let component: AddSongComponent;
let fixture: ComponentFixture<AddSongComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AddSongComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(AddSongComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {BrandComponent} from './brand.component';
@@ -6,11 +6,11 @@ describe('BrandComponent', () => {
let component: BrandComponent;
let fixture: ComponentFixture<BrandComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [BrandComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(BrandComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {FilterComponent} from './filter.component';
@@ -6,11 +6,11 @@ describe('FilterComponent', () => {
let component: FilterComponent;
let fixture: ComponentFixture<FilterComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FilterComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(FilterComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {LinkComponent} from './link.component';
@@ -6,11 +6,11 @@ describe('LinkComponent', () => {
let component: LinkComponent;
let fixture: ComponentFixture<LinkComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LinkComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(LinkComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {NavigationComponent} from './navigation.component';
@@ -6,11 +6,11 @@ describe('NavigationComponent', () => {
let component: NavigationComponent;
let fixture: ComponentFixture<NavigationComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NavigationComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(NavigationComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ButtonRowComponent} from './button-row.component';
@@ -6,11 +6,11 @@ describe('ButtonRowComponent', () => {
let component: ButtonRowComponent;
let fixture: ComponentFixture<ButtonRowComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ButtonRowComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(ButtonRowComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ButtonComponent} from './button.component';
@@ -6,11 +6,11 @@ describe('ButtonComponent', () => {
let component: ButtonComponent;
let fixture: ComponentFixture<ButtonComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ButtonComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(ButtonComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {CardComponent} from './card.component';
@@ -6,11 +6,11 @@ describe('CardComponent', () => {
let component: CardComponent;
let fixture: ComponentFixture<CardComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CardComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(CardComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ListHeaderComponent} from './list-header.component';
@@ -6,11 +6,11 @@ describe('ListHeaderComponent', () => {
let component: ListHeaderComponent;
let fixture: ComponentFixture<ListHeaderComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ListHeaderComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(ListHeaderComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {LogoComponent} from './logo.component';
@@ -6,11 +6,11 @@ describe('LogoComponent', () => {
let component: LogoComponent;
let fixture: ComponentFixture<LogoComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LogoComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(LogoComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {MenuButtonComponent} from './menu-button.component';
@@ -6,11 +6,11 @@ describe('MenuButtonComponent', () => {
let component: MenuButtonComponent;
let fixture: ComponentFixture<MenuButtonComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MenuButtonComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(MenuButtonComponent);

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {SongTextComponent} from './song-text.component';
@@ -6,11 +6,11 @@ describe('SongTextComponent', () => {
let component: SongTextComponent;
let fixture: ComponentFixture<SongTextComponent>;
beforeEach(waitForAsync(() => {
void TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SongTextComponent],
}).compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(SongTextComponent);

View File

@@ -1,6 +1,6 @@
import {TestBed} from '@angular/core/testing';
import {Router} from '@angular/router';
import {of} from 'rxjs';
import {firstValueFrom, of} from 'rxjs';
import {UserService} from '../../services/user/user.service';
import {RoleGuard} from './role.guard';
@@ -30,14 +30,13 @@ describe('RoleGuard', () => {
expect(() => guard.canActivate({data: {}} as never)).toThrowError('requiredRoles is not defined!');
});
it('should deny access when there is no current user', done => {
guard.canActivate({data: {requiredRoles: ['leader']}} as never).subscribe(result => {
expect(result).toEqual({commands: ['brand', 'new-user']} as never);
done();
});
it('should deny access when there is no current user', async () => {
await expectAsync(firstValueFrom(guard.canActivate({data: {requiredRoles: ['leader']}} as never))).toBeResolvedTo(
{commands: ['brand', 'new-user']} as never
);
});
it('should allow admins regardless of requiredRoles', done => {
it('should allow admins regardless of requiredRoles', async () => {
TestBed.resetTestingModule();
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
TestBed.configureTestingModule({
@@ -48,13 +47,10 @@ describe('RoleGuard', () => {
});
guard = TestBed.inject(RoleGuard);
guard.canActivate({data: {requiredRoles: ['leader']}} as never).subscribe(result => {
expect(result).toBeTrue();
done();
});
await expectAsync(firstValueFrom(guard.canActivate({data: {requiredRoles: ['leader']}} as never))).toBeResolvedTo(true);
});
it('should allow users with a matching required role', done => {
it('should allow users with a matching required role', async () => {
TestBed.resetTestingModule();
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
TestBed.configureTestingModule({
@@ -65,13 +61,10 @@ describe('RoleGuard', () => {
});
guard = TestBed.inject(RoleGuard);
guard.canActivate({data: {requiredRoles: ['leader']}} as never).subscribe(result => {
expect(result).toBeTrue();
done();
});
await expectAsync(firstValueFrom(guard.canActivate({data: {requiredRoles: ['leader']}} as never))).toBeResolvedTo(true);
});
it('should redirect users without the required role to their role default route', done => {
it('should redirect users without the required role to their role default route', async () => {
TestBed.resetTestingModule();
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
routerSpy.createUrlTree.and.returnValue({redirect: ['presentation']} as never);
@@ -83,14 +76,12 @@ describe('RoleGuard', () => {
});
guard = TestBed.inject(RoleGuard);
guard.canActivate({data: {requiredRoles: ['leader']}} as never).subscribe(result => {
expect(routerSpy.createUrlTree).toHaveBeenCalledWith(['presentation']);
expect(result).toEqual({redirect: ['presentation']} as never);
done();
});
const result = await firstValueFrom(guard.canActivate({data: {requiredRoles: ['leader']}} as never));
expect(routerSpy.createUrlTree).toHaveBeenCalledWith(['presentation']);
expect(result).toEqual({redirect: ['presentation']} as never);
});
it('should redirect members to shows instead of new-user', done => {
it('should redirect members to shows instead of new-user', async () => {
TestBed.resetTestingModule();
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
routerSpy.createUrlTree.and.returnValue({redirect: ['shows']} as never);
@@ -102,14 +93,12 @@ describe('RoleGuard', () => {
});
guard = TestBed.inject(RoleGuard);
guard.canActivate({data: {requiredRoles: ['user']}} as never).subscribe(result => {
expect(routerSpy.createUrlTree).toHaveBeenCalledWith(['shows']);
expect(result).toEqual({redirect: ['shows']} as never);
done();
});
const result = await firstValueFrom(guard.canActivate({data: {requiredRoles: ['user']}} as never));
expect(routerSpy.createUrlTree).toHaveBeenCalledWith(['shows']);
expect(result).toEqual({redirect: ['shows']} as never);
});
it('should choose a matching default route from all assigned roles', done => {
it('should choose a matching default route from all assigned roles', async () => {
TestBed.resetTestingModule();
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
routerSpy.createUrlTree.and.callFake(commands => ({redirect: commands}) as never);
@@ -121,10 +110,8 @@ describe('RoleGuard', () => {
});
guard = TestBed.inject(RoleGuard);
guard.canActivate({data: {requiredRoles: ['presenter']}} as never).subscribe(result => {
expect(routerSpy.createUrlTree).toHaveBeenCalledWith(['shows']);
expect(result).toEqual({redirect: ['shows']} as never);
done();
});
const result = await firstValueFrom(guard.canActivate({data: {requiredRoles: ['presenter']}} as never));
expect(routerSpy.createUrlTree).toHaveBeenCalledWith(['shows']);
expect(result).toEqual({redirect: ['shows']} as never);
});
});

View File

@@ -28,7 +28,7 @@
right: 0;
bottom: 0;
display: flex;
z-index: 1;
z-index: 100;
justify-content: center;
align-items: center;
}

228
src/test-vitest.ts Normal file
View File

@@ -0,0 +1,228 @@
import {expect, vi} from 'vitest';
import 'zone.js/testing';
import {TestBed} from '@angular/core/testing';
import {provideNoopAnimations} from '@angular/platform-browser/animations';
import {ActivatedRoute, provideRouter} from '@angular/router';
import {BehaviorSubject, Observable, of} from 'rxjs';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {provideNativeDateAdapter} from '@angular/material/core';
import {getApp, getApps, initializeApp, provideFirebaseApp} from '@angular/fire/app';
import {getAuth, provideAuth} from '@angular/fire/auth';
import {initializeFirestore, provideFirestore} from '@angular/fire/firestore';
import {getStorage, provideStorage} from '@angular/fire/storage';
import {environment} from './environments/environment';
import {DbService} from './app/services/db.service';
type TestingModuleDefinition = Parameters<typeof TestBed.configureTestingModule>[0];
type TestingProviderList = NonNullable<NonNullable<TestingModuleDefinition>['providers']>;
type CollectionStub = {
valueChanges: () => Observable<unknown[]>;
add: () => Promise<{id: string}>;
};
type DocumentStub = {
set: () => Promise<void>;
update: () => Promise<void>;
delete: () => Promise<void>;
collection: () => CollectionStub;
};
type MockFunction = ReturnType<typeof vi.fn> & {
and: {
returnValue: (value: unknown) => MockFunction;
resolveTo: (value: unknown) => MockFunction;
rejectWith: (value: unknown) => MockFunction;
callFake: (fn: (...args: unknown[]) => unknown) => MockFunction;
callThrough: () => MockFunction;
};
calls: {
argsFor: (index: number) => unknown[];
mostRecent: () => {args: unknown[]};
};
};
const routeParams$ = new BehaviorSubject<Record<string, unknown>>({});
const queryParams$ = new BehaviorSubject<Record<string, unknown>>({});
const defaultFirebaseApp = getApps().length > 0 ? getApp() : initializeApp(environment.firebase);
const defaultTestingProviders: TestingProviderList = [
provideNoopAnimations(),
provideNativeDateAdapter(),
provideRouter([]),
provideFirebaseApp(() => defaultFirebaseApp),
provideAuth(() => getAuth(defaultFirebaseApp)),
provideFirestore(() => initializeFirestore(defaultFirebaseApp, {})),
provideStorage(() => getStorage(defaultFirebaseApp)),
{
provide: ActivatedRoute,
useValue: {
snapshot: {params: {}, queryParams: {}, data: {}},
params: routeParams$.asObservable(),
queryParams: queryParams$.asObservable(),
data: of({}),
fragment: of(null),
},
},
{provide: MAT_DIALOG_DATA, useValue: {}},
{provide: MatDialogRef, useValue: {close: () => void 0}},
{
provide: DbService,
useValue: {
col$: () => of([]),
doc$: () => of(null),
col: (): CollectionStub => ({
valueChanges: () => of([]),
add: () => Promise.resolve({id: 'test-id'}),
}),
doc: (): DocumentStub => ({
set: () => Promise.resolve(),
update: () => Promise.resolve(),
delete: () => Promise.resolve(),
collection: (): CollectionStub => ({
valueChanges: () => of([]),
add: () => Promise.resolve({id: 'test-id'}),
}),
}),
},
},
];
const originalConfigureTestingModule = TestBed.configureTestingModule.bind(TestBed);
const configureTestingModule: typeof TestBed.configureTestingModule = moduleDef => {
const extraProviders: TestingProviderList = moduleDef?.providers ?? [];
const mergedModuleDef: TestingModuleDefinition = moduleDef ? {...moduleDef} : {};
mergedModuleDef.providers = defaultTestingProviders.concat(extraProviders);
return originalConfigureTestingModule(mergedModuleDef);
};
TestBed.configureTestingModule = configureTestingModule;
function decorateMock<T extends ReturnType<typeof vi.fn>>(mock: T): T & MockFunction {
const decorated = mock as T & MockFunction;
Object.defineProperty(decorated, 'and', {
configurable: true,
get: () => ({
returnValue(value: unknown) {
decorated.mockReturnValue(value);
return decorated;
},
resolveTo(value: unknown) {
decorated.mockResolvedValue(value);
return decorated;
},
rejectWith(value: unknown) {
decorated.mockRejectedValue(value);
return decorated;
},
callFake(fn: (...args: unknown[]) => unknown) {
decorated.mockImplementation(fn);
return decorated;
},
callThrough() {
return decorated;
},
}),
});
Object.defineProperty(decorated, 'calls', {
configurable: true,
get: () => ({
argsFor(index: number) {
return decorated.mock.calls[index] ?? [];
},
mostRecent() {
const args = decorated.mock.lastCall ?? [];
return {args};
},
}),
});
return decorated;
}
function createSpy(name?: string): MockFunction {
const spy = decorateMock(vi.fn());
if (name) {
spy.mockName(name);
}
return spy;
}
function createSpyObj<T>(
baseName: string,
methodNames: string[] | Record<string, unknown>,
propertyValues?: Record<string, unknown>
): T {
const result: Record<string, unknown> = {};
const methods = Array.isArray(methodNames) ? methodNames : Object.keys(methodNames);
for (const methodName of methods) {
result[methodName] = createSpy(`${baseName}.${methodName}`);
}
if (!Array.isArray(methodNames)) {
for (const [key, value] of Object.entries(methodNames)) {
(result[key] as MockFunction).and.returnValue(value);
}
}
if (propertyValues) {
for (const [key, value] of Object.entries(propertyValues)) {
result[key] = value;
}
}
return result as T;
}
function spyOnCompat<T extends object, K extends keyof T>(object: T, methodName: K): MockFunction {
const spy = vi.spyOn(object as Record<PropertyKey, (...args: unknown[]) => unknown>, methodName as PropertyKey);
return decorateMock(spy as unknown as ReturnType<typeof vi.fn>);
}
function expectAsyncCompat<T>(value: Promise<T>) {
return {
async toBeResolvedTo(expected: T) {
await expect(value).resolves.toEqual(expected);
},
async toBeRejectedWithError(expected?: string | RegExp | Error) {
if (expected instanceof Error) {
await expect(value).rejects.toThrowError(expected.message);
} else if (expected !== undefined) {
await expect(value).rejects.toThrowError(expected);
} else {
await expect(value).rejects.toThrowError();
}
},
};
}
expect.extend({
toBeTrue(received: unknown) {
return {
pass: received === true,
message: () => `expected ${String(received)} to be true`,
};
},
toBeFalse(received: unknown) {
return {
pass: received === false,
message: () => `expected ${String(received)} to be false`,
};
},
});
Object.assign(globalThis, {
spyOn: spyOnCompat,
expectAsync: expectAsyncCompat,
jasmine: {
createSpy,
createSpyObj,
any: expect.any,
anything: expect.anything,
objectContaining: expect.objectContaining,
stringMatching: expect.stringMatching,
},
});

View File

@@ -1,90 +0,0 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import {getTestBed, TestBed} from '@angular/core/testing';
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
import {provideNoopAnimations} from '@angular/platform-browser/animations';
import {ActivatedRoute, provideRouter} from '@angular/router';
import {BehaviorSubject, of} from 'rxjs';
import {Observable} from 'rxjs';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {provideNativeDateAdapter} from '@angular/material/core';
import {provideFirebaseApp, initializeApp} from '@angular/fire/app';
import {getApp, getApps} from '@angular/fire/app';
import {getAuth, provideAuth} from '@angular/fire/auth';
import {initializeFirestore, provideFirestore} from '@angular/fire/firestore';
import {getStorage, provideStorage} from '@angular/fire/storage';
import {environment} from './environments/environment';
import {DbService} from './app/services/db.service';
type req = {keys: () => {map: (context: req) => void}};
type TestingModuleDefinition = Parameters<typeof TestBed.configureTestingModule>[0];
type TestingProviderList = NonNullable<NonNullable<TestingModuleDefinition>['providers']>;
type CollectionStub = {
valueChanges: () => Observable<unknown[]>;
add: () => Promise<{id: string}>;
};
type DocumentStub = {
set: () => Promise<void>;
update: () => Promise<void>;
delete: () => Promise<void>;
collection: () => CollectionStub;
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
const routeParams$ = new BehaviorSubject<Record<string, unknown>>({});
const queryParams$ = new BehaviorSubject<Record<string, unknown>>({});
const defaultTestingProviders: TestingProviderList = [
provideNoopAnimations(),
provideNativeDateAdapter(),
provideRouter([]),
provideFirebaseApp(() => (getApps().some(app => app.name === 'wgenerator-tests') ? getApp('wgenerator-tests') : initializeApp(environment.firebase, 'wgenerator-tests'))),
provideAuth(() => getAuth(getApp('wgenerator-tests'))),
provideFirestore(() => initializeFirestore(getApp('wgenerator-tests'), {})),
provideStorage(() => getStorage(getApp('wgenerator-tests'))),
{
provide: ActivatedRoute,
useValue: {
snapshot: {params: {}, queryParams: {}, data: {}},
params: routeParams$.asObservable(),
queryParams: queryParams$.asObservable(),
data: of({}),
fragment: of(null),
},
},
{provide: MAT_DIALOG_DATA, useValue: {}},
{provide: MatDialogRef, useValue: {close: () => void 0}},
{
provide: DbService,
useValue: {
col$: () => of([]),
doc$: () => of(null),
col: (): CollectionStub => ({
valueChanges: () => of([]),
add: () => Promise.resolve({id: 'test-id'}),
}),
doc: (): DocumentStub => ({
set: () => Promise.resolve(),
update: () => Promise.resolve(),
delete: () => Promise.resolve(),
collection: (): CollectionStub => ({
valueChanges: () => of([]),
add: () => Promise.resolve({id: 'test-id'}),
}),
}),
},
},
];
const originalConfigureTestingModule = TestBed.configureTestingModule.bind(TestBed);
const configureTestingModule: typeof TestBed.configureTestingModule = moduleDef => {
const extraProviders: TestingProviderList = moduleDef?.providers ?? [];
const mergedModuleDef: TestingModuleDefinition = moduleDef ? {...moduleDef} : {};
mergedModuleDef.providers = defaultTestingProviders.concat(extraProviders);
return originalConfigureTestingModule(mergedModuleDef);
};
TestBed.configureTestingModule = configureTestingModule;

44
src/types/jasmine-compat.d.ts vendored Normal file
View File

@@ -0,0 +1,44 @@
type SpyAnd = {
returnValue(value?: unknown): jasmine.Spy;
resolveTo(value?: unknown): jasmine.Spy;
rejectWith(value?: unknown): jasmine.Spy;
callFake(fn: (...args: any[]) => unknown): jasmine.Spy;
callThrough(): jasmine.Spy;
};
type SpyCalls = {
argsFor(index: number): any[];
mostRecent(): {args: any[]};
};
declare global {
function spyOn<T = any>(object: T, methodName: any): jasmine.Spy;
function expectAsync<T>(value: Promise<T>): {
toBeResolvedTo(expected: T): Promise<void>;
toBeRejectedWithError(expected?: string | RegExp | Error): Promise<void>;
};
namespace jasmine {
type Spy<T extends (...args: any[]) => any = (...args: any[]) => any> = T & ReturnType<typeof import('vitest')['vi']['fn']> & {
and: SpyAnd;
calls: SpyCalls;
};
type SpyObj<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? Spy<T[K]> : T[K];
};
function createSpy(name?: string): Spy;
function createSpyObj<T>(
baseName: string,
methodNames: string[] | Record<string, unknown>,
propertyValues?: Record<string, unknown>
): SpyObj<T>;
function any(expectedClass: unknown): unknown;
function anything(): unknown;
function objectContaining<T>(value: Partial<T>): unknown;
function stringMatching(value: string | RegExp): unknown;
}
}
export {};

1
src/types/vitest-globals.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vitest/globals" />

13
src/types/vitest-matchers.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
import 'vitest';
declare module 'vitest' {
interface Assertion<T = any> {
toBeTrue(): T;
toBeFalse(): T;
}
interface AsymmetricMatchersContaining {
toBeTrue(): void;
toBeFalse(): void;
}
}

View File

@@ -3,15 +3,12 @@
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/test-vitest.ts",
"src/polyfills.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]

7
vitest-base.config.ts Normal file
View File

@@ -0,0 +1,7 @@
import {defineConfig} from 'vitest/config';
export default defineConfig({
test: {
globals: true,
},
});