diff --git a/TODO.md b/TODO.md index bd2b916..c6f1464 100644 --- a/TODO.md +++ b/TODO.md @@ -8,4 +8,3 @@ - create weight graph - create weight endpoints which retuns weight in last day, week, month, year - fix pgexporter logs about missing role -- fix test for notification, notifications + service diff --git a/client/src/app/common-components/badge/badge.component.spec.ts b/client/src/app/common-components/badge/badge.component.spec.ts new file mode 100644 index 0000000..17ba4af --- /dev/null +++ b/client/src/app/common-components/badge/badge.component.spec.ts @@ -0,0 +1,37 @@ +import { TestBed } from '@angular/core/testing'; + +import { Component } from '@angular/core'; +import { BadgeComponent } from './badge.component'; + +async function setup({ + template, +}: { + template?: string; +} = {}) { + @Component({ + template, + }) + class TestComponent {} + + await TestBed.configureTestingModule({ + declarations: [BadgeComponent, TestComponent], + }).compileComponents(); + + const fixture = TestBed.createComponent(TestComponent); + const debugElement = fixture.debugElement.children[0]; + fixture.detectChanges(); + + return { + fixture, + nativeElement: debugElement.nativeElement as HTMLElement, + }; +} + +describe('BadgeComponent', () => { + it('renders content', async () => { + const { nativeElement } = await setup({ + template: 'test text', + }); + expect(nativeElement.textContent).toBe('test text'); + }); +}); diff --git a/client/src/app/common-components/header/header.component.spec.ts b/client/src/app/common-components/header/header.component.spec.ts index 10f7744..1a60f70 100644 --- a/client/src/app/common-components/header/header.component.spec.ts +++ b/client/src/app/common-components/header/header.component.spec.ts @@ -2,27 +2,45 @@ import { TestBed } from '@angular/core/testing'; import { HeadingComponent } from '../heading/heading.component'; import { HeaderComponent } from './header.component'; +import { Component } from '@angular/core'; + +async function setup({ + template, +}: { + template?: string; +} = {}) { + @Component({ + template, + }) + class TestComponent {} -async function setup() { await TestBed.configureTestingModule({ - declarations: [HeaderComponent, HeadingComponent], + declarations: [HeaderComponent, TestComponent, HeadingComponent], }).compileComponents(); - const fixture = TestBed.createComponent(HeaderComponent); + const fixture = TestBed.createComponent(TestComponent); + const debugElement = fixture.debugElement.children[0]; + fixture.detectChanges(); return { fixture, - component: fixture.componentInstance, - element: fixture.nativeElement as HTMLElement, + nativeElement: debugElement.nativeElement as HTMLElement, }; } describe('HeaderComponent', () => { + it('render content', async () => { + const { nativeElement } = await setup({ + template: 'test text', + }); + expect(nativeElement.textContent).toBe('test text'); + }); + it('render title', async () => { - const { fixture, component, element } = await setup(); - component.title = 'test title'; - fixture.detectChanges(); - expect(element.querySelector('app-heading')?.textContent).toBe( + const { nativeElement } = await setup({ + template: '', + }); + expect(nativeElement.querySelector('app-heading')?.textContent).toBe( 'test title' ); }); diff --git a/client/src/app/common-components/heading/heading.component.spec.ts b/client/src/app/common-components/heading/heading.component.spec.ts index e612f6b..8b8f6d0 100644 --- a/client/src/app/common-components/heading/heading.component.spec.ts +++ b/client/src/app/common-components/heading/heading.component.spec.ts @@ -1,34 +1,46 @@ import { TestBed } from '@angular/core/testing'; - +import { Component } from '@angular/core'; import { HeadingComponent } from './heading.component'; -async function setup() { +async function setup({ + template, +}: { + template?: string; +} = {}) { + @Component({ + template, + }) + class TestComponent {} + await TestBed.configureTestingModule({ - declarations: [HeadingComponent], + declarations: [TestComponent, HeadingComponent], }).compileComponents(); - const fixture = TestBed.createComponent(HeadingComponent); + const fixture = TestBed.createComponent(TestComponent); + const debugElement = fixture.debugElement.children[0]; + fixture.detectChanges(); return { fixture, - component: fixture.componentInstance, - element: fixture.nativeElement as HTMLElement, + nativeElement: debugElement.nativeElement as HTMLElement, }; } describe('HeadingComponent', () => { + it('renders content', async () => { + const { nativeElement } = await setup({ template: 'test text'}); + expect(nativeElement.textContent).toContain('test text'); + }); + it('defaults to level 1', async () => { - const { element, fixture } = await setup(); - fixture.detectChanges(); - expect(element.className).toContain('level1'); + const { nativeElement } = await setup({ template: ''}); + expect(nativeElement.className).toContain('level1'); }); [1, 2, 3, 4, 5, 6].forEach((level) => { it(`renderes level ${level}`, async () => { - const { component, element, fixture } = await setup(); - component.level = level; - fixture.detectChanges(); - expect(element.className).toContain(`level${level}`); + const { nativeElement } = await setup({ template: ``}); + expect(nativeElement.className).toContain(`level${level}`); }); }); }); diff --git a/client/src/app/common-components/main/main.component.spec.ts b/client/src/app/common-components/main/main.component.spec.ts new file mode 100644 index 0000000..91477e3 --- /dev/null +++ b/client/src/app/common-components/main/main.component.spec.ts @@ -0,0 +1,37 @@ +import { TestBed } from '@angular/core/testing'; + +import { Component } from '@angular/core'; +import { MainComponent } from './main.component'; + +async function setup({ + template, +}: { + template?: string; +} = {}) { + @Component({ + template, + }) + class TestComponent {} + + await TestBed.configureTestingModule({ + declarations: [MainComponent, TestComponent], + }).compileComponents(); + + const fixture = TestBed.createComponent(TestComponent); + const debugElement = fixture.debugElement.children[0]; + fixture.detectChanges(); + + return { + fixture, + nativeElement: debugElement.nativeElement as HTMLElement, + }; +} + +describe('MainComponent', () => { + it('renders content', async () => { + const { nativeElement } = await setup({ + template: 'test text', + }); + expect(nativeElement.textContent).toBe('test text'); + }); +}); diff --git a/client/src/app/common-components/notification.service.spec.ts b/client/src/app/common-components/notification.service.spec.ts index c4f2cd6..04d79ee 100644 --- a/client/src/app/common-components/notification.service.spec.ts +++ b/client/src/app/common-components/notification.service.spec.ts @@ -1,16 +1,55 @@ import { TestBed } from '@angular/core/testing'; -import { NotificationService } from './notification.service'; +import { Notification, NotificationService } from './notification.service'; -describe('NotificationService', () => { - let service: NotificationService; +function setup() { + TestBed.configureTestingModule({}); + + const service = TestBed.inject(NotificationService); + return { service }; +} - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(NotificationService); +describe('NotificationService', () => { + it('stores notifications', () => { + const notifications: Notification[] = []; + const { service } = setup(); + service.$notifications.subscribe((notification) => + notifications.push(notification) + ); + service.showNotification('test notification'); + expect(notifications).toHaveSize(1); + expect(notifications[0]).toEqual({ + type: 'success', + message: 'test notification', + }); }); - it('should be created', () => { - expect(service).toBeTruthy(); + it('stores multiple notifications', () => { + const notifications: Notification[] = []; + const { service } = setup(); + service.$notifications.subscribe((notification) => + notifications.push(notification) + ); + service.showNotification('test notification 1'); + service.showNotification('test notification 2', 'error'); + service.showNotification('test notification 3', 'success'); + service.showNotification('test notification 4', 'error'); + expect(notifications).toHaveSize(4); + expect(notifications[0]).toEqual({ + type: 'success', + message: 'test notification 1', + }); + expect(notifications[1]).toEqual({ + type: 'error', + message: 'test notification 2', + }); + expect(notifications[2]).toEqual({ + type: 'success', + message: 'test notification 3', + }); + expect(notifications[3]).toEqual({ + type: 'error', + message: 'test notification 4', + }); }); }); diff --git a/client/src/app/common-components/notification/notification.component.spec.ts b/client/src/app/common-components/notification/notification.component.spec.ts index e6109ce..de8b5af 100644 --- a/client/src/app/common-components/notification/notification.component.spec.ts +++ b/client/src/app/common-components/notification/notification.component.spec.ts @@ -1,25 +1,63 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; -import { NotificationComponent } from './notification.component'; +import { Component } from '@angular/core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { NotificationComponent } from './notification.component'; + +async function setup({ + template, +}: { + template?: string; +} = {}) { + @Component({ + template, + }) + class TestComponent {} + + await TestBed.configureTestingModule({ + declarations: [NotificationComponent, TestComponent], + imports: [NoopAnimationsModule], + }).compileComponents(); + + const fixture = TestBed.createComponent(TestComponent); + const debugElement = fixture.debugElement.children[0]; + fixture.detectChanges(); + + return { + fixture, + nativeElement: debugElement.nativeElement as HTMLElement, + }; +} describe('NotificationComponent', () => { - let component: NotificationComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ NotificationComponent ], - imports: [NoopAnimationsModule] - }) - .compileComponents(); - - fixture = TestBed.createComponent(NotificationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); + it('renders content', async () => { + const { nativeElement } = await setup({ + template: 'Test notification', + }); + expect(nativeElement.textContent).toBe('Test notification'); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('defaults to success type', async () => { + const { nativeElement } = await setup({ + template: '', + }); + + expect(nativeElement.classList.contains('success')).toBe(true); + }); + + it('renders success type', async () => { + const { nativeElement } = await setup({ + template: '', + }); + + expect(nativeElement.classList.contains('success')).toBe(true); + }); + + it('renders error type', async () => { + const { nativeElement } = await setup({ + template: '', + }); + + expect(nativeElement.classList.contains('error')).toBe(true); }); }); diff --git a/client/src/app/common-components/notifications/notifications.component.spec.ts b/client/src/app/common-components/notifications/notifications.component.spec.ts index 07f2f87..aa25d9a 100644 --- a/client/src/app/common-components/notifications/notifications.component.spec.ts +++ b/client/src/app/common-components/notifications/notifications.component.spec.ts @@ -1,23 +1,78 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { NotificationsComponent } from './notifications.component'; +import { Component } from '@angular/core'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { NotificationService } from '../notification.service'; +import { NotificationComponent } from '../notification/notification.component'; -describe('NotificationsComponent', () => { - let component: NotificationsComponent; - let fixture: ComponentFixture; +async function setup({ + template, +}: { + template?: string; +} = {}) { + @Component({ + template, + }) + class TestComponent {} + + await TestBed.configureTestingModule({ + declarations: [ + NotificationsComponent, + TestComponent, + NotificationComponent, + ], + imports: [NoopAnimationsModule], + providers: [NotificationService], + }).compileComponents(); + + const fixture = TestBed.createComponent(TestComponent); + const debugElement = fixture.debugElement.children[0]; + fixture.detectChanges(); - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ NotificationsComponent ] - }) - .compileComponents(); + const notificationService = TestBed.inject(NotificationService); - fixture = TestBed.createComponent(NotificationsComponent); - component = fixture.componentInstance; + return { + fixture, + nativeElement: debugElement.nativeElement as HTMLElement, + notificationService, + }; +} + +describe('NotificationsComponent', () => { + it('should render notifications added by notification service', async () => { + const { nativeElement, fixture, notificationService } = await setup({ + template: '', + }); + notificationService.showNotification('test notification'); fixture.detectChanges(); + const notifications = Array.from( + nativeElement.querySelectorAll('app-notification') + ); + expect(notifications).toHaveSize(1); + expect(notifications[0].textContent).toBe('test notification'); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('should render multiple notifications added by notification service', async () => { + const { nativeElement, fixture, notificationService } = await setup({ + template: '', + }); + notificationService.showNotification('test notification 1'); + notificationService.showNotification('test notification 2', 'error'); + notificationService.showNotification('test notification 3', 'success'); + notificationService.showNotification('test notification 4', 'error'); + fixture.detectChanges(); + const notifications = Array.from( + nativeElement.querySelectorAll('app-notification') + ); + expect(notifications).toHaveSize(4); + expect(notifications[0].textContent).toBe('test notification 1'); + expect(notifications[0].classList.contains('success')).toBe(true); + expect(notifications[1].textContent).toBe('test notification 2'); + expect(notifications[1].classList.contains('error')).toBe(true); + expect(notifications[2].textContent).toBe('test notification 3'); + expect(notifications[2].classList.contains('success')).toBe(true); + expect(notifications[3].textContent).toBe('test notification 4'); + expect(notifications[3].classList.contains('error')).toBe(true); }); }); diff --git a/client/src/app/weight.service.spec.ts b/client/src/app/weight.service.spec.ts index 4dfe564..33588fa 100644 --- a/client/src/app/weight.service.spec.ts +++ b/client/src/app/weight.service.spec.ts @@ -1,29 +1,26 @@ -import { of } from 'rxjs'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; import { WeightResponse } from './types'; import { WeightService } from './weight.service'; -async function setup() { - const mockHttpClient = jasmine.createSpyObj('HttpClient', ['get']); - - mockHttpClient.get.and.callFake((url: string) => - url === '/api/weight' ? of({ weight: 89.6 } as WeightResponse) : of() - ); - - const service = new WeightService(mockHttpClient); - - return { - service, - }; +function setup() { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule] + }); + const service = TestBed.inject(WeightService); + const httpTestingController = TestBed.inject(HttpTestingController) + return {service, httpTestingController} } describe('WeightService', () => { describe('getWeight', () => { - it('should return weight', async () => { - const { service } = await setup(); - const weight = await new Promise((resolve) => - service.getWeight().subscribe(resolve) - ); - expect(weight).toEqual(89.6); + it('should return weight', () => { + const { service, httpTestingController } = setup(); + service.getWeight().subscribe(weight => { + expect(weight).toEqual(89.6); + }) + httpTestingController.expectOne('/api/weight').flush({ weight: 89.6 } as WeightResponse) + httpTestingController.verify(); }); }); }); diff --git a/client/src/app/withings.service.spec.ts b/client/src/app/withings.service.spec.ts index 689d9c8..4781567 100644 --- a/client/src/app/withings.service.spec.ts +++ b/client/src/app/withings.service.spec.ts @@ -1,49 +1,42 @@ -import { Observable, of, throwError } from 'rxjs'; +import { + HttpClientTestingModule, + HttpTestingController, +} from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; import { WithingsService } from './withings.service'; -import { HttpErrorResponse } from '@angular/common/http'; -async function setup( - { $sync }: { $sync: Observable } = { $sync: of(undefined) } -) { - const locationAssign = jasmine.createSpy(); - const mockHttpClient = jasmine.createSpyObj('HttpClient', ['post']); - - mockHttpClient.post.and.callFake((url: string) => - url === '/api/withings/sync' ? $sync : of() - ); - - const service = new WithingsService(mockHttpClient, { assign: locationAssign } as unknown as Location ); - - return { - service, - locationAssign - }; +function setup() { + const mockLocation = jasmine.createSpyObj(['assign']) as Location; + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [{ provide: Location, useValue: mockLocation }], + }); + const service = TestBed.inject(WithingsService); + const httpTestingController = TestBed.inject(HttpTestingController); + return { service, httpTestingController, mockLocation }; } describe('WithingsService', () => { describe('sync', () => { - it('should return undefined', async () => { - const { service } = await setup(); - const response = await new Promise((resolve) => - service.sync().subscribe(resolve) - ); - expect(response).toBe(undefined); + it('should sync with Withings', () => { + const { service, httpTestingController } = setup(); + service.sync().subscribe(); + const request = httpTestingController.expectOne('/api/withings/sync'); + request.flush({}); + expect(request.request.method).toBe('POST'); + httpTestingController.verify(); }); it('should open authorization page if 401 is returned', async () => { - const { service, locationAssign } = await setup({ - $sync: throwError( - () => - new HttpErrorResponse({ - status: 401, - error: { _links: { oauth2Login: { href: '/withings/auth' } } }, - }) - ), - }); - const response = await new Promise((resolve) => - service.sync().subscribe(resolve) + const { service, mockLocation, httpTestingController } = setup(); + service.sync().subscribe(); + const request = httpTestingController.expectOne('/api/withings/sync'); + request.flush( + { _links: { oauth2Login: { href: '/withings/auth' } } }, + { status: 401, statusText: 'Unauthorized' } ); - expect(locationAssign).toHaveBeenCalledWith('/withings/auth') + expect(mockLocation.assign).toHaveBeenCalledWith('/withings/auth'); + httpTestingController.verify(); }); }); });