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`] = ` + +
+
+ Some children +
+
+ +`; + +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`] = ` + +
+
+
+ Some children +
+
+
+ +`; 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;