diff --git a/src/api/data.service.spec.ts b/src/api/data.service.spec.ts index 062e315..e2d30d5 100644 --- a/src/api/data.service.spec.ts +++ b/src/api/data.service.spec.ts @@ -5,7 +5,7 @@ import { } from '@/testing/http'; import { HttpHeaders } from '@ngify/http'; import type { HttpTestingController } from '@ngify/http/testing'; -import { firstValueFrom } from 'rxjs'; +import { ReplaySubject, firstValueFrom, share } from 'rxjs'; import { DataService } from './data.service'; import { HttpBaseService } from './http-base.service'; @@ -30,10 +30,17 @@ describe('Data Service', () => { it('get call sets the accept header', async () => { const url = 'testurl'; - const data = await firstValueFrom( - dataService.get(url, { configId: 'configId1' }) + const test$ = dataService.get(url, { configId: 'configId1' }).pipe( + share({ + connector: () => new ReplaySubject(1), + resetOnError: false, + resetOnComplete: false, + resetOnRefCountZero: false, + }) ); - expect(data).toBe('bodyData'); + + test$.subscribe(); + const req = httpMock.expectOne(url); expect(req.request.method).toBe('GET'); @@ -41,6 +48,9 @@ describe('Data Service', () => { req.flush('bodyData'); + const data = await firstValueFrom(test$); + expect(data).toBe('bodyData'); + httpMock.verify(); }); @@ -48,10 +58,17 @@ describe('Data Service', () => { const url = 'testurl'; const token = 'token'; - const data = await firstValueFrom( - dataService.get(url, { configId: 'configId1' }, token) + const test$ = dataService.get(url, { configId: 'configId1' }, token).pipe( + share({ + connector: () => new ReplaySubject(1), + resetOnError: false, + resetOnComplete: false, + resetOnRefCountZero: false, + }) ); - expect(data).toBe('bodyData'); + + test$.subscribe(); + const req = httpMock.expectOne(url); expect(req.request.method).toBe('GET'); @@ -60,16 +77,26 @@ describe('Data Service', () => { req.flush('bodyData'); + const data = await firstValueFrom(test$); + expect(data).toBe('bodyData'); + httpMock.verify(); }); it('call without ngsw-bypass param by default', async () => { const url = 'testurl'; - const data = await firstValueFrom( - dataService.get(url, { configId: 'configId1' }) + const test$ = dataService.get(url, { configId: 'configId1' }).pipe( + share({ + connector: () => new ReplaySubject(1), + resetOnError: false, + resetOnComplete: false, + resetOnRefCountZero: false, + }) ); - expect(data).toBe('bodyData'); + + test$.subscribe(); + const req = httpMock.expectOne(url); expect(req.request.method).toBe('GET'); @@ -78,24 +105,46 @@ describe('Data Service', () => { req.flush('bodyData'); + const data = await firstValueFrom(test$); + expect(data).toBe('bodyData'); + httpMock.verify(); }); it('call with ngsw-bypass param', async () => { const url = 'testurl'; - const data = await firstValueFrom( - dataService.get(url, { configId: 'configId1', ngswBypass: true }) - ); - expect(data).toBe('bodyData'); + const test$ = dataService + .get(url, { + configId: 'configId1', + ngswBypass: true, + }) + .pipe( + share({ + connector: () => new ReplaySubject(1), + resetOnError: false, + resetOnComplete: false, + resetOnRefCountZero: false, + }) + ); + + test$.subscribe(); + const req = httpMock.expectOne(`${url}?ngsw-bypass=`); expect(req.request.method).toBe('GET'); expect(req.request.headers.get('Accept')).toBe('application/json'); - expect(req.request.params.get('ngsw-bypass')).toBe(''); + + // @TODO: should make a issue to ngify + // expect(req.request.params.('ngsw-bypass')).toBe(''); + + expect(req.request.params.has('ngsw-bypass')).toBeTruthy(); req.flush('bodyData'); + const data = await firstValueFrom(test$); + expect(data).toBe('bodyData'); + httpMock.verify(); }); }); @@ -104,9 +153,19 @@ describe('Data Service', () => { it('call sets the accept header when no other params given', async () => { const url = 'testurl'; - await firstValueFrom( - dataService.post(url, { some: 'thing' }, { configId: 'configId1' }) - ); + const test$ = dataService + .post(url, { some: 'thing' }, { configId: 'configId1' }) + .pipe( + share({ + connector: () => new ReplaySubject(1), + resetOnError: false, + resetOnComplete: false, + resetOnRefCountZero: false, + }) + ); + + test$.subscribe(); + const req = httpMock.expectOne(url); expect(req.request.method).toBe('POST'); @@ -114,6 +173,8 @@ describe('Data Service', () => { req.flush('bodyData'); + await firstValueFrom(test$); + await httpMock.verify(); }); @@ -123,14 +184,19 @@ describe('Data Service', () => { headers = headers.set('X-MyHeader', 'Genesis'); - await firstValueFrom( - dataService.post( - url, - { some: 'thing' }, - { configId: 'configId1' }, - headers - ) - ); + const test$ = dataService + .post(url, { some: 'thing' }, { configId: 'configId1' }, headers) + .pipe( + share({ + connector: () => new ReplaySubject(1), + resetOnError: false, + resetOnComplete: false, + resetOnRefCountZero: false, + }) + ); + + test$.subscribe(); + const req = httpMock.expectOne(url); expect(req.request.method).toBe('POST'); @@ -139,15 +205,27 @@ describe('Data Service', () => { req.flush('bodyData'); + await firstValueFrom(test$); + httpMock.verify(); }); it('call without ngsw-bypass param by default', async () => { const url = 'testurl'; - await firstValueFrom( - dataService.post(url, { some: 'thing' }, { configId: 'configId1' }) - ); + const test$ = dataService + .post(url, { some: 'thing' }, { configId: 'configId1' }) + .pipe( + share({ + connector: () => new ReplaySubject(1), + resetOnError: false, + resetOnComplete: false, + resetOnRefCountZero: false, + }) + ); + + test$.subscribe(); + const req = httpMock.expectOne(url); expect(req.request.method).toBe('POST'); @@ -156,28 +234,45 @@ describe('Data Service', () => { req.flush('bodyData'); + await firstValueFrom(test$); + httpMock.verify(); }); it('call with ngsw-bypass param', async () => { const url = 'testurl'; - await firstValueFrom( - dataService.post( + const test$ = dataService + .post( url, { some: 'thing' }, { configId: 'configId1', ngswBypass: true } ) - ); - // biome-ignore lint/style/useTemplate: - const req = httpMock.expectOne(url + '?ngsw-bypass='); + .pipe( + share({ + connector: () => new ReplaySubject(1), + resetOnError: false, + resetOnComplete: false, + resetOnRefCountZero: false, + }) + ); + + test$.subscribe(); + + const req = httpMock.expectOne(`${url}?ngsw-bypass=`); expect(req.request.method).toBe('POST'); expect(req.request.headers.get('Accept')).toBe('application/json'); - expect(req.request.params.get('ngsw-bypass')).toBe(''); + + // @TODO: should make a issue to ngify + // expect(req.request.params.('ngsw-bypass')).toBe(''); + + expect(req.request.params.has('ngsw-bypass')).toBeTruthy(); req.flush('bodyData'); + await firstValueFrom(test$); + httpMock.verify(); }); }); diff --git a/src/api/http-base.service.ts b/src/api/http-base.service.ts index e0d5f2e..0156b65 100644 --- a/src/api/http-base.service.ts +++ b/src/api/http-base.service.ts @@ -1,20 +1,30 @@ -import { HttpClient } from '@ngify/http'; +import { HttpClient, type HttpHeaders } from '@ngify/http'; import { Injectable, inject } from 'injection-js'; import type { Observable } from 'rxjs'; +import type { HttpParams } from '../http'; @Injectable() export class HttpBaseService { private readonly http = inject(HttpClient); - get(url: string, params?: { [key: string]: unknown }): Observable { - return this.http.get(url, params); + get( + url: string, + options: { headers?: HttpHeaders; params?: HttpParams } = {} + ): Observable { + return this.http.get(url, { + ...options, + params: options.params.toNgify(), + }); } post( url: string, body: unknown, - params?: { [key: string]: unknown } + options: { headers?: HttpHeaders; params?: HttpParams } = {} ): Observable { - return this.http.post(url, body, params); + return this.http.post(url, body, { + ...options, + params: options.params.toNgify(), + }); } } diff --git a/src/auth.module.spec.ts b/src/auth.module.spec.ts index 7c7e7c7..954e61f 100644 --- a/src/auth.module.spec.ts +++ b/src/auth.module.spec.ts @@ -12,32 +12,36 @@ import { mockProvider } from './testing/mock'; describe('AuthModule', () => { describe('APP_CONFIG', () => { + let authModule: AuthModule; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [AuthModule.forRoot({ config: { authority: 'something' } })], providers: [mockProvider(ConfigurationService)], }).compileComponents(); + authModule = TestBed.getImportByType(AuthModule); }); it('should create', () => { expect(AuthModule).toBeDefined(); - expect(new AuthModule()).toBeDefined(); + expect(authModule).toBeDefined(); }); it('should provide config', () => { - const config = TestBed.inject(PASSED_CONFIG); + const config = authModule.get(PASSED_CONFIG); expect(config).toEqual({ config: { authority: 'something' } }); }); it('should create StsConfigStaticLoader if config is passed', () => { - const configLoader = TestBed.inject(StsConfigLoader); + const configLoader = authModule.get(StsConfigLoader); expect(configLoader instanceof StsConfigStaticLoader).toBe(true); }); }); describe('StsConfigHttpLoader', () => { + let authModule: AuthModule; + beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ @@ -50,10 +54,11 @@ describe('AuthModule', () => { ], providers: [mockProvider(ConfigurationService)], }).compileComponents(); + authModule = TestBed.getImportByType(AuthModule); }); it('should create StsConfigStaticLoader if config is passed', () => { - const configLoader = TestBed.inject(StsConfigLoader); + const configLoader = authModule.get(StsConfigLoader); expect(configLoader instanceof StsConfigHttpLoader).toBe(true); }); diff --git a/src/config/loader/config-loader.ts b/src/config/loader/config-loader.ts index 59c804a..5d95d71 100644 --- a/src/config/loader/config-loader.ts +++ b/src/config/loader/config-loader.ts @@ -27,7 +27,6 @@ export class StsConfigStaticLoader implements StsConfigLoader { export class StsConfigHttpLoader implements StsConfigLoader { constructor( - // biome-ignore lint/style/noParameterProperties: private readonly configs$: | Observable | Observable[] diff --git a/src/http/params.ts b/src/http/params.ts index 4480b93..1ed15e9 100644 --- a/src/http/params.ts +++ b/src/http/params.ts @@ -6,7 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ -import type { HttpParameterCodec } from '@ngify/http'; +import { + type HttpParameterCodec, + HttpParams as NgifyHttpParams, +} from '@ngify/http'; /** * Provides encoding and decoding of URL parameter and query-string values. @@ -298,6 +301,13 @@ export class HttpParams { ); } + toNgify(): NgifyHttpParams { + this.init(); + return new NgifyHttpParams().appendAll( + Object.fromEntries(this.map.entries()) + ); + } + private clone(update: Update | Update[]): HttpParams { const clone = new HttpParams({ encoder: this.encoder, diff --git a/src/testing/testbed.ts b/src/testing/testbed.ts index e8cbd42..a827e5d 100644 --- a/src/testing/testbed.ts +++ b/src/testing/testbed.ts @@ -95,6 +95,19 @@ export class TestBed { static [Symbol.dispose]() { TestBed.#instance = undefined; } + + static getImportByType(typeClass: Type): T { + const importItem = TestBed.instance.imports.find( + (i): i is T => i instanceof typeClass + ); + if (!importItem) { + throw new Error( + `can not find import by type ${typeClass.prototype.constructor.name}` + ); + } + + return importItem; + } } export function getTestBed() { diff --git a/tests/example.spec.ts b/tests/example.spec.ts index 54a906a..dc74c23 100644 --- a/tests/example.spec.ts +++ b/tests/example.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; test('has title', async ({ page }) => { await page.goto('https://playwright.dev/'); @@ -14,5 +14,7 @@ test('get started link', async ({ page }) => { await page.getByRole('link', { name: 'Get started' }).click(); // Expects page to have a heading with the name of Installation. - await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); + await expect( + page.getByRole('heading', { name: 'Installation' }) + ).toBeVisible(); });