diff --git a/plugins/cost-insights/src/components/CostGrowth/CostGrowth.test.tsx b/plugins/cost-insights/src/components/CostGrowth/CostGrowth.test.tsx index 6302d9929f..026b16c74a 100644 --- a/plugins/cost-insights/src/components/CostGrowth/CostGrowth.test.tsx +++ b/plugins/cost-insights/src/components/CostGrowth/CostGrowth.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { ReactNode } from 'react'; +import React, { PropsWithChildren } from 'react'; import { renderInTestApp } from '@backstage/test-utils'; import CostGrowth from './CostGrowth'; import { @@ -37,21 +37,12 @@ const MockContext = ({ children, currency, engineerCost, -}: { - children: ReactNode; +}: PropsWithChildren<{ currency: Currency; engineerCost: number; -}) => ( - - - {children} - +}>) => ( + + {children} ); diff --git a/plugins/cost-insights/src/components/CostInsightsNavigation/CostInsightsNavigation.test.tsx b/plugins/cost-insights/src/components/CostInsightsNavigation/CostInsightsNavigation.test.tsx index 7ca4958c8e..ac8561aa91 100644 --- a/plugins/cost-insights/src/components/CostInsightsNavigation/CostInsightsNavigation.test.tsx +++ b/plugins/cost-insights/src/components/CostInsightsNavigation/CostInsightsNavigation.test.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { default as HappyFace } from '@material-ui/icons/SentimentSatisfiedAlt'; import { renderInTestApp } from '@backstage/test-utils'; import CostInsightsNavigation from './CostInsightsNavigation'; -import { defaultCurrencies, Metric, Product, Icon } from '../../types'; +import { Product, Icon } from '../../types'; import { MockConfigProvider, MockScrollProvider } from '../../utils/tests'; import { getDefaultNavigationItems } from '../../utils/navigation'; @@ -36,23 +36,9 @@ const mockProducts: Product[] = [ }, ]; -const mockMetrics: Metric[] = [ - { - kind: 'some-metric', - name: 'Some Metric', - default: false, - }, -]; - const renderWrapped = (children: React.ReactNode) => renderInTestApp( - + {children} , ); diff --git a/plugins/cost-insights/src/components/CostInsightsTabs/CostInsightsTabs.test.tsx b/plugins/cost-insights/src/components/CostInsightsTabs/CostInsightsTabs.test.tsx index 0859ad18ce..b75e0ab0d9 100644 --- a/plugins/cost-insights/src/components/CostInsightsTabs/CostInsightsTabs.test.tsx +++ b/plugins/cost-insights/src/components/CostInsightsTabs/CostInsightsTabs.test.tsx @@ -17,14 +17,11 @@ import React from 'react'; import CostInsightsTabs from './CostInsightsTabs'; import UserEvent from '@testing-library/user-event'; -import { Group, defaultCurrencies } from '../../types'; -import { MockFilterProvider, MockConfigProvider } from '../../utils/tests'; -import { LoadingContext } from '../../hooks/useLoading'; +import { Group } from '../../types'; +import { MockFilterProvider, MockLoadingProvider } from '../../utils/tests'; import { renderInTestApp } from '@backstage/test-utils'; -import { mockDefaultState } from '../../utils/mockData'; const mockSetPageFilters = jest.fn(); -const mockLoadingDispatch = jest.fn(); const mockGroups: Group[] = [ { @@ -41,28 +38,9 @@ const mockGroups: Group[] = [ describe('', () => { const renderWrapped = (children: React.ReactNode) => renderInTestApp( - - - - {children} - - - , + + {children} + , ); it('Does NOT display the tabs bar if owner belongs to less than two GROUPS', async () => { diff --git a/plugins/cost-insights/src/components/PeriodSelect/PeriodSelect.test.tsx b/plugins/cost-insights/src/components/PeriodSelect/PeriodSelect.test.tsx index b5381cdd84..3c55285908 100644 --- a/plugins/cost-insights/src/components/PeriodSelect/PeriodSelect.test.tsx +++ b/plugins/cost-insights/src/components/PeriodSelect/PeriodSelect.test.tsx @@ -16,11 +16,10 @@ import React from 'react'; import { getByRole, waitFor } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; import UserEvent from '@testing-library/user-event'; import PeriodSelect, { getDefaultOptions } from './PeriodSelect'; import { Duration, getDefaultPageFilters, Group } from '../../types'; - -import { renderInTestApp } from '@backstage/test-utils'; import { MockBillingDateProvider } from '../../utils/tests'; const DefaultPageFilters = getDefaultPageFilters([{ id: 'tools' }] as Group[]); diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx index 1ca1b48c6c..bae4eef591 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx @@ -15,50 +15,27 @@ */ import React from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; import ProductInsightsCard from './ProductInsightsCard'; +import { CostInsightsApi } from '../../api'; import { createMockEntity, createMockProductCost, + mockDefaultLoadingState, MockComputeEngine, - mockDefaultState, + MockProductFilters, } from '../../utils/mockData'; import { - ApiProvider, - ApiRegistry, - IdentityApi, - identityApiRef, -} from '@backstage/core'; -import { CostInsightsApi, costInsightsApiRef } from '../../api'; -import { renderInTestApp } from '@backstage/test-utils'; -import { GroupsContext } from '../../hooks/useGroups'; -import { LoadingContext } from '../../hooks/useLoading'; -import { - defaultCurrencies, - Duration, - findAlways, - Product, - ProductCost, -} from '../../types'; -import { + MockCostInsightsApiProvider, MockBillingDateProvider, MockConfigProvider, MockCurrencyProvider, MockFilterProvider, + MockGroupsProvider, MockScrollProvider, + MockLoadingProvider, } from '../../utils/tests'; - -const mockLoadingDispatch = jest.fn(); -const mockSetPageFilters = jest.fn(); -const mockSetProductFilters = jest.fn(); -const mockSetCurrency = jest.fn(); -const engineers = findAlways(defaultCurrencies, c => c.kind === null); - -const identityApi: Partial = { - getProfile: () => ({ - email: 'test-email@example.com', - displayName: 'User 1', - }), -}; +import { Duration, Product, ProductCost, ProductPeriod } from '../../types'; const costInsightsApi = ( productCost: ProductCost, @@ -67,13 +44,6 @@ const costInsightsApi = ( Promise.resolve(productCost) as Promise, }); -const getApis = (productCost: ProductCost) => { - return ApiRegistry.from([ - [identityApiRef, identityApi], - [costInsightsApiRef, costInsightsApi(productCost)], - ]); -}; - const mockProductCost = createMockProductCost(() => ({ entities: [], aggregation: [3000, 4000], @@ -89,42 +59,28 @@ const renderProductInsightsCardInTestApp = async ( duration: Duration, ) => await renderInTestApp( - - - - - + + + + + ({ + ...p, + duration: duration, + }))} > - + - - + + - , + , ); describe('', () => { diff --git a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx index dd70433003..97775258c9 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx @@ -46,15 +46,9 @@ describe('', () => { ); const title = new RegExp(`Project growth for ${MockProject}`); const rendered = await renderInTestApp( - + - + , @@ -71,13 +65,7 @@ describe('', () => { const subheader = new RegExp('1 product'); const title = new RegExp(`Project growth for ${MockProject}`); const rendered = await renderInTestApp( - + ', () => { let Component: React.ReactNode; beforeEach(() => { Component = () => ( - + ); diff --git a/plugins/cost-insights/src/components/ResourceGrowthBarChartLegend/ResourceGrowthBarChartLegend.test.tsx b/plugins/cost-insights/src/components/ResourceGrowthBarChartLegend/ResourceGrowthBarChartLegend.test.tsx index ecc601a0b7..e3c2506296 100644 --- a/plugins/cost-insights/src/components/ResourceGrowthBarChartLegend/ResourceGrowthBarChartLegend.test.tsx +++ b/plugins/cost-insights/src/components/ResourceGrowthBarChartLegend/ResourceGrowthBarChartLegend.test.tsx @@ -14,31 +14,17 @@ * limitations under the License. */ -import React, { ReactNode } from 'react'; +import React, { PropsWithChildren } from 'react'; import { renderInTestApp } from '@backstage/test-utils'; import ResourceGrowthBarChartLegend from './ResourceGrowthBarChartLegend'; -import { Currency, defaultCurrencies, Duration, findAlways } from '../../types'; +import { defaultCurrencies, Duration, findAlways } from '../../types'; import { MockConfigProvider, MockCurrencyProvider } from '../../utils/tests'; const engineers = findAlways(defaultCurrencies, c => c.kind === null); -const MockContext = ({ - children, - currency, -}: { - children: ReactNode; - currency: Currency; -}) => ( - - - {children} - +const MockContext = ({ children }: PropsWithChildren<{}>) => ( + + {children} ); @@ -52,7 +38,7 @@ describe('', () => { ({ ratio, amount, costText, engineerTest }) => { it(`Should display the correct cost and engineer text for ${ratio} percent change`, async () => { const rendered = await renderInTestApp( - + = ({ dispatch({ [DefaultLoadingAction.UserGroups]: isLoading }), }); -type GroupsContextProps = { +export type GroupsContextProps = { groups: Group[]; }; diff --git a/plugins/cost-insights/src/index.ts b/plugins/cost-insights/src/index.ts index ba912d2316..71cc66dba6 100644 --- a/plugins/cost-insights/src/index.ts +++ b/plugins/cost-insights/src/index.ts @@ -19,3 +19,4 @@ export * from './api'; export * from './components'; export { useCurrency } from './hooks'; export * from './types'; +export * from './utils/tests'; diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/utils/mockData.ts index a25f0dad20..11c70eee25 100644 --- a/plugins/cost-insights/src/utils/mockData.ts +++ b/plugins/cost-insights/src/utils/mockData.ts @@ -19,7 +19,7 @@ import { Duration, Entity, findAlways, - getDefaultState, + getDefaultState as getDefaultLoadingState, Product, ProductCost, ProductFilters, @@ -154,7 +154,9 @@ export const MockLoadingActions = ([ DefaultLoadingAction.CostInsightsPage, ] as string[]).concat(MockProducts.map(product => product.kind)); -export const mockDefaultState = getDefaultState(MockLoadingActions); +export const mockDefaultLoadingState = getDefaultLoadingState( + MockLoadingActions, +); export const MockComputeEngine = findAlways( MockProducts, diff --git a/plugins/cost-insights/src/utils/tests.tsx b/plugins/cost-insights/src/utils/tests.tsx index fbf665c131..4d2cad8999 100644 --- a/plugins/cost-insights/src/utils/tests.tsx +++ b/plugins/cost-insights/src/utils/tests.tsx @@ -13,96 +13,221 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { Dispatch, PropsWithChildren, SetStateAction } from 'react'; + +import React, { PropsWithChildren } from 'react'; +import { costInsightsApiRef, CostInsightsApi } from '../api'; import { - Duration, - getDefaultPageFilters, - Group, - Maybe, - PageFilters, - ProductFilters, - ProductPeriod, -} from '../types'; -import { FilterContext } from '../hooks/useFilters'; + ApiProvider, + ApiRegistry, + IdentityApi, + identityApiRef, +} from '@backstage/core'; +import { LoadingContext, LoadingContextProps } from '../hooks/useLoading'; +import { GroupsContext, GroupsContextProps } from '../hooks/useGroups'; +import { FilterContext, FilterContextProps } from '../hooks/useFilters'; import { ConfigContext, ConfigContextProps } from '../hooks/useConfig'; import { CurrencyContext, CurrencyContextProps } from '../hooks/useCurrency'; -import { ScrollContext } from '../hooks/useScroll'; import { BillingDateContext, BillingDateContextProps, } from '../hooks/useLastCompleteBillingDate'; -import { MockProductFilters } from './mockData'; +import { ScrollContext, ScrollContextProps } from '../hooks/useScroll'; +import { Duration, Group } from '../types'; + +/* + Mock Providers and types are exposed publically to allow users to test custom implementations + such as alerts, which may require context. + + Utility functions such as getDefaultPageFilters, etc. are intentionally + omitted as we do not want to expose explictly or implicitly internal implementations + that may be subject to change. + + Each Mock Context provides minimal defaults which can be overridden, allowing users to define + context props only when necessary. +*/ + +type PartialPropsWithChildren = PropsWithChildren>; export const MockGroups: Group[] = [{ id: 'tech' }, { id: 'mock-group' }]; -type MockFilterProviderProps = { - setPageFilters: Dispatch>>; - setProductFilters: Dispatch>>; - duration?: Duration; -}; +export type MockFilterProviderProps = PartialPropsWithChildren< + FilterContextProps +>; export const MockFilterProvider = ({ - setPageFilters, - setProductFilters, children, - duration = Duration.P1M, -}: PropsWithChildren) => { - const pageFilters = getDefaultPageFilters(MockGroups); + ...context +}: MockFilterProviderProps) => { + const defaultContext: FilterContextProps = { + pageFilters: { + group: 'tech', + project: null, + duration: Duration.P90D, + metric: null, + }, + productFilters: [], + setPageFilters: jest.fn(), + setProductFilters: jest.fn(), + }; return ( - ({ - ...period, - duration: duration, - })), - setPageFilters: setPageFilters, - setProductFilters: setProductFilters, - }} - > + {children} ); }; -export const MockConfigProvider = ({ - metrics, - products, - icons, - engineerCost, - currencies, +export type MockLoadingProviderProps = PartialPropsWithChildren< + LoadingContextProps +>; + +export const MockLoadingProvider = ({ children, -}: PropsWithChildren) => ( - - {children} - -); + ...context +}: MockLoadingProviderProps) => { + const defaultContext: LoadingContextProps = { + state: {}, + actions: [], + dispatch: jest.fn(), + }; + return ( + + {children} + + ); +}; + +export type MockConfigProviderProps = PartialPropsWithChildren< + ConfigContextProps +>; + +export const MockConfigProvider = ({ + children, + ...context +}: MockConfigProviderProps) => { + const defaultContext: ConfigContextProps = { + metrics: [], + products: [], + icons: [], + engineerCost: 0, + currencies: [], + }; + return ( + + {children} + + ); +}; + +export type MockCurrencyProviderProps = PartialPropsWithChildren< + CurrencyContextProps +>; export const MockCurrencyProvider = ({ - currency, - setCurrency, children, -}: PropsWithChildren) => ( - - {children} - -); + ...context +}: MockCurrencyProviderProps) => { + const defaultContext: CurrencyContextProps = { + currency: { + kind: null, + label: 'Engineers 🛠', + unit: 'engineer', + }, + setCurrency: jest.fn(), + }; + return ( + + {children} + + ); +}; + +export type MockBillingDateProviderProps = PartialPropsWithChildren< + BillingDateContextProps +>; export const MockBillingDateProvider = ({ - lastCompleteBillingDate, children, -}: PropsWithChildren) => ( - - {children} - -); + ...context +}: MockBillingDateProviderProps) => { + const defaultContext: BillingDateContextProps = { + lastCompleteBillingDate: '2020-10-01', + }; + return ( + + {children} + + ); +}; -export const MockScrollProvider = ({ children }: PropsWithChildren<{}>) => ( - - {children} - -); +export type MockScrollProviderProps = PropsWithChildren<{}>; + +export const MockScrollProvider = ({ children }: MockScrollProviderProps) => { + const defaultContext: ScrollContextProps = { + scrollTo: null, + setScrollTo: jest.fn(), + }; + return ( + + {children} + + ); +}; + +export type MockGroupsProviderProps = PartialPropsWithChildren< + GroupsContextProps +>; + +export const MockGroupsProvider = ({ + children, + ...context +}: MockGroupsProviderProps) => { + const defaultContext: GroupsContextProps = { + groups: [], + }; + return ( + + {children} + + ); +}; + +export type MockCostInsightsApiProviderProps = PartialPropsWithChildren<{ + identityApi: Partial; + costInsightsApi: Partial; +}>; + +export const MockCostInsightsApiProvider = ({ + children, + ...context +}: MockCostInsightsApiProviderProps) => { + const defaultIdentityApi: IdentityApi = { + getProfile: jest.fn(), + getIdToken: jest.fn(), + getUserId: jest.fn(), + signOut: jest.fn(), + }; + + const defaultCostInsightsApi: CostInsightsApi = { + getAlerts: jest.fn(), + getDailyMetricData: jest.fn(), + getGroupDailyCost: jest.fn(), + getGroupProjects: jest.fn(), + getLastCompleteBillingDate: jest.fn(), + getProductInsights: jest.fn(), + getProjectDailyCost: jest.fn(), + getUserGroups: jest.fn(), + }; + + // TODO: defaultConfigApiRef: ConfigApiRef + + const defaultContext = ApiRegistry.from([ + [identityApiRef, { ...defaultIdentityApi, ...context.identityApi }], + [ + costInsightsApiRef, + { ...defaultCostInsightsApi, ...context.costInsightsApi }, + ], + // [configApiRef, { ...defaultConfigApiRef, ...context.configApiRef }] + ]); + + return {children}; +};