diff --git a/projects/common/src/layout/layout-change.service.ts b/projects/common/src/layout/layout-change.service.ts index 6d46bade1..ed853626c 100644 --- a/projects/common/src/layout/layout-change.service.ts +++ b/projects/common/src/layout/layout-change.service.ts @@ -20,13 +20,15 @@ export class LayoutChangeService implements OnDestroy { @SkipSelf() @Optional() private readonly parentLayoutChange?: LayoutChangeService ) { this.boundingElement = hostElementRef.nativeElement; - // Get layout changes from our parent LayoutChangeService, or if it's root, collect from window - const parentChange$ = this.parentLayoutChange - ? this.parentLayoutChange.layoutChangeSubject - : this.getWindowResizeEvents(); // Filter out layout changes from our parent if we didn't change size - parentChange$.pipe(filter(event => this.hasLayoutChanged(event))).subscribe(this.layoutChangeSubject); + (this.parentLayoutChange?.getLayoutChangeEventObservable() ?? this.getWindowResizeEvents()) + .pipe(filter(event => this.hasLayoutChanged(event))) + .subscribe(this.layoutChangeSubject); + } + + public getLayoutChangeEventObservable(): Observable { + return this.layoutChangeSubject; } public publishLayoutChange(): void { diff --git a/projects/components/src/layout/layout-change.directive.ts b/projects/components/src/layout/layout-change.directive.ts index fd2ee361a..63267bff6 100644 --- a/projects/components/src/layout/layout-change.directive.ts +++ b/projects/components/src/layout/layout-change.directive.ts @@ -1,19 +1,16 @@ -import { AfterViewInit, Directive, EventEmitter, Output } from '@angular/core'; +import { Directive, EventEmitter, Output } from '@angular/core'; import { LayoutChangeService, SubscriptionLifecycle } from '@hypertrace/common'; @Directive({ selector: '[htLayoutChange]', providers: [SubscriptionLifecycle, LayoutChangeService] }) -export class LayoutChangeDirective implements AfterViewInit { +export class LayoutChangeDirective { @Output('htLayoutChange') public readonly changeEmitter: EventEmitter = new EventEmitter(); public constructor(private readonly layoutChange: LayoutChangeService, subscriptionLifecycle: SubscriptionLifecycle) { subscriptionLifecycle.add(layoutChange.layout$.subscribe(this.changeEmitter)); - } - - public ngAfterViewInit(): void { this.layoutChange.initialize(); } } diff --git a/projects/components/src/titled-content/titled-content.component.scss b/projects/components/src/titled-content/titled-content.component.scss index d179bac4b..394b87a13 100644 --- a/projects/components/src/titled-content/titled-content.component.scss +++ b/projects/components/src/titled-content/titled-content.component.scss @@ -7,6 +7,7 @@ width: 100%; .header { + max-height: 28px; display: flex; flex-direction: row; align-items: center; @@ -31,7 +32,7 @@ } .content { - flex: 1 1; + flex: 1 1 auto; overflow: hidden; } diff --git a/projects/components/src/titled-content/titled-content.component.test.ts b/projects/components/src/titled-content/titled-content.component.test.ts index 4503b139a..dd8a1ca21 100644 --- a/projects/components/src/titled-content/titled-content.component.test.ts +++ b/projects/components/src/titled-content/titled-content.component.test.ts @@ -1,6 +1,8 @@ import { fakeAsync } from '@angular/core/testing'; -import { NavigationService } from '@hypertrace/common'; +import { LayoutChangeService, NavigationService } from '@hypertrace/common'; import { createHostFactory, mockProvider, Spectator } from '@ngneat/spectator/jest'; +import { MockDirective } from 'ng-mocks'; +import { LayoutChangeTriggerDirective } from '../layout/layout-change-trigger.directive'; import { TitledContentComponent } from './titled-content.component'; import { TitledContentModule } from './titled-content.module'; @@ -11,6 +13,8 @@ describe('Titled content component', () => { declareComponent: false, component: TitledContentComponent, imports: [TitledContentModule], + declarations: [MockDirective(LayoutChangeTriggerDirective)], + componentProviders: [LayoutChangeService], providers: [mockProvider(NavigationService)] }); diff --git a/projects/components/src/titled-content/titled-content.component.ts b/projects/components/src/titled-content/titled-content.component.ts index e37d3c5a3..ccb3f5d4d 100644 --- a/projects/components/src/titled-content/titled-content.component.ts +++ b/projects/components/src/titled-content/titled-content.component.ts @@ -10,7 +10,7 @@ import { TitledHeaderControlDirective } from './header-controls/titled-header-co changeDetection: ChangeDetectionStrategy.OnPush, template: `
-
+
EMPTY, getCurrentTimeRange: jest.fn().mockReturnValue(new RelativeTimeRange(new TimeDuration(15, TimeUnit.Minute))) @@ -53,3 +58,31 @@ export const mockDashboardProviders = [ }), ...getMockFlexLayoutProviders() ]; + +export const rendererApiFactoryBuilder = (model: TModel) => () => ({ + getTimeRange: jest.fn(), + model: model, + change$: EMPTY, + dataRefresh$: EMPTY, + timeRangeChanged$: EMPTY +}); + +export const mockDashboardWidgetProviders: (model: T) => StaticProvider[] = model => [ + { + provide: RENDERER_API, + useFactory: rendererApiFactoryBuilder(model) + }, + mockProvider(GraphQlRequestService, { + query: jest.fn(() => EMPTY) + }), + mockProvider(ColorService), + mockProvider(LayoutChangeService, { + getLayoutChangeEventObservable: jest.fn().mockReturnValue(of({})), + layout$: of() + }), + mockProvider(NavigationService, { + navigation$: EMPTY, + getAllValuesForQueryParameter: () => [] + }), + ...getMockFlexLayoutProviders() +]; diff --git a/projects/dashboards/src/widgets/divider/divider-widget-renderer.component.test.ts b/projects/dashboards/src/widgets/divider/divider-widget-renderer.component.test.ts index 00917ae29..a42f5483c 100644 --- a/projects/dashboards/src/widgets/divider/divider-widget-renderer.component.test.ts +++ b/projects/dashboards/src/widgets/divider/divider-widget-renderer.component.test.ts @@ -1,6 +1,5 @@ -import { RENDERER_API } from '@hypertrace/hyperdash-angular'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { EMPTY } from 'rxjs'; +import { mockDashboardWidgetProviders } from '../../test/dashboard-verification'; import { DividerWidgetRendererComponent } from './divider-widget-renderer.component'; describe('Divider Widget Renderer Component', () => { @@ -8,18 +7,7 @@ describe('Divider Widget Renderer Component', () => { const createComponent = createComponentFactory({ component: DividerWidgetRendererComponent, - providers: [ - { - provide: RENDERER_API, - useFactory: () => ({ - model: {}, - getTimeRange: jest.fn(), - change$: EMPTY, - dataRefresh$: EMPTY, - timeRangeChanged$: EMPTY - }) - } - ], + providers: [...mockDashboardWidgetProviders({})], shallow: true }); diff --git a/projects/dashboards/src/widgets/greeting-label/greeting-label-widget-renderer.component.test.ts b/projects/dashboards/src/widgets/greeting-label/greeting-label-widget-renderer.component.test.ts index 3a8ede279..e34c4a06a 100644 --- a/projects/dashboards/src/widgets/greeting-label/greeting-label-widget-renderer.component.test.ts +++ b/projects/dashboards/src/widgets/greeting-label/greeting-label-widget-renderer.component.test.ts @@ -1,7 +1,6 @@ import { GreetingLabelComponent } from '@hypertrace/components'; -import { RENDERER_API } from '@hypertrace/hyperdash-angular'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { EMPTY } from 'rxjs'; +import { mockDashboardWidgetProviders } from '../../test/dashboard-verification'; import { GreetingLabelWidgetRendererComponent } from './greeting-label-widget-renderer.component'; import { GreetingLabelWidgetModel } from './greeting-label-widget.model'; @@ -11,19 +10,7 @@ describe('Greeting label widget renderer component', () => { const createComponent = createComponentFactory({ component: GreetingLabelWidgetRendererComponent, entryComponents: [GreetingLabelComponent], - providers: [ - { - provide: RENDERER_API, - useFactory: () => ({ - model: mockModel, - getDataFromModelDataSource: EMPTY, - getTimeRange: jest.fn(), - change$: EMPTY, - dataRefresh$: EMPTY, - timeRangeChanged$: EMPTY - }) - } - ] + providers: [...mockDashboardWidgetProviders(mockModel)] }); beforeEach(() => { diff --git a/projects/dashboards/src/widgets/link/link-widget-renderer.component.test.ts b/projects/dashboards/src/widgets/link/link-widget-renderer.component.test.ts index 8406cde4f..c6e6df2a9 100644 --- a/projects/dashboards/src/widgets/link/link-widget-renderer.component.test.ts +++ b/projects/dashboards/src/widgets/link/link-widget-renderer.component.test.ts @@ -1,6 +1,5 @@ -import { RENDERER_API } from '@hypertrace/hyperdash-angular'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { EMPTY } from 'rxjs'; +import { mockDashboardWidgetProviders } from '../../test/dashboard-verification'; import { LinkWidgetRendererComponent } from './link-widget-renderer.component'; import { LinkWidgetModel } from './link-widget.model'; @@ -9,18 +8,6 @@ describe('Link widget renderer component', () => { let mockModel: Partial = {}; const createComponent = createComponentFactory({ component: LinkWidgetRendererComponent, - providers: [ - { - provide: RENDERER_API, - useFactory: () => ({ - model: mockModel, - getTimeRange: jest.fn(), - change$: EMPTY, - dataRefresh$: EMPTY, - timeRangeChanged$: EMPTY - }) - } - ], shallow: true }); @@ -31,7 +18,9 @@ describe('Link widget renderer component', () => { test('Link should be displayed as expected if url and displayText are defined', () => { mockModel.url = '#'; mockModel.displayText = 'Test'; - spectator = createComponent(); + spectator = createComponent({ + providers: [...mockDashboardWidgetProviders(mockModel)] + }); expect(spectator.query('ht-link')).toExist(); expect(spectator.component.getDisplayText()).toEqual('Test'); @@ -39,7 +28,9 @@ describe('Link widget renderer component', () => { test('Link should use url as displayText if displayText is undefined', () => { mockModel.url = '#'; - spectator = createComponent(); + spectator = createComponent({ + providers: [...mockDashboardWidgetProviders(mockModel)] + }); expect(spectator.query('ht-link')).toExist(); expect(spectator.component.getDisplayText()).toEqual(mockModel.url); diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall-widget-renderer.component.test.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall-widget-renderer.component.test.ts index e51443321..d90bd2fe0 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall-widget-renderer.component.test.ts +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall-widget-renderer.component.test.ts @@ -1,31 +1,21 @@ import { LoadAsyncModule, OverlayService } from '@hypertrace/components'; -import { RENDERER_API } from '@hypertrace/hyperdash-angular'; +import { mockDashboardWidgetProviders } from '@hypertrace/dashboards/testing'; import { createComponentFactory, mockProvider } from '@ngneat/spectator/jest'; import { MockComponent } from 'ng-mocks'; -import { EMPTY, of } from 'rxjs'; +import { of } from 'rxjs'; import { WaterfallWidgetRendererComponent } from './waterfall-widget-renderer.component'; import { WaterfallChartComponent } from './waterfall/waterfall-chart.component'; describe('Waterfall widget renderer component', () => { + const mockModel = { + getData: jest.fn(() => of([{}])) + }; + const createComponent = createComponentFactory({ component: WaterfallWidgetRendererComponent, shallow: true, imports: [LoadAsyncModule], - providers: [ - { - provide: RENDERER_API, - useValue: { - getTimeRange: jest.fn(), - model: { - getData: jest.fn(() => of([{}])) - }, - change$: EMPTY, - dataRefresh$: EMPTY, - timeRangeChanged$: EMPTY - } - }, - mockProvider(OverlayService) - ], + providers: [...mockDashboardWidgetProviders(mockModel), mockProvider(OverlayService)], declarations: [MockComponent(WaterfallChartComponent)] }); diff --git a/projects/observability/src/shared/components/explore-query-editor/explore-query-editor.component.test.ts b/projects/observability/src/shared/components/explore-query-editor/explore-query-editor.component.test.ts index 93ea57d87..c6d755846 100644 --- a/projects/observability/src/shared/components/explore-query-editor/explore-query-editor.component.test.ts +++ b/projects/observability/src/shared/components/explore-query-editor/explore-query-editor.component.test.ts @@ -12,7 +12,7 @@ import { import { AttributeMetadata, AttributeMetadataType, MetricAggregationType } from '@hypertrace/distributed-tracing'; import { GraphQlRequestService } from '@hypertrace/graphql-client'; import { createHostFactory, mockProvider } from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; +import { EMPTY, of } from 'rxjs'; import { ObservabilityTraceType } from '../../graphql/model/schema/observability-traces'; import { ExploreQueryEditorComponent } from './explore-query-editor.component'; import { ExploreQueryEditorModule } from './explore-query-editor.module'; @@ -86,7 +86,9 @@ describe('Explore query editor', () => { getAutoDurationFromTimeDurations: () => new TimeDuration(15, TimeUnit.Second), getAutoDuration: () => new TimeDuration(15, TimeUnit.Second) }), - mockProvider(NavigationService) + mockProvider(NavigationService, { + navigation$: EMPTY + }) ], declareComponent: false }); diff --git a/projects/observability/src/shared/components/explore-query-editor/group-by/explore-query-group-by-editor.component.test.ts b/projects/observability/src/shared/components/explore-query-editor/group-by/explore-query-group-by-editor.component.test.ts index e4c145cc2..c480a05da 100644 --- a/projects/observability/src/shared/components/explore-query-editor/group-by/explore-query-group-by-editor.component.test.ts +++ b/projects/observability/src/shared/components/explore-query-editor/group-by/explore-query-group-by-editor.component.test.ts @@ -5,7 +5,7 @@ import { NavigationService } from '@hypertrace/common'; import { SelectComponent, SelectModule } from '@hypertrace/components'; import { AttributeMetadata, MetadataService } from '@hypertrace/distributed-tracing'; import { byText, createHostFactory, mockProvider } from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; +import { EMPTY, of } from 'rxjs'; import { ObservabilityTraceType } from '../../../graphql/model/schema/observability-traces'; import { ExploreQueryGroupByEditorComponent } from './explore-query-group-by-editor.component'; @@ -20,7 +20,9 @@ describe('Explore Query Group by Editor component', () => { getAttributeDisplayName: (attribute: AttributeMetadata) => attribute.name, getGroupableAttributes: () => of(attributeMetadata) }), - mockProvider(NavigationService) + mockProvider(NavigationService, { + navigation$: EMPTY + }) ], shallow: true }); diff --git a/projects/observability/src/shared/components/explore-query-editor/series/explore-query-series-editor.component.test.ts b/projects/observability/src/shared/components/explore-query-editor/series/explore-query-series-editor.component.test.ts index 52eeffc77..af3f75cee 100644 --- a/projects/observability/src/shared/components/explore-query-editor/series/explore-query-series-editor.component.test.ts +++ b/projects/observability/src/shared/components/explore-query-editor/series/explore-query-series-editor.component.test.ts @@ -10,7 +10,7 @@ import { MetricAggregationType } from '@hypertrace/distributed-tracing'; import { byText, createHostFactory, mockProvider } from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; +import { EMPTY, of } from 'rxjs'; import { ObservabilityTraceType } from '../../../graphql/model/schema/observability-traces'; import { ExploreSpecificationBuilder } from '../../../graphql/request/builders/specification/explore/explore-specification-builder'; import { CartesianSeriesVisualizationType } from '../../cartesian/chart'; @@ -28,7 +28,9 @@ describe('Explore Query Series Editor component', () => { getAttributeDisplayName: (attribute: AttributeMetadata) => attribute.name, getSelectionAttributes: jest.fn(() => of(attributeMetadata())) }), - mockProvider(NavigationService) + mockProvider(NavigationService, { + navigation$: EMPTY + }) ], shallow: true }); diff --git a/projects/observability/src/shared/components/explore-query-editor/series/explore-query-series-group-editor.component.test.ts b/projects/observability/src/shared/components/explore-query-editor/series/explore-query-series-group-editor.component.test.ts index 73a75cc07..622249954 100644 --- a/projects/observability/src/shared/components/explore-query-editor/series/explore-query-series-group-editor.component.test.ts +++ b/projects/observability/src/shared/components/explore-query-editor/series/explore-query-series-group-editor.component.test.ts @@ -5,7 +5,7 @@ import { LoggerService, NavigationService } from '@hypertrace/common'; import { SelectModule } from '@hypertrace/components'; import { AttributeMetadata, MetadataService, MetricAggregationType } from '@hypertrace/distributed-tracing'; import { createHostFactory, mockProvider } from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; +import { EMPTY, of } from 'rxjs'; import { ObservabilityTraceType } from '../../../graphql/model/schema/observability-traces'; import { ExploreSpecificationBuilder } from '../../../graphql/request/builders/specification/explore/explore-specification-builder'; import { CartesianSeriesVisualizationType } from '../../cartesian/chart'; @@ -34,7 +34,9 @@ describe('Explore Query Series Group Editor component', () => { getAttributeDisplayName: (attribute: AttributeMetadata) => attribute.name, getSelectionAttributes: () => of(attributeMetadata) }), - mockProvider(NavigationService) + mockProvider(NavigationService, { + navigation$: EMPTY + }) ], shallow: true }); diff --git a/projects/observability/src/shared/dashboard/widgets/card-list/card-list-widget-renderer.component.test.ts b/projects/observability/src/shared/dashboard/widgets/card-list/card-list-widget-renderer.component.test.ts index f5d6e05f4..1e6e346bf 100644 --- a/projects/observability/src/shared/dashboard/widgets/card-list/card-list-widget-renderer.component.test.ts +++ b/projects/observability/src/shared/dashboard/widgets/card-list/card-list-widget-renderer.component.test.ts @@ -5,11 +5,11 @@ import { SummaryCardComponent, TitledContentComponent } from '@hypertrace/components'; -import { RENDERER_API } from '@hypertrace/hyperdash-angular'; +import { mockDashboardWidgetProviders } from '@hypertrace/dashboards/testing'; import { Entity, entityIdKey, entityTypeKey, ObservabilityEntityType } from '@hypertrace/observability'; import { createComponentFactory } from '@ngneat/spectator/jest'; import { MockComponent } from 'ng-mocks'; -import { EMPTY, of } from 'rxjs'; +import { of } from 'rxjs'; import { Card, CardType } from './card'; import { CardListWidgetModel } from './card-list-widget-model'; import { CardListWidgetRendererComponent } from './card-list-widget-renderer.component'; @@ -20,18 +20,6 @@ describe('Card List Widget Renderer', () => { const componentFactory = createComponentFactory({ component: CardListWidgetRendererComponent, imports: [FormattingModule, LoadAsyncModule], - providers: [ - { - provide: RENDERER_API, - useFactory: () => ({ - getTimeRange: jest.fn(), - model: mockModel, - change$: EMPTY, - dataRefresh$: EMPTY, - timeRangeChanged$: EMPTY - }) - } - ], declarations: [MockComponent(TitledContentComponent), MockComponent(SummaryCardComponent)], shallow: true }); @@ -70,7 +58,9 @@ describe('Card List Widget Renderer', () => { } }; - const spectator = componentFactory(); + const spectator = componentFactory({ + providers: [...mockDashboardWidgetProviders(mockModel)] + }); expect(spectator.query(TitledContentComponent)!.title).toEqual('I AM TITLE'); diff --git a/projects/observability/src/shared/dashboard/widgets/charts/cartesian-widget/cartesian-widget-renderer.component.test.ts b/projects/observability/src/shared/dashboard/widgets/charts/cartesian-widget/cartesian-widget-renderer.component.test.ts index 3bcd0a083..39fb11e37 100644 --- a/projects/observability/src/shared/dashboard/widgets/charts/cartesian-widget/cartesian-widget-renderer.component.test.ts +++ b/projects/observability/src/shared/dashboard/widgets/charts/cartesian-widget/cartesian-widget-renderer.component.test.ts @@ -8,32 +8,20 @@ import { TimeUnit } from '@hypertrace/common'; import { LoadAsyncModule } from '@hypertrace/components'; +import { mockDashboardWidgetProviders } from '@hypertrace/dashboards/testing'; import { ModelApi } from '@hypertrace/hyperdash'; -import { RENDERER_API } from '@hypertrace/hyperdash-angular'; import { runFakeRxjs } from '@hypertrace/test-utils'; import { createComponentFactory, mockProvider } from '@ngneat/spectator/jest'; -import { EMPTY, of } from 'rxjs'; +import { of } from 'rxjs'; import { CartesianSeriesVisualizationType } from '../../../../components/cartesian/chart'; import { MetricSeriesDataFetcher, SeriesModel } from '../series.model'; import { CartesianWidgetRendererComponent } from './cartesian-widget-renderer.component'; import { CartesianWidgetModel } from './cartesian-widget.model'; describe('Cartesian widget renderer component', () => { - const rendererApiFactory = (model: RecursivePartial> = {}) => ({ - getTimeRange: jest.fn(), - model: model, - change$: EMPTY, - dataRefresh$: EMPTY, - timeRangeChanged$: EMPTY - }); - const buildComponent = createComponentFactory({ component: CartesianWidgetRendererComponent, providers: [ - { - provide: RENDERER_API, - useValue: rendererApiFactory() - }, mockProvider(IntervalDurationService, { getAvailableIntervalsForTimeRange: () => [ new TimeDuration(1, TimeUnit.Minute), @@ -81,39 +69,33 @@ describe('Cartesian widget renderer component', () => { Object.assign(new CartesianWidgetModel<[number, number]>(), cartesianPartial); test('builds series with data', () => { - const spectator = buildComponent({ - providers: [ - { - provide: RENDERER_API, - useValue: rendererApiFactory( - cartesianModelFactory({ - series: [ - seriesFactory( - { - name: 'Series 1', - color: 'blue' - }, - fetcherFactory([ - [1, 2], - [2, 4] - ]) - ), - seriesFactory( - { - name: 'Series 2', - color: 'red' - }, - fetcherFactory([ - [3, 5], - [4, 6] - ]) - ) - ] - }) - ) - } + const mockModel = cartesianModelFactory({ + series: [ + seriesFactory( + { + name: 'Series 1', + color: 'blue' + }, + fetcherFactory([ + [1, 2], + [2, 4] + ]) + ), + seriesFactory( + { + name: 'Series 2', + color: 'red' + }, + fetcherFactory([ + [3, 5], + [4, 6] + ]) + ) ] }); + const spectator = buildComponent({ + providers: [...mockDashboardWidgetProviders(mockModel)] + }); runFakeRxjs(({ expectObservable }) => { expectObservable(spectator.component.data$!).toBe('(x|)', { @@ -148,36 +130,24 @@ describe('Cartesian widget renderer component', () => { }); test('calculates correct interval to use', () => { - const spectator = buildComponent({ - providers: [ - { - provide: RENDERER_API, - useValue: rendererApiFactory( - cartesianModelFactory({ - series: [ - seriesFactory({}, fetcherFactory([], new TimeDuration(3, TimeUnit.Minute))), - seriesFactory({}, fetcherFactory([], new TimeDuration(3, TimeUnit.Minute))) - ] - }) - ) - } + const mockModel = cartesianModelFactory({ + series: [ + seriesFactory({}, fetcherFactory([], new TimeDuration(3, TimeUnit.Minute))), + seriesFactory({}, fetcherFactory([], new TimeDuration(3, TimeUnit.Minute))) ] }); + const spectator = buildComponent({ + providers: [...mockDashboardWidgetProviders(mockModel)] + }); expect(spectator.component.selectedInterval).toEqual(new TimeDuration(3, TimeUnit.Minute)); }); test('provides expected interval options', () => { + const mockModel = cartesianModelFactory({ + series: [seriesFactory({}, fetcherFactory([]))] + }); const spectator = buildComponent({ - providers: [ - { - provide: RENDERER_API, - useValue: rendererApiFactory( - cartesianModelFactory({ - series: [seriesFactory({}, fetcherFactory([]))] - }) - ) - } - ] + providers: [...mockDashboardWidgetProviders(mockModel)] }); expect(spectator.component.intervalOptions).toEqual([ 'AUTO', @@ -189,18 +159,12 @@ describe('Cartesian widget renderer component', () => { test('updates data on interval change', () => { const fetcher = fetcherFactory([]); const series = seriesFactory({}, fetcher); + const mockModel = cartesianModelFactory({ + series: [series], + maxSeriesDataPoints: 20 + }); const spectator = buildComponent({ - providers: [ - { - provide: RENDERER_API, - useValue: rendererApiFactory( - cartesianModelFactory({ - series: [series], - maxSeriesDataPoints: 20 - }) - ) - } - ] + providers: [...mockDashboardWidgetProviders(mockModel)] }); const originalDataObservable = spectator.component.data$; spectator.component.onIntervalChange(new TimeDuration(3, TimeUnit.Minute)); diff --git a/projects/observability/src/shared/dashboard/widgets/donut/donut-widget-renderer.component.test.ts b/projects/observability/src/shared/dashboard/widgets/donut/donut-widget-renderer.component.test.ts index 04e875826..8689d9370 100644 --- a/projects/observability/src/shared/dashboard/widgets/donut/donut-widget-renderer.component.test.ts +++ b/projects/observability/src/shared/dashboard/widgets/donut/donut-widget-renderer.component.test.ts @@ -1,9 +1,9 @@ import { FormattingModule } from '@hypertrace/common'; import { LoadAsyncModule, TitledContentComponent } from '@hypertrace/components'; -import { RENDERER_API } from '@hypertrace/hyperdash-angular'; +import { mockDashboardWidgetProviders } from '@hypertrace/dashboards/testing'; import { createComponentFactory } from '@ngneat/spectator/jest'; import { MockComponent } from 'ng-mocks'; -import { EMPTY, of } from 'rxjs'; +import { of } from 'rxjs'; import { DonutComponent } from '../../../components/donut/donut.component'; import { LegendPosition } from '../../../components/legend/legend.component'; import { DonutWidgetRendererComponent } from './donut-widget-renderer.component'; @@ -15,20 +15,9 @@ describe('Donut widget renderer component', () => { component: DonutWidgetRendererComponent, shallow: true, imports: [FormattingModule, LoadAsyncModule], - providers: [ - { - provide: RENDERER_API, - useFactory: () => ({ - getTimeRange: jest.fn(), - model: mockModel, - change$: EMPTY, - dataRefresh$: EMPTY, - timeRangeChanged$: EMPTY - }) - } - ], declarations: [MockComponent(DonutComponent), MockComponent(TitledContentComponent)] }); + test('should render provided data with title and legend', () => { mockModel = { header: { @@ -56,7 +45,9 @@ describe('Donut widget renderer component', () => { displayLegendCounts: false }; - const spectator = componentFactory(); + const spectator = componentFactory({ + providers: [...mockDashboardWidgetProviders(mockModel)] + }); expect(spectator.query(TitledContentComponent)!.title).toBe('TEST TITLE'); expect(spectator.query(DonutComponent)!.series).toEqual([ diff --git a/projects/observability/src/shared/dashboard/widgets/gauge/gauge-widget-renderer.component.test.ts b/projects/observability/src/shared/dashboard/widgets/gauge/gauge-widget-renderer.component.test.ts index 43ff434ac..42932b892 100644 --- a/projects/observability/src/shared/dashboard/widgets/gauge/gauge-widget-renderer.component.test.ts +++ b/projects/observability/src/shared/dashboard/widgets/gauge/gauge-widget-renderer.component.test.ts @@ -1,9 +1,9 @@ import { Color, FormattingModule } from '@hypertrace/common'; import { LoadAsyncModule, TitledContentComponent } from '@hypertrace/components'; -import { RENDERER_API } from '@hypertrace/hyperdash-angular'; +import { mockDashboardWidgetProviders } from '@hypertrace/dashboards/testing'; import { createComponentFactory } from '@ngneat/spectator/jest'; import { MockComponent } from 'ng-mocks'; -import { EMPTY, of } from 'rxjs'; +import { of } from 'rxjs'; import { GaugeComponent } from './../../../components/gauge/gauge.component'; import { GaugeWidgetRendererComponent } from './gauge-widget-renderer.component'; import { GaugeWidgetModel } from './gauge-widget.model'; @@ -14,18 +14,6 @@ describe('Gauge widget renderer component', () => { component: GaugeWidgetRendererComponent, shallow: true, imports: [FormattingModule, LoadAsyncModule], - providers: [ - { - provide: RENDERER_API, - useFactory: () => ({ - getTimeRange: jest.fn(), - model: mockModel, - change$: EMPTY, - dataRefresh$: EMPTY, - timeRangeChanged$: EMPTY - }) - } - ], declarations: [MockComponent(GaugeComponent), MockComponent(TitledContentComponent)] }); @@ -48,7 +36,9 @@ describe('Gauge widget renderer component', () => { ) }; - const spectator = componentFactory(); + const spectator = componentFactory({ + providers: [...mockDashboardWidgetProviders(mockModel)] + }); expect(spectator.query(TitledContentComponent)!.title).toBe('TEST TITLE'); const gaugeComponent = spectator.query(GaugeComponent); diff --git a/projects/observability/src/shared/dashboard/widgets/radar/radar-widget-renderer.component.test.ts b/projects/observability/src/shared/dashboard/widgets/radar/radar-widget-renderer.component.test.ts index 724cd246e..8dd98903a 100644 --- a/projects/observability/src/shared/dashboard/widgets/radar/radar-widget-renderer.component.test.ts +++ b/projects/observability/src/shared/dashboard/widgets/radar/radar-widget-renderer.component.test.ts @@ -2,11 +2,10 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { fakeAsync } from '@angular/core/testing'; import { IconLibraryTestingModule } from '@hypertrace/assets-library'; import { DomElementMeasurerService, NavigationService, TimeUnit } from '@hypertrace/common'; -import { GraphQlRequestService } from '@hypertrace/graphql-client'; -import { RENDERER_API } from '@hypertrace/hyperdash-angular'; -import { getMockFlexLayoutProviders, runFakeRxjs } from '@hypertrace/test-utils'; +import { mockDashboardWidgetProviders } from '@hypertrace/dashboards/testing'; +import { runFakeRxjs } from '@hypertrace/test-utils'; import { createComponentFactory, mockProvider } from '@ngneat/spectator/jest'; -import { EMPTY, of } from 'rxjs'; +import { of } from 'rxjs'; import { EntitiesGraphqlQueryBuilderService } from '../../../graphql/request/handlers/entities/query/entities-graphql-query-builder.service'; import { RadarWidgetDataFetcher } from './data/radar-data-source.model'; import { RadarWidgetRendererComponent } from './radar-widget-renderer.component'; @@ -103,32 +102,20 @@ describe('Radar Widget renderer', () => { } }; - const rendererApiFactory = () => ({ - getTimeRange: jest.fn(), - model: { - getData: jest.fn(() => of(mockResponse)), - title: title, - comparisonDurations: comparisonDurations, - currentSeries: { - name: 'latency', - color: 'blue' - } - }, - change$: EMPTY, - dataRefresh$: EMPTY, - timeRangeChanged$: EMPTY - }); + const model = { + getData: jest.fn(() => of(mockResponse)), + title: title, + comparisonDurations: comparisonDurations, + currentSeries: { + name: 'latency', + color: 'blue' + } + }; const createComponent = createComponentFactory({ component: RadarWidgetRendererComponent, providers: [ - { - provide: RENDERER_API, - useFactory: rendererApiFactory - }, - mockProvider(GraphQlRequestService, { - query: jest.fn(() => EMPTY) - }), + ...mockDashboardWidgetProviders(model), mockProvider(EntitiesGraphqlQueryBuilderService), mockProvider(DomElementMeasurerService, { measureSvgElement: () => ({ @@ -138,11 +125,11 @@ describe('Radar Widget renderer', () => { height: 0 }), getComputedTextLength: () => 0 - }), - ...getMockFlexLayoutProviders() + }) ], mocks: [NavigationService], declareComponent: false, + shallow: true, imports: [RadarWidgetModule, HttpClientTestingModule, IconLibraryTestingModule] }); diff --git a/projects/observability/src/shared/dashboard/widgets/top-n/top-n-widget-renderer.component.test.ts b/projects/observability/src/shared/dashboard/widgets/top-n/top-n-widget-renderer.component.test.ts index 8a4722619..2f2c5c3d4 100644 --- a/projects/observability/src/shared/dashboard/widgets/top-n/top-n-widget-renderer.component.test.ts +++ b/projects/observability/src/shared/dashboard/widgets/top-n/top-n-widget-renderer.component.test.ts @@ -1,13 +1,12 @@ import { FormattingModule, NavigationService } from '@hypertrace/common'; import { LoadAsyncModule, TitledContentComponent } from '@hypertrace/components'; +import { mockDashboardWidgetProviders } from '@hypertrace/dashboards/testing'; import { MetricAggregationType } from '@hypertrace/distributed-tracing'; -import { GraphQlRequestService } from '@hypertrace/graphql-client'; -import { RENDERER_API } from '@hypertrace/hyperdash-angular'; import { ExploreSpecificationBuilder } from '@hypertrace/observability'; -import { getMockFlexLayoutProviders, runFakeRxjs } from '@hypertrace/test-utils'; +import { runFakeRxjs } from '@hypertrace/test-utils'; import { createComponentFactory, mockProvider } from '@ngneat/spectator/jest'; import { MockComponent } from 'ng-mocks'; -import { EMPTY, of } from 'rxjs'; +import { of } from 'rxjs'; import { entityIdKey, entityTypeKey, ObservabilityEntityType } from '../../../graphql/model/schema/entity'; import { EntityNavigationService } from '../../../services/navigation/entity/entity-navigation.service'; import { TopNWidgetDataFetcher } from './data/top-n-data-source.model'; @@ -17,35 +16,13 @@ import { TopNWidgetRendererComponent } from './top-n-widget-renderer.component'; describe('Top N Widget renderer', () => { let mockResponse: TopNWidgetDataFetcher; let optionMetricSpecifications: TopNExploreSelectionSpecificationModel[] = []; - let title = ''; - - const rendererApiFactory = () => ({ - getTimeRange: jest.fn(), - model: { - getData: jest.fn(() => of(mockResponse)), - header: { - title: title - } - }, - change$: EMPTY, - dataRefresh$: EMPTY, - timeRangeChanged$: EMPTY - }); const createComponent = createComponentFactory({ component: TopNWidgetRendererComponent, providers: [ - { - provide: RENDERER_API, - useFactory: rendererApiFactory - }, - mockProvider(GraphQlRequestService, { - query: () => EMPTY - }), mockProvider(EntityNavigationService, { navigateToEntity: jest.fn() - }), - ...getMockFlexLayoutProviders() + }) ], mocks: [NavigationService], imports: [FormattingModule, LoadAsyncModule], @@ -98,9 +75,17 @@ describe('Top N Widget renderer', () => { getOptions: () => optionMetricSpecifications }; - title = 'Top Apis'; + const mockModel = { + getData: jest.fn(() => of(mockResponse)), + header: { + title: 'Top Apis' + } + }; + + const spectator = createComponent({ + providers: [...mockDashboardWidgetProviders(mockModel)] + }); - const spectator = createComponent(); expect(spectator.query(TitledContentComponent)!.title).toEqual('TOP APIS'); runFakeRxjs(({ expectObservable }) => { @@ -164,7 +149,16 @@ describe('Top N Widget renderer', () => { getOptions: () => optionMetricSpecifications }; - const spectator = createComponent(); + const mockModel = { + getData: jest.fn(() => of(mockResponse)), + header: { + title: '' + } + }; + + const spectator = createComponent({ + providers: [...mockDashboardWidgetProviders(mockModel)] + }); spectator.component.data$?.subscribe(); spectator.component.onItemClicked(data[0]); diff --git a/projects/observability/src/shared/dashboard/widgets/topology/topology-widget-renderer.component.test.ts b/projects/observability/src/shared/dashboard/widgets/topology/topology-widget-renderer.component.test.ts index a26b6285e..42eb6bf19 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/topology-widget-renderer.component.test.ts +++ b/projects/observability/src/shared/dashboard/widgets/topology/topology-widget-renderer.component.test.ts @@ -3,14 +3,13 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { Renderer2 } from '@angular/core'; import { discardPeriodicTasks, fakeAsync, flush, TestBed } from '@angular/core/testing'; import { IconLibraryTestingModule, IconRegistryService } from '@hypertrace/assets-library'; -import { DomElementMeasurerService, NavigationService, selector } from '@hypertrace/common'; +import { DomElementMeasurerService, selector } from '@hypertrace/common'; +import { mockDashboardWidgetProviders } from '@hypertrace/dashboards/testing'; import { MetricAggregationType, MetricHealth } from '@hypertrace/distributed-tracing'; -import { GraphQlRequestService } from '@hypertrace/graphql-client'; -import { RENDERER_API } from '@hypertrace/hyperdash-angular'; import { addWidthAndHeightToSvgElForTest } from '@hypertrace/test-utils'; import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest'; import { uniq } from 'lodash-es'; -import { EMPTY, of } from 'rxjs'; +import { of } from 'rxjs'; import { TopologyNodeRendererService } from '../../../components/topology/renderers/node/topology-node-renderer.service'; import { D3UtilService } from '../../../components/utils/d3/d3-util.service'; import { EntityMetadata, ENTITY_METADATA } from '../../../constants/entity-metadata'; @@ -76,28 +75,21 @@ describe('Topology Widget renderer', () => { return node!; }; + const mockModel = { + getData: jest.fn(() => + of({ + nodes: mockResponse, + nodeSpecification: nodeSpec, + edgeSpecification: edgeSpec, + nodeTypes: uniq(mockResponse.map(node => node.data[entityTypeKey])) + }) + ) + }; + const createComponent = createComponentFactory({ component: TopologyWidgetRendererComponent, providers: [ - { - provide: RENDERER_API, - useValue: { - getTimeRange: jest.fn(), - model: { - getData: jest.fn(() => - of({ - nodes: mockResponse, - nodeSpecification: nodeSpec, - edgeSpecification: edgeSpec, - nodeTypes: uniq(mockResponse.map(node => node.data[entityTypeKey])) - }) - ) - }, - change$: EMPTY, - dataRefresh$: EMPTY, - timeRangeChanged$: EMPTY - } - }, + ...mockDashboardWidgetProviders(mockModel), mockProvider(DomElementMeasurerService, { measureSvgElement: () => ({ x: 0, @@ -107,10 +99,6 @@ describe('Topology Widget renderer', () => { }), getComputedTextLength: () => 0 }), - mockProvider(GraphQlRequestService, { - query: () => EMPTY - }), - mockProvider(NavigationService), { provide: ENTITY_METADATA, useValue: new Map([ diff --git a/projects/test-utils/src/flex/flex.ts b/projects/test-utils/src/flex/flex.ts index b8093b897..3868379c7 100644 --- a/projects/test-utils/src/flex/flex.ts +++ b/projects/test-utils/src/flex/flex.ts @@ -1,6 +1,7 @@ +import { StaticProvider } from '@angular/core'; import { StyleUtils, ɵMockMatchMediaProvider } from '@angular/flex-layout'; -export const getMockFlexLayoutProviders = () => [ +export const getMockFlexLayoutProviders = (): StaticProvider[] => [ { provide: StyleUtils, useValue: {