Skip to content

Commit

Permalink
Explicitly test custom appRoutes (#55405) (#57165)
Browse files Browse the repository at this point in the history
* Explicitly test custom appRoutes

* Extract common navigation function

Fixing flaky CI tests for custom appRoutes
  • Loading branch information
mshustov authored Feb 8, 2020
1 parent 5dd3bba commit 5557d71
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 63 deletions.
95 changes: 86 additions & 9 deletions src/core/public/application/integration_tests/router.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { createMemoryHistory, History, createHashHistory } from 'history';

import { AppRouter, AppNotFound } from '../ui';
import { EitherApp, MockedMounterMap, MockedMounterTuple } from '../test_types';
import { createRenderer, createAppMounter, createLegacyAppMounter } from './utils';
import { createRenderer, createAppMounter, createLegacyAppMounter, getUnmounter } from './utils';
import { AppStatus } from '../types';

describe('AppContainer', () => {
Expand All @@ -36,7 +36,6 @@ describe('AppContainer', () => {
history.push(path);
return update();
};

const mockMountersToMounters = () =>
new Map([...mounters].map(([appId, { mounter }]) => [appId, mounter]));
const setAppLeaveHandlerMock = () => undefined;
Expand All @@ -58,7 +57,8 @@ describe('AppContainer', () => {
createLegacyAppMounter('legacyApp1', jest.fn()),
createAppMounter('app2', '<div>App 2</div>'),
createLegacyAppMounter('baseApp:legacyApp2', jest.fn()),
createAppMounter('app3', '<div>App 3</div>', '/custom/path'),
createAppMounter('app3', '<div>Chromeless A</div>', '/chromeless-a/path'),
createAppMounter('app4', '<div>Chromeless B</div>', '/chromeless-b/path'),
createAppMounter('disabledApp', '<div>Disabled app</div>'),
createLegacyAppMounter('disabledLegacyApp', jest.fn()),
] as Array<MockedMounterTuple<EitherApp>>);
Expand All @@ -75,30 +75,107 @@ describe('AppContainer', () => {
});

it('calls mount handler and returned unmount function when navigating between apps', async () => {
const dom1 = await navigate('/app/app1');
const app1 = mounters.get('app1')!;
const app2 = mounters.get('app2')!;
let dom = await navigate('/app/app1');

expect(app1.mounter.mount).toHaveBeenCalled();
expect(dom1?.html()).toMatchInlineSnapshot(`
expect(dom?.html()).toMatchInlineSnapshot(`
"<div><div>
basename: /app/app1
html: <span>App 1</span>
</div></div>"
`);

const app1Unmount = await app1.mounter.mount.mock.results[0].value;
const dom2 = await navigate('/app/app2');
const app1Unmount = await getUnmounter(app1);
dom = await navigate('/app/app2');

expect(app1Unmount).toHaveBeenCalled();
expect(mounters.get('app2')!.mounter.mount).toHaveBeenCalled();
expect(dom2?.html()).toMatchInlineSnapshot(`
expect(app2.mounter.mount).toHaveBeenCalled();
expect(dom?.html()).toMatchInlineSnapshot(`
"<div><div>
basename: /app/app2
html: <div>App 2</div>
</div></div>"
`);
});

it('can navigate between standard application and one with custom appRoute', async () => {
const standardApp = mounters.get('app1')!;
const chromelessApp = mounters.get('app3')!;
let dom = await navigate('/app/app1');

expect(standardApp.mounter.mount).toHaveBeenCalled();
expect(dom?.html()).toMatchInlineSnapshot(`
"<div><div>
basename: /app/app1
html: <span>App 1</span>
</div></div>"
`);

const standardAppUnmount = await getUnmounter(standardApp);
dom = await navigate('/chromeless-a/path');

expect(standardAppUnmount).toHaveBeenCalled();
expect(chromelessApp.mounter.mount).toHaveBeenCalled();
expect(dom?.html()).toMatchInlineSnapshot(`
"<div><div>
basename: /chromeless-a/path
html: <div>Chromeless A</div>
</div></div>"
`);

const chromelessAppUnmount = await getUnmounter(standardApp);
dom = await navigate('/app/app1');

expect(chromelessAppUnmount).toHaveBeenCalled();
expect(standardApp.mounter.mount).toHaveBeenCalledTimes(2);
expect(dom?.html()).toMatchInlineSnapshot(`
"<div><div>
basename: /app/app1
html: <span>App 1</span>
</div></div>"
`);
});

it('can navigate between two applications with custom appRoutes', async () => {
const chromelessAppA = mounters.get('app3')!;
const chromelessAppB = mounters.get('app4')!;
let dom = await navigate('/chromeless-a/path');

expect(chromelessAppA.mounter.mount).toHaveBeenCalled();
expect(dom?.html()).toMatchInlineSnapshot(`
"<div><div>
basename: /chromeless-a/path
html: <div>Chromeless A</div>
</div></div>"
`);

const chromelessAppAUnmount = await getUnmounter(chromelessAppA);
dom = await navigate('/chromeless-b/path');

expect(chromelessAppAUnmount).toHaveBeenCalled();
expect(chromelessAppB.mounter.mount).toHaveBeenCalled();
expect(dom?.html()).toMatchInlineSnapshot(`
"<div><div>
basename: /chromeless-b/path
html: <div>Chromeless B</div>
</div></div>"
`);

const chromelessAppBUnmount = await getUnmounter(chromelessAppB);
dom = await navigate('/chromeless-a/path');

expect(chromelessAppBUnmount).toHaveBeenCalled();
expect(chromelessAppA.mounter.mount).toHaveBeenCalledTimes(2);
expect(dom?.html()).toMatchInlineSnapshot(`
"<div><div>
basename: /chromeless-a/path
html: <div>Chromeless A</div>
</div></div>"
`);
});

it('should not mount when partial route path matches', async () => {
mounters.set(...createAppMounter('spaces', '<div>Custom Space</div>', '/spaces/fake-login'));
mounters.set(...createAppMounter('login', '<div>Login Page</div>', '/fake-login'));
Expand Down
6 changes: 5 additions & 1 deletion src/core/public/application/integration_tests/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { mount } from 'enzyme';
import { I18nProvider } from '@kbn/i18n/react';

import { App, LegacyApp, AppMountParameters } from '../types';
import { MockedMounter, MockedMounterTuple } from '../test_types';
import { EitherApp, MockedMounter, MockedMounterTuple, Mountable } from '../test_types';

type Dom = ReturnType<typeof mount> | null;
type Renderer = () => Dom | Promise<Dom>;
Expand Down Expand Up @@ -80,3 +80,7 @@ export const createLegacyAppMounter = (
unmount: jest.fn(),
},
];

export function getUnmounter(app: Mountable<EitherApp>) {
return app.mounter.mount.mock.results[0].value;
}
17 changes: 9 additions & 8 deletions src/core/public/application/test_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,19 @@ export type ApplicationServiceContract = PublicMethodsOf<ApplicationService>;
export type EitherApp = App | LegacyApp;
/** @internal */
export type MockedUnmount = jest.Mocked<AppUnmount>;

/** @internal */
export interface Mountable<T extends EitherApp> {
mounter: MockedMounter<T>;
unmount: MockedUnmount;
}

/** @internal */
export type MockedMounter<T extends EitherApp> = jest.Mocked<Mounter<jest.Mocked<T>>>;
/** @internal */
export type MockedMounterTuple<T extends EitherApp> = [
string,
{ mounter: MockedMounter<T>; unmount: MockedUnmount }
];
export type MockedMounterTuple<T extends EitherApp> = [string, Mountable<T>];
/** @internal */
export type MockedMounterMap<T extends EitherApp> = Map<
string,
{ mounter: MockedMounter<T>; unmount: MockedUnmount }
>;
export type MockedMounterMap<T extends EitherApp> = Map<string, Mountable<T>>;
/** @internal */
export type MockLifecycle<
T extends keyof ApplicationService,
Expand Down
12 changes: 12 additions & 0 deletions test/functional/services/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,11 +311,23 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
/**
* Moves forwards in the browser history.
* https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Navigation.html#forward
*
* @return {Promise<void>}
*/
public async goForward() {
await driver.navigate().forward();
}

/**
* Navigates to a URL via the browser history.
* https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Navigation.html#to
*
* @return {Promise<void>}
*/
public async navigateTo(url: string) {
await driver.navigate().to(url);
}

/**
* Sends a sequance of keyboard keys. For each key, this will record a pair of keyDown and keyUp actions
* https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#sendKeys
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,6 @@ export class CorePluginChromelessPlugin
return renderApp(context, params);
},
});

return {
getGreeting() {
return 'Hello from Plugin Chromeless!';
},
};
}

public start() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,24 @@ export class RenderingPlugin implements Plugin {
core.application.register({
id: 'rendering',
title: 'Rendering',
appRoute: '/render',
appRoute: '/render/core',
async mount(context, { element }) {
render(<h1 data-test-subj="renderingHeader">rendering service</h1>, element);

return () => unmountComponentAtNode(element);
},
});

core.application.register({
id: 'custom-app-route',
title: 'Custom App Route',
appRoute: '/custom/appRoute',
async mount(context, { element }) {
render(<h1 data-test-subj="customAppRouteHeader">Custom App Route</h1>, element);

return () => unmountComponentAtNode(element);
},
});
}

public start() {}
Expand Down
Loading

0 comments on commit 5557d71

Please sign in to comment.