implement analytics too
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -5,4 +5,5 @@
|
||||
|
||||
Added a `mockApis` export, which will replace the `MockX` API implementation classes and their related types. This is analogous with the backend's `mockServices`.
|
||||
|
||||
Deprecated `MockConfigApi`, please use `mockApis.config` instead.
|
||||
- Deprecated `MockAnalyticsApi`, please use `mockApis.analytics` instead.
|
||||
- Deprecated `MockConfigApi`, please use `mockApis.config` instead.
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { LocalStorageFeatureFlags, NoOpAnalyticsApi } from '../apis';
|
||||
import { LocalStorageFeatureFlags } from '../apis';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
renderWithEffects,
|
||||
withLogCollector,
|
||||
registerMswTestHooks,
|
||||
@@ -59,7 +59,7 @@ describe('Integration Test', () => {
|
||||
|
||||
const noOpAnalyticsApi = createApiFactory(
|
||||
analyticsApiRef,
|
||||
new NoOpAnalyticsApi(),
|
||||
mockApis.analytics(),
|
||||
);
|
||||
const noopErrorApi = createApiFactory(errorApiRef, {
|
||||
error$() {
|
||||
@@ -575,7 +575,7 @@ describe('Integration Test', () => {
|
||||
});
|
||||
|
||||
it('should track route changes via analytics api', async () => {
|
||||
const mockAnalyticsApi = new MockAnalyticsApi();
|
||||
const mockAnalyticsApi = mockApis.analytics();
|
||||
const apis = [createApiFactory(analyticsApiRef, mockAnalyticsApi)];
|
||||
const app = new AppManager({
|
||||
apis,
|
||||
@@ -608,26 +608,27 @@ describe('Integration Test', () => {
|
||||
);
|
||||
|
||||
// Capture initial and subsequent navigation events with expected context.
|
||||
const capturedEvents = mockAnalyticsApi.getEvents();
|
||||
expect(capturedEvents[0]).toMatchObject({
|
||||
expect(mockAnalyticsApi.captureEvent).toHaveBeenCalledTimes(2);
|
||||
expect(mockAnalyticsApi.captureEvent).toHaveBeenNthCalledWith(1, {
|
||||
action: 'navigate',
|
||||
subject: '/',
|
||||
attributes: {},
|
||||
context: {
|
||||
extension: 'App',
|
||||
pluginId: 'blob',
|
||||
routeRef: 'ref-1-2',
|
||||
},
|
||||
});
|
||||
expect(capturedEvents[1]).toMatchObject({
|
||||
expect(mockAnalyticsApi.captureEvent).toHaveBeenNthCalledWith(2, {
|
||||
action: 'navigate',
|
||||
subject: '/foo',
|
||||
attributes: {},
|
||||
context: {
|
||||
extension: 'App',
|
||||
pluginId: 'plugin2',
|
||||
routeRef: 'ref-2',
|
||||
},
|
||||
});
|
||||
expect(capturedEvents).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should throw some error when the route has duplicate params', async () => {
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
|
||||
import { TestApiProvider, mockApis } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { BackstageRouteObject } from './types';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { RouteTracker } from './RouteTracker';
|
||||
import { Link, MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import {
|
||||
AnalyticsApi,
|
||||
analyticsApiRef,
|
||||
createPlugin,
|
||||
createRouteRef,
|
||||
@@ -68,9 +68,7 @@ describe('RouteTracker', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const mockedAnalytics: jest.Mocked<AnalyticsApi> = {
|
||||
captureEvent: jest.fn(),
|
||||
};
|
||||
const mockedAnalytics = mockApis.analytics();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React, { ComponentType } from 'react';
|
||||
import { fireEvent, waitFor, screen, renderHook } from '@testing-library/react';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
TestApiProvider,
|
||||
renderInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -71,7 +71,7 @@ describe('<Link />', () => {
|
||||
|
||||
it('captures click using analytics api', async () => {
|
||||
const linkText = 'Navigate!';
|
||||
const analyticsApi = new MockAnalyticsApi();
|
||||
const analyticsApi = mockApis.analytics();
|
||||
const customOnClick = jest.fn();
|
||||
|
||||
await renderInTestApp(
|
||||
@@ -86,13 +86,15 @@ describe('<Link />', () => {
|
||||
|
||||
// Analytics event should have been fired.
|
||||
await waitFor(() => {
|
||||
expect(analyticsApi.getEvents()[0]).toMatchObject({
|
||||
action: 'click',
|
||||
subject: linkText,
|
||||
attributes: {
|
||||
to: '/test',
|
||||
},
|
||||
});
|
||||
expect(analyticsApi.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'click',
|
||||
subject: linkText,
|
||||
attributes: {
|
||||
to: '/test',
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Custom onClick handler should have still been fired too.
|
||||
expect(customOnClick).toHaveBeenCalled();
|
||||
@@ -101,7 +103,7 @@ describe('<Link />', () => {
|
||||
|
||||
it('does not capture click when noTrack is set', async () => {
|
||||
const linkText = 'Navigate!';
|
||||
const analyticsApi = new MockAnalyticsApi();
|
||||
const analyticsApi = mockApis.analytics();
|
||||
const customOnClick = jest.fn();
|
||||
|
||||
await renderInTestApp(
|
||||
@@ -120,7 +122,7 @@ describe('<Link />', () => {
|
||||
expect(customOnClick).toHaveBeenCalled();
|
||||
|
||||
// But there should be no analytics event.
|
||||
expect(analyticsApi.getEvents()).toHaveLength(0);
|
||||
expect(analyticsApi.captureEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
TestApiProvider,
|
||||
renderInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -36,9 +36,8 @@ const useStyles = makeStyles({
|
||||
},
|
||||
});
|
||||
|
||||
let analyticsApiMock: MockAnalyticsApi;
|
||||
|
||||
const handleSidebarItemClick = jest.fn();
|
||||
const analyticsApiMock = mockApis.analytics();
|
||||
|
||||
async function renderSidebar() {
|
||||
const { result } = renderHook(() => useStyles());
|
||||
@@ -79,7 +78,6 @@ async function renderSidebar() {
|
||||
describe('Items', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
analyticsApiMock = new MockAnalyticsApi();
|
||||
await renderSidebar();
|
||||
});
|
||||
|
||||
@@ -107,8 +105,7 @@ describe('Items', () => {
|
||||
await screen.findByRole('button', { name: /create/i }),
|
||||
);
|
||||
expect(handleSidebarItemClick).toHaveBeenCalledTimes(1);
|
||||
expect(analyticsApiMock.getEvents()).toHaveLength(1);
|
||||
expect(analyticsApiMock.getEvents()[0]).toMatchObject({
|
||||
expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith({
|
||||
action: 'click',
|
||||
subject: 'Create...',
|
||||
context: { routeRef: 'unknown', pluginId: 'root', extension: 'App' },
|
||||
@@ -119,8 +116,7 @@ describe('Items', () => {
|
||||
it('should send link clicks to analytics', async () => {
|
||||
await userEvent.click(await screen.findByRole('link', { name: /docs/i }));
|
||||
expect(handleSidebarItemClick).toHaveBeenCalledTimes(1);
|
||||
expect(analyticsApiMock.getEvents()).toHaveLength(1);
|
||||
expect(analyticsApiMock.getEvents()[0]).toMatchObject({
|
||||
expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith({
|
||||
action: 'click',
|
||||
subject: 'Docs',
|
||||
context: { routeRef: 'unknown', pluginId: 'root', extension: 'App' },
|
||||
@@ -132,10 +128,10 @@ describe('Items', () => {
|
||||
await userEvent.click(
|
||||
await screen.findByRole('link', { name: /explore/i }),
|
||||
);
|
||||
expect(handleSidebarItemClick).toHaveBeenCalledTimes(1);
|
||||
expect(analyticsApiMock.getEvents()).toHaveLength(0);
|
||||
expect(analyticsApiMock.captureEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SidebarSearchField', () => {
|
||||
it('should be defaultPrevented when enter is pressed', async () => {
|
||||
const searchEvent = createEvent.keyDown(
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import React, { useEffect } from 'react';
|
||||
import { BackstageRouteObject } from './types';
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { act, screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
TestApiProvider,
|
||||
withLogCollector,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -93,7 +93,7 @@ describe('ExtensionBoundary', () => {
|
||||
it('should wrap children with analytics context', async () => {
|
||||
const action = 'render';
|
||||
const subject = 'analytics';
|
||||
const analyticsApiMock = new MockAnalyticsApi();
|
||||
const analyticsApiMock = mockApis.analytics();
|
||||
|
||||
const AnalyticsComponent = () => {
|
||||
const analytics = useAnalytics();
|
||||
@@ -112,17 +112,15 @@ describe('ExtensionBoundary', () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const event = analyticsApiMock
|
||||
.getEvents()
|
||||
.find(e => e.subject === subject);
|
||||
|
||||
expect(event).toMatchObject({
|
||||
action,
|
||||
subject,
|
||||
context: {
|
||||
extensionId: 'test',
|
||||
},
|
||||
});
|
||||
expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action,
|
||||
subject,
|
||||
context: expect.objectContaining({
|
||||
extensionId: 'test',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -136,7 +134,7 @@ describe('ExtensionBoundary', () => {
|
||||
});
|
||||
return null;
|
||||
};
|
||||
const analyticsApiMock = new MockAnalyticsApi();
|
||||
const analyticsApiMock = mockApis.analytics();
|
||||
|
||||
await act(async () => {
|
||||
renderInTestApp(
|
||||
@@ -147,7 +145,7 @@ describe('ExtensionBoundary', () => {
|
||||
);
|
||||
});
|
||||
|
||||
expect(analyticsApiMock.getEvents()).toEqual([
|
||||
expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'navigate',
|
||||
subject: '/',
|
||||
@@ -156,7 +154,9 @@ describe('ExtensionBoundary', () => {
|
||||
extensionId: 'test',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ action: 'dummy' }),
|
||||
]);
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,7 +81,9 @@ export type LogCollector = AsyncLogCollector | SyncLogCollector;
|
||||
// @public
|
||||
export type LogFuncs = 'log' | 'warn' | 'error';
|
||||
|
||||
// @public
|
||||
// Warning: (ae-unresolved-link) The @link reference could not be resolved: The reference is ambiguous because "analytics" has more than one declaration; you need to add a TSDoc member reference selector
|
||||
//
|
||||
// @public @deprecated
|
||||
export class MockAnalyticsApi implements AnalyticsApi {
|
||||
// (undocumented)
|
||||
captureEvent(event: AnalyticsEvent): void;
|
||||
@@ -91,6 +93,15 @@ export class MockAnalyticsApi implements AnalyticsApi {
|
||||
|
||||
// @public
|
||||
export namespace mockApis {
|
||||
// (undocumented)
|
||||
export function analytics(): jest.Mocked<AnalyticsApi>;
|
||||
// (undocumented)
|
||||
export namespace analytics {
|
||||
const // (undocumented)
|
||||
factory: () => ApiFactory<AnalyticsApi, AnalyticsApi, {}>;
|
||||
const // (undocumented)
|
||||
mock: () => jest.Mocked<AnalyticsApi>;
|
||||
}
|
||||
export function config(options?: { data?: JsonObject }): ConfigApi;
|
||||
export namespace config {
|
||||
const factory: (
|
||||
@@ -303,8 +314,8 @@ export function wrapInTestApp(
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/deprecated.d.ts:5:1 - (ae-undocumented) Missing documentation for "setupRequestMockHandlers".
|
||||
// src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.d.ts:10:5 - (ae-undocumented) Missing documentation for "captureEvent".
|
||||
// src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.d.ts:11:5 - (ae-undocumented) Missing documentation for "getEvents".
|
||||
// src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.d.ts:11:5 - (ae-undocumented) Missing documentation for "captureEvent".
|
||||
// src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.d.ts:12:5 - (ae-undocumented) Missing documentation for "getEvents".
|
||||
// src/testUtils/apis/ErrorApi/MockErrorApi.d.ts:28:5 - (ae-undocumented) Missing documentation for "post".
|
||||
// src/testUtils/apis/ErrorApi/MockErrorApi.d.ts:29:5 - (ae-undocumented) Missing documentation for "error$".
|
||||
// src/testUtils/apis/ErrorApi/MockErrorApi.d.ts:33:5 - (ae-undocumented) Missing documentation for "getErrors".
|
||||
@@ -316,4 +327,8 @@ export function wrapInTestApp(
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:22:5 - (ae-undocumented) Missing documentation for "set".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:23:5 - (ae-undocumented) Missing documentation for "remove".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:24:5 - (ae-undocumented) Missing documentation for "observe$".
|
||||
// src/testUtils/apis/mockApis.d.ts:44:5 - (ae-undocumented) Missing documentation for "analytics".
|
||||
// src/testUtils/apis/mockApis.d.ts:45:5 - (ae-undocumented) Missing documentation for "analytics".
|
||||
// src/testUtils/apis/mockApis.d.ts:46:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/testUtils/apis/mockApis.d.ts:47:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
```
|
||||
|
||||
@@ -21,6 +21,7 @@ import { AnalyticsApi, AnalyticsEvent } from '@backstage/core-plugin-api';
|
||||
* Use getEvents in tests to verify captured events.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use {@link mockApis.analytics} instead
|
||||
*/
|
||||
export class MockAnalyticsApi implements AnalyticsApi {
|
||||
private events: AnalyticsEvent[] = [];
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
AnalyticsApi,
|
||||
ApiFactory,
|
||||
ApiRef,
|
||||
ConfigApi,
|
||||
analyticsApiRef,
|
||||
configApiRef,
|
||||
createApiFactory,
|
||||
} from '@backstage/core-plugin-api';
|
||||
@@ -103,6 +105,17 @@ function simpleMock<TApi>(
|
||||
* ```
|
||||
*/
|
||||
export namespace mockApis {
|
||||
const analyticsMockSkeleton = (): jest.Mocked<AnalyticsApi> => ({
|
||||
captureEvent: jest.fn(),
|
||||
});
|
||||
export function analytics() {
|
||||
return analyticsMockSkeleton();
|
||||
}
|
||||
export namespace analytics {
|
||||
export const factory = simpleFactory(analyticsApiRef, analytics);
|
||||
export const mock = analyticsMockSkeleton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake implementation of {@link @backstage/frontend-plugin-api#ConfigApi}
|
||||
* with optional data supplied.
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
TestApiRegistry,
|
||||
@@ -212,9 +212,9 @@ describe('<CatalogGraphCard/>', () => {
|
||||
],
|
||||
}));
|
||||
|
||||
const analyticsSpy = new MockAnalyticsApi();
|
||||
const analyticsApi = mockApis.analytics();
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsSpy]]}>
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsApi]]}>
|
||||
{wrapper}
|
||||
</TestApiProvider>,
|
||||
{
|
||||
@@ -228,12 +228,14 @@ describe('<CatalogGraphCard/>', () => {
|
||||
expect(await screen.findByText('b:d/c')).toBeInTheDocument();
|
||||
await userEvent.click(await screen.findByText('b:d/c'));
|
||||
|
||||
expect(analyticsSpy.getEvents()[0]).toMatchObject({
|
||||
action: 'click',
|
||||
subject: 'b:d/c',
|
||||
attributes: {
|
||||
to: '/entity/{kind}/{namespace}/{name}',
|
||||
},
|
||||
});
|
||||
expect(analyticsApi.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'click',
|
||||
subject: 'b:d/c',
|
||||
attributes: {
|
||||
to: '/entity/{kind}/{namespace}/{name}',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ import { analyticsApiRef } from '@backstage/core-plugin-api';
|
||||
import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react';
|
||||
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -227,9 +227,9 @@ describe.skip('<CatalogGraphPage/>', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const analyticsSpy = new MockAnalyticsApi();
|
||||
const analyticsApi = mockApis.analytics();
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsSpy]]}>
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsApi]]}>
|
||||
{wrapper}
|
||||
</TestApiProvider>,
|
||||
{
|
||||
@@ -243,10 +243,12 @@ describe.skip('<CatalogGraphPage/>', () => {
|
||||
|
||||
await userEvent.click(screen.getByText('b:d/e'));
|
||||
|
||||
expect(analyticsSpy.getEvents()[0]).toMatchObject({
|
||||
action: 'click',
|
||||
subject: 'b:d/e',
|
||||
});
|
||||
expect(analyticsApi.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'click',
|
||||
subject: 'b:d/e',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should capture analytics event when navigating to entity', async () => {
|
||||
@@ -256,9 +258,9 @@ describe.skip('<CatalogGraphPage/>', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const analyticsSpy = new MockAnalyticsApi();
|
||||
const analyticsApi = mockApis.analytics();
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsSpy]]}>
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsApi]]}>
|
||||
{wrapper}
|
||||
</TestApiProvider>,
|
||||
{
|
||||
@@ -274,12 +276,14 @@ describe.skip('<CatalogGraphPage/>', () => {
|
||||
await user.keyboard('{Shift>}');
|
||||
await user.click(screen.getByText('b:d/e'));
|
||||
|
||||
expect(analyticsSpy.getEvents()[0]).toMatchObject({
|
||||
action: 'click',
|
||||
subject: 'b:d/e',
|
||||
attributes: {
|
||||
to: '/entity/b/d/e',
|
||||
},
|
||||
});
|
||||
expect(analyticsApi.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'click',
|
||||
subject: 'b:d/e',
|
||||
attributes: {
|
||||
to: '/entity/b/d/e',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { analyticsApiRef, useAnalytics } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
TestApiRegistry,
|
||||
withLogCollector,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -57,7 +57,7 @@ describe('useEntity', () => {
|
||||
});
|
||||
|
||||
it('should provide entityRef analytics context', () => {
|
||||
const analyticsSpy = new MockAnalyticsApi();
|
||||
const analyticsSpy = mockApis.analytics();
|
||||
const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]);
|
||||
const { result } = renderHook(() => useAnalytics(), {
|
||||
wrapper: ({ children }: React.PropsWithChildren<{}>) => (
|
||||
@@ -69,9 +69,13 @@ describe('useEntity', () => {
|
||||
|
||||
result.current.captureEvent('test', 'value');
|
||||
|
||||
expect(analyticsSpy.getEvents()[0]).toMatchObject({
|
||||
context: { entityRef: 'mykind:default/my-entity' },
|
||||
});
|
||||
expect(analyticsSpy.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
context: expect.objectContaining({
|
||||
entityRef: 'mykind:default/my-entity',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -127,7 +131,7 @@ describe('useAsyncEntity', () => {
|
||||
});
|
||||
|
||||
it('should provide entityRef analytics context', () => {
|
||||
const analyticsSpy = new MockAnalyticsApi();
|
||||
const analyticsSpy = mockApis.analytics();
|
||||
const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]);
|
||||
const { result } = renderHook(() => useAnalytics(), {
|
||||
wrapper: ({ children }: React.PropsWithChildren<{}>) => (
|
||||
@@ -144,13 +148,13 @@ describe('useAsyncEntity', () => {
|
||||
|
||||
result.current.captureEvent('test', 'value');
|
||||
|
||||
expect(analyticsSpy.getEvents()[0]).toMatchObject({
|
||||
context: { entityRef: 'mykind:default/my-entity' },
|
||||
});
|
||||
expect(analyticsSpy.captureEvent.mock.calls[0][0].context.entityRef).toBe(
|
||||
'mykind:default/my-entity',
|
||||
);
|
||||
});
|
||||
|
||||
it('should omit entityRef analytics context', () => {
|
||||
const analyticsSpy = new MockAnalyticsApi();
|
||||
const analyticsSpy = mockApis.analytics();
|
||||
const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]);
|
||||
const { result } = renderHook(() => useAnalytics(), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
@@ -162,6 +166,8 @@ describe('useAsyncEntity', () => {
|
||||
|
||||
result.current.captureEvent('test', 'value');
|
||||
|
||||
expect(analyticsSpy.getEvents()[0].context).not.toHaveProperty('entityRef');
|
||||
expect(
|
||||
analyticsSpy.captureEvent.mock.calls[0][0].context,
|
||||
).not.toHaveProperty('entityRef');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
TestApiRegistry,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -42,7 +42,7 @@ const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
|
||||
|
||||
const catalogApi = catalogApiMock.mock();
|
||||
|
||||
const analyticsMock = new MockAnalyticsApi();
|
||||
const analyticsMock = mockApis.analytics();
|
||||
const apis = TestApiRegistry.from(
|
||||
[scaffolderApiRef, scaffolderApiMock],
|
||||
[catalogApiRef, catalogApi],
|
||||
|
||||
+25
-14
@@ -17,7 +17,7 @@
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { analyticsApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
TestApiRegistry,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -56,12 +56,12 @@ const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
|
||||
};
|
||||
|
||||
const catalogApi = catalogApiMock.mock();
|
||||
const analyticsApi = mockApis.analytics();
|
||||
|
||||
const analyticsMock = new MockAnalyticsApi();
|
||||
const apis = TestApiRegistry.from(
|
||||
[scaffolderApiRef, scaffolderApiMock],
|
||||
[catalogApiRef, catalogApi],
|
||||
[analyticsApiRef, analyticsMock],
|
||||
[analyticsApiRef, analyticsApi],
|
||||
[catalogApiRef, catalogApi],
|
||||
);
|
||||
|
||||
@@ -81,6 +81,7 @@ const entityRefResponse = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('TemplateWizardPage', () => {
|
||||
it('captures expected analytics events', async () => {
|
||||
scaffolderApiMock.scaffold.mockResolvedValue({ taskId: 'xyz' });
|
||||
@@ -130,20 +131,29 @@ describe('TemplateWizardPage', () => {
|
||||
});
|
||||
|
||||
// The "Next Step" button should have fired an event
|
||||
expect(analyticsMock.getEvents()[0]).toMatchObject({
|
||||
action: 'click',
|
||||
subject: 'Next Step (1)',
|
||||
context: { entityRef: 'template:default/test' },
|
||||
});
|
||||
expect(analyticsApi.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'click',
|
||||
subject: 'Next Step (1)',
|
||||
context: expect.objectContaining({
|
||||
entityRef: 'template:default/test',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
// And the "Create" button should have fired an event
|
||||
expect(analyticsMock.getEvents()[1]).toMatchObject({
|
||||
action: 'create',
|
||||
subject: 'expected-name',
|
||||
context: { entityRef: 'template:default/test' },
|
||||
value: 120,
|
||||
});
|
||||
expect(analyticsApi.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'create',
|
||||
subject: 'expected-name',
|
||||
context: expect.objectContaining({
|
||||
entityRef: 'template:default/test',
|
||||
}),
|
||||
value: 120,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe('scaffolder page context menu', () => {
|
||||
it('should render if editUrl is set to url', async () => {
|
||||
catalogApi.getEntityByRef.mockResolvedValue({
|
||||
@@ -175,6 +185,7 @@ describe('TemplateWizardPage', () => {
|
||||
);
|
||||
expect(queryByTestId('menu-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render if editUrl is undefined', async () => {
|
||||
catalogApi.getEntityByRef.mockResolvedValue({
|
||||
apiVersion: 'v1',
|
||||
|
||||
@@ -20,7 +20,7 @@ import userEvent from '@testing-library/user-event';
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
import { ConfigReader } from '@backstage/core-app-api';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
TestApiProvider,
|
||||
renderInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -273,7 +273,7 @@ describe('SearchBar', () => {
|
||||
});
|
||||
|
||||
it('Does not capture analytics event if not enabled in app', async () => {
|
||||
const analyticsApiMock = new MockAnalyticsApi();
|
||||
const analyticsApiMock = mockApis.analytics();
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider
|
||||
@@ -296,7 +296,7 @@ describe('SearchBar', () => {
|
||||
|
||||
await waitFor(() => expect(textbox).toHaveValue(value));
|
||||
|
||||
expect(analyticsApiMock.getEvents()).toHaveLength(0);
|
||||
expect(analyticsApiMock.captureEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Renders custom search icon', async () => {
|
||||
|
||||
@@ -24,7 +24,7 @@ import DocsIcon from '@material-ui/icons/InsertDriveFile';
|
||||
import {
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
} from '@backstage/test-utils';
|
||||
import { createPlugin, analyticsApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
|
||||
const query = jest.fn().mockResolvedValue({ results: [] });
|
||||
const searchApiMock = { query };
|
||||
const analyticsApiMock = new MockAnalyticsApi();
|
||||
const analyticsApiMock = mockApis.analytics();
|
||||
|
||||
describe('SearchResultGroup', () => {
|
||||
const results = [
|
||||
|
||||
@@ -20,7 +20,7 @@ import { screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
TestApiProvider,
|
||||
renderInTestApp,
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
} from '@backstage/test-utils';
|
||||
import { analyticsApiRef, createPlugin } from '@backstage/core-plugin-api';
|
||||
|
||||
@@ -31,7 +31,7 @@ import { SearchResultList } from './SearchResultList';
|
||||
|
||||
const query = jest.fn().mockResolvedValue({ results: [] });
|
||||
const searchApiMock = { query };
|
||||
const analyticsApiMock = new MockAnalyticsApi();
|
||||
const analyticsApiMock = mockApis.analytics();
|
||||
|
||||
describe('SearchResultList', () => {
|
||||
const results = [
|
||||
|
||||
@@ -422,9 +422,7 @@ describe('SearchContext', () => {
|
||||
|
||||
describe('analytics', () => {
|
||||
it('captures analytics events if enabled in app', async () => {
|
||||
const analyticsApiMock = {
|
||||
captureEvent: jest.fn(),
|
||||
} satisfies typeof analyticsApiRef.T;
|
||||
const analyticsApiMock = mockApis.analytics();
|
||||
|
||||
searchApiMock.query.mockResolvedValue({
|
||||
results: [],
|
||||
@@ -481,9 +479,7 @@ describe('SearchContext', () => {
|
||||
});
|
||||
|
||||
it('captures analytics events even if number of results does not exist', async () => {
|
||||
const analyticsApiMock = {
|
||||
captureEvent: jest.fn(),
|
||||
} satisfies typeof analyticsApiRef.T;
|
||||
const analyticsApiMock = mockApis.analytics();
|
||||
|
||||
searchApiMock.query.mockResolvedValue({
|
||||
results: [],
|
||||
|
||||
@@ -23,7 +23,7 @@ import ListItemText from '@material-ui/core/ListItemText';
|
||||
import {
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
} from '@backstage/test-utils';
|
||||
import {
|
||||
createPlugin,
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
SearchResultListItemExtensionOptions,
|
||||
} from './extensions';
|
||||
|
||||
const analyticsApiMock = new MockAnalyticsApi();
|
||||
const analyticsApiMock = mockApis.analytics();
|
||||
|
||||
const results = [
|
||||
{
|
||||
@@ -118,7 +118,7 @@ describe('extensions', () => {
|
||||
screen.getByRole('link', { name: /Search Result 1/ }),
|
||||
);
|
||||
|
||||
expect(analyticsApiMock.getEvents()[0]).toMatchObject({
|
||||
expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith({
|
||||
action: 'discover',
|
||||
subject: 'Search Result 1',
|
||||
context: { routeRef: 'unknown', pluginId: 'root', extension: 'App' },
|
||||
@@ -141,7 +141,7 @@ describe('extensions', () => {
|
||||
|
||||
await userEvent.click(screen.getByRole('listitem'));
|
||||
|
||||
expect(analyticsApiMock.getEvents()[0]).toMatchObject({
|
||||
expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith({
|
||||
action: 'discover',
|
||||
subject: 'Search Result 1',
|
||||
context: { routeRef: 'unknown', pluginId: 'root', extension: 'App' },
|
||||
|
||||
@@ -19,7 +19,7 @@ import { screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
|
||||
import { analyticsApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
TestApiProvider,
|
||||
renderInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -55,11 +55,11 @@ const defaultGitlabProps = {
|
||||
};
|
||||
|
||||
describe('FeedbackLink', () => {
|
||||
const apiSpy = new MockAnalyticsApi();
|
||||
const analytics = mockApis.analytics();
|
||||
|
||||
it('Should open new Github issue tab', async () => {
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, apiSpy]]}>
|
||||
<TestApiProvider apis={[[analyticsApiRef, analytics]]}>
|
||||
<IssueLink {...defaultGithubProps} />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
@@ -77,7 +77,7 @@ describe('FeedbackLink', () => {
|
||||
|
||||
it('Should open new Gitlab issue tab', async () => {
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, apiSpy]]}>
|
||||
<TestApiProvider apis={[[analyticsApiRef, analytics]]}>
|
||||
<IssueLink {...defaultGitlabProps} />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
@@ -95,7 +95,7 @@ describe('FeedbackLink', () => {
|
||||
|
||||
it('Should track click events', async () => {
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, apiSpy]]}>
|
||||
<TestApiProvider apis={[[analyticsApiRef, analytics]]}>
|
||||
<IssueLink {...defaultGithubProps} />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
@@ -103,10 +103,12 @@ describe('FeedbackLink', () => {
|
||||
fireEvent.click(screen.getByText(/Open new Github issue/));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(apiSpy.getEvents()[0]).toMatchObject({
|
||||
action: 'click',
|
||||
subject: 'Open new Github issue',
|
||||
});
|
||||
expect(analytics.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'click',
|
||||
subject: 'Open new Github issue',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,11 +20,7 @@ import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { ThemeProvider } from '@material-ui/core/styles';
|
||||
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
TestApiProvider,
|
||||
} from '@backstage/test-utils';
|
||||
import { mockApis, TestApiProvider } from '@backstage/test-utils';
|
||||
import { Entity, CompoundEntityRef } from '@backstage/catalog-model';
|
||||
import {
|
||||
analyticsApiRef,
|
||||
@@ -66,7 +62,7 @@ const techdocsApiMock = {
|
||||
getTechDocsMetadata: jest.fn().mockResolvedValue(mockTechDocsMetadata),
|
||||
};
|
||||
|
||||
const analyticsApiMock = new MockAnalyticsApi();
|
||||
const analyticsApiMock = mockApis.analytics();
|
||||
|
||||
const wrapper = ({
|
||||
entityRef = {
|
||||
@@ -170,12 +166,12 @@ describe('useTechDocsReaderPage', () => {
|
||||
wrapper,
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(analyticsApiMock.getEvents()[0]).toMatchObject({
|
||||
expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith({
|
||||
action: 'action',
|
||||
subject: 'subject',
|
||||
context: {
|
||||
context: expect.objectContaining({
|
||||
entityRef: 'component:default/test',
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,12 +16,11 @@
|
||||
|
||||
import { TechDocsNotFound } from './TechDocsNotFound';
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
TestApiProvider,
|
||||
renderInTestApp,
|
||||
wrapInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
import { analyticsApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
@@ -58,18 +57,16 @@ describe('<TechDocsNotFound />', () => {
|
||||
});
|
||||
|
||||
it('should trigger analytics event not-found', async () => {
|
||||
const mockAnalyticsApi = new MockAnalyticsApi();
|
||||
const mockAnalyticsApi = mockApis.analytics();
|
||||
|
||||
render(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, mockAnalyticsApi]]}>
|
||||
<TechDocsNotFound />
|
||||
</TestApiProvider>,
|
||||
),
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, mockAnalyticsApi]]}>
|
||||
<TechDocsNotFound />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAnalyticsApi.getEvents()[0]).toMatchObject({
|
||||
expect(mockAnalyticsApi.captureEvent).toHaveBeenCalledWith({
|
||||
action: 'not-found',
|
||||
subject: '/the/pathname?the=search#the-anchor',
|
||||
attributes: {
|
||||
@@ -77,6 +74,7 @@ describe('<TechDocsNotFound />', () => {
|
||||
namespace: 'namespace',
|
||||
kind: 'kind',
|
||||
},
|
||||
context: expect.anything(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user