From 0863eb48742c491a58f23d014ac22ab7d098ca11 Mon Sep 17 00:00:00 2001 From: csehatt741 Date: Fri, 30 May 2025 13:01:53 +0200 Subject: [PATCH] Storybook test cases created --- .../mocks/httpClient.mock.ts | 23 ++++ .../symbol-autocomplete.component.html | 38 +----- .../symbol-autocomplete.component.stories.ts | 108 ++++++++++++++++++ .../symbol-autocomplete.component.ts | 49 ++++---- 4 files changed, 155 insertions(+), 63 deletions(-) create mode 100644 libs/ui/src/lib/symbol-autocomplete/mocks/httpClient.mock.ts create mode 100644 libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.stories.ts diff --git a/libs/ui/src/lib/symbol-autocomplete/mocks/httpClient.mock.ts b/libs/ui/src/lib/symbol-autocomplete/mocks/httpClient.mock.ts new file mode 100644 index 000000000..4911760d3 --- /dev/null +++ b/libs/ui/src/lib/symbol-autocomplete/mocks/httpClient.mock.ts @@ -0,0 +1,23 @@ +import { LookupResponse } from '@ghostfolio/common/interfaces'; + +import { Observable } from 'rxjs'; + +export class HttpClientMock { + public constructor( + private readonly url: string, + private readonly mockData: LookupResponse + ) {} + + get(url: string, options?: any): Observable { + if (url === this.url && options) { + return new Observable((subscriber) => { + subscriber.next(this.mockData as T); + subscriber.complete(); + }); + } + + return new Observable((subscriber) => { + subscriber.error(new Error(`No mock data for URL: ${url}`)); + }); + } +} diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html index 3b0d92c84..c6327c318 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html @@ -1,45 +1,15 @@ - - @for (lookupItem of defaultLookupItems; track lookupItem) { - - {{ lookupItem.name }} - @if (lookupItem.dataProviderInfo.isPremium) { - - } - - {{ lookupItem.symbol | gfSymbol }} · {{ lookupItem.currency }} - @if (lookupItem.dataProviderInfo.name) { - · {{ lookupItem.dataProviderInfo.name }} - } - - - } - - @if (!isLoading) { @for (lookupItem of filteredLookupItems; track lookupItem) { @@ -65,7 +35,7 @@ } @empty { - @if (inputControl.value?.length > 1) { + @if (control.value?.length > 1) { Oops! Could not find any assets. diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.stories.ts b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.stories.ts new file mode 100644 index 000000000..ff1eb6a5c --- /dev/null +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.stories.ts @@ -0,0 +1,108 @@ +import { LookupItem } from '@ghostfolio/common/interfaces'; + +import { CommonModule } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; +import { importProvidersFrom } from '@angular/core'; +import { + FormControl, + FormsModule, + NgControl, + ReactiveFormsModule +} from '@angular/forms'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; +import { applicationConfig, Meta, StoryObj } from '@storybook/angular'; + +import { HttpClientMock } from './mocks/httpClient.mock'; +import { GfSymbolAutocompleteComponent } from './symbol-autocomplete.component'; + +const FILTERED_OPTIONS: LookupItem[] = [ + { + assetClass: 'COMMODITY', + assetSubClass: 'ETF', + currency: 'USD', + dataProviderInfo: { name: 'YAHOO', isPremium: false }, + dataSource: null, + name: 'Test3', + symbol: 'TEST3' + }, + { + assetClass: 'EQUITY', + assetSubClass: 'STOCK', + currency: 'USD', + dataProviderInfo: { name: 'YAHOO', isPremium: false }, + dataSource: null, + name: 'Test4', + symbol: 'TEST4' + } +]; + +export default { + title: 'Symbol Autocomplete', + component: GfSymbolAutocompleteComponent, + decorators: [ + applicationConfig({ + providers: [ + provideNoopAnimations(), + importProvidersFrom(CommonModule, FormsModule, ReactiveFormsModule), + { + provide: NgControl, + useValue: { + control: new FormControl(), + valueAccessor: null + } + }, + { + provide: HttpClient, + useValue: new HttpClientMock('/api/v1/symbol/lookup', { + items: FILTERED_OPTIONS + }) + } + ] + }) + ], + parameters: { + mockData: [ + { + url: '/api/v1/symbol/lookup', + method: 'GET', + status: 200, + response: { + items: FILTERED_OPTIONS + } + } + ] + } +} as Meta; + +type Story = StoryObj; + +export const WithoutDefaults: Story = { + args: { + defaultLookupItems: [] + } +}; + +export const WithDefaults: Story = { + args: { + defaultLookupItems: [ + { + assetClass: 'COMMODITY', + assetSubClass: 'ETF', + currency: 'USD', + dataProviderInfo: { name: 'YAHOO', isPremium: false }, + dataSource: null, + name: 'Test1', + symbol: 'TEST1' + }, + { + assetClass: 'EQUITY', + assetSubClass: 'STOCK', + currency: 'USD', + dataProviderInfo: { name: 'YAHOO', isPremium: false }, + dataSource: null, + name: 'Test2', + symbol: 'TEST2' + } + ] + } +}; diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts index 901944f25..75f46d32e 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts @@ -23,6 +23,7 @@ import { ReactiveFormsModule } from '@angular/forms'; import { + MatAutocomplete, MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; @@ -32,7 +33,6 @@ import { } from '@angular/material/form-field'; import { MatInput, MatInputModule } from '@angular/material/input'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { MatSelectChange, MatSelectModule } from '@angular/material/select'; import { isString } from 'lodash'; import { Subject, tap } from 'rxjs'; import { @@ -59,7 +59,6 @@ import { GfPremiumIndicatorComponent } from '../premium-indicator'; MatFormFieldModule, MatInputModule, MatProgressSpinnerModule, - MatSelectModule, ReactiveFormsModule ], providers: [ @@ -77,15 +76,13 @@ export class GfSymbolAutocompleteComponent extends AbstractMatFormField implements OnInit, OnDestroy { - @Input() public isLoading = false; - @Input() public isAutocomplete = false; + public isLoading = false; @Input() public defaultLookupItems: LookupItem[] = []; @ViewChild(MatInput) private input: MatInput; + @ViewChild('symbolAutocomplete') public symbolAutocomplete: MatAutocomplete; - public selectControl = new FormControl(); - public inputControl = new FormControl(); - + public control = new FormControl(); public filteredLookupItems: (LookupItem & { assetSubClassString: string })[] = []; @@ -105,11 +102,21 @@ export class GfSymbolAutocompleteComponent public ngOnInit() { if (this.disabled) { - this.selectControl.disable(); - this.inputControl.disable(); + this.control.disable(); + } + + if (this.defaultLookupItems?.length) { + this.filteredLookupItems = this.defaultLookupItems.map((lookupItem) => { + return { + ...lookupItem, + assetSubClassString: translate(lookupItem.assetSubClass) + }; + }); + + this.changeDetectorRef.markForCheck(); } - this.inputControl.valueChanges + this.control.valueChanges .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(() => { if (super.value) { @@ -117,7 +124,7 @@ export class GfSymbolAutocompleteComponent } }); - this.inputControl.valueChanges + this.control.valueChanges .pipe( filter((query) => { return isString(query) && query.length > 1; @@ -176,23 +183,7 @@ export class GfSymbolAutocompleteComponent } } - public onShowAutocomplete(event: KeyboardEvent) { - if (event.key.length === 1) { - this.inputControl.setValue(event.key); - this.isAutocomplete = true; - - this.changeDetectorRef.markForCheck(); - } - } - - public onSelectUpdateSymbol(event: MatSelectChange) { - super.value = { - dataSource: event.source.value.dataSource, - symbol: event.source.value.symbol - } as LookupItem; - } - - public onAutocompleteUpdateSymbol(event: MatAutocompleteSelectedEvent) { + public onUpdateSymbol(event: MatAutocompleteSelectedEvent) { super.value = { dataSource: event.option.value.dataSource, symbol: event.option.value.symbol @@ -200,7 +191,7 @@ export class GfSymbolAutocompleteComponent } public set value(value: LookupItem) { - this.inputControl.setValue(value); + this.control.setValue(value); super.value = value; }