mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Extract React Application as a Feature (#7441)
* Fix webpack config for react Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce package for discovering html elements in unit tests Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using discovery of html elements from package Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for starting react application inside the Feature Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move stuff in application start to earlier timeslot having no real need to be done so late Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using react application root feature being more friendly to extending Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using more familiar pattern of higher order components for wrapping react application Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Adapt to more familiar pattern for higher order components Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Rename feature for clarity Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> --------- Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
1 parent
5827fc9d9a
commit
b5e564271e
50 files changed
+968
-172
No files matched your search
+41
@@ -0,0 +1,41 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`react-application renders 1`] = `
|
||||
<body>
|
||||
<div />
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`react-application when content is registered and enabled renders 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
data-some-content-test="true"
|
||||
>
|
||||
Some children
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`react-application when content is registered and enabled when content is disabled renders 1`] = `
|
||||
<body>
|
||||
<div />
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`react-application when content is registered and enabled when higher order component is registered renders 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
data-some-higher-order-component-test="true"
|
||||
>
|
||||
<div
|
||||
data-some-content-test="true"
|
||||
>
|
||||
Some children
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { getFeature } from "@k8slens/feature-core";
|
||||
import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration";
|
||||
import { applicationFeature } from "@k8slens/application";
|
||||
|
||||
export const reactApplicationFeature = getFeature({
|
||||
id: "react-application",
|
||||
|
||||
register: (di) => {
|
||||
autoRegister({
|
||||
di,
|
||||
targetModule: module,
|
||||
getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)],
|
||||
});
|
||||
},
|
||||
|
||||
dependencies: [applicationFeature],
|
||||
});
|
||||
@@ -0,0 +1,137 @@
|
||||
import { registerFeature } from "@k8slens/feature-core";
|
||||
import { createContainer, DiContainer, getInjectable } from "@ogre-tools/injectable";
|
||||
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
||||
import { registerInjectableReact } from "@ogre-tools/injectable-react";
|
||||
import { reactApplicationFeature } from "./feature";
|
||||
import { runInAction, computed, observable, IObservableValue } from "mobx";
|
||||
import { startApplicationInjectionToken } from "@k8slens/application";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import { render, act } from "@testing-library/react";
|
||||
import renderInjectable from "./render-application/render.injectable";
|
||||
import { reactApplicationChildrenInjectionToken } from "./react-application/react-application-children-injection-token";
|
||||
import React from "react";
|
||||
import { Discover, discoverFor } from "@k8slens/react-testing-library-discovery";
|
||||
import {
|
||||
ReactApplicationHigherOrderComponent,
|
||||
reactApplicationHigherOrderComponentInjectionToken,
|
||||
} from "./react-application/react-application-higher-order-component-injection-token";
|
||||
|
||||
const SomeContent = () => <div data-some-content-test>Some children</div>;
|
||||
|
||||
describe("react-application", () => {
|
||||
let rendered: RenderResult;
|
||||
let di: DiContainer;
|
||||
let discover: Discover;
|
||||
|
||||
beforeEach(async () => {
|
||||
di = createContainer("some-container");
|
||||
|
||||
registerInjectableReact(di);
|
||||
|
||||
registerMobX(di);
|
||||
|
||||
runInAction(() => {
|
||||
registerFeature(di, reactApplicationFeature);
|
||||
});
|
||||
|
||||
di.override(renderInjectable, () => (application) => {
|
||||
rendered = render(application);
|
||||
});
|
||||
|
||||
const startApplication = di.inject(startApplicationInjectionToken);
|
||||
|
||||
await startApplication();
|
||||
|
||||
discover = discoverFor(() => rendered);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when content is registered and enabled", () => {
|
||||
let someObservable: IObservableValue<boolean>;
|
||||
|
||||
beforeEach(() => {
|
||||
someObservable = observable.box(true);
|
||||
|
||||
const someContentInjectable = getInjectable({
|
||||
id: "some-content",
|
||||
|
||||
instantiate: () => ({
|
||||
id: "some-content",
|
||||
Component: SomeContent,
|
||||
enabled: computed(() => someObservable.get()),
|
||||
}),
|
||||
|
||||
injectionToken: reactApplicationChildrenInjectionToken,
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
di.register(someContentInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the content", () => {
|
||||
const { discovered } = discover.getSingleElement("some-content");
|
||||
|
||||
expect(discovered).not.toBeNull();
|
||||
});
|
||||
|
||||
describe("when higher order component is registered", () => {
|
||||
beforeEach(() => {
|
||||
const SomeHigherOrderComponent: ReactApplicationHigherOrderComponent = ({ children }) => (
|
||||
<div data-some-higher-order-component-test>{children}</div>
|
||||
);
|
||||
|
||||
const someHigherOrderComponentInjectable = getInjectable({
|
||||
id: "some-higher-order-component",
|
||||
|
||||
instantiate: () => SomeHigherOrderComponent,
|
||||
|
||||
injectionToken: reactApplicationHigherOrderComponentInjectionToken,
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
di.register(someHigherOrderComponentInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the content inside the higher order component", () => {
|
||||
const { discovered } = discover
|
||||
.getSingleElement("some-higher-order-component")
|
||||
.getSingleElement("some-content");
|
||||
|
||||
expect(discovered).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when content is disabled", () => {
|
||||
beforeEach(() => {
|
||||
act(() => {
|
||||
runInAction(() => {
|
||||
someObservable.set(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not render the content", () => {
|
||||
const { discovered } = discover.querySingleElement("some-content");
|
||||
|
||||
expect(discovered).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type React from "react";
|
||||
import type { IComputedValue } from "mobx";
|
||||
|
||||
export interface ReactApplicationChildren {
|
||||
id: string;
|
||||
Component: React.ComponentType;
|
||||
enabled: IComputedValue<boolean>;
|
||||
}
|
||||
|
||||
export const reactApplicationChildrenInjectionToken = getInjectionToken<ReactApplicationChildren>({
|
||||
id: "react-application-children-injection-token",
|
||||
});
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
|
||||
import React from "react";
|
||||
import {
|
||||
ReactApplicationChildren,
|
||||
reactApplicationChildrenInjectionToken,
|
||||
} from "./react-application-children-injection-token";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { observer, Observer } from "mobx-react";
|
||||
|
||||
type Dependencies = { contents: IComputedValue<ReactApplicationChildren[]> };
|
||||
|
||||
const NonInjectedContent = observer(({ contents }: Dependencies) => (
|
||||
<>
|
||||
{contents.get().map((child) => (
|
||||
<Observer key={child.id}>{() => (child.enabled.get() ? <child.Component /> : null)}</Observer>
|
||||
))}
|
||||
</>
|
||||
));
|
||||
|
||||
export const ReactApplicationContent = withInjectables<Dependencies>(
|
||||
NonInjectedContent,
|
||||
|
||||
{
|
||||
getProps: (di) => ({
|
||||
contents: di.inject(computedInjectManyInjectable)(reactApplicationChildrenInjectionToken),
|
||||
}),
|
||||
},
|
||||
);
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type React from "react";
|
||||
|
||||
export type ReactApplicationHigherOrderComponent = React.ComponentType<{
|
||||
children: React.ReactNode;
|
||||
}>;
|
||||
|
||||
export const reactApplicationHigherOrderComponentInjectionToken =
|
||||
getInjectionToken<ReactApplicationHigherOrderComponent>({
|
||||
id: "react-application-higher-order-component-injection-token",
|
||||
});
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import type { DiContainerForInjection } from "@ogre-tools/injectable";
|
||||
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
|
||||
import { DiContextProvider } from "@ogre-tools/injectable-react";
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import {
|
||||
ReactApplicationHigherOrderComponent,
|
||||
reactApplicationHigherOrderComponentInjectionToken,
|
||||
} from "./react-application-higher-order-component-injection-token";
|
||||
|
||||
import { ReactApplicationContent } from "./react-application-content";
|
||||
|
||||
interface ReactApplicationProps {
|
||||
di: DiContainerForInjection;
|
||||
}
|
||||
|
||||
const render = (components: ReactApplicationHigherOrderComponent[]) => {
|
||||
const [Component, ...rest] = components;
|
||||
|
||||
if (!Component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Component>{render(rest)}</Component>;
|
||||
};
|
||||
|
||||
export const ReactApplication = observer(({ di }: ReactApplicationProps) => {
|
||||
const computedInjectMany = di.inject(computedInjectManyInjectable);
|
||||
|
||||
const higherOrderComponents = computedInjectMany(
|
||||
reactApplicationHigherOrderComponentInjectionToken,
|
||||
);
|
||||
|
||||
const Components = [...higherOrderComponents.get(), ReactApplicationContent];
|
||||
|
||||
return <DiContextProvider value={{ di }}>{render(Components)}</DiContextProvider>;
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { afterApplicationIsLoadedInjectionToken } from "@k8slens/application";
|
||||
import renderInjectable from "./render.injectable";
|
||||
import { ReactApplication } from "../react-application/react-application";
|
||||
import React from "react";
|
||||
|
||||
export const renderApplicationWhenApplicationIsReadyInjectable = getInjectable({
|
||||
id: "render-application-when-application-is-ready",
|
||||
|
||||
instantiate: (di) => {
|
||||
const render = di.inject(renderInjectable);
|
||||
|
||||
return {
|
||||
run: () => {
|
||||
render(<ReactApplication di={di} />);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: afterApplicationIsLoadedInjectionToken,
|
||||
});
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
|
||||
import { render } from "react-dom";
|
||||
import type React from "react";
|
||||
|
||||
export type Render = (application: React.ReactElement) => void;
|
||||
|
||||
export const renderInjectionToken = getInjectionToken<Render>({
|
||||
id: "render-injection-token",
|
||||
});
|
||||
|
||||
const renderInjectable = getInjectable({
|
||||
id: "render",
|
||||
|
||||
/* c8 ignore next */
|
||||
instantiate: () => (application) => render(application, document.getElementById("app")),
|
||||
|
||||
causesSideEffects: true,
|
||||
|
||||
injectionToken: renderInjectionToken,
|
||||
});
|
||||
|
||||
export default renderInjectable;
|
||||
Reference in new issue
Block a user