Skip to content

Commit

Permalink
feat(all): introduce instanceConfig (#350)
Browse files Browse the repository at this point in the history
Co-authored-by: Mathis Wiehl <[email protected]>
  • Loading branch information
2 people authored and clebert committed Feb 12, 2019
1 parent 634c03a commit 9a25084
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 52 deletions.
5 changes: 3 additions & 2 deletions packages/async-ssr-manager/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ async function simulateAsyncOperation(result: number): Promise<number> {
}

describe('asyncSsrManagerDefinition', () => {
let mockEnv: FeatureAppEnvironment<AsyncSsrManagerConfig, {}>;
let mockEnv: FeatureAppEnvironment<AsyncSsrManagerConfig, undefined, {}>;

beforeEach(() => {
mockEnv = {
config: {timeout: 5},
featureServices: {},
idSpecifier: undefined
idSpecifier: undefined,
instanceConfig: undefined
};
});

Expand Down
65 changes: 30 additions & 35 deletions packages/core/src/__tests__/feature-app-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,17 +152,18 @@ describe('FeatureAppManager', () => {

describe('#getFeatureAppScope', () => {
it('creates a Feature App with a consumer environment using the Feature Service registry', () => {
const mockConfig = {kind: 'test'};
const config = {kind: 'test'};
const idSpecifier = 'testIdSpecifier';
const instanceConfig = 'testInstanceConfig';

featureAppManager = new FeatureAppManager(mockFeatureServiceRegistry, {
configs: {[mockFeatureAppDefinition.id]: mockConfig}
configs: {[mockFeatureAppDefinition.id]: config}
});

featureAppManager.getFeatureAppScope(
mockFeatureAppDefinition,
idSpecifier
);
featureAppManager.getFeatureAppScope(mockFeatureAppDefinition, {
idSpecifier,
instanceConfig
});

expect(mockFeatureServiceRegistry.bindFeatureServices.mock.calls).toEqual(
[[mockFeatureAppDefinition, idSpecifier]]
Expand All @@ -171,7 +172,7 @@ describe('FeatureAppManager', () => {
const {featureServices} = mockFeatureServicesBinding;

expect(mockFeatureAppCreate.mock.calls).toEqual([
[{config: mockConfig, featureServices, idSpecifier}]
[{config, instanceConfig, featureServices, idSpecifier}]
]);
});

Expand Down Expand Up @@ -329,10 +330,9 @@ describe('FeatureAppManager', () => {
});

it("registers the Feature App's own Feature Services before binding its required Feature Services", () => {
featureAppManager.getFeatureAppScope(
mockFeatureAppDefinition,
'testIdSpecifier'
);
featureAppManager.getFeatureAppScope(mockFeatureAppDefinition, {
idSpecifier: 'testIdSpecifier'
});

expect(
mockFeatureServiceRegistry.registerFeatureServices.mock.calls
Expand Down Expand Up @@ -388,10 +388,9 @@ describe('FeatureAppManager', () => {

describe('and an id specifier', () => {
it('logs an info message after creation', () => {
featureAppManager.getFeatureAppScope(
mockFeatureAppDefinition,
'testIdSpecifier'
);
featureAppManager.getFeatureAppScope(mockFeatureAppDefinition, {
idSpecifier: 'testIdSpecifier'
});

expect(stubbedConsole.stub.info.mock.calls).toEqual([
[
Expand All @@ -403,31 +402,29 @@ describe('FeatureAppManager', () => {
it('returns the same Feature App scope', () => {
const featureAppScope = featureAppManager.getFeatureAppScope(
mockFeatureAppDefinition,
'testIdSpecifier'
{idSpecifier: 'testIdSpecifier'}
);

expect(
featureAppManager.getFeatureAppScope(
mockFeatureAppDefinition,
'testIdSpecifier'
)
featureAppManager.getFeatureAppScope(mockFeatureAppDefinition, {
idSpecifier: 'testIdSpecifier'
})
).toBe(featureAppScope);
});

describe('when destroy() is called on the Feature App scope', () => {
it('returns another Feature App scope', () => {
const featureAppScope = featureAppManager.getFeatureAppScope(
mockFeatureAppDefinition,
'testIdSpecifier'
{idSpecifier: 'testIdSpecifier'}
);

featureAppScope.destroy();

expect(
featureAppManager.getFeatureAppScope(
mockFeatureAppDefinition,
'testIdSpecifier'
)
featureAppManager.getFeatureAppScope(mockFeatureAppDefinition, {
idSpecifier: 'testIdSpecifier'
})
).not.toBe(featureAppScope);
});
});
Expand All @@ -440,10 +437,9 @@ describe('FeatureAppManager', () => {
);

expect(
featureAppManager.getFeatureAppScope(
mockFeatureAppDefinition,
'testIdSpecifier'
)
featureAppManager.getFeatureAppScope(mockFeatureAppDefinition, {
idSpecifier: 'testIdSpecifier'
})
).not.toBe(featureAppScope);
});
});
Expand Down Expand Up @@ -473,7 +469,7 @@ describe('FeatureAppManager', () => {
it('throws an error when destroy is called multiple times', () => {
const featureAppScope = featureAppManager.getFeatureAppScope(
mockFeatureAppDefinition,
'testIdSpecifier'
{idSpecifier: 'testIdSpecifier'}
);

featureAppScope.destroy();
Expand All @@ -488,14 +484,13 @@ describe('FeatureAppManager', () => {
it('fails to destroy an already destroyed Feature App scope, even if this scope has been re-created', () => {
const featureAppScope = featureAppManager.getFeatureAppScope(
mockFeatureAppDefinition,
'testIdSpecifier'
{idSpecifier: 'testIdSpecifier'}
);

featureAppScope.destroy();
featureAppManager.getFeatureAppScope(
mockFeatureAppDefinition,
'testIdSpecifier'
);
featureAppManager.getFeatureAppScope(mockFeatureAppDefinition, {
idSpecifier: 'testIdSpecifier'
});

expect(() => featureAppScope.destroy()).toThrowError(
new Error(
Expand Down
42 changes: 34 additions & 8 deletions packages/core/src/feature-app-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,21 @@ import {isFeatureAppModule} from './internal/is-feature-app-module';

export interface FeatureAppEnvironment<
TConfig,
TInstanceConfig,
TFeatureServices extends FeatureServices
> {
/**
* A Feature App config object that is provided by the integrator.
* A config object that is provided by the integrator. The same config object
* is used for all Feature App instances with the same ID, which is defined in
* their {@link FeatureAppDefinition}.
*/
readonly config: TConfig;

/**
* A config object that is intended for a specific Feature App instance.
*/
readonly instanceConfig: TInstanceConfig;

/**
* An object of required Feature Services that are semver-compatible with the
* declared dependencies in the Feature App definition.
Expand All @@ -35,13 +43,16 @@ export interface FeatureAppEnvironment<
export interface FeatureAppDefinition<
TFeatureApp,
TConfig = unknown,
TInstanceConfig = unknown,
TFeatureServices extends FeatureServices = FeatureServices
> extends FeatureServiceConsumerDefinition {
readonly ownFeatureServiceDefinitions?: FeatureServiceProviderDefinition<
SharedFeatureService
>[];

create(env: FeatureAppEnvironment<TConfig, TFeatureServices>): TFeatureApp;
create(
env: FeatureAppEnvironment<TConfig, TInstanceConfig, TFeatureServices>
): TFeatureApp;
}

export type ModuleLoader = (url: string) => Promise<unknown>;
Expand All @@ -56,14 +67,27 @@ export interface FeatureAppConfigs {
readonly [featureAppId: string]: unknown;
}

export interface FeatureAppScopeOptions {
/**
* A specifier to distinguish the Feature App instances from others created
* from the same definition.
*/
readonly idSpecifier?: string;

/**
* A config object that is intended for a specific Feature App instance.
*/
readonly instanceConfig?: unknown;
}

export interface FeatureAppManagerLike {
getAsyncFeatureAppDefinition(
url: string
): AsyncValue<FeatureAppDefinition<unknown>>;

getFeatureAppScope<TFeatureApp>(
featureAppDefinition: FeatureAppDefinition<TFeatureApp>,
idSpecifier?: string
options?: FeatureAppScopeOptions
): FeatureAppScope<TFeatureApp>;

preloadFeatureApp(url: string): Promise<void>;
Expand Down Expand Up @@ -165,18 +189,17 @@ export class FeatureAppManager implements FeatureAppManagerLike {
*
* @param featureAppDefinition The definition of the Feature App to create a
* scope for.
* @param idSpecifier A specifier to distinguish the Feature App instances
* from others created from the same definition.
*
* @returns A {@link FeatureAppScope} for the provided {@link
* FeatureAppDefinition} and ID specifier. If `getFeatureAppScope` is called
* multiple times with the same arguments, it returns the {@link
* FeatureAppScope} it created on the first call.
* multiple times with the same {@link FeatureAppDefinition} and ID specifier,
* it returns the {@link FeatureAppScope} it created on the first call.
*/
public getFeatureAppScope<TFeatureApp>(
featureAppDefinition: FeatureAppDefinition<TFeatureApp>,
idSpecifier?: string
options: FeatureAppScopeOptions = {}
): FeatureAppScope<TFeatureApp> {
const {idSpecifier, instanceConfig} = options;
const {id: featureAppId} = featureAppDefinition;
const featureAppUid = createUid(featureAppId, idSpecifier);

Expand All @@ -191,6 +214,7 @@ export class FeatureAppManager implements FeatureAppManagerLike {
featureAppScope = this.createFeatureAppScope(
featureAppDefinition,
idSpecifier,
instanceConfig,
deleteFeatureAppScope
);

Expand Down Expand Up @@ -270,6 +294,7 @@ export class FeatureAppManager implements FeatureAppManagerLike {
private createFeatureAppScope<TFeatureApp>(
featureAppDefinition: FeatureAppDefinition<TFeatureApp>,
idSpecifier: string | undefined,
instanceConfig: unknown,
deleteFeatureAppScope: () => void
): FeatureAppScope<TFeatureApp> {
this.validateExternals(featureAppDefinition);
Expand All @@ -285,6 +310,7 @@ export class FeatureAppManager implements FeatureAppManagerLike {

const featureApp = featureAppDefinition.create({
config,
instanceConfig,
featureServices: binding.featureServices,
idSpecifier
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const inBrowser = typeof window !== 'undefined';
export const historyConsumerDefinition: FeatureAppDefinition<
ReactFeatureApp,
undefined,
undefined,
{'s2:history': HistoryServiceV0}
> = {
id: 'test:history-consumer',
Expand Down
1 change: 1 addition & 0 deletions packages/demos/src/server-side-rendering/feature-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ async function fetchSubject(): Promise<string> {
const featureAppDefinition: FeatureAppDefinition<
ReactFeatureApp,
undefined,
undefined,
Dependencies
> = {
id: 'test:hello-world',
Expand Down
8 changes: 6 additions & 2 deletions packages/react/src/__tests__/feature-app-container.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,20 @@ describe('FeatureAppContainer', () => {
options
);

it('calls the Feature App manager with the given Feature App definition and id specifier', () => {
it('calls the Feature App manager with the given Feature App definition, id specifier, and instance config', () => {
renderWithFeatureHubContext(
<FeatureAppContainer
featureAppDefinition={mockFeatureAppDefinition}
idSpecifier="testIdSpecifier"
instanceConfig="testInstanceConfig"
/>
);

expect(mockGetFeatureAppScope.mock.calls).toEqual([
[mockFeatureAppDefinition, 'testIdSpecifier']
[
mockFeatureAppDefinition,
{idSpecifier: 'testIdSpecifier', instanceConfig: 'testInstanceConfig'}
]
]);
});

Expand Down
9 changes: 7 additions & 2 deletions packages/react/src/__tests__/feature-app-loader.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,19 @@ describe('FeatureAppLoader', () => {

it('renders a FeatureAppContainer', () => {
const testRenderer = renderWithFeatureHubContext(
<FeatureAppLoader src="example.js" idSpecifier="testIdSpecifier" />
<FeatureAppLoader
src="example.js"
idSpecifier="testIdSpecifier"
instanceConfig="testInstanceConfig"
/>
);

expect(testRenderer.toJSON()).toBe('mocked FeatureAppContainer');

const expectedProps = {
featureAppDefinition: mockFeatureAppDefinition,
idSpecifier: 'testIdSpecifier'
idSpecifier: 'testIdSpecifier',
instanceConfig: 'testInstanceConfig'
};

expect(FeatureAppContainer).toHaveBeenCalledWith(expectedProps, {});
Expand Down
15 changes: 13 additions & 2 deletions packages/react/src/feature-app-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ export interface FeatureAppContainerProps {
* defined.
*/
readonly idSpecifier?: string;

/**
* A config object that is intended for the specific Feature App instance that
* the `FeatureAppContainer` renders.
*/
readonly instanceConfig?: unknown;
}

type InternalFeatureAppContainerProps = FeatureAppContainerProps &
Expand Down Expand Up @@ -86,12 +92,17 @@ class InternalFeatureAppContainer extends React.PureComponent<
public constructor(props: InternalFeatureAppContainerProps) {
super(props);

const {featureAppManager, featureAppDefinition, idSpecifier} = props;
const {
featureAppManager,
featureAppDefinition,
idSpecifier,
instanceConfig
} = props;

try {
this.featureAppScope = featureAppManager.getFeatureAppScope(
featureAppDefinition,
idSpecifier
{idSpecifier, instanceConfig}
);

if (!isFeatureApp(this.featureAppScope.featureApp)) {
Expand Down
Loading

0 comments on commit 9a25084

Please sign in to comment.