Skip to content

Commit

Permalink
feat(react): add custom logger option to FeatureHubContext (#408)
Browse files Browse the repository at this point in the history
The FeatureHubContext provides the custom logger to the FeatureAppLoader and FeatureAppContainer.
  • Loading branch information
unstubbable authored Mar 11, 2019
1 parent 3b372dc commit 470acd3
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 79 deletions.
100 changes: 62 additions & 38 deletions packages/react/src/__tests__/feature-app-container.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import {Stubbed, stubMethods} from 'jest-stub-methods';
import * as React from 'react';
import TestRenderer from 'react-test-renderer';
import {FeatureAppContainer, FeatureHubContextProvider} from '..';
import {logger} from './logger';

describe('FeatureAppContainer', () => {
let mockFeatureAppManager: FeatureAppManager;
let mockGetFeatureAppScope: jest.Mock;
let mockFeatureAppDefinition: FeatureAppDefinition<unknown>;
let mockFeatureAppScope: FeatureAppScope<unknown>;
let stubbedConsole: Stubbed<Console>;

beforeEach(() => {
mockFeatureAppDefinition = {id: 'testId', create: jest.fn()};
Expand All @@ -27,12 +27,6 @@ describe('FeatureAppContainer', () => {
getFeatureAppScope: mockGetFeatureAppScope,
preloadFeatureApp: jest.fn()
} as Partial<FeatureAppManager>) as FeatureAppManager;

stubbedConsole = stubMethods(console);
});

afterEach(() => {
stubbedConsole.restore();
});

it('throws an error when rendered without a FeatureHubContextProvider', () => {
Expand All @@ -49,15 +43,24 @@ describe('FeatureAppContainer', () => {

const renderWithFeatureHubContext = (
node: React.ReactNode,
options?: TestRenderer.TestRendererOptions
{
customLogger = true,
testRendererOptions
}: {
customLogger?: boolean;
testRendererOptions?: TestRenderer.TestRendererOptions;
} = {}
) =>
TestRenderer.create(
<FeatureHubContextProvider
value={{featureAppManager: mockFeatureAppManager}}
value={{
featureAppManager: mockFeatureAppManager,
logger: customLogger ? logger : undefined
}}
>
{node}
</FeatureHubContextProvider>,
options
testRendererOptions
);

it('calls the Feature App manager with the given Feature App definition, id specifier, and instance config', () => {
Expand Down Expand Up @@ -132,7 +135,7 @@ describe('FeatureAppContainer', () => {
/>
);

expect(stubbedConsole.stub.error.mock.calls).toEqual([[mockError]]);
expect(logger.error.mock.calls).toEqual([[mockError]]);
});

it('renders null', () => {
Expand Down Expand Up @@ -187,14 +190,7 @@ describe('FeatureAppContainer', () => {
/>
);

const expectedErrorBoundaryMessage = expect.stringMatching(
'^The above error occurred in'
);

expect(stubbedConsole.stub.error.mock.calls).toEqual([
[expect.stringContaining(mockError.message), mockError],
[expectedErrorBoundaryMessage]
]);
expect(logger.error.mock.calls).toEqual([[mockError]]);
});

it('renders null', () => {
Expand Down Expand Up @@ -243,7 +239,7 @@ describe('FeatureAppContainer', () => {

testRenderer.unmount();

expect(stubbedConsole.stub.error.mock.calls).toEqual([[mockError]]);
expect(logger.error.mock.calls).toEqual([[mockError]]);
});
});
});
Expand All @@ -267,11 +263,13 @@ describe('FeatureAppContainer', () => {
const testRenderer = renderWithFeatureHubContext(
<FeatureAppContainer featureAppDefinition={mockFeatureAppDefinition} />,
{
createNodeMock: () => ({
set innerHTML(html: string) {
mockSetInnerHtml(html);
}
})
testRendererOptions: {
createNodeMock: () => ({
set innerHTML(html: string) {
mockSetInnerHtml(html);
}
})
}
}
);

Expand Down Expand Up @@ -304,9 +302,7 @@ describe('FeatureAppContainer', () => {
<FeatureAppContainer
featureAppDefinition={mockFeatureAppDefinition}
/>,
{
createNodeMock: () => ({})
}
{testRendererOptions: {createNodeMock: () => ({})}}
)
).not.toThrowError(mockError);
});
Expand All @@ -316,22 +312,18 @@ describe('FeatureAppContainer', () => {
<FeatureAppContainer
featureAppDefinition={mockFeatureAppDefinition}
/>,
{
createNodeMock: () => ({})
}
{testRendererOptions: {createNodeMock: () => ({})}}
);

expect(stubbedConsole.stub.error.mock.calls).toEqual([[mockError]]);
expect(logger.error.mock.calls).toEqual([[mockError]]);
});

it('renders null', () => {
const testRenderer = renderWithFeatureHubContext(
<FeatureAppContainer
featureAppDefinition={mockFeatureAppDefinition}
/>,
{
createNodeMock: () => ({})
}
{testRendererOptions: {createNodeMock: () => ({})}}
);

expect(testRenderer.toJSON()).toBeNull();
Expand Down Expand Up @@ -373,7 +365,7 @@ describe('FeatureAppContainer', () => {

testRenderer.unmount();

expect(stubbedConsole.stub.error.mock.calls).toEqual([[mockError]]);
expect(logger.error.mock.calls).toEqual([[mockError]]);
});
});
});
Expand Down Expand Up @@ -409,7 +401,7 @@ describe('FeatureAppContainer', () => {
'Invalid Feature App found. The Feature App must be an object with either 1) a `render` method that returns a React element, or 2) an `attachTo` method that accepts a container DOM element.'
);

expect(stubbedConsole.stub.error.mock.calls).toEqual([[expectedError]]);
expect(logger.error.mock.calls).toEqual([[expectedError]]);
});
});
}
Expand All @@ -432,7 +424,7 @@ describe('FeatureAppContainer', () => {

expect(testRenderer.toJSON()).toBeNull();

expect(stubbedConsole.stub.error.mock.calls).toEqual([[mockError]]);
expect(logger.error.mock.calls).toEqual([[mockError]]);
});

describe('when unmounted', () => {
Expand All @@ -447,4 +439,36 @@ describe('FeatureAppContainer', () => {
});
});
});

describe('without a custom logger', () => {
let stubbedConsole: Stubbed<Console>;

beforeEach(() => {
stubbedConsole = stubMethods(console);
});

afterEach(() => {
stubbedConsole.restore();
});

it('logs messages using the console', () => {
const mockError = new Error('Failed to render.');

mockFeatureAppScope = {
...mockFeatureAppScope,
featureApp: {
render: () => {
throw mockError;
}
}
};

renderWithFeatureHubContext(
<FeatureAppContainer featureAppDefinition={mockFeatureAppDefinition} />,
{customLogger: false}
);

expect(stubbedConsole.stub.error.mock.calls).toEqual([[mockError]]);
});
});
});
56 changes: 44 additions & 12 deletions packages/react/src/__tests__/feature-app-loader.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as React from 'react';
import TestRenderer from 'react-test-renderer';
import {FeatureAppContainer, FeatureAppLoader} from '..';
import {FeatureHubContextProvider} from '../feature-hub-context';
import {logger} from './logger';

interface MockAsyncSsrManager extends AsyncSsrManagerV1 {
scheduleRerender: ((promise: Promise<unknown>) => void) & jest.Mock;
Expand All @@ -26,7 +27,6 @@ describe('FeatureAppLoader', () => {
let mockAsyncFeatureAppDefinition: AsyncValue<FeatureAppDefinition<unknown>>;
let mockAsyncSsrManager: MockAsyncSsrManager;
let mockAddUrlForHydration: jest.Mock;
let stubbedConsole: Stubbed<Console>;

beforeEach(() => {
if (document.head) {
Expand All @@ -53,12 +53,6 @@ describe('FeatureAppLoader', () => {
};

mockAddUrlForHydration = jest.fn();

stubbedConsole = stubMethods(console);
});

afterEach(() => {
stubbedConsole.restore();
});

it('throws an error when rendered without a FeatureHubContextProvider', () => {
Expand All @@ -71,13 +65,17 @@ describe('FeatureAppLoader', () => {
);
});

const renderWithFeatureHubContext = (node: React.ReactNode) =>
const renderWithFeatureHubContext = (
node: React.ReactNode,
options: {customLogger?: boolean} = {customLogger: true}
) =>
TestRenderer.create(
<FeatureHubContextProvider
value={{
featureAppManager: mockFeatureAppManager,
asyncSsrManager: mockAsyncSsrManager,
addUrlForHydration: mockAddUrlForHydration
addUrlForHydration: mockAddUrlForHydration,
logger: options.customLogger ? logger : undefined
}}
>
{node}
Expand Down Expand Up @@ -204,7 +202,7 @@ describe('FeatureAppLoader', () => {

expect(testRenderer.toJSON()).toBeNull();

expect(stubbedConsole.stub.error.mock.calls).toEqual([
expect(logger.error.mock.calls).toEqual([
[
'The Feature App for the src "example.js" and the ID specifier "testIdSpecifier" could not be rendered.',
mockError
Expand Down Expand Up @@ -308,7 +306,7 @@ describe('FeatureAppLoader', () => {

expect(testRenderer.toJSON()).toBeNull();

expect(stubbedConsole.stub.error.mock.calls).toEqual([
expect(logger.error.mock.calls).toEqual([
[
'The Feature App for the src "example.js" and the ID specifier "testIdSpecifier" could not be rendered.',
mockError
Expand All @@ -332,7 +330,7 @@ describe('FeatureAppLoader', () => {
expect(error).toBe(mockError);
}

expect(stubbedConsole.stub.error.mock.calls).toEqual([
expect(logger.error.mock.calls).toEqual([
[
'The Feature App for the src "example.js" could not be rendered.',
mockError
Expand All @@ -341,4 +339,38 @@ describe('FeatureAppLoader', () => {
});
});
});

describe('without a custom logger', () => {
let stubbedConsole: Stubbed<Console>;

beforeEach(() => {
stubbedConsole = stubMethods(console);
});

afterEach(() => {
stubbedConsole.restore();
});

it('logs messages using the console', () => {
const mockError = new Error('Failed to load Feature App module.');

mockAsyncFeatureAppDefinition = new AsyncValue(
Promise.reject(mockError),
undefined,
mockError
);

renderWithFeatureHubContext(
<FeatureAppLoader src="example.js" idSpecifier="testIdSpecifier" />,
{customLogger: false}
);

expect(stubbedConsole.stub.error.mock.calls).toEqual([
[
'The Feature App for the src "example.js" and the ID specifier "testIdSpecifier" could not be rendered.',
mockError
]
]);
});
});
});
13 changes: 13 additions & 0 deletions packages/react/src/__tests__/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {Logger} from '@feature-hub/core';

export type MockLogger = {
readonly [key in keyof Logger]: Logger[key] & jest.Mock
};

export const logger: MockLogger = {
trace: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn()
};
Loading

0 comments on commit 470acd3

Please sign in to comment.