diff --git a/projects/spectator/jest/test/function-output/function-output.component.spec.ts b/projects/spectator/jest/test/function-output/function-output.component.spec.ts new file mode 100644 index 00000000..68dccdc1 --- /dev/null +++ b/projects/spectator/jest/test/function-output/function-output.component.spec.ts @@ -0,0 +1,47 @@ +import { createComponentFactory, createHostFactory, Spectator, SpectatorHost } from '@ngneat/spectator/jest'; +import { FunctionOutputComponent } from '../../../test/function-output/function-output.component'; + +describe('FunctionOutputComponent', () => { + describe('with Spectator', () => { + let spectator: Spectator; + + const createComponent = createComponentFactory({ + component: FunctionOutputComponent, + }); + + beforeEach(() => { + spectator = createComponent(); + }); + + it('should emit the event on button click', () => { + let output; + spectator.output('buttonClick').subscribe((result) => (output = result)); + + spectator.click('button'); + + expect(output).toEqual(true); + }); + }); + + describe('with SpectatorHost', () => { + let host: SpectatorHost; + + const createHost = createHostFactory({ + component: FunctionOutputComponent, + template: ``, + }); + + beforeEach(() => { + host = createHost(); + }); + + it('should emit the event on button click', () => { + let output; + host.output('buttonClick').subscribe((result) => (output = result)); + + host.click('button'); + + expect(output).toEqual(true); + }); + }); +}); diff --git a/projects/spectator/src/lib/base/dom-spectator.ts b/projects/spectator/src/lib/base/dom-spectator.ts index 941e2bcf..e8420569 100644 --- a/projects/spectator/src/lib/base/dom-spectator.ts +++ b/projects/spectator/src/lib/base/dom-spectator.ts @@ -1,4 +1,4 @@ -import { DebugElement, ElementRef, EventEmitter, Type } from '@angular/core'; +import { DebugElement, ElementRef, EventEmitter, OutputEmitterRef, Type } from '@angular/core'; import { ComponentFixture, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Observable } from 'rxjs'; @@ -18,6 +18,11 @@ import { BaseSpectator } from './base-spectator'; const KEY_UP = 'keyup'; +type KeysMatchingReturnType = keyof { [P in keyof T as T[P] extends V ? P : never]: P } & keyof T; +type KeysMatchingOutputFunction = KeysMatchingReturnType>; +type KeysMatchingClassicOutput = KeysMatchingReturnType>; +type KeysMatchingOutput = KeysMatchingOutputFunction | KeysMatchingClassicOutput; + /** * @internal */ @@ -159,14 +164,17 @@ export abstract class DomSpectator extends BaseSpectator { return null; } - public output(output: K): Observable { - const observable = this.instance[output]; + public output = KeysMatchingOutput>(output: K): I[K]; + public output = KeysMatchingClassicOutput>(output: K): Observable; + public output = KeysMatchingOutputFunction>(output: K): OutputEmitterRef; + public output>(output: K): I[K] | Observable | OutputEmitterRef { + const eventEmitter = this.instance[output]; - if (!(observable instanceof Observable)) { - throw new Error(`${String(output)} is not an @Output`); + if (!(eventEmitter instanceof Observable) && !(eventEmitter instanceof OutputEmitterRef)) { + throw new Error(`${String(output)} is not an @Output or an output function`); } - return observable as Observable; + return eventEmitter; } public tick(millis?: number): void { diff --git a/projects/spectator/test/function-output/function-output.component.spec.ts b/projects/spectator/test/function-output/function-output.component.spec.ts new file mode 100644 index 00000000..ffadbacf --- /dev/null +++ b/projects/spectator/test/function-output/function-output.component.spec.ts @@ -0,0 +1,47 @@ +import { createComponentFactory, createHostFactory, Spectator, SpectatorHost } from '@ngneat/spectator'; +import { FunctionOutputComponent } from './function-output.component'; + +describe('FunctionOutputComponent', () => { + describe('with Spectator', () => { + let spectator: Spectator; + + const createComponent = createComponentFactory({ + component: FunctionOutputComponent, + }); + + beforeEach(() => { + spectator = createComponent(); + }); + + it('should emit the event on button click', () => { + let output; + spectator.output('buttonClick').subscribe((result) => (output = result)); + + spectator.click('button'); + + expect(output).toEqual(true); + }); + }); + + describe('with SpectatorHost', () => { + let host: SpectatorHost; + + const createHost = createHostFactory({ + component: FunctionOutputComponent, + template: ``, + }); + + beforeEach(() => { + host = createHost(); + }); + + it('should emit the event on button click', () => { + let output; + host.output('buttonClick').subscribe((result) => (output = result)); + + host.click('button'); + + expect(output).toEqual(true); + }); + }); +}); diff --git a/projects/spectator/test/function-output/function-output.component.ts b/projects/spectator/test/function-output/function-output.component.ts new file mode 100644 index 00000000..27b1a6c6 --- /dev/null +++ b/projects/spectator/test/function-output/function-output.component.ts @@ -0,0 +1,10 @@ +import { Component, input, output, ɵINPUT_SIGNAL_BRAND_WRITE_TYPE } from '@angular/core'; + +@Component({ + selector: 'app-function-output', + template: ` `, + standalone: true, +}) +export class FunctionOutputComponent { + public buttonClick = output(); +}