diff --git a/package-lock.json b/package-lock.json
index b0230bf37f..60e1c5dc69 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4634,6 +4634,14 @@
"resolved": "packages/node-fetch",
"link": true
},
+ "node_modules/@k8slens/react-application": {
+ "resolved": "packages/technical-features/react-application",
+ "link": true
+ },
+ "node_modules/@k8slens/react-testing-library-discovery": {
+ "resolved": "packages/utility-features/react-testing-library-discovery",
+ "link": true
+ },
"node_modules/@k8slens/release-tool": {
"resolved": "packages/release-tool",
"link": true
@@ -34325,6 +34333,57 @@
"integrity": "sha512-ZOzvDRWp8dCVBmgnkIqYCArgdFOO9YzocZp8Ra25N/RStKiWvMOXHMz+GjSeVNe5TstaTmTWPucGJkDw0XXJWA==",
"dev": true
},
+ "packages/business-features/dock": {
+ "name": "@k8slens/dock",
+ "version": "1.0.0-alpha.0",
+ "extraneous": true,
+ "license": "MIT",
+ "devDependencies": {
+ "@async-fn/jest": "^1.6.4",
+ "@k8slens/eslint-config": "6.5.0-alpha.1",
+ "@k8slens/react-testing-library-discovery": "^1.0.0-alpha.0"
+ },
+ "peerDependencies": {
+ "@k8slens/feature-core": "^6.5.0-alpha.0",
+ "@ogre-tools/fp": "^15.1.2",
+ "@ogre-tools/injectable": "^15.1.2",
+ "@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
+ "lodash": "^4.17.21"
+ }
+ },
+ "packages/business-features/dock/agnostic": {
+ "version": "1.0.0-alpha.0",
+ "extraneous": true,
+ "license": "MIT",
+ "devDependencies": {
+ "@async-fn/jest": "^1.6.4",
+ "@k8slens/eslint-config": "6.5.0-alpha.1"
+ },
+ "peerDependencies": {
+ "@k8slens/feature-core": "^6.5.0-alpha.0",
+ "@ogre-tools/injectable": "^15.1.2",
+ "@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2"
+ }
+ },
+ "packages/business-features/keyboard-shortcuts": {
+ "name": "@k8slens/keyboard-shortcuts",
+ "version": "1.0.0-alpha.0",
+ "extraneous": true,
+ "license": "MIT",
+ "devDependencies": {
+ "@async-fn/jest": "^1.6.4",
+ "@k8slens/eslint-config": "6.5.0-alpha.1",
+ "@k8slens/react-testing-library-discovery": "^1.0.0-alpha.0"
+ },
+ "peerDependencies": {
+ "@k8slens/feature-core": "^6.5.0-alpha.0",
+ "@k8slens/react-application": "^1.0.0-alpha.0",
+ "@ogre-tools/fp": "^15.1.2",
+ "@ogre-tools/injectable": "^15.1.2",
+ "@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
+ "lodash": "^4.17.21"
+ }
+ },
"packages/cluster-settings": {
"name": "@k8slens/cluster-settings",
"version": "6.5.0-alpha.1",
@@ -34354,6 +34413,7 @@
"@hapi/subtext": "^7.1.0",
"@k8slens/cluster-settings": "^6.5.0-alpha.1",
"@k8slens/node-fetch": "^6.5.0-alpha.1",
+ "@k8slens/react-application": "^1.0.0-alpha.0",
"@kubernetes/client-node": "^0.18.1",
"@material-ui/styles": "^4.11.5",
"@ogre-tools/fp": "^15.1.2",
@@ -34422,6 +34482,7 @@
"devDependencies": {
"@async-fn/jest": "1.6.4",
"@k8slens/messaging-fake-bridge": "^1.0.0-alpha.1",
+ "@k8slens/react-testing-library-discovery": "^1.0.0-alpha.0",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
@@ -37892,6 +37953,7 @@
"@k8slens/messaging": "^1.0.0-alpha.1",
"@k8slens/messaging-for-main": "^1.0.0-alpha.1",
"@k8slens/messaging-for-renderer": "^1.0.0-alpha.1",
+ "@k8slens/react-application": "^1.0.0-alpha.0",
"@k8slens/run-many": "^1.0.0-alpha.1",
"@k8slens/startable-stoppable": "^1.0.0-alpha.1",
"@k8slens/test-utils": "^1.0.0-alpha.1",
@@ -39543,6 +39605,179 @@
"lodash": "^4.17.21"
}
},
+ "packages/technical-features/react-application": {
+ "name": "@k8slens/react-application",
+ "version": "1.0.0-alpha.0",
+ "license": "MIT",
+ "devDependencies": {
+ "@async-fn/jest": "^1.6.4",
+ "@k8slens/eslint-config": "6.5.0-alpha.1",
+ "@k8slens/react-testing-library-discovery": "*",
+ "@testing-library/react": "^12.1.5"
+ },
+ "peerDependencies": {
+ "@k8slens/application": "^6.5.0-alpha.2",
+ "@k8slens/feature-core": "^6.5.0-alpha.0",
+ "@ogre-tools/fp": "^15.1.2",
+ "@ogre-tools/injectable": "^15.1.2",
+ "@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
+ "@ogre-tools/injectable-extension-for-mobx": "^15.1.2",
+ "@ogre-tools/injectable-react": "^15.1.2",
+ "lodash": "^4.17.15",
+ "mobx": "^6.8.0",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2"
+ }
+ },
+ "packages/technical-features/react-application-root": {
+ "name": "@k8slens/react-application-root",
+ "version": "1.0.0-alpha.0",
+ "extraneous": true,
+ "license": "MIT",
+ "devDependencies": {
+ "@async-fn/jest": "^1.6.4",
+ "@k8slens/eslint-config": "6.5.0-alpha.1",
+ "@k8slens/react-testing-library-discovery": "*",
+ "@testing-library/react": "^12.1.5"
+ },
+ "peerDependencies": {
+ "@k8slens/application": "^6.5.0-alpha.2",
+ "@k8slens/feature-core": "^6.5.0-alpha.0",
+ "@ogre-tools/fp": "^15.1.2",
+ "@ogre-tools/injectable": "^15.1.2",
+ "@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
+ "@ogre-tools/injectable-extension-for-mobx": "^15.1.2",
+ "@ogre-tools/injectable-react": "^15.1.2",
+ "lodash": "^4.17.15",
+ "mobx": "^6.8.0",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2"
+ }
+ },
+ "packages/utility-features/react-testing-library-discovery": {
+ "name": "@k8slens/react-testing-library-discovery",
+ "version": "1.0.0-alpha.0",
+ "license": "MIT",
+ "dependencies": {
+ "@testing-library/dom": "^8.19.0",
+ "@testing-library/jest-dom": "^5.16.5",
+ "@testing-library/react": "^13.4.0"
+ }
+ },
+ "packages/utility-features/react-testing-library-discovery/node_modules/@testing-library/dom": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz",
+ "integrity": "sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==",
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "^5.0.0",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.4.4",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "packages/utility-features/react-testing-library-discovery/node_modules/@testing-library/react": {
+ "version": "13.4.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz",
+ "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@testing-library/dom": "^8.5.0",
+ "@types/react-dom": "^18.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ }
+ },
+ "packages/utility-features/react-testing-library-discovery/node_modules/@types/aria-query": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz",
+ "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q=="
+ },
+ "packages/utility-features/react-testing-library-discovery/node_modules/@types/react-dom": {
+ "version": "18.0.11",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz",
+ "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "packages/utility-features/react-testing-library-discovery/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "packages/utility-features/react-testing-library-discovery/node_modules/aria-query": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
+ "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
+ "dependencies": {
+ "deep-equal": "^2.0.5"
+ }
+ },
+ "packages/utility-features/react-testing-library-discovery/node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "packages/utility-features/react-testing-library-discovery/node_modules/react": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "packages/utility-features/react-testing-library-discovery/node_modules/react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ },
+ "peerDependencies": {
+ "react": "^18.2.0"
+ }
+ },
+ "packages/utility-features/react-testing-library-discovery/node_modules/scheduler": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
"packages/utility-features/run-many": {
"name": "@k8slens/run-many",
"version": "1.0.0-alpha.1",
diff --git a/packages/core/package.json b/packages/core/package.json
index b619871e5c..8c7d22ee2c 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -122,6 +122,7 @@
"@hapi/subtext": "^7.1.0",
"@k8slens/cluster-settings": "^6.5.0-alpha.1",
"@k8slens/node-fetch": "^6.5.0-alpha.1",
+ "@k8slens/react-application": "^1.0.0-alpha.0",
"@kubernetes/client-node": "^0.18.1",
"@material-ui/styles": "^4.11.5",
"@ogre-tools/fp": "^15.1.2",
@@ -190,6 +191,7 @@
"devDependencies": {
"@async-fn/jest": "1.6.4",
"@k8slens/messaging-fake-bridge": "^1.0.0-alpha.1",
+ "@k8slens/react-testing-library-discovery": "^1.0.0-alpha.0",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
diff --git a/packages/core/src/features/preferences/extension-adding-preference-tabs.test.tsx b/packages/core/src/features/preferences/extension-adding-preference-tabs.test.tsx
index a68f39f547..4631a6415d 100644
--- a/packages/core/src/features/preferences/extension-adding-preference-tabs.test.tsx
+++ b/packages/core/src/features/preferences/extension-adding-preference-tabs.test.tsx
@@ -7,8 +7,8 @@ import type { IObservableValue } from "mobx";
import { runInAction, computed, observable } from "mobx";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
-import type { Discover } from "../../renderer/components/test-utils/discovery-of-html-elements";
-import { discoverFor } from "../../renderer/components/test-utils/discovery-of-html-elements";
+import type { Discover } from "@k8slens/react-testing-library-discovery";
+import { discoverFor } from "@k8slens/react-testing-library-discovery";
import React from "react";
describe("preferences: extension adding preference tabs", () => {
diff --git a/packages/core/src/features/preferences/hiding-of-empty-branches.test.tsx b/packages/core/src/features/preferences/hiding-of-empty-branches.test.tsx
index 0bc2d67d24..9e4ef01a5c 100644
--- a/packages/core/src/features/preferences/hiding-of-empty-branches.test.tsx
+++ b/packages/core/src/features/preferences/hiding-of-empty-branches.test.tsx
@@ -10,8 +10,8 @@ import { runInAction } from "mobx";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { preferenceItemInjectionToken } from "./renderer/preference-items/preference-item-injection-token";
-import type { Discover } from "../../renderer/components/test-utils/discovery-of-html-elements";
-import { discoverFor } from "../../renderer/components/test-utils/discovery-of-html-elements";
+import type { Discover } from "@k8slens/react-testing-library-discovery";
+import { discoverFor } from "@k8slens/react-testing-library-discovery";
describe("preferences - hiding-of-empty-branches, given in preferences page", () => {
let builder: ApplicationBuilder;
diff --git a/packages/core/src/features/preferences/navigation-to-application-preferences.test.tsx b/packages/core/src/features/preferences/navigation-to-application-preferences.test.tsx
index bcb4db587d..47673b4637 100644
--- a/packages/core/src/features/preferences/navigation-to-application-preferences.test.tsx
+++ b/packages/core/src/features/preferences/navigation-to-application-preferences.test.tsx
@@ -7,8 +7,8 @@ import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import navigateToProxyPreferencesInjectable from "./common/navigate-to-proxy-preferences.injectable";
-import type { Discover } from "../../renderer/components/test-utils/discovery-of-html-elements";
-import { discoverFor } from "../../renderer/components/test-utils/discovery-of-html-elements";
+import type { Discover } from "@k8slens/react-testing-library-discovery";
+import { discoverFor } from "@k8slens/react-testing-library-discovery";
import type { FakeExtensionOptions } from "../../renderer/components/test-utils/get-extension-fake";
describe("preferences - navigation to application preferences", () => {
diff --git a/packages/core/src/features/preferences/navigation-to-editor-preferences.test.ts b/packages/core/src/features/preferences/navigation-to-editor-preferences.test.ts
index 018a27210a..51f93afa3f 100644
--- a/packages/core/src/features/preferences/navigation-to-editor-preferences.test.ts
+++ b/packages/core/src/features/preferences/navigation-to-editor-preferences.test.ts
@@ -5,8 +5,8 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
-import type { Discover } from "../../renderer/components/test-utils/discovery-of-html-elements";
-import { discoverFor } from "../../renderer/components/test-utils/discovery-of-html-elements";
+import type { Discover } from "@k8slens/react-testing-library-discovery";
+import { discoverFor } from "@k8slens/react-testing-library-discovery";
describe("preferences - navigation to editor preferences", () => {
let applicationBuilder: ApplicationBuilder;
diff --git a/packages/core/src/features/preferences/navigation-to-extension-specific-preferences.test.tsx b/packages/core/src/features/preferences/navigation-to-extension-specific-preferences.test.tsx
index 84c8f4e395..e7a04a4b04 100644
--- a/packages/core/src/features/preferences/navigation-to-extension-specific-preferences.test.tsx
+++ b/packages/core/src/features/preferences/navigation-to-extension-specific-preferences.test.tsx
@@ -8,8 +8,8 @@ import { getApplicationBuilder } from "../../renderer/components/test-utils/get-
import React from "react";
import "@testing-library/jest-dom/extend-expect";
import type { FakeExtensionOptions } from "../../renderer/components/test-utils/get-extension-fake";
-import type { Discover } from "../../renderer/components/test-utils/discovery-of-html-elements";
-import { discoverFor } from "../../renderer/components/test-utils/discovery-of-html-elements";
+import type { Discover } from "@k8slens/react-testing-library-discovery";
+import { discoverFor } from "@k8slens/react-testing-library-discovery";
import logErrorInjectable from "../../common/log-error.injectable";
describe("preferences - navigation to extension specific preferences", () => {
diff --git a/packages/core/src/features/preferences/navigation-to-kubernetes-preferences.test.ts b/packages/core/src/features/preferences/navigation-to-kubernetes-preferences.test.ts
index 15c853d3c3..06c1fda87e 100644
--- a/packages/core/src/features/preferences/navigation-to-kubernetes-preferences.test.ts
+++ b/packages/core/src/features/preferences/navigation-to-kubernetes-preferences.test.ts
@@ -7,8 +7,8 @@ import type { ApplicationBuilder } from "../../renderer/components/test-utils/ge
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import requestPublicHelmRepositoriesInjectable from "../helm-charts/child-features/preferences/renderer/adding-of-public-helm-repository/public-helm-repositories/request-public-helm-repositories.injectable";
import getActiveHelmRepositoriesInjectable from "../../main/helm/repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
-import type { Discover } from "../../renderer/components/test-utils/discovery-of-html-elements";
-import { discoverFor } from "../../renderer/components/test-utils/discovery-of-html-elements";
+import type { Discover } from "@k8slens/react-testing-library-discovery";
+import { discoverFor } from "@k8slens/react-testing-library-discovery";
describe("preferences - navigation to kubernetes preferences", () => {
let builder: ApplicationBuilder;
diff --git a/packages/core/src/features/preferences/navigation-to-proxy-preferences.test.ts b/packages/core/src/features/preferences/navigation-to-proxy-preferences.test.ts
index 2372b78187..fa22e15d17 100644
--- a/packages/core/src/features/preferences/navigation-to-proxy-preferences.test.ts
+++ b/packages/core/src/features/preferences/navigation-to-proxy-preferences.test.ts
@@ -5,8 +5,8 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
-import type { Discover } from "../../renderer/components/test-utils/discovery-of-html-elements";
-import { discoverFor } from "../../renderer/components/test-utils/discovery-of-html-elements";
+import type { Discover } from "@k8slens/react-testing-library-discovery";
+import { discoverFor } from "@k8slens/react-testing-library-discovery";
describe("preferences - navigation to proxy preferences", () => {
let applicationBuilder: ApplicationBuilder;
diff --git a/packages/core/src/features/preferences/navigation-to-telemetry-preferences.test.tsx b/packages/core/src/features/preferences/navigation-to-telemetry-preferences.test.tsx
index 58d0121a9e..ce8158cc4c 100644
--- a/packages/core/src/features/preferences/navigation-to-telemetry-preferences.test.tsx
+++ b/packages/core/src/features/preferences/navigation-to-telemetry-preferences.test.tsx
@@ -9,8 +9,8 @@ import { getApplicationBuilder } from "../../renderer/components/test-utils/get-
import navigateToTelemetryPreferencesInjectable from "./common/navigate-to-telemetry-preferences.injectable";
import sentryDataSourceNameInjectable from "../../common/vars/sentry-dsn-url.injectable";
import type { FakeExtensionOptions } from "../../renderer/components/test-utils/get-extension-fake";
-import type { Discover } from "../../renderer/components/test-utils/discovery-of-html-elements";
-import { discoverFor } from "../../renderer/components/test-utils/discovery-of-html-elements";
+import type { Discover } from "@k8slens/react-testing-library-discovery";
+import { discoverFor } from "@k8slens/react-testing-library-discovery";
describe("preferences - navigation to telemetry preferences", () => {
let builder: ApplicationBuilder;
diff --git a/packages/core/src/features/preferences/navigation-to-terminal-preferences.test.ts b/packages/core/src/features/preferences/navigation-to-terminal-preferences.test.ts
index 3023852481..1542659d17 100644
--- a/packages/core/src/features/preferences/navigation-to-terminal-preferences.test.ts
+++ b/packages/core/src/features/preferences/navigation-to-terminal-preferences.test.ts
@@ -5,8 +5,8 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
-import type { Discover } from "../../renderer/components/test-utils/discovery-of-html-elements";
-import { discoverFor } from "../../renderer/components/test-utils/discovery-of-html-elements";
+import type { Discover } from "@k8slens/react-testing-library-discovery";
+import { discoverFor } from "@k8slens/react-testing-library-discovery";
describe("preferences - navigation to terminal preferences", () => {
let applicationBuilder: ApplicationBuilder;
diff --git a/packages/core/src/features/preferences/navigation-using-application-menu.test.ts b/packages/core/src/features/preferences/navigation-using-application-menu.test.ts
index ae143fe468..020bc25259 100644
--- a/packages/core/src/features/preferences/navigation-using-application-menu.test.ts
+++ b/packages/core/src/features/preferences/navigation-using-application-menu.test.ts
@@ -6,8 +6,8 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
-import type { Discover } from "../../renderer/components/test-utils/discovery-of-html-elements";
-import { discoverFor } from "../../renderer/components/test-utils/discovery-of-html-elements";
+import type { Discover } from "@k8slens/react-testing-library-discovery";
+import { discoverFor } from "@k8slens/react-testing-library-discovery";
describe("preferences - navigation using application menu", () => {
let applicationBuilder: ApplicationBuilder;
diff --git a/packages/core/src/features/preferences/navigation-using-tray.test.ts b/packages/core/src/features/preferences/navigation-using-tray.test.ts
index e48dba482e..63d2bd5290 100644
--- a/packages/core/src/features/preferences/navigation-using-tray.test.ts
+++ b/packages/core/src/features/preferences/navigation-using-tray.test.ts
@@ -5,8 +5,8 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
-import type { Discover } from "../../renderer/components/test-utils/discovery-of-html-elements";
-import { discoverFor } from "../../renderer/components/test-utils/discovery-of-html-elements";
+import type { Discover } from "@k8slens/react-testing-library-discovery";
+import { discoverFor } from "@k8slens/react-testing-library-discovery";
describe("show-about-using-tray", () => {
let applicationBuilder: ApplicationBuilder;
diff --git a/packages/core/src/features/preferences/urls-of-legacy-extensions.test.tsx b/packages/core/src/features/preferences/urls-of-legacy-extensions.test.tsx
index 1eba04c683..4b04e5500f 100644
--- a/packages/core/src/features/preferences/urls-of-legacy-extensions.test.tsx
+++ b/packages/core/src/features/preferences/urls-of-legacy-extensions.test.tsx
@@ -5,8 +5,8 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
-import type { Discover } from "../../renderer/components/test-utils/discovery-of-html-elements";
-import { discoverFor } from "../../renderer/components/test-utils/discovery-of-html-elements";
+import type { Discover } from "@k8slens/react-testing-library-discovery";
+import { discoverFor } from "@k8slens/react-testing-library-discovery";
import React from "react";
import type { Navigate } from "../../renderer/navigation/navigate.injectable";
import navigateInjectable from "../../renderer/navigation/navigate.injectable";
diff --git a/packages/core/src/renderer/bootstrap.injectable.ts b/packages/core/src/renderer/bootstrap.injectable.ts
index 7cc172e3f1..24c01deecc 100644
--- a/packages/core/src/renderer/bootstrap.injectable.ts
+++ b/packages/core/src/renderer/bootstrap.injectable.ts
@@ -3,9 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
-import {
- afterApplicationIsLoadedInjectionToken,
-} from "@k8slens/application";
+import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
import { bootstrap } from "./bootstrap";
import startFrameInjectable from "./start-frame/start-frame.injectable";
@@ -22,7 +20,7 @@ const bootstrapInjectable = getInjectable({
causesSideEffects: true,
- injectionToken: afterApplicationIsLoadedInjectionToken,
+ injectionToken: onLoadOfApplicationInjectionToken,
});
export default bootstrapInjectable;
diff --git a/packages/core/src/renderer/bootstrap.tsx b/packages/core/src/renderer/bootstrap.tsx
index a811f07d5c..fcd2dc7415 100644
--- a/packages/core/src/renderer/bootstrap.tsx
+++ b/packages/core/src/renderer/bootstrap.tsx
@@ -5,10 +5,7 @@
import "./components/app.scss";
-import React from "react";
-import { render, unmountComponentAtNode } from "react-dom";
-import { DefaultProps } from "./mui-base-theme";
-import { DiContextProvider } from "@ogre-tools/injectable-react";
+import { unmountComponentAtNode } from "react-dom";
import type {
DiContainerForInjection,
} from "@ogre-tools/injectable";
@@ -17,8 +14,6 @@ import extensionDiscoveryInjectable from "../extensions/extension-discovery/exte
import extensionInstallationStateStoreInjectable from "../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import initRootFrameInjectable from "./frames/root-frame/init-root-frame.injectable";
import initClusterFrameInjectable from "./frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable";
-import { Router } from "react-router";
-import historyInjectable from "./navigation/history.injectable";
import assert from "assert";
export async function bootstrap(di: DiContainerForInjection) {
@@ -30,16 +25,13 @@ export async function bootstrap(di: DiContainerForInjection) {
await di.inject(extensionDiscoveryInjectable).init();
di.inject(extensionInstallationStateStoreInjectable).bindIpcListeners();
- let App;
let initializeApp;
// TODO: Introduce proper architectural boundaries between root and cluster iframes
if (process.isMainFrame) {
initializeApp = di.inject(initRootFrameInjectable);
- App = (await import("./frames/root-frame/root-frame")).RootFrame;
} else {
initializeApp = di.inject(initClusterFrameInjectable);
- App = (await import("./frames/cluster-frame/cluster-frame")).ClusterFrame;
}
try {
@@ -50,15 +42,4 @@ export async function bootstrap(di: DiContainerForInjection) {
isTopFrameView: process.isMainFrame,
});
}
-
- const history = di.inject(historyInjectable);
-
- render(
-
-
- {DefaultProps(App)}
-
- ,
- rootElem,
- );
}
diff --git a/packages/core/src/renderer/components/test-utils/discovery-of-html-elements.ts b/packages/core/src/renderer/components/test-utils/discovery-of-html-elements.ts
deleted file mode 100644
index 472199fc60..0000000000
--- a/packages/core/src/renderer/components/test-utils/discovery-of-html-elements.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * Copyright (c) OpenLens Authors. All rights reserved.
- * Licensed under MIT License. See LICENSE in root directory for more information.
- */
-import type { RenderResult } from "@testing-library/react";
-
-type DiscoverySourceTypes = RenderResult | Element;
-
-export type QuerySingleElement = (
- attributeName: string,
- attributeValue?: string
-) => { discovered: Element | null } & Discover;
-
-export type GetSingleElement = (
- attributeName: string,
- attributeValue?: string
-) => { discovered: Element } & Discover;
-
-export type QueryAllElements = (attributeName: string) => {
- discovered: Element[];
- attributeValues: (string | null)[];
-};
-
-export interface Discover {
- querySingleElement: QuerySingleElement;
- queryAllElements: QueryAllElements;
- getSingleElement: GetSingleElement;
-}
-
-export const discoverFor = (getSource: () => DiscoverySourceTypes): Discover => ({
- querySingleElement: querySingleElement(getSource),
- queryAllElements: queryAllElements(getSource),
- getSingleElement: getSingleElement(getSource),
-});
-
-export const querySingleElement =
- (getSource: () => DiscoverySourceTypes): QuerySingleElement =>
- (attributeName, attributeValue) => {
- const source = getSource();
-
- const dataAttribute = `data-${attributeName}-test`;
-
- const selector = attributeValue
- ? `[${dataAttribute}="${attributeValue}"]`
- : `[${dataAttribute}]`;
-
- const discovered = getBaseElement(source).querySelector(selector);
-
- const nestedDiscover = discoverFor(() => {
- if (!discovered) {
- throw new Error("Tried to do nested discover using source that does not exist");
- }
-
- return discovered;
- });
-
- return {
- discovered,
-
- ...nestedDiscover,
- };
- };
-
-export const queryAllElements =
- (getSource: () => DiscoverySourceTypes): QueryAllElements =>
- (attributeName) => {
- const source = getSource();
-
- const dataAttribute = `data-${attributeName}-test`;
-
- const results = [
- ...getBaseElement(source).querySelectorAll(`[${dataAttribute}]`),
- ];
-
- return {
- discovered: results,
-
- attributeValues: results.map((result) =>
- result.getAttribute(dataAttribute),
- ),
- };
- };
-
-export const getSingleElement =
- (getSource: () => DiscoverySourceTypes): GetSingleElement =>
- (attributeName, attributeValue) => {
- const dataAttribute = `data-${attributeName}-test`;
-
- const { discovered, ...nestedDiscover } = querySingleElement(getSource)(
- attributeName,
- attributeValue,
- );
-
- if (!discovered) {
- const validValues =
- queryAllElements(getSource)(attributeName).attributeValues;
-
- if (attributeValue) {
- throw new Error(
- `Couldn't find HTML-element with attribute "${dataAttribute}" with value "${attributeValue}". Present values are:\n\n"${validValues.join(
- '",\n"',
- )}"`,
- );
- }
-
- throw new Error(
- `Couldn't find HTML-element with attribute "${dataAttribute}"`,
- );
- }
-
- return { discovered, ...nestedDiscover };
- };
-
-const getBaseElement = (source: DiscoverySourceTypes) =>
- "baseElement" in source ? source.baseElement : source;
diff --git a/packages/core/src/renderer/components/test-utils/get-application-builder.tsx b/packages/core/src/renderer/components/test-utils/get-application-builder.tsx
index 731c5ba8c7..ce103d7a83 100644
--- a/packages/core/src/renderer/components/test-utils/get-application-builder.tsx
+++ b/packages/core/src/renderer/components/test-utils/get-application-builder.tsx
@@ -59,7 +59,7 @@ import { Namespace } from "../../../common/k8s-api/endpoints";
import { getOverrideFsWithFakes } from "../../../test-utils/override-fs-with-fakes";
import applicationMenuItemCompositeInjectable from "../../../features/application-menu/main/application-menu-item-composite.injectable";
import { getCompositePaths } from "../../../common/utils/composite/get-composite-paths/get-composite-paths";
-import { discoverFor } from "./discovery-of-html-elements";
+import { discoverFor } from "@k8slens/react-testing-library-discovery";
import { findComposite } from "../../../common/utils/composite/find-composite/find-composite";
import shouldStartHiddenInjectable from "../../../main/electron-app/features/should-start-hidden.injectable";
import fsInjectable from "../../../common/fs/fs.injectable";
diff --git a/packages/core/src/renderer/frames/frame-application-root.injectable.ts b/packages/core/src/renderer/frames/frame-application-root.injectable.ts
new file mode 100644
index 0000000000..afae5a5fb3
--- /dev/null
+++ b/packages/core/src/renderer/frames/frame-application-root.injectable.ts
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable } from "@ogre-tools/injectable";
+import {
+ reactApplicationChildrenInjectionToken,
+} from "@k8slens/react-application";
+import { computed } from "mobx";
+
+const frameApplicationRootInjectable = getInjectable({
+ id: "frame-application-root",
+
+ instantiate: () => {
+ const Frame = process.isMainFrame
+ ? require("./root-frame/root-frame").RootFrame
+ : require("./cluster-frame/cluster-frame").ClusterFrame;
+
+ return {
+ id: "frame-application-root",
+ Component: Frame,
+ enabled: computed(() => true),
+ };
+ },
+
+ causesSideEffects: true,
+
+ injectionToken: reactApplicationChildrenInjectionToken,
+});
+
+export default frameApplicationRootInjectable;
diff --git a/packages/core/src/renderer/frames/routing-react-application-hoc.injectable.tsx b/packages/core/src/renderer/frames/routing-react-application-hoc.injectable.tsx
new file mode 100644
index 0000000000..6853b0eaed
--- /dev/null
+++ b/packages/core/src/renderer/frames/routing-react-application-hoc.injectable.tsx
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable } from "@ogre-tools/injectable";
+import { Router } from "react-router";
+import historyInjectable from "../navigation/history.injectable";
+import React from "react";
+
+import {
+ reactApplicationHigherOrderComponentInjectionToken,
+} from "@k8slens/react-application";
+
+const routingReactApplicationHocInjectable = getInjectable({
+ id: "routing-react-application-hoc",
+
+ instantiate: (di) => {
+ const history = di.inject(historyInjectable);
+
+ return ({ children }) =>
+ (
+
+ {children}
+
+ );
+ },
+
+ injectionToken: reactApplicationHigherOrderComponentInjectionToken,
+});
+
+export default routingReactApplicationHocInjectable;
diff --git a/packages/core/src/renderer/frames/theme-provider-react-application-hoc.injectable.tsx b/packages/core/src/renderer/frames/theme-provider-react-application-hoc.injectable.tsx
new file mode 100644
index 0000000000..45f94c6404
--- /dev/null
+++ b/packages/core/src/renderer/frames/theme-provider-react-application-hoc.injectable.tsx
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable } from "@ogre-tools/injectable";
+import React from "react";
+import { reactApplicationHigherOrderComponentInjectionToken } from "@k8slens/react-application";
+import { ThemeProvider } from "@material-ui/core";
+import { defaultMuiBaseTheme } from "../mui-base-theme";
+
+const themeProviderReactApplicationHocInjectable = getInjectable({
+ id: "theme-provider-react-application-hoc",
+
+ instantiate:
+ () =>
+ ({ children }) =>
+ {children},
+
+ injectionToken: reactApplicationHigherOrderComponentInjectionToken,
+});
+
+export default themeProviderReactApplicationHocInjectable;
diff --git a/packages/core/src/renderer/mui-base-theme.tsx b/packages/core/src/renderer/mui-base-theme.tsx
index c32374cf58..7238462172 100644
--- a/packages/core/src/renderer/mui-base-theme.tsx
+++ b/packages/core/src/renderer/mui-base-theme.tsx
@@ -6,7 +6,7 @@
import React from "react";
import { createTheme, ThemeProvider } from "@material-ui/core";
-const defaultTheme = createTheme({
+export const defaultMuiBaseTheme = createTheme({
props: {
MuiIconButton: {
color: "inherit",
@@ -32,7 +32,7 @@ const defaultTheme = createTheme({
export function DefaultProps(App: React.ComponentType | React.FunctionComponent) {
return (
-
+
);
diff --git a/packages/core/src/renderer/start-frame/start-frame.injectable.ts b/packages/core/src/renderer/start-frame/start-frame.injectable.ts
index b4bddc9c30..668419191c 100644
--- a/packages/core/src/renderer/start-frame/start-frame.injectable.ts
+++ b/packages/core/src/renderer/start-frame/start-frame.injectable.ts
@@ -6,9 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import { runManyFor } from "@k8slens/run-many";
import * as tokens from "../before-frame-starts/tokens";
import currentlyInClusterFrameInjectable from "../routes/currently-in-cluster-frame.injectable";
-import {
- afterApplicationIsLoadedInjectionToken,
-} from "@k8slens/application";
+import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
const startFrameInjectable = getInjectable({
id: "start-frame",
@@ -44,7 +42,7 @@ const startFrameInjectable = getInjectable({
};
},
- injectionToken: afterApplicationIsLoadedInjectionToken,
+ injectionToken: onLoadOfApplicationInjectionToken,
});
export default startFrameInjectable;
diff --git a/packages/infrastructure/webpack/src/react-config.js b/packages/infrastructure/webpack/src/react-config.js
index 3b25515208..201a3c9a6b 100644
--- a/packages/infrastructure/webpack/src/react-config.js
+++ b/packages/infrastructure/webpack/src/react-config.js
@@ -1,7 +1,7 @@
const path = require("path");
const getReactConfig = require("./get-react-config");
-module.exports = getReactConfig({
+module.exports = getReactConfig()({
entrypointFilePath: "./index.ts",
outputDirectory: path.resolve(process.cwd(), "dist"),
});
diff --git a/packages/open-lens/package.json b/packages/open-lens/package.json
index 630e51223c..34d259ed9c 100644
--- a/packages/open-lens/package.json
+++ b/packages/open-lens/package.json
@@ -206,6 +206,7 @@
"@k8slens/messaging": "^1.0.0-alpha.1",
"@k8slens/messaging-for-main": "^1.0.0-alpha.1",
"@k8slens/messaging-for-renderer": "^1.0.0-alpha.1",
+ "@k8slens/react-application": "^1.0.0-alpha.0",
"@k8slens/run-many": "^1.0.0-alpha.1",
"@k8slens/startable-stoppable": "^1.0.0-alpha.1",
"@k8slens/test-utils": "^1.0.0-alpha.1",
diff --git a/packages/open-lens/src/renderer/index.ts b/packages/open-lens/src/renderer/index.ts
index 207fc2265c..2d5dda2f04 100644
--- a/packages/open-lens/src/renderer/index.ts
+++ b/packages/open-lens/src/renderer/index.ts
@@ -15,6 +15,7 @@ import { createContainer } from "@ogre-tools/injectable";
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
import { registerInjectableReact } from "@ogre-tools/injectable-react";
import { messagingFeatureForRenderer } from "@k8slens/messaging-for-renderer";
+import { reactApplicationFeature } from "@k8slens/react-application";
const environment = "renderer";
@@ -24,7 +25,13 @@ runInAction(() => {
registerMobX(di);
registerInjectableReact(di);
registerLensCore(di, environment);
- registerFeature(di, applicationFeature, messagingFeatureForRenderer);
+
+ registerFeature(
+ di,
+ applicationFeature,
+ messagingFeatureForRenderer,
+ reactApplicationFeature
+ );
autoRegister({
di,
diff --git a/packages/technical-features/react-application/.eslintrc.json b/packages/technical-features/react-application/.eslintrc.json
new file mode 100644
index 0000000000..b15115cb69
--- /dev/null
+++ b/packages/technical-features/react-application/.eslintrc.json
@@ -0,0 +1,6 @@
+{
+ "extends": "@k8slens/eslint-config/eslint",
+ "parserOptions": {
+ "project": "./tsconfig.json"
+ }
+}
diff --git a/packages/technical-features/react-application/.prettierrc b/packages/technical-features/react-application/.prettierrc
new file mode 100644
index 0000000000..edd47b479e
--- /dev/null
+++ b/packages/technical-features/react-application/.prettierrc
@@ -0,0 +1 @@
+"@k8slens/eslint-config/prettier"
diff --git a/packages/technical-features/react-application/README.md b/packages/technical-features/react-application/README.md
new file mode 100644
index 0000000000..2650d6f527
--- /dev/null
+++ b/packages/technical-features/react-application/README.md
@@ -0,0 +1,19 @@
+# @k8slens/react-application
+
+# Usage
+
+```bash
+$ npm install @k8slens/react-application
+```
+
+```typescript
+import { reactApplicationFeature } from "@k8slens/react-application";
+import { registerFeature } from "@k8slens/feature-core";
+import { createContainer } from "@ogre-tools/injectable";
+
+const di = createContainer("some-container");
+
+registerFeature(di, reactApplicationRootFeature);
+```
+
+## Extendability
diff --git a/packages/technical-features/react-application/index.ts b/packages/technical-features/react-application/index.ts
new file mode 100644
index 0000000000..e3f5236d54
--- /dev/null
+++ b/packages/technical-features/react-application/index.ts
@@ -0,0 +1,10 @@
+export { renderInjectionToken } from "./src/render-application/render.injectable";
+export type { Render } from "./src/render-application/render.injectable";
+
+export { reactApplicationChildrenInjectionToken } from "./src/react-application/react-application-children-injection-token";
+export type { ReactApplicationChildren } from "./src/react-application/react-application-children-injection-token";
+
+export { reactApplicationHigherOrderComponentInjectionToken } from "./src/react-application/react-application-higher-order-component-injection-token";
+export type { ReactApplicationHigherOrderComponent } from "./src/react-application/react-application-higher-order-component-injection-token";
+
+export { reactApplicationFeature } from "./src/feature";
diff --git a/packages/technical-features/react-application/jest.config.js b/packages/technical-features/react-application/jest.config.js
new file mode 100644
index 0000000000..38d54ab7b6
--- /dev/null
+++ b/packages/technical-features/react-application/jest.config.js
@@ -0,0 +1 @@
+module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact;
diff --git a/packages/technical-features/react-application/package.json b/packages/technical-features/react-application/package.json
new file mode 100644
index 0000000000..e3ca7956a8
--- /dev/null
+++ b/packages/technical-features/react-application/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "@k8slens/react-application",
+ "private": false,
+ "version": "1.0.0-alpha.0",
+ "description": "Package for React Application",
+ "type": "commonjs",
+ "files": [
+ "dist"
+ ],
+ "publishConfig": {
+ "access": "public",
+ "registry": "https://registry.npmjs.org/"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/lensapp/lens.git"
+ },
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "author": {
+ "name": "OpenLens Authors",
+ "email": "info@k8slens.dev"
+ },
+ "license": "MIT",
+ "homepage": "https://github.com/lensapp/lens",
+ "scripts": {
+ "build": "webpack",
+ "dev": "webpack --mode=development --watch",
+ "test:unit": "jest --coverage --runInBand",
+ "lint": "lens-lint",
+ "lint:fix": "lens-lint --fix"
+ },
+ "peerDependencies": {
+ "@k8slens/feature-core": "^6.5.0-alpha.0",
+ "@k8slens/application": "^6.5.0-alpha.2",
+ "@ogre-tools/fp": "^15.1.2",
+ "@ogre-tools/injectable": "^15.1.2",
+ "@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
+ "@ogre-tools/injectable-extension-for-mobx": "^15.1.2",
+ "@ogre-tools/injectable-react": "^15.1.2",
+ "lodash": "^4.17.15",
+ "mobx": "^6.8.0",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2"
+ },
+ "devDependencies": {
+ "@async-fn/jest": "^1.6.4",
+ "@k8slens/eslint-config": "6.5.0-alpha.1",
+ "@testing-library/react": "^12.1.5",
+ "@k8slens/react-testing-library-discovery": "*"
+ }
+}
diff --git a/packages/technical-features/react-application/src/__snapshots__/react-application.test.tsx.snap b/packages/technical-features/react-application/src/__snapshots__/react-application.test.tsx.snap
new file mode 100644
index 0000000000..a029ece5f9
--- /dev/null
+++ b/packages/technical-features/react-application/src/__snapshots__/react-application.test.tsx.snap
@@ -0,0 +1,41 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`react-application renders 1`] = `
+
+
+
+`;
+
+exports[`react-application when content is registered and enabled renders 1`] = `
+
+
+
+`;
+
+exports[`react-application when content is registered and enabled when content is disabled renders 1`] = `
+
+
+
+`;
+
+exports[`react-application when content is registered and enabled when higher order component is registered renders 1`] = `
+
+
+
+`;
diff --git a/packages/technical-features/react-application/src/feature.ts b/packages/technical-features/react-application/src/feature.ts
new file mode 100644
index 0000000000..96860c90df
--- /dev/null
+++ b/packages/technical-features/react-application/src/feature.ts
@@ -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],
+});
diff --git a/packages/technical-features/react-application/src/react-application.test.tsx b/packages/technical-features/react-application/src/react-application.test.tsx
new file mode 100644
index 0000000000..12fcdb6bac
--- /dev/null
+++ b/packages/technical-features/react-application/src/react-application.test.tsx
@@ -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 = () => Some children
;
+
+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;
+
+ 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 }) => (
+ {children}
+ );
+
+ 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();
+ });
+ });
+ });
+});
diff --git a/packages/technical-features/react-application/src/react-application/react-application-children-injection-token.ts b/packages/technical-features/react-application/src/react-application/react-application-children-injection-token.ts
new file mode 100644
index 0000000000..398f55d89a
--- /dev/null
+++ b/packages/technical-features/react-application/src/react-application/react-application-children-injection-token.ts
@@ -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;
+}
+
+export const reactApplicationChildrenInjectionToken = getInjectionToken({
+ id: "react-application-children-injection-token",
+});
diff --git a/packages/technical-features/react-application/src/react-application/react-application-content.tsx b/packages/technical-features/react-application/src/react-application/react-application-content.tsx
new file mode 100644
index 0000000000..2307969542
--- /dev/null
+++ b/packages/technical-features/react-application/src/react-application/react-application-content.tsx
@@ -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 };
+
+const NonInjectedContent = observer(({ contents }: Dependencies) => (
+ <>
+ {contents.get().map((child) => (
+ {() => (child.enabled.get() ? : null)}
+ ))}
+ >
+));
+
+export const ReactApplicationContent = withInjectables(
+ NonInjectedContent,
+
+ {
+ getProps: (di) => ({
+ contents: di.inject(computedInjectManyInjectable)(reactApplicationChildrenInjectionToken),
+ }),
+ },
+);
diff --git a/packages/technical-features/react-application/src/react-application/react-application-higher-order-component-injection-token.ts b/packages/technical-features/react-application/src/react-application/react-application-higher-order-component-injection-token.ts
new file mode 100644
index 0000000000..cae1a6b468
--- /dev/null
+++ b/packages/technical-features/react-application/src/react-application/react-application-higher-order-component-injection-token.ts
@@ -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({
+ id: "react-application-higher-order-component-injection-token",
+ });
diff --git a/packages/technical-features/react-application/src/react-application/react-application.tsx b/packages/technical-features/react-application/src/react-application/react-application.tsx
new file mode 100644
index 0000000000..933d0ad389
--- /dev/null
+++ b/packages/technical-features/react-application/src/react-application/react-application.tsx
@@ -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 {render(rest)};
+};
+
+export const ReactApplication = observer(({ di }: ReactApplicationProps) => {
+ const computedInjectMany = di.inject(computedInjectManyInjectable);
+
+ const higherOrderComponents = computedInjectMany(
+ reactApplicationHigherOrderComponentInjectionToken,
+ );
+
+ const Components = [...higherOrderComponents.get(), ReactApplicationContent];
+
+ return {render(Components)};
+});
diff --git a/packages/technical-features/react-application/src/render-application/render-application-when-application-is-ready.injectable.tsx b/packages/technical-features/react-application/src/render-application/render-application-when-application-is-ready.injectable.tsx
new file mode 100644
index 0000000000..c18f18fc5f
--- /dev/null
+++ b/packages/technical-features/react-application/src/render-application/render-application-when-application-is-ready.injectable.tsx
@@ -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();
+ },
+ };
+ },
+
+ injectionToken: afterApplicationIsLoadedInjectionToken,
+});
diff --git a/packages/technical-features/react-application/src/render-application/render.injectable.tsx b/packages/technical-features/react-application/src/render-application/render.injectable.tsx
new file mode 100644
index 0000000000..944823f10a
--- /dev/null
+++ b/packages/technical-features/react-application/src/render-application/render.injectable.tsx
@@ -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({
+ 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;
diff --git a/packages/technical-features/react-application/tsconfig.json b/packages/technical-features/react-application/tsconfig.json
new file mode 100644
index 0000000000..ec29a8f75f
--- /dev/null
+++ b/packages/technical-features/react-application/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "@k8slens/typescript/config/base.json",
+ "include": ["**/*.ts", "**/*.tsx"]
+}
diff --git a/packages/technical-features/react-application/webpack.config.js b/packages/technical-features/react-application/webpack.config.js
new file mode 100644
index 0000000000..1cda407f5a
--- /dev/null
+++ b/packages/technical-features/react-application/webpack.config.js
@@ -0,0 +1 @@
+module.exports = require("@k8slens/webpack").configForReact;
diff --git a/packages/utility-features/react-testing-library-discovery/.eslintrc.json b/packages/utility-features/react-testing-library-discovery/.eslintrc.json
new file mode 100644
index 0000000000..b15115cb69
--- /dev/null
+++ b/packages/utility-features/react-testing-library-discovery/.eslintrc.json
@@ -0,0 +1,6 @@
+{
+ "extends": "@k8slens/eslint-config/eslint",
+ "parserOptions": {
+ "project": "./tsconfig.json"
+ }
+}
diff --git a/packages/utility-features/react-testing-library-discovery/.prettierrc b/packages/utility-features/react-testing-library-discovery/.prettierrc
new file mode 100644
index 0000000000..edd47b479e
--- /dev/null
+++ b/packages/utility-features/react-testing-library-discovery/.prettierrc
@@ -0,0 +1 @@
+"@k8slens/eslint-config/prettier"
diff --git a/packages/utility-features/react-testing-library-discovery/index.ts b/packages/utility-features/react-testing-library-discovery/index.ts
new file mode 100644
index 0000000000..109c6af90f
--- /dev/null
+++ b/packages/utility-features/react-testing-library-discovery/index.ts
@@ -0,0 +1,13 @@
+export type {
+ Discover,
+ GetSingleElement,
+ QueryAllElements,
+ QuerySingleElement,
+} from "./src/discovery-of-html-elements";
+
+export {
+ discoverFor,
+ getSingleElement,
+ queryAllElements,
+ querySingleElement,
+} from "./src/discovery-of-html-elements";
diff --git a/packages/utility-features/react-testing-library-discovery/package.json b/packages/utility-features/react-testing-library-discovery/package.json
new file mode 100644
index 0000000000..489549a187
--- /dev/null
+++ b/packages/utility-features/react-testing-library-discovery/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "@k8slens/react-testing-library-discovery",
+ "private": false,
+ "version": "1.0.0-alpha.0",
+ "description": "A way to discover HTML-elements using react-testing-library",
+ "type": "commonjs",
+ "files": [
+ "dist"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/lensapp/lens.git"
+ },
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "author": {
+ "name": "OpenLens Authors",
+ "email": "info@k8slens.dev"
+ },
+ "license": "MIT",
+ "homepage": "https://github.com/lensapp/lens",
+ "scripts": {
+ "build": "webpack",
+ "dev": "webpack --mode=development --watch",
+ "lint": "lens-lint",
+ "lint:fix": "lens-lint --fix"
+ },
+ "dependencies": {
+ "@testing-library/dom": "^8.19.0",
+ "@testing-library/jest-dom": "^5.16.5",
+ "@testing-library/react": "^13.4.0"
+ }
+}
diff --git a/packages/utility-features/react-testing-library-discovery/src/discovery-of-html-elements.ts b/packages/utility-features/react-testing-library-discovery/src/discovery-of-html-elements.ts
new file mode 100644
index 0000000000..0df7d9202d
--- /dev/null
+++ b/packages/utility-features/react-testing-library-discovery/src/discovery-of-html-elements.ts
@@ -0,0 +1,125 @@
+import type { RenderResult } from "@testing-library/react";
+import { prettyDOM as prettyDom } from "@testing-library/dom";
+
+type DiscoverySourceTypes = RenderResult | Element;
+
+export type QuerySingleElement = (
+ attributeName: string,
+ attributeValue?: string,
+) => { discovered: Element | null } & Discover;
+
+type Clickable = { click: () => void };
+
+export type GetSingleElement = (
+ attributeName: string,
+ attributeValue?: string,
+) => { discovered: Element } & Discover & Clickable;
+
+export type QueryAllElements = (attributeName: string) => {
+ discovered: Element[];
+ attributeValues: (string | null)[];
+};
+
+export interface Discover {
+ querySingleElement: QuerySingleElement;
+ queryAllElements: QueryAllElements;
+ getSingleElement: GetSingleElement;
+}
+
+const getBaseElement = (source: DiscoverySourceTypes) =>
+ "baseElement" in source ? source.baseElement : source;
+
+export function querySingleElement(getSource: () => DiscoverySourceTypes): QuerySingleElement {
+ return (attributeName, attributeValue) => {
+ const source = getSource();
+
+ const dataAttribute = `data-${attributeName}-test`;
+
+ const selector = attributeValue
+ ? `[${dataAttribute}="${attributeValue}"]`
+ : `[${dataAttribute}]`;
+
+ const discovered = getBaseElement(source).querySelector(selector);
+
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ const nestedDiscover = discoverFor(() => {
+ if (!discovered) {
+ throw new Error("Tried to do nested discover using source that does not exist");
+ }
+
+ return discovered;
+ });
+
+ return {
+ discovered,
+
+ ...nestedDiscover,
+ };
+ };
+}
+
+export function queryAllElements(getSource: () => DiscoverySourceTypes): QueryAllElements {
+ return (attributeName) => {
+ const source = getSource();
+
+ const dataAttribute = `data-${attributeName}-test`;
+
+ const results = [...getBaseElement(source).querySelectorAll(`[${dataAttribute}]`)];
+
+ return {
+ discovered: results,
+
+ attributeValues: results.map((result) => result.getAttribute(dataAttribute)),
+ };
+ };
+}
+
+export function getSingleElement(getSource: () => DiscoverySourceTypes): GetSingleElement {
+ return (attributeName, attributeValue) => {
+ const dataAttribute = `data-${attributeName}-test`;
+
+ const { discovered, ...nestedDiscover } = querySingleElement(getSource)(
+ attributeName,
+ attributeValue,
+ );
+
+ if (!discovered) {
+ // eslint-disable-next-line xss/no-mixed-html
+ const html = prettyDom(getBaseElement(getSource()));
+
+ if (attributeValue) {
+ const validValues = queryAllElements(getSource)(attributeName).attributeValues;
+
+ throw new Error(
+ `Couldn't find HTML-element with attribute "${dataAttribute}" with value "${attributeValue}".\n\nPresent values are:\n\n"${validValues.join(
+ '",\n"',
+ )}"\n\nHTML is:\n\n${html}`,
+ );
+ }
+
+ throw new Error(
+ `Couldn't find HTML-element with attribute "${dataAttribute}"\n\nHTML is:\n\n${html}`,
+ );
+ }
+
+ const click = () => {
+ if ("click" in discovered && typeof discovered.click === "function") {
+ discovered.click();
+ } else {
+ throw new Error(
+ `Tried to click something that was not clickable:\n\n${prettyDom(discovered)}`,
+ );
+ }
+ };
+
+ return { discovered, click, ...nestedDiscover };
+ };
+}
+
+export function discoverFor(getSource: () => DiscoverySourceTypes): Discover {
+ return {
+ querySingleElement: querySingleElement(getSource),
+ queryAllElements: queryAllElements(getSource),
+ getSingleElement: getSingleElement(getSource),
+ };
+}
diff --git a/packages/utility-features/react-testing-library-discovery/tsconfig.json b/packages/utility-features/react-testing-library-discovery/tsconfig.json
new file mode 100644
index 0000000000..1819203dc1
--- /dev/null
+++ b/packages/utility-features/react-testing-library-discovery/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "@k8slens/typescript/config/base.json",
+ "include": ["**/*.ts"]
+}
diff --git a/packages/utility-features/react-testing-library-discovery/webpack.config.js b/packages/utility-features/react-testing-library-discovery/webpack.config.js
new file mode 100644
index 0000000000..3183f30179
--- /dev/null
+++ b/packages/utility-features/react-testing-library-discovery/webpack.config.js
@@ -0,0 +1 @@
+module.exports = require("@k8slens/webpack").configForNode;