1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge branch 'master' into compare-update-and-release-dates

This commit is contained in:
Alex Andreev 2022-06-09 10:27:53 +03:00
commit 3207a6f057
375 changed files with 12294 additions and 2430 deletions

View File

@ -16,8 +16,8 @@ jobs:
vmImage: windows-2019
strategy:
matrix:
node_14.x:
node_version: 14.x
node:
node_version: 16.x
steps:
- powershell: |
$CI_BUILD_TAG = git describe --tags
@ -64,8 +64,8 @@ jobs:
vmImage: macOS-11
strategy:
matrix:
node_14.x:
node_version: 14.x
node:
node_version: 16.x
steps:
- script: CI_BUILD_TAG=`git describe --tags` && echo "##vso[task.setvariable variable=CI_BUILD_TAG]$CI_BUILD_TAG"
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
@ -94,9 +94,25 @@ jobs:
GH_TOKEN: $(LENS_IDE_GH_TOKEN)
displayName: Customize config
- script: make build
- bash: |
set -e
echo "Importing codesign certificate ..."
echo $CSC_LINK | base64 -D > certificate.p12
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 21600 build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
security import certificate.p12 -k build.keychain -P $CSC_KEY_PASSWORD -T /usr/bin/codesign -T /usr/bin/security -A
security set-key-partition-list -S apple-tool:,apple: -k $KEYCHAIN_PASSWORD build.keychain
rm certificate.p12
echo "Codesign certificate imported!"
make build
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
env:
KEYCHAIN_PASSWORD: secretz
APPLEID: $(APPLEID)
APPLEIDPASS: $(APPLEIDPASS)
CSC_LINK: $(CSC_LINK)
@ -112,8 +128,8 @@ jobs:
vmImage: ubuntu-18.04
strategy:
matrix:
node_14.x:
node_version: 14.x
node:
node_version: 16.x
steps:
- script: CI_BUILD_TAG=`git describe --tags` && echo "##vso[task.setvariable variable=CI_BUILD_TAG]$CI_BUILD_TAG"
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"

View File

@ -9,7 +9,7 @@ jobs:
if: ${{ contains(github.event.pull_request.labels.*.name, 'area/documentation') }}
strategy:
matrix:
node-version: [14.x]
node-version: [16.x]
steps:
- name: Checkout Release from lens
uses: actions/checkout@v2

View File

@ -14,12 +14,12 @@ jobs:
- uses: actions/setup-node@v2
with:
node-version: "14"
node-version: "16"
- uses: doyensec/electronegativity-action@v1.1
with:
input: src/
electron-version: "14.2.4"
electron-version: "15.5.7"
severity: medium
- name: Upload sarif

View File

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [16.x]
steps:
- name: Checkout Release from lens
uses: actions/checkout@v2

View File

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [16.x]
steps:
- name: Set up Python 3.7
uses: actions/setup-python@v2
@ -46,7 +46,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [16.x]
needs: verify-docs
steps:
- name: Set up Python 3.7

View File

@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [16.x]
steps:
- name: Set up Python 3.7
uses: actions/setup-python@v2

View File

@ -14,7 +14,7 @@ jobs:
${{ github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'area/extension') }}
strategy:
matrix:
node-version: [14.x]
node-version: [16.x]
steps:
- name: Checkout Release
uses: actions/checkout@v2

View File

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [16.x]
steps:
- name: Checkout Release
uses: actions/checkout@v2

View File

@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-18.04, macos-11, windows-2019]
node-version: [14.x]
node-version: [16.x]
steps:
- name: Checkout Release from lens
uses: actions/checkout@v2

View File

@ -1,3 +1,3 @@
disturl "https://atom.io/download/electron"
target "14.2.4"
target "15.5.0"
runtime "electron"

View File

@ -56,6 +56,7 @@ integration: build
build: node_modules binaries/client
yarn run npm:fix-build-version
$(MAKE) build-extensions -B
yarn run build:tray-icons
yarn run compile
ifeq "$(DETECTED_OS)" "Windows"
# https://github.com/ukoloff/win-ca#clear-pem-folder-on-publish

View File

@ -1,7 +1,7 @@
# Lens Open Source Project (OpenLens)
[![Build Status](https://github.com/lensapp/lens/actions/workflows/test.yml/badge.svg)](https://github.com/lensapp/lens/actions/workflows/test.yml)
[![Chat on Slack](https://img.shields.io/badge/chat-on%20slack-blue.svg?logo=slack&longCache=true&style=flat)](https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI)
[![Chat on Slack](https://img.shields.io/badge/chat-on%20slack-blue.svg?logo=slack&longCache=true&style=flat)](https://join.slack.com/t/k8slens/shared_invite/zt-198iepl92-EPJsCckkJ~f887vWqJcgGA)
## The Repository

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { readFileSync } from "fs";
import { ensureDirSync } from "fs-extra";
import { ensureDir, readFile } from "fs-extra";
import { JSDOM } from "jsdom";
import path from "path";
import sharp from "sharp";
@ -12,39 +11,120 @@ import sharp from "sharp";
const size = Number(process.env.OUTPUT_SIZE || "16");
const outputFolder = process.env.OUTPUT_DIR || "./build/tray";
const inputFile = process.env.INPUT_SVG_PATH || "./src/renderer/components/icon/logo-lens.svg";
const noticeFile = process.env.NOTICE_SVG_PATH || "./src/renderer/components/icon/notice.svg";
const svgData = readFileSync(inputFile, { encoding: "utf-8" });
const svgDom = new JSDOM(`<body>${svgData}</body>`);
const svgRoot = svgDom.window.document.body.getElementsByTagName("svg")[0];
async function ensureOutputFoler() {
await ensureDir(outputFolder);
}
svgRoot.innerHTML += `<style>* {fill: white !important;}</style>`;
const lightTemplate = svgRoot.outerHTML;
function getSvgStyling(colouring: "dark" | "light"): string {
return `
<style>
ellipse {
stroke: ${colouring === "dark" ? "white" : "black"} !important;
}
path, rect {
fill: ${colouring === "dark" ? "white" : "black"} !important;
}
</style>
`;
}
svgRoot.innerHTML += `<style>* {fill: black !important;}</style>`;
type TargetSystems = "macos" | "windows-or-linux";
const darkTemplate = svgRoot.outerHTML;
async function getBaseIconImage(system: TargetSystems) {
const svgData = await readFile(inputFile, { encoding: "utf-8" });
const dom = new JSDOM(`<body>${svgData}</body>`);
const root = dom.window.document.body.getElementsByTagName("svg")[0];
console.log("Generating tray icon pngs");
root.innerHTML += getSvgStyling(system === "macos" ? "light" : "dark");
ensureDirSync(outputFolder);
return Buffer.from(root.outerHTML);
}
Promise.all([
sharp(Buffer.from(lightTemplate))
async function generateImage(image: Buffer, size: number, namePrefix: string) {
sharp(image)
.resize({ width: size, height: size })
.png()
.toFile(path.join(outputFolder, "trayIconDarkTemplate.png")),
sharp(Buffer.from(lightTemplate))
.resize({ width: size*2, height: size*2 })
.png()
.toFile(path.join(outputFolder, "trayIconDarkTemplate@2x.png")),
sharp(Buffer.from(darkTemplate))
.resize({ width: size, height: size })
.png()
.toFile(path.join(outputFolder, "trayIconTemplate.png")),
sharp(Buffer.from(darkTemplate))
.resize({ width: size*2, height: size*2 })
.png()
.toFile(path.join(outputFolder, "trayIconTemplate@2x.png")),
])
.then((resolutions) => console.log(`Generated ${resolutions.length} images`))
.catch(console.error);
.toFile(path.join(outputFolder, `${namePrefix}.png`));
}
async function generateImages(image: Buffer, size: number, name: string) {
await Promise.all([
generateImage(image, size, name),
generateImage(image, size*2, `${name}@2x`),
generateImage(image, size*3, `${name}@3x`),
generateImage(image, size*4, `${name}@4x`),
]);
}
async function generateUpdateAvailableImages(baseImage: Buffer, system: TargetSystems) {
const noticeIconImage = await getNoticeIconImage(system);
const circleBuffer = await sharp(Buffer.from(`
<svg viewBox="0 0 64 64">
<circle cx="32" cy="32" r="32" fill="black" />
</svg>
`))
.toBuffer();
return sharp(baseImage)
.resize({ width: 128, height: 128 })
.composite([
{
input: circleBuffer,
top: 64,
left: 64,
blend: "dest-out",
},
{
input: (
await sharp(noticeIconImage)
.resize({
width: 60,
height: 60,
})
.toBuffer()
),
top: 66,
left: 66,
},
])
.toBuffer();
}
async function getNoticeIconImage(system: TargetSystems) {
const svgData = await readFile(noticeFile, { encoding: "utf-8" });
const root = new JSDOM(svgData).window.document.getElementsByTagName("svg")[0];
root.innerHTML += getSvgStyling(system === "macos" ? "light" : "dark");
return Buffer.from(root.outerHTML);
}
async function generateTrayIcons() {
try {
console.log("Generating tray icon pngs");
await ensureOutputFoler();
const baseIconTemplateImage = await getBaseIconImage("macos");
const updateAvailableTemplateImage = await generateUpdateAvailableImages(baseIconTemplateImage, "macos");
const baseIconImage = await getBaseIconImage("windows-or-linux");
const updateAvailableImage = await generateUpdateAvailableImages(baseIconImage, "windows-or-linux");
await Promise.all([
// Templates are for macOS only
generateImages(baseIconTemplateImage, size, "trayIconTemplate"),
generateImages(updateAvailableTemplateImage, size, "trayIconUpdateAvailableTemplate"),
// Non-templates are for windows and linux
generateImages(baseIconImage, size, "trayIcon"),
generateImages(updateAvailableImage, size, "trayIconUpdateAvailable"),
]);
console.log("Generated all images");
} catch (error) {
console.error(error);
}
}
generateTrayIcons();

View File

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 392 B

View File

Before

Width:  |  Height:  |  Size: 724 B

After

Width:  |  Height:  |  Size: 724 B

BIN
build/tray/trayIcon@3x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
build/tray/trayIcon@4x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -79,7 +79,7 @@ Some of the most-important fields include:
}
```
## Webpack configuation
## Webpack configuration
The following webpack `externals` are provided by `Lens` and must be used (when available) to make sure that the versions used are in sync.

View File

@ -224,7 +224,7 @@ export default class ExampleExtension extends Renderer.LensExtension {
{
id: "bonjour",
components: {
Page: () => <ExemplePage extension={this} />,
Page: () => <ExamplePage extension={this} />,
},
},
];
@ -250,7 +250,7 @@ export default class ExampleExtension extends Renderer.LensExtension {
target: { pageId: "bonjour" },
title: "Bonjour le monde",
components: {
Icon: ExempleIcon,
Icon: ExampleIcon,
},
},
];

View File

@ -24,11 +24,6 @@ spec:
operator: In
values:
- linux
- matchExpressions:
- key: beta.kubernetes.io/os
operator: In
values:
- linux
# <%- if config.node_selector -%>
# nodeSelector:
# <%- node_selector.to_h.each do |key, value| -%>

View File

@ -30,11 +30,6 @@ spec:
operator: In
values:
- linux
- matchExpressions:
- key: beta.kubernetes.io/os
operator: In
values:
- linux
securityContext:
runAsNonRoot: true
runAsUser: 65534

View File

@ -23,11 +23,6 @@ spec:
operator: In
values:
- linux
- matchExpressions:
- key: beta.kubernetes.io/os
operator: In
values:
- linux
serviceAccountName: kube-state-metrics
containers:
- name: kube-state-metrics

View File

@ -52,7 +52,7 @@
"sentryDsn": ""
},
"engines": {
"node": ">=14 <15"
"node": ">=16 <17"
},
"jest": {
"collectCoverage": false,
@ -72,6 +72,9 @@
"<rootDir>/src/jest.setup.ts",
"jest-canvas-mock"
],
"setupFilesAfterEnv": [
"<rootDir>/src/jest-after-env.setup.ts"
],
"globals": {
"ts-jest": {
"isolatedModules": true
@ -225,22 +228,22 @@
"filehound": "^1.17.6",
"fs-extra": "^9.0.1",
"glob-to-regexp": "^0.4.1",
"got": "^11.8.3",
"got": "^11.8.5",
"grapheme-splitter": "^1.0.4",
"handlebars": "^4.7.7",
"history": "^4.10.1",
"http-proxy": "^1.18.1",
"immer": "^9.0.12",
"immer": "^9.0.14",
"joi": "^17.6.0",
"js-yaml": "^4.1.0",
"jsdom": "^16.7.0",
"lodash": "^4.17.15",
"mac-ca": "^1.0.6",
"marked": "^4.0.15",
"marked": "^4.0.16",
"md5-file": "^5.0.0",
"mobx": "^6.5.0",
"mobx-observable-history": "^2.0.3",
"mobx-react": "^7.3.0",
"mobx-react": "^7.5.0",
"mobx-utils": "^6.0.4",
"mock-fs": "^5.1.2",
"moment": "^2.29.3",
@ -248,7 +251,7 @@
"monaco-editor": "^0.29.1",
"monaco-editor-webpack-plugin": "^5.0.0",
"node-fetch": "lensapp/node-fetch#2.x",
"node-pty": "^0.10.1",
"node-pty": "^0.11.0-beta19",
"npm": "^6.14.17",
"p-limit": "^3.1.0",
"path-to-regexp": "^6.2.0",
@ -276,14 +279,14 @@
"winston": "^3.7.2",
"winston-console-format": "^1.0.8",
"winston-transport-browserconsole": "^1.0.5",
"ws": "^8.5.0"
"ws": "^8.7.0"
},
"devDependencies": {
"@async-fn/jest": "1.6.0",
"@async-fn/jest": "1.6.1",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
"@sentry/types": "^6.19.7",
"@testing-library/dom": "^7.31.2",
"@testing-library/jest-dom": "^5.16.4",
@ -292,7 +295,7 @@
"@types/byline": "^4.2.33",
"@types/chart.js": "^2.9.36",
"@types/circular-dependency-plugin": "5.0.5",
"@types/cli-progress": "^3.9.2",
"@types/cli-progress": "^3.11.0",
"@types/color": "^3.0.3",
"@types/command-line-args": "^5.2.0",
"@types/crypto-js": "^3.1.47",
@ -311,7 +314,7 @@
"@types/md5-file": "^4.0.2",
"@types/mini-css-extract-plugin": "^2.4.0",
"@types/mock-fs": "^4.13.1",
"@types/node": "14.18.17",
"@types/node": "^16.11.39",
"@types/node-fetch": "^2.6.1",
"@types/npm": "^2.0.32",
"@types/proper-lockfile": "^4.1.2",
@ -321,7 +324,7 @@
"@types/react-dom": "^17.0.16",
"@types/react-router": "^5.1.18",
"@types/react-router-dom": "^5.3.3",
"@types/react-table": "^7.7.11",
"@types/react-table": "^7.7.12",
"@types/react-virtualized-auto-sizer": "^1.0.1",
"@types/react-window": "^1.8.5",
"@types/readable-stream": "^2.3.13",
@ -339,33 +342,33 @@
"@types/uuid": "^8.3.4",
"@types/webpack": "^5.28.0",
"@types/webpack-dev-server": "^4.7.2",
"@types/webpack-env": "^1.16.4",
"@types/webpack-env": "^1.17.0",
"@types/webpack-node-externals": "^2.5.3",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.17.0",
"@typescript-eslint/eslint-plugin": "^5.27.1",
"@typescript-eslint/parser": "^5.27.0",
"ansi_up": "^5.1.0",
"chart.js": "^2.9.4",
"circular-dependency-plugin": "^5.2.2",
"cli-progress": "^3.11.0",
"cli-progress": "^3.11.1",
"color": "^3.2.1",
"command-line-args": "^5.2.1",
"concurrently": "^7.1.0",
"concurrently": "^7.2.1",
"css-loader": "^6.7.1",
"deepdash": "^5.3.9",
"dompurify": "^2.3.6",
"electron": "^14.2.9",
"dompurify": "^2.3.8",
"electron": "^15.5.7",
"electron-builder": "^23.0.3",
"electron-notarize": "^0.3.0",
"esbuild": "^0.14.38",
"esbuild-loader": "^2.18.0",
"eslint": "^8.14.0",
"esbuild-loader": "^2.19.0",
"eslint": "^8.16.0",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react": "^7.30.0",
"eslint-plugin-react-hooks": "^4.5.0",
"eslint-plugin-unused-imports": "^2.0.0",
"flex.box": "^3.4.4",
"fork-ts-checker-webpack-plugin": "^6.5.0",
"fork-ts-checker-webpack-plugin": "^6.5.2",
"gunzip-maybe": "^1.4.2",
"html-webpack-plugin": "^5.5.0",
"identity-obj-proxy": "^3.0.0",
@ -381,37 +384,36 @@
"node-gyp": "7.1.2",
"node-loader": "^2.0.0",
"nodemon": "^2.0.16",
"playwright": "^1.20.2",
"postcss": "^8.4.12",
"playwright": "^1.22.2",
"postcss": "^8.4.14",
"postcss-loader": "^6.2.1",
"randomcolor": "^0.6.2",
"react-beautiful-dnd": "^13.1.0",
"react-refresh": "^0.12.0",
"react-refresh-typescript": "^2.0.4",
"react-router-dom": "^5.3.1",
"react-refresh": "^0.13.0",
"react-refresh-typescript": "^2.0.5",
"react-router-dom": "^5.3.3",
"react-select": "^5.3.2",
"react-select-event": "^5.5.0",
"react-table": "^7.7.0",
"react-table": "^7.8.0",
"react-window": "^1.8.7",
"sass": "^1.51.0",
"sass": "^1.52.2",
"sass-loader": "^12.6.0",
"sharp": "^0.30.4",
"sharp": "^0.30.6",
"style-loader": "^3.3.1",
"tailwindcss": "^3.0.23",
"tar-stream": "^2.2.0",
"ts-jest": "26.5.6",
"ts-loader": "^9.2.8",
"ts-node": "^10.7.0",
"type-fest": "^2.12.2",
"type-fest": "^2.13.0",
"typed-emitter": "^1.4.0",
"typedoc": "0.22.15",
"typedoc": "0.22.17",
"typedoc-plugin-markdown": "^3.11.12",
"typeface-roboto": "^1.1.13",
"typescript": "^4.5.5",
"typescript-plugin-css-modules": "^3.4.0",
"webpack": "^5.72.0",
"webpack": "^5.73.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.0",
"webpack-dev-server": "^4.9.1",
"webpack-node-externals": "^3.0.0",
"xterm": "^4.18.0",
"xterm-addon-fit": "^0.5.0"

View File

@ -1,11 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`extension special characters in page registrations renders 1`] = `<div />`;
exports[`extension special characters in page registrations renders 1`] = `
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
exports[`extension special characters in page registrations when navigating to route with ID having special characters renders 1`] = `
<div>
<div>
Some page
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -1,12 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`navigate to extension page renders 1`] = `<div />`;
exports[`navigate to extension page renders 1`] = `
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
exports[`navigate to extension page when extension navigates to child route renders 1`] = `
<div>
<div>
Child page
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -31,6 +40,9 @@ exports[`navigate to extension page when extension navigates to route with param
Some button
</button>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -55,6 +67,9 @@ exports[`navigate to extension page when extension navigates to route without pa
Some button
</button>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -79,5 +94,8 @@ exports[`navigate to extension page when extension navigates to route without pa
Some button
</button>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -8,6 +8,9 @@ exports[`navigating between routes given route with optional path parameters whe
"someOtherParameter": "some-other-value"
}
</pre>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -16,5 +19,8 @@ exports[`navigating between routes given route without path parameters when navi
<div>
Some component
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -1,6 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`add-cluster - navigation using application menu renders 1`] = `<div />`;
exports[`add-cluster - navigation using application menu renders 1`] = `
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
exports[`add-cluster - navigation using application menu when navigating to add cluster using application menu renders 1`] = `
<div>
@ -85,5 +91,8 @@ exports[`add-cluster - navigation using application menu when navigating to add
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -6,16 +6,17 @@
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 isAutoUpdateEnabledInjectable from "../../main/is-auto-update-enabled.injectable";
import React from "react";
// TODO: Make components free of side effects by making them deterministic
jest.mock("../../renderer/components/tooltip/tooltip", () => ({
Tooltip: () => null,
}));
jest.mock("../../renderer/components/tooltip/withTooltip", () => ({
withTooltip: (Target: any) => ({ tooltip, tooltipOverrideDisabled, ...props }: any) => <Target {...props} />,
}));
jest.mock("../../renderer/components/monaco-editor/monaco-editor", () => ({
MonacoEditor: () => null,
}));
@ -25,9 +26,7 @@ describe("add-cluster - navigation using application menu", () => {
let rendered: RenderResult;
beforeEach(async () => {
applicationBuilder = getApplicationBuilder().beforeApplicationStart(({ mainDi }) => {
mainDi.override(isAutoUpdateEnabledInjectable, () => () => false);
});
applicationBuilder = getApplicationBuilder();
rendered = await applicationBuilder.render();
});

View File

@ -0,0 +1,536 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`installing update using tray when started renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
</body>
`;
exports[`installing update using tray when started when user checks for updates using tray renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
>
<div
class="Animate opacity notification flex info enter"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="info_outline"
>
info_outline
</span>
</i>
</div>
<div
class="message box grow"
>
Checking for updates...
</div>
<div
class="box"
>
<i
class="Icon close material interactive focusable"
data-testid="close-notification-for-notification_16"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
</div>
</div>
</div>
</div>
</body>
`;
exports[`installing update using tray when started when user checks for updates using tray when new update is discovered renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
>
<div
class="Animate opacity notification flex info enter"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="info_outline"
>
info_outline
</span>
</i>
</div>
<div
class="message box grow"
>
Checking for updates...
</div>
<div
class="box"
>
<i
class="Icon close material interactive focusable"
data-testid="close-notification-for-notification_115"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
</div>
</div>
<div
class="Animate opacity notification flex info enter"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="info_outline"
>
info_outline
</span>
</i>
</div>
<div
class="message box grow"
>
Download for version some-version started...
</div>
<div
class="box"
>
<i
class="Icon close material interactive focusable"
data-testid="close-notification-for-notification_118"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
</div>
</div>
</div>
</div>
</body>
`;
exports[`installing update using tray when started when user checks for updates using tray when new update is discovered when download fails renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
>
<div
class="Animate opacity notification flex info enter"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="info_outline"
>
info_outline
</span>
</i>
</div>
<div
class="message box grow"
>
Checking for updates...
</div>
<div
class="box"
>
<i
class="Icon close material interactive focusable"
data-testid="close-notification-for-notification_183"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
</div>
</div>
<div
class="Animate opacity notification flex info enter"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="info_outline"
>
info_outline
</span>
</i>
</div>
<div
class="message box grow"
>
Download for version some-version started...
</div>
<div
class="box"
>
<i
class="Icon close material interactive focusable"
data-testid="close-notification-for-notification_186"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
</div>
</div>
<div
class="Animate opacity notification flex info enter"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="info_outline"
>
info_outline
</span>
</i>
</div>
<div
class="message box grow"
>
Download of update failed
</div>
<div
class="box"
>
<i
class="Icon close material interactive focusable"
data-testid="close-notification-for-notification_191"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
</div>
</div>
</div>
</div>
</body>
`;
exports[`installing update using tray when started when user checks for updates using tray when new update is discovered when download succeeds renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
>
<div
class="Animate opacity notification flex info enter"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="info_outline"
>
info_outline
</span>
</i>
</div>
<div
class="message box grow"
>
Checking for updates...
</div>
<div
class="box"
>
<i
class="Icon close material interactive focusable"
data-testid="close-notification-for-notification_266"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
</div>
</div>
<div
class="Animate opacity notification flex info enter"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="info_outline"
>
info_outline
</span>
</i>
</div>
<div
class="message box grow"
>
Download for version some-version started...
</div>
<div
class="box"
>
<i
class="Icon close material interactive focusable"
data-testid="close-notification-for-notification_269"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
</div>
</div>
<div
class="Animate opacity notification flex info enter"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="info_outline"
>
info_outline
</span>
</i>
</div>
<div
class="message box grow"
>
<div
class="flex column gaps"
data-testid="ask-boolean-some-irrelevant-random-id"
>
<b>
Update Available
</b>
<p>
Version some-version of Lens IDE is available and ready to be installed. Would you like to update now?
Lens should restart automatically, if it doesn't please restart manually. Installed extensions might require updating.
</p>
<div
class="flex gaps row align-left box grow"
>
<button
class="Button light"
data-testid="ask-boolean-some-irrelevant-random-id-button-yes"
type="button"
>
Yes
</button>
<button
class="Button active outlined"
data-testid="ask-boolean-some-irrelevant-random-id-button-no"
type="button"
>
No
</button>
</div>
</div>
</div>
<div
class="box"
>
<i
class="Icon close material interactive focusable"
data-testid="close-notification-for-ask-boolean-for-some-irrelevant-random-id"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
</div>
</div>
</div>
</div>
</body>
`;
exports[`installing update using tray when started when user checks for updates using tray when no new update is discovered renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
>
<div
class="Animate opacity notification flex info enter"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="info_outline"
>
info_outline
</span>
</i>
</div>
<div
class="message box grow"
>
Checking for updates...
</div>
<div
class="box"
>
<i
class="Icon close material interactive focusable"
data-testid="close-notification-for-notification_59"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
</div>
</div>
<div
class="Animate opacity notification flex info enter"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="info_outline"
>
info_outline
</span>
</i>
</div>
<div
class="message box grow"
>
No new updates available
</div>
<div
class="box"
>
<i
class="Icon close material interactive focusable"
data-testid="close-notification-for-notification_62"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
</div>
</div>
</div>
</div>
</body>
`;

View File

@ -0,0 +1,81 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`installing update when started renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
</body>
`;
exports[`installing update when started when user checks for updates renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
</body>
`;
exports[`installing update when started when user checks for updates when new update is discovered renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
</body>
`;
exports[`installing update when started when user checks for updates when new update is discovered when download fails renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
</body>
`;
exports[`installing update when started when user checks for updates when new update is discovered when download succeeds renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
</body>
`;
exports[`installing update when started when user checks for updates when new update is discovered when download succeeds when user answers not to install the update renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
</body>
`;
exports[`installing update when started when user checks for updates when new update is discovered when download succeeds when user answers to install the update renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
</body>
`;
exports[`installing update when started when user checks for updates when no new update is discovered renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
</body>
`;

View File

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`periodical checking of updates given updater is enabled and configuration exists, when started renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
</body>
`;

View File

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`selection of update stability when started renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
</body>
`;

View File

@ -0,0 +1,87 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import electronUpdaterIsActiveInjectable from "../../main/electron-app/features/electron-updater-is-active.injectable";
import publishIsConfiguredInjectable from "../../main/application-update/publish-is-configured.injectable";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import type { CheckForPlatformUpdates } from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
import checkForPlatformUpdatesInjectable from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable";
import selectedUpdateChannelInjectable from "../../common/application-update/selected-update-channel/selected-update-channel.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
import appVersionInjectable from "../../common/get-configuration-file-model/app-version/app-version.injectable";
import { updateChannels } from "../../common/application-update/update-channels";
describe("downgrading version update", () => {
let applicationBuilder: ApplicationBuilder;
let checkForPlatformUpdatesMock: AsyncFnMock<CheckForPlatformUpdates>;
let mainDi: DiContainer;
beforeEach(() => {
jest.useFakeTimers();
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
checkForPlatformUpdatesMock = asyncFn();
mainDi.override(
checkForPlatformUpdatesInjectable,
() => checkForPlatformUpdatesMock,
);
mainDi.override(electronUpdaterIsActiveInjectable, () => true);
mainDi.override(publishIsConfiguredInjectable, () => true);
});
mainDi = applicationBuilder.dis.mainDi;
});
[
{
updateChannel: updateChannels.latest,
appVersion: "4.0.0-beta",
downgradeIsAllowed: true,
},
{
updateChannel: updateChannels.beta,
appVersion: "4.0.0-beta",
downgradeIsAllowed: false,
},
{
updateChannel: updateChannels.beta,
appVersion: "4.0.0-beta.1",
downgradeIsAllowed: false,
},
{
updateChannel: updateChannels.alpha,
appVersion: "4.0.0-beta",
downgradeIsAllowed: true,
},
{
updateChannel: updateChannels.alpha,
appVersion: "4.0.0-alpha",
downgradeIsAllowed: false,
},
].forEach(({ appVersion, updateChannel, downgradeIsAllowed }) => {
it(`given application version "${appVersion}" and update channel "${updateChannel.id}", when checking for updates, can${downgradeIsAllowed ? "": "not"} downgrade`, async () => {
mainDi.override(appVersionInjectable, () => appVersion);
await applicationBuilder.render();
const selectedUpdateChannel = mainDi.inject(selectedUpdateChannelInjectable);
selectedUpdateChannel.setValue(updateChannel.id);
const processCheckingForUpdates = mainDi.inject(processCheckingForUpdatesInjectable);
processCheckingForUpdates();
expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith(expect.any(Object), { allowDowngrade: downgradeIsAllowed });
});
});
});

View File

@ -0,0 +1,262 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import type { RenderResult } from "@testing-library/react";
import electronUpdaterIsActiveInjectable from "../../main/electron-app/features/electron-updater-is-active.injectable";
import publishIsConfiguredInjectable from "../../main/application-update/publish-is-configured.injectable";
import type { CheckForPlatformUpdates } from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
import checkForPlatformUpdatesInjectable from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import type { DownloadPlatformUpdate } from "../../main/application-update/download-platform-update/download-platform-update.injectable";
import downloadPlatformUpdateInjectable from "../../main/application-update/download-platform-update/download-platform-update.injectable";
import showApplicationWindowInjectable from "../../main/start-main-application/lens-window/show-application-window.injectable";
import progressOfUpdateDownloadInjectable from "../../common/application-update/progress-of-update-download/progress-of-update-download.injectable";
import type { TrayIconPaths } from "../../main/tray/tray-icon-path.injectable";
import trayIconPathsInjectable from "../../main/tray/tray-icon-path.injectable";
describe("installing update using tray", () => {
let applicationBuilder: ApplicationBuilder;
let checkForPlatformUpdatesMock: AsyncFnMock<CheckForPlatformUpdates>;
let downloadPlatformUpdateMock: AsyncFnMock<DownloadPlatformUpdate>;
let showApplicationWindowMock: jest.Mock;
let trayIconPaths: TrayIconPaths;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
checkForPlatformUpdatesMock = asyncFn();
downloadPlatformUpdateMock = asyncFn();
showApplicationWindowMock = jest.fn();
mainDi.override(showApplicationWindowInjectable, () => showApplicationWindowMock);
mainDi.override(
checkForPlatformUpdatesInjectable,
() => checkForPlatformUpdatesMock,
);
mainDi.override(
downloadPlatformUpdateInjectable,
() => downloadPlatformUpdateMock,
);
mainDi.override(electronUpdaterIsActiveInjectable, () => true);
mainDi.override(publishIsConfiguredInjectable, () => true);
trayIconPaths = mainDi.inject(trayIconPathsInjectable);
});
});
describe("when started", () => {
let rendered: RenderResult;
beforeEach(async () => {
rendered = await applicationBuilder.render();
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("should use the normal tray icon", () => {
expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.normal);
});
it("user cannot install update yet", () => {
expect(applicationBuilder.tray.get("install-update")).toBeNull();
});
describe("when user checks for updates using tray", () => {
let processCheckingForUpdatesPromise: Promise<void>;
beforeEach(async () => {
processCheckingForUpdatesPromise = applicationBuilder.tray.click("check-for-updates");
});
it("does not show application window yet", () => {
expect(showApplicationWindowMock).not.toHaveBeenCalled();
});
it("should still use the normal tray icon", () => {
expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.normal);
});
it("user cannot check for updates again", () => {
expect(
applicationBuilder.tray.get("check-for-updates")?.enabled.get(),
).toBe(false);
});
it("name of tray item for checking updates indicates that checking is happening", () => {
expect(
applicationBuilder.tray.get("check-for-updates")?.label?.get(),
).toBe("Checking for updates...");
});
it("user cannot install update yet", () => {
expect(applicationBuilder.tray.get("install-update")).toBeNull();
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
describe("when no new update is discovered", () => {
beforeEach(async () => {
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: false,
});
await processCheckingForUpdatesPromise;
});
it("shows application window", () => {
expect(showApplicationWindowMock).toHaveBeenCalled();
});
it("should still use the normal tray icon", () => {
expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.normal);
});
it("user cannot install update", () => {
expect(applicationBuilder.tray.get("install-update")).toBeNull();
});
it("user can check for updates again", () => {
expect(
applicationBuilder.tray.get("check-for-updates")?.enabled.get(),
).toBe(true);
});
it("name of tray item for checking updates no longer indicates that checking is happening", () => {
expect(
applicationBuilder.tray.get("check-for-updates")?.label?.get(),
).toBe("Check for updates");
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
});
describe("when new update is discovered", () => {
beforeEach(async () => {
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: true,
version: "some-version",
});
await processCheckingForUpdatesPromise;
});
it("shows application window", () => {
expect(showApplicationWindowMock).toHaveBeenCalled();
});
it("should use the update available icon", () => {
expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.updateAvailable);
});
it("user cannot check for updates again yet", () => {
expect(
applicationBuilder.tray.get("check-for-updates")?.enabled.get(),
).toBe(false);
});
it("name of tray item for checking updates indicates that downloading is happening", () => {
expect(
applicationBuilder.tray.get("check-for-updates")?.label?.get(),
).toBe("Downloading update some-version (0%)...");
});
it("when download progresses with decimals, percentage increases as integers", () => {
const progressOfUpdateDownload = applicationBuilder.dis.mainDi.inject(
progressOfUpdateDownloadInjectable,
);
progressOfUpdateDownload.set({ percentage: 42.424242 });
expect(
applicationBuilder.tray.get("check-for-updates")?.label?.get(),
).toBe("Downloading update some-version (42%)...");
});
it("user still cannot install update", () => {
expect(applicationBuilder.tray.get("install-update")).toBeNull();
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
describe("when download fails", () => {
beforeEach(async () => {
await downloadPlatformUpdateMock.resolve({ downloadWasSuccessful: false });
});
it("user cannot install update", () => {
expect(
applicationBuilder.tray.get("install-update"),
).toBeNull();
});
it("should revert to use the normal tray icon", () => {
expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.normal);
});
it("user can check for updates again", () => {
expect(
applicationBuilder.tray.get("check-for-updates")?.enabled.get(),
).toBe(true);
});
it("name of tray item for checking updates no longer indicates that downloading is happening", () => {
expect(
applicationBuilder.tray.get("check-for-updates")?.label?.get(),
).toBe("Check for updates");
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
});
describe("when download succeeds", () => {
beforeEach(async () => {
await downloadPlatformUpdateMock.resolve({ downloadWasSuccessful: true });
});
it("user can install update", () => {
expect(
applicationBuilder.tray.get("install-update")?.label?.get(),
).toBe("Install update some-version");
});
it("should use the update available icon", () => {
expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.updateAvailable);
});
it("user can check for updates again", () => {
expect(
applicationBuilder.tray.get("check-for-updates")?.enabled.get(),
).toBe(true);
});
it("name of tray item for checking updates no longer indicates that downloading is happening", () => {
expect(
applicationBuilder.tray.get("check-for-updates")?.label?.get(),
).toBe("Check for updates");
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
});
});
});
});
});

View File

@ -0,0 +1,225 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import quitAndInstallUpdateInjectable from "../../main/electron-app/features/quit-and-install-update.injectable";
import type { RenderResult } from "@testing-library/react";
import electronUpdaterIsActiveInjectable from "../../main/electron-app/features/electron-updater-is-active.injectable";
import publishIsConfiguredInjectable from "../../main/application-update/publish-is-configured.injectable";
import type { CheckForPlatformUpdates } from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
import checkForPlatformUpdatesInjectable from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import type { DownloadPlatformUpdate } from "../../main/application-update/download-platform-update/download-platform-update.injectable";
import downloadPlatformUpdateInjectable from "../../main/application-update/download-platform-update/download-platform-update.injectable";
import setUpdateOnQuitInjectable from "../../main/electron-app/features/set-update-on-quit.injectable";
import type { AskBoolean } from "../../main/ask-boolean/ask-boolean.injectable";
import askBooleanInjectable from "../../main/ask-boolean/ask-boolean.injectable";
import showInfoNotificationInjectable from "../../renderer/components/notifications/show-info-notification.injectable";
import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable";
describe("installing update", () => {
let applicationBuilder: ApplicationBuilder;
let quitAndInstallUpdateMock: jest.Mock;
let checkForPlatformUpdatesMock: AsyncFnMock<CheckForPlatformUpdates>;
let downloadPlatformUpdateMock: AsyncFnMock<DownloadPlatformUpdate>;
let setUpdateOnQuitMock: jest.Mock;
let showInfoNotificationMock: jest.Mock;
let askBooleanMock: AsyncFnMock<AskBoolean>;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ mainDi, rendererDi }) => {
quitAndInstallUpdateMock = jest.fn();
checkForPlatformUpdatesMock = asyncFn();
downloadPlatformUpdateMock = asyncFn();
setUpdateOnQuitMock = jest.fn();
showInfoNotificationMock = jest.fn(() => () => {});
askBooleanMock = asyncFn();
rendererDi.override(showInfoNotificationInjectable, () => showInfoNotificationMock);
mainDi.override(askBooleanInjectable, () => askBooleanMock);
mainDi.override(setUpdateOnQuitInjectable, () => setUpdateOnQuitMock);
mainDi.override(
checkForPlatformUpdatesInjectable,
() => checkForPlatformUpdatesMock,
);
mainDi.override(
downloadPlatformUpdateInjectable,
() => downloadPlatformUpdateMock,
);
mainDi.override(
quitAndInstallUpdateInjectable,
() => quitAndInstallUpdateMock,
);
mainDi.override(electronUpdaterIsActiveInjectable, () => true);
mainDi.override(publishIsConfiguredInjectable, () => true);
});
});
describe("when started", () => {
let rendered: RenderResult;
let processCheckingForUpdates: () => Promise<void>;
beforeEach(async () => {
rendered = await applicationBuilder.render();
processCheckingForUpdates = applicationBuilder.dis.mainDi.inject(processCheckingForUpdatesInjectable);
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
describe("when user checks for updates", () => {
let processCheckingForUpdatesPromise: Promise<void>;
beforeEach(async () => {
processCheckingForUpdatesPromise = processCheckingForUpdates();
});
it("checks for updates", () => {
expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith(
expect.any(Object),
{ allowDowngrade: true },
);
});
it("notifies the user that checking for updates is happening", () => {
expect(showInfoNotificationMock).toHaveBeenCalledWith("Checking for updates...");
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
describe("when no new update is discovered", () => {
beforeEach(async () => {
showInfoNotificationMock.mockClear();
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: false,
});
await processCheckingForUpdatesPromise;
});
it("notifies the user", () => {
expect(showInfoNotificationMock).toHaveBeenCalledWith("No new updates available");
});
it("does not start downloading update", () => {
expect(downloadPlatformUpdateMock).not.toHaveBeenCalled();
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
});
describe("when new update is discovered", () => {
beforeEach(async () => {
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: true,
version: "some-version",
});
await processCheckingForUpdatesPromise;
});
it("starts downloading the update", () => {
expect(downloadPlatformUpdateMock).toHaveBeenCalled();
});
it("notifies the user that download is happening", () => {
expect(showInfoNotificationMock).toHaveBeenCalledWith("Download for version some-version started...");
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
describe("when download fails", () => {
beforeEach(async () => {
await downloadPlatformUpdateMock.resolve({ downloadWasSuccessful: false });
});
it("does not quit and install update yet", () => {
expect(quitAndInstallUpdateMock).not.toHaveBeenCalled();
});
it("notifies the user about failed download", () => {
expect(showInfoNotificationMock).toHaveBeenCalledWith("Download of update failed");
});
it("does not ask user to install update", () => {
expect(askBooleanMock).not.toHaveBeenCalled();
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
});
describe("when download succeeds", () => {
beforeEach(async () => {
await downloadPlatformUpdateMock.resolve({ downloadWasSuccessful: true });
});
it("does not quit and install update yet", () => {
expect(quitAndInstallUpdateMock).not.toHaveBeenCalled();
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("asks user to install update immediately", () => {
expect(askBooleanMock).toHaveBeenCalledWith({
title: "Update Available",
question:
"Version some-version of Lens IDE is available and ready to be installed. Would you like to update now?\n\n" +
"Lens should restart automatically, if it doesn't please restart manually. Installed extensions might require updating.",
});
});
describe("when user answers to install the update", () => {
beforeEach(async () => {
await askBooleanMock.resolve(true);
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("quits application and installs the update", () => {
expect(quitAndInstallUpdateMock).toHaveBeenCalled();
});
});
describe("when user answers not to install the update", () => {
beforeEach(async () => {
await askBooleanMock.resolve(false);
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("does not quit application and install the update", () => {
expect(quitAndInstallUpdateMock).not.toHaveBeenCalled();
});
});
});
});
});
});
});

View File

@ -0,0 +1,117 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import type { RenderResult } from "@testing-library/react";
import electronUpdaterIsActiveInjectable from "../../main/electron-app/features/electron-updater-is-active.injectable";
import publishIsConfiguredInjectable from "../../main/application-update/publish-is-configured.injectable";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable";
import periodicalCheckForUpdatesInjectable from "../../main/application-update/periodical-check-for-updates/periodical-check-for-updates.injectable";
const ENOUGH_TIME = 1000 * 60 * 60 * 2;
describe("periodical checking of updates", () => {
let applicationBuilder: ApplicationBuilder;
let processCheckingForUpdatesMock: AsyncFnMock<() => Promise<void>>;
beforeEach(() => {
jest.useFakeTimers();
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
mainDi.unoverride(periodicalCheckForUpdatesInjectable);
mainDi.permitSideEffects(periodicalCheckForUpdatesInjectable);
processCheckingForUpdatesMock = asyncFn();
mainDi.override(
processCheckingForUpdatesInjectable,
() => processCheckingForUpdatesMock,
);
});
});
describe("given updater is enabled and configuration exists, when started", () => {
let rendered: RenderResult;
beforeEach(async () => {
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
mainDi.override(electronUpdaterIsActiveInjectable, () => true);
mainDi.override(publishIsConfiguredInjectable, () => true);
});
rendered = await applicationBuilder.render();
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("checks for updates", () => {
expect(processCheckingForUpdatesMock).toHaveBeenCalled();
});
it("when just not enough time passes, does not check for updates again automatically yet", () => {
processCheckingForUpdatesMock.mockClear();
jest.advanceTimersByTime(ENOUGH_TIME - 1);
expect(processCheckingForUpdatesMock).not.toHaveBeenCalled();
});
it("when just enough time passes, checks for updates again automatically", () => {
processCheckingForUpdatesMock.mockClear();
jest.advanceTimersByTime(ENOUGH_TIME);
expect(processCheckingForUpdatesMock).toHaveBeenCalled();
});
});
describe("given updater is enabled but no configuration exist, when started", () => {
beforeEach(async () => {
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
mainDi.override(electronUpdaterIsActiveInjectable, () => true);
mainDi.override(publishIsConfiguredInjectable, () => false);
});
await applicationBuilder.render();
});
it("does not check for updates", () => {
expect(processCheckingForUpdatesMock).not.toHaveBeenCalled();
});
it("when time passes, never checks for updates", () => {
jest.runOnlyPendingTimers();
expect(processCheckingForUpdatesMock).not.toHaveBeenCalled();
});
});
describe("given updater is not enabled but and configuration exist, when started", () => {
beforeEach(async () => {
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
mainDi.override(electronUpdaterIsActiveInjectable, () => false);
mainDi.override(publishIsConfiguredInjectable, () => true);
});
await applicationBuilder.render();
});
it("does not check for updates", () => {
expect(processCheckingForUpdatesMock).not.toHaveBeenCalled();
});
it("when time passes, never checks for updates", () => {
jest.runOnlyPendingTimers();
expect(processCheckingForUpdatesMock).not.toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,331 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import quitAndInstallUpdateInjectable from "../../main/electron-app/features/quit-and-install-update.injectable";
import type { RenderResult } from "@testing-library/react";
import electronUpdaterIsActiveInjectable from "../../main/electron-app/features/electron-updater-is-active.injectable";
import publishIsConfiguredInjectable from "../../main/application-update/publish-is-configured.injectable";
import type { CheckForPlatformUpdates } from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
import checkForPlatformUpdatesInjectable from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import type { UpdateChannel, UpdateChannelId } from "../../common/application-update/update-channels";
import { updateChannels } from "../../common/application-update/update-channels";
import type { DownloadPlatformUpdate } from "../../main/application-update/download-platform-update/download-platform-update.injectable";
import downloadPlatformUpdateInjectable from "../../main/application-update/download-platform-update/download-platform-update.injectable";
import selectedUpdateChannelInjectable from "../../common/application-update/selected-update-channel/selected-update-channel.injectable";
import type { IComputedValue } from "mobx";
import setUpdateOnQuitInjectable from "../../main/electron-app/features/set-update-on-quit.injectable";
import type { AskBoolean } from "../../main/ask-boolean/ask-boolean.injectable";
import askBooleanInjectable from "../../main/ask-boolean/ask-boolean.injectable";
import showInfoNotificationInjectable from "../../renderer/components/notifications/show-info-notification.injectable";
import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable";
import appVersionInjectable from "../../common/get-configuration-file-model/app-version/app-version.injectable";
describe("selection of update stability", () => {
let applicationBuilder: ApplicationBuilder;
let quitAndInstallUpdateMock: jest.Mock;
let checkForPlatformUpdatesMock: AsyncFnMock<CheckForPlatformUpdates>;
let downloadPlatformUpdateMock: AsyncFnMock<DownloadPlatformUpdate>;
let setUpdateOnQuitMock: jest.Mock;
let showInfoNotificationMock: jest.Mock;
let askBooleanMock: AsyncFnMock<AskBoolean>;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ mainDi, rendererDi }) => {
quitAndInstallUpdateMock = jest.fn();
checkForPlatformUpdatesMock = asyncFn();
downloadPlatformUpdateMock = asyncFn();
setUpdateOnQuitMock = jest.fn();
showInfoNotificationMock = jest.fn(() => () => {});
askBooleanMock = asyncFn();
rendererDi.override(showInfoNotificationInjectable, () => showInfoNotificationMock);
mainDi.override(askBooleanInjectable, () => askBooleanMock);
mainDi.override(setUpdateOnQuitInjectable, () => setUpdateOnQuitMock);
mainDi.override(
checkForPlatformUpdatesInjectable,
() => checkForPlatformUpdatesMock,
);
mainDi.override(
downloadPlatformUpdateInjectable,
() => downloadPlatformUpdateMock,
);
mainDi.override(
quitAndInstallUpdateInjectable,
() => quitAndInstallUpdateMock,
);
mainDi.override(electronUpdaterIsActiveInjectable, () => true);
mainDi.override(publishIsConfiguredInjectable, () => true);
});
});
describe("when started", () => {
let rendered: RenderResult;
let processCheckingForUpdates: () => Promise<void>;
beforeEach(async () => {
rendered = await applicationBuilder.render();
processCheckingForUpdates = applicationBuilder.dis.mainDi.inject(processCheckingForUpdatesInjectable);
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
describe('given update channel "alpha" is selected, when checking for updates', () => {
let selectedUpdateChannel: {
value: IComputedValue<UpdateChannel>;
setValue: (channelId: UpdateChannelId) => void;
};
beforeEach(() => {
selectedUpdateChannel = applicationBuilder.dis.mainDi.inject(
selectedUpdateChannelInjectable,
);
selectedUpdateChannel.setValue(updateChannels.alpha.id);
processCheckingForUpdates();
});
it('checks updates from update channel "alpha"', () => {
expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith(
updateChannels.alpha,
{ allowDowngrade: true },
);
});
it("when update is discovered, does not check update from other update channels", async () => {
checkForPlatformUpdatesMock.mockClear();
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: true,
version: "some-version",
});
expect(checkForPlatformUpdatesMock).not.toHaveBeenCalled();
});
describe("when no update is discovered", () => {
beforeEach(async () => {
checkForPlatformUpdatesMock.mockClear();
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: false,
});
});
it('checks updates from update channel "beta"', () => {
expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith(
updateChannels.beta,
{ allowDowngrade: true },
);
});
it("when update is discovered, does not check update from other update channels", async () => {
checkForPlatformUpdatesMock.mockClear();
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: true,
version: "some-version",
});
expect(checkForPlatformUpdatesMock).not.toHaveBeenCalled();
});
describe("when no update is discovered again", () => {
beforeEach(async () => {
checkForPlatformUpdatesMock.mockClear();
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: false,
});
});
it('finally checks updates from update channel "latest"', () => {
expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith(
updateChannels.latest,
{ allowDowngrade: true },
);
});
it("when update is discovered, does not check update from other update channels", async () => {
checkForPlatformUpdatesMock.mockClear();
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: true,
version: "some-version",
});
expect(checkForPlatformUpdatesMock).not.toHaveBeenCalled();
});
});
});
});
describe('given update channel "beta" is selected', () => {
let selectedUpdateChannel: {
value: IComputedValue<UpdateChannel>;
setValue: (channelId: UpdateChannelId) => void;
};
beforeEach(() => {
selectedUpdateChannel = applicationBuilder.dis.mainDi.inject(
selectedUpdateChannelInjectable,
);
selectedUpdateChannel.setValue(updateChannels.beta.id);
});
describe("when checking for updates", () => {
beforeEach(() => {
processCheckingForUpdates();
});
describe('when update from "beta" channel is discovered', () => {
beforeEach(async () => {
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: true,
version: "some-beta-version",
});
});
describe("when update is downloaded", () => {
beforeEach(async () => {
await downloadPlatformUpdateMock.resolve({ downloadWasSuccessful: true });
});
it("when user would close the application, installs the update", () => {
expect(setUpdateOnQuitMock).toHaveBeenLastCalledWith(true);
});
it('given user changes update channel to "latest", when user would close the application, does not install the update for not being stable enough', () => {
selectedUpdateChannel.setValue(updateChannels.latest.id);
expect(setUpdateOnQuitMock).toHaveBeenLastCalledWith(false);
});
it('given user changes update channel to "alpha", when user would close the application, installs the update for being stable enough', () => {
selectedUpdateChannel.setValue(updateChannels.alpha.id);
expect(setUpdateOnQuitMock).toHaveBeenLastCalledWith(false);
});
});
});
});
});
});
it("given valid update channel selection is stored, when checking for updates, checks for updates from the update channel", async () => {
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
// TODO: Switch to more natural way of setting initial value
// TODO: UserStore is currently responsible for getting and setting initial value
const selectedUpdateChannel = mainDi.inject(selectedUpdateChannelInjectable);
selectedUpdateChannel.setValue(updateChannels.beta.id);
});
await applicationBuilder.render();
const processCheckingForUpdates = applicationBuilder.dis.mainDi.inject(processCheckingForUpdatesInjectable);
processCheckingForUpdates();
expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith(updateChannels.beta, expect.any(Object));
});
it("given invalid update channel selection is stored, when checking for updates, checks for updates from the update channel", async () => {
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
// TODO: Switch to more natural way of setting initial value
// TODO: UserStore is currently responsible for getting and setting initial value
const selectedUpdateChannel = mainDi.inject(selectedUpdateChannelInjectable);
selectedUpdateChannel.setValue("something-invalid" as UpdateChannelId);
});
await applicationBuilder.render();
const processCheckingForUpdates = applicationBuilder.dis.mainDi.inject(processCheckingForUpdatesInjectable);
processCheckingForUpdates();
expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith(updateChannels.latest, expect.any(Object));
});
it('given no update channel selection is stored and currently using stable release, when user checks for updates, checks for updates from "latest" update channel by default', async () => {
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
mainDi.override(appVersionInjectable, () => "1.0.0");
});
await applicationBuilder.render();
const processCheckingForUpdates = applicationBuilder.dis.mainDi.inject(processCheckingForUpdatesInjectable);
processCheckingForUpdates();
expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith(
updateChannels.latest,
{ allowDowngrade: true },
);
});
it('given no update channel selection is stored and currently using alpha release, when checking for updates, checks for updates from "alpha" channel', async () => {
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
mainDi.override(appVersionInjectable, () => "1.0.0-alpha");
});
await applicationBuilder.render();
const processCheckingForUpdates = applicationBuilder.dis.mainDi.inject(processCheckingForUpdatesInjectable);
processCheckingForUpdates();
expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith(updateChannels.alpha, expect.any(Object));
});
it('given no update channel selection is stored and currently using beta release, when checking for updates, checks for updates from "beta" channel', async () => {
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
mainDi.override(appVersionInjectable, () => "1.0.0-beta");
});
await applicationBuilder.render();
const processCheckingForUpdates = applicationBuilder.dis.mainDi.inject(processCheckingForUpdatesInjectable);
processCheckingForUpdates();
expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith(updateChannels.beta, expect.any(Object));
});
it("given update channel selection is stored and currently using prerelease, when checking for updates, checks for updates from stored channel", async () => {
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
mainDi.override(appVersionInjectable, () => "1.0.0-alpha");
// TODO: Switch to more natural way of setting initial value
// TODO: UserStore is currently responsible for getting and setting initial value
const selectedUpdateChannel = mainDi.inject(selectedUpdateChannelInjectable);
selectedUpdateChannel.setValue(updateChannels.beta.id);
});
await applicationBuilder.render();
const processCheckingForUpdates = applicationBuilder.dis.mainDi.inject(processCheckingForUpdatesInjectable);
processCheckingForUpdates();
expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith(updateChannels.beta, expect.any(Object));
});
});

View File

@ -328,6 +328,9 @@ exports[`cluster - order of sidebar items when rendered renders 1`] = `
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -723,5 +726,8 @@ exports[`cluster - order of sidebar items when rendered when parent is expanded
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -293,6 +293,9 @@ exports[`cluster - sidebar and tab navigation for core given core registrations
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -589,6 +592,9 @@ exports[`cluster - sidebar and tab navigation for core given core registrations
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -909,6 +915,9 @@ exports[`cluster - sidebar and tab navigation for core given core registrations
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -1234,6 +1243,9 @@ exports[`cluster - sidebar and tab navigation for core given core registrations
<div
data-testid="some-child-page"
/>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -1534,6 +1546,9 @@ exports[`cluster - sidebar and tab navigation for core given core registrations
<div
data-testid="some-child-page"
/>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -1854,6 +1869,9 @@ exports[`cluster - sidebar and tab navigation for core given core registrations
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -2150,5 +2168,8 @@ exports[`cluster - sidebar and tab navigation for core given core registrations
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -293,6 +293,9 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -589,6 +592,9 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -929,6 +935,9 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -1313,6 +1322,9 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
</main>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -1697,6 +1709,9 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
</main>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -2036,6 +2051,9 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
</main>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -2376,6 +2394,9 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -2672,5 +2693,8 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -261,6 +261,9 @@ exports[`cluster - visibility of sidebar items given kube resource for route is
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -573,5 +576,8 @@ exports[`cluster - visibility of sidebar items given kube resource for route is
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -1,6 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`extensions - navigation using application menu renders 1`] = `<div />`;
exports[`extensions - navigation using application menu renders 1`] = `
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
exports[`extensions - navigation using application menu when navigating to extensions using application menu renders 1`] = `
<div>
@ -118,5 +124,8 @@ exports[`extensions - navigation using application menu when navigating to exten
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -6,12 +6,7 @@
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 isAutoUpdateEnabledInjectable from "../../main/is-auto-update-enabled.injectable";
import extensionsStoreInjectable from "../../extensions/extensions-store/extensions-store.injectable";
import type { ExtensionsStore } from "../../extensions/extensions-store/extensions-store";
import fileSystemProvisionerStoreInjectable from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
import type { FileSystemProvisionerStore } from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store";
import focusWindowInjectable from "../../renderer/ipc-channel-listeners/focus-window.injectable";
import focusWindowInjectable from "../../renderer/navigation/focus-window.injectable";
// TODO: Make components free of side effects by making them deterministic
jest.mock("../../renderer/components/input/input");
@ -22,11 +17,7 @@ describe("extensions - navigation using application menu", () => {
let focusWindowMock: jest.Mock;
beforeEach(async () => {
applicationBuilder = getApplicationBuilder().beforeApplicationStart(({ mainDi, rendererDi }) => {
mainDi.override(isAutoUpdateEnabledInjectable, () => () => false);
rendererDi.override(extensionsStoreInjectable, () => ({}) as unknown as ExtensionsStore);
rendererDi.override(fileSystemProvisionerStoreInjectable, () => ({}) as unknown as FileSystemProvisionerStore);
applicationBuilder = getApplicationBuilder().beforeApplicationStart(({ rendererDi }) => {
focusWindowMock = jest.fn();
rendererDi.override(focusWindowInjectable, () => focusWindowMock);

View File

@ -454,5 +454,8 @@ exports[`helm-charts - navigation to Helm charts when navigating to Helm charts
</div>
</main>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -356,13 +356,12 @@ exports[`preferences - closing-preferences given accessing preferences directly
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-update-channel-input-placeholder"
class="Select__single-value css-qc6sy-singleValue"
>
Select...
Stable
</div>
<div
class="Select__input-container css-6j8wv5-Input"
@ -370,7 +369,6 @@ exports[`preferences - closing-preferences given accessing preferences directly
>
<input
aria-autocomplete="list"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@ -537,6 +535,9 @@ exports[`preferences - closing-preferences given accessing preferences directly
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -679,6 +680,9 @@ exports[`preferences - closing-preferences given accessing preferences directly
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -687,6 +691,9 @@ exports[`preferences - closing-preferences given accessing preferences directly
<div>
Some front page
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -695,6 +702,9 @@ exports[`preferences - closing-preferences given accessing preferences directly
<div>
Some front page
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -1054,13 +1064,12 @@ exports[`preferences - closing-preferences given already in a page and then navi
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-update-channel-input-placeholder"
class="Select__single-value css-qc6sy-singleValue"
>
Select...
Stable
</div>
<div
class="Select__input-container css-6j8wv5-Input"
@ -1068,7 +1077,6 @@ exports[`preferences - closing-preferences given already in a page and then navi
>
<input
aria-autocomplete="list"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@ -1235,6 +1243,9 @@ exports[`preferences - closing-preferences given already in a page and then navi
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -1377,6 +1388,9 @@ exports[`preferences - closing-preferences given already in a page and then navi
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -1519,6 +1533,9 @@ exports[`preferences - closing-preferences given already in a page and then navi
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -1661,5 +1678,8 @@ exports[`preferences - closing-preferences given already in a page and then navi
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -199,6 +199,9 @@ exports[`preferences - navigation to application preferences given in some child
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -546,13 +549,12 @@ exports[`preferences - navigation to application preferences given in some child
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-update-channel-input-placeholder"
class="Select__single-value css-qc6sy-singleValue"
>
Select...
Stable
</div>
<div
class="Select__input-container css-6j8wv5-Input"
@ -560,7 +562,6 @@ exports[`preferences - navigation to application preferences given in some child
>
<input
aria-autocomplete="list"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@ -727,5 +728,8 @@ exports[`preferences - navigation to application preferences given in some child
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -344,13 +344,12 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-update-channel-input-placeholder"
class="Select__single-value css-qc6sy-singleValue"
>
Select...
Stable
</div>
<div
class="Select__input-container css-6j8wv5-Input"
@ -358,7 +357,6 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<input
aria-autocomplete="list"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@ -525,6 +523,9 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -935,5 +936,8 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -344,13 +344,12 @@ exports[`preferences - navigation to extension specific preferences given in pre
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-update-channel-input-placeholder"
class="Select__single-value css-qc6sy-singleValue"
>
Select...
Stable
</div>
<div
class="Select__input-container css-6j8wv5-Input"
@ -358,7 +357,6 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<input
aria-autocomplete="list"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@ -525,6 +523,9 @@ exports[`preferences - navigation to extension specific preferences given in pre
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -884,13 +885,12 @@ exports[`preferences - navigation to extension specific preferences given in pre
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-update-channel-input-placeholder"
class="Select__single-value css-qc6sy-singleValue"
>
Select...
Stable
</div>
<div
class="Select__input-container css-6j8wv5-Input"
@ -898,7 +898,6 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<input
aria-autocomplete="list"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@ -1065,6 +1064,9 @@ exports[`preferences - navigation to extension specific preferences given in pre
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -1239,5 +1241,8 @@ exports[`preferences - navigation to extension specific preferences given in pre
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -344,13 +344,12 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-update-channel-input-placeholder"
class="Select__single-value css-qc6sy-singleValue"
>
Select...
Stable
</div>
<div
class="Select__input-container css-6j8wv5-Input"
@ -358,7 +357,6 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<input
aria-autocomplete="list"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@ -525,6 +523,9 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -836,7 +837,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
class="flex gaps"
>
<div
class="Select theme-lens box grow css-b62m3t-container"
class="Select theme-lens box grow Select--is-disabled css-3iigni-container"
>
<span
class="css-1f43avz-a11yText-A11yText"
@ -849,7 +850,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
class="css-1f43avz-a11yText-A11yText"
/>
<div
class="Select__control css-1s2u09g-control"
class="Select__control Select__control--is-disabled css-1insrsq-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
@ -861,7 +862,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
Repositories
</div>
<div
class="Select__input-container css-6j8wv5-Input"
class="Select__input-container css-jzldcf-Input"
data-value=""
>
<input
@ -873,6 +874,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
autocomplete="off"
autocorrect="off"
class="Select__input"
disabled=""
id="HelmRepoSelect"
role="combobox"
spellcheck="false"
@ -886,8 +888,22 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
<div
class="Select__indicators css-1hb7zxy-IndicatorsContainer"
>
<div
aria-hidden="true"
class="Select__indicator Select__loading-indicator css-at12u2-loadingIndicator"
>
<span
class="css-1xtdfmb-LoadingDot"
/>
<span
class="css-zoievk-LoadingDot"
/>
<span
class="css-x748d8-LoadingDot"
/>
</div>
<span
class="Select__indicator-separator css-1okebmr-indicatorSeparator"
class="Select__indicator-separator css-109onse-indicatorSeparator"
/>
<div
aria-hidden="true"
@ -920,13 +936,11 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
class="repos"
>
<div
class="notice"
class="pt-5 relative"
>
<div
class="flex-grow text-center"
>
The repositories have not been added yet
</div>
class="Spinner singleColor center"
/>
</div>
</div>
</div>
@ -969,5 +983,8 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -344,13 +344,12 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-update-channel-input-placeholder"
class="Select__single-value css-qc6sy-singleValue"
>
Select...
Stable
</div>
<div
class="Select__input-container css-6j8wv5-Input"
@ -358,7 +357,6 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
>
<input
aria-autocomplete="list"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@ -525,6 +523,9 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -727,5 +728,8 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -185,6 +185,9 @@ exports[`preferences - navigation to telemetry preferences given URL for Sentry
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -532,13 +535,12 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-update-channel-input-placeholder"
class="Select__single-value css-qc6sy-singleValue"
>
Select...
Stable
</div>
<div
class="Select__input-container css-6j8wv5-Input"
@ -546,7 +548,6 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@ -713,6 +714,9 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -1072,13 +1076,12 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-update-channel-input-placeholder"
class="Select__single-value css-qc6sy-singleValue"
>
Select...
Stable
</div>
<div
class="Select__input-container css-6j8wv5-Input"
@ -1086,7 +1089,6 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@ -1253,6 +1255,9 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -1429,6 +1434,9 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -1568,5 +1576,8 @@ exports[`preferences - navigation to telemetry preferences given no URL for Sent
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -344,13 +344,12 @@ exports[`preferences - navigation to terminal preferences given in preferences,
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-update-channel-input-placeholder"
class="Select__single-value css-qc6sy-singleValue"
>
Select...
Stable
</div>
<div
class="Select__input-container css-6j8wv5-Input"
@ -358,7 +357,6 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@ -525,6 +523,9 @@ exports[`preferences - navigation to terminal preferences given in preferences,
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@ -770,6 +771,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<input
class="input box grow"
max="50"
min="10"
spellcheck="false"
type="number"
@ -789,22 +791,78 @@ exports[`preferences - navigation to terminal preferences given in preferences,
</div>
<div
class="Input theme round black"
class="Select theme-lens css-b62m3t-container"
>
<label
class="input-area flex gaps align-center"
id=""
>
<input
class="input box grow"
spellcheck="false"
type="text"
value=""
/>
</label>
<div
class="input-info flex gaps"
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
/>
<div
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-2-placeholder"
>
Select...
</div>
<div
class="Select__input-container css-6j8wv5-Input"
data-value=""
>
<input
aria-autocomplete="list"
aria-describedby="react-select-2-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="Select__input"
id="react-select-2-input"
role="combobox"
spellcheck="false"
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
</div>
</div>
<div
class="Select__indicators css-1hb7zxy-IndicatorsContainer"
>
<span
class="Select__indicator-separator css-1okebmr-indicatorSeparator"
/>
<div
aria-hidden="true"
class="Select__indicator Select__dropdown-indicator css-tlfecz-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
>
<path
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
/>
</svg>
</div>
</div>
</div>
</div>
</section>
</section>
@ -845,5 +903,8 @@ exports[`preferences - navigation to terminal preferences given in preferences,
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -1,6 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`preferences - navigation using application menu renders 1`] = `<div />`;
exports[`preferences - navigation using application menu renders 1`] = `
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
exports[`preferences - navigation using application menu when navigating to preferences using application menu renders 1`] = `
<div>
@ -346,13 +352,12 @@ exports[`preferences - navigation using application menu when navigating to pref
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-update-channel-input-placeholder"
class="Select__single-value css-qc6sy-singleValue"
>
Select...
Stable
</div>
<div
class="Select__input-container css-6j8wv5-Input"
@ -360,7 +365,6 @@ exports[`preferences - navigation using application menu when navigating to pref
>
<input
aria-autocomplete="list"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@ -527,5 +531,8 @@ exports[`preferences - navigation using application menu when navigating to pref
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -0,0 +1,542 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`show-about-using-tray renders 1`] = `
<body>
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
</body>
`;
exports[`show-about-using-tray when navigating using tray renders 1`] = `
<body>
<div>
<div
class="SettingLayout showNavigation Preferences"
data-testid="application-preferences-page"
>
<nav
class="sidebarRegion"
>
<div
class="sidebar"
>
<div
class="Tabs flex column"
>
<div
class="header"
>
Preferences
</div>
<div
class="Tab flex gaps align-center active"
data-testid="tab-link-for-application"
role="tab"
tabindex="0"
>
<div
class="label"
>
App
</div>
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-proxy"
role="tab"
tabindex="0"
>
<div
class="label"
>
Proxy
</div>
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-kubernetes"
role="tab"
tabindex="0"
>
<div
class="label"
>
Kubernetes
</div>
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-editor"
role="tab"
tabindex="0"
>
<div
class="label"
>
Editor
</div>
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-terminal"
role="tab"
tabindex="0"
>
<div
class="label"
>
Terminal
</div>
</div>
</div>
</div>
</nav>
<div
class="contentRegion"
id="ScrollSpyRoot"
>
<div
class="content"
>
<section
id="application"
>
<h2
data-testid="application-header"
>
Application
</h2>
<section
id="appearance"
>
<div
class="SubTitle"
>
Theme
</div>
<div
class="Select theme-lens css-b62m3t-container"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-theme-input-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
/>
<div
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-theme-input-placeholder"
>
Select...
</div>
<div
class="Select__input-container css-6j8wv5-Input"
data-value=""
>
<input
aria-autocomplete="list"
aria-describedby="react-select-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="Select__input"
id="theme-input"
role="combobox"
spellcheck="false"
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
</div>
</div>
<div
class="Select__indicators css-1hb7zxy-IndicatorsContainer"
>
<span
class="Select__indicator-separator css-1okebmr-indicatorSeparator"
/>
<div
aria-hidden="true"
class="Select__indicator Select__dropdown-indicator css-tlfecz-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
>
<path
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
/>
</svg>
</div>
</div>
</div>
</div>
</section>
<hr />
<section
id="extensionRegistryUrl"
>
<div
class="SubTitle"
>
Extension Install Registry
</div>
<div
class="Select theme-lens css-b62m3t-container"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-extension-install-registry-input-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
/>
<div
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-extension-install-registry-input-placeholder"
>
Select...
</div>
<div
class="Select__input-container css-6j8wv5-Input"
data-value=""
>
<input
aria-autocomplete="list"
aria-describedby="react-select-extension-install-registry-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="Select__input"
id="extension-install-registry-input"
role="combobox"
spellcheck="false"
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
</div>
</div>
<div
class="Select__indicators css-1hb7zxy-IndicatorsContainer"
>
<span
class="Select__indicator-separator css-1okebmr-indicatorSeparator"
/>
<div
aria-hidden="true"
class="Select__indicator Select__dropdown-indicator css-tlfecz-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
>
<path
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
/>
</svg>
</div>
</div>
</div>
</div>
<p
class="mt-4 mb-5 leading-relaxed"
>
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your
<b>
.npmrc
</b>
file or in the input below.
</p>
<div
class="Input theme round black disabled invalid"
>
<label
class="input-area flex gaps align-center"
id=""
>
<input
class="input box grow"
disabled=""
placeholder="Custom Extension Registry URL..."
spellcheck="false"
value="some-custom-url"
/>
</label>
<div
class="input-info flex gaps"
/>
</div>
</section>
<hr />
<section
id="other"
>
<div
class="SubTitle"
>
Start-up
</div>
<label
class="Switch"
data-testid="switch"
>
Automatically start Lens on login
<input
role="switch"
type="checkbox"
/>
</label>
</section>
<hr />
<section
id="update-channel"
>
<div
class="SubTitle"
>
Update Channel
</div>
<div
class="Select theme-lens css-b62m3t-container"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-update-channel-input-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
/>
<div
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
>
<div
class="Select__single-value css-qc6sy-singleValue"
>
Stable
</div>
<div
class="Select__input-container css-6j8wv5-Input"
data-value=""
>
<input
aria-autocomplete="list"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="Select__input"
id="update-channel-input"
role="combobox"
spellcheck="false"
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
</div>
</div>
<div
class="Select__indicators css-1hb7zxy-IndicatorsContainer"
>
<span
class="Select__indicator-separator css-1okebmr-indicatorSeparator"
/>
<div
aria-hidden="true"
class="Select__indicator Select__dropdown-indicator css-tlfecz-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
>
<path
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
/>
</svg>
</div>
</div>
</div>
</div>
</section>
<hr />
<section
id="locale"
>
<div
class="SubTitle"
>
Locale Timezone
</div>
<div
class="Select theme-lens css-b62m3t-container"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-timezone-input-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
/>
<div
class="Select__control css-1s2u09g-control"
>
<div
class="Select__value-container css-319lph-ValueContainer"
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-timezone-input-placeholder"
>
Select...
</div>
<div
class="Select__input-container css-6j8wv5-Input"
data-value=""
>
<input
aria-autocomplete="list"
aria-describedby="react-select-timezone-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="Select__input"
id="timezone-input"
role="combobox"
spellcheck="false"
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
</div>
</div>
<div
class="Select__indicators css-1hb7zxy-IndicatorsContainer"
>
<span
class="Select__indicator-separator css-1okebmr-indicatorSeparator"
/>
<div
aria-hidden="true"
class="Select__indicator Select__dropdown-indicator css-tlfecz-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
>
<path
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
/>
</svg>
</div>
</div>
</div>
</div>
</section>
</section>
</div>
<div
class="toolsRegion"
>
<div
class="fixed top-[60px]"
>
<div
data-testid="close-preferences"
>
<div
aria-label="Close"
class="closeButton"
role="button"
>
<i
class="Icon icon material focusable"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
</div>
<div
aria-hidden="true"
class="esc"
>
ESC
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
</body>
`;

View File

@ -5,17 +5,12 @@
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 defaultShellInjectable from "../../renderer/components/+preferences/default-shell.injectable";
describe("preferences - navigation to terminal preferences", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.override(defaultShellInjectable, () => "some-default-shell");
});
});
describe("given in preferences, when rendered", () => {

View File

@ -0,0 +1,44 @@
/**
* 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";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
describe("show-about-using-tray", () => {
let applicationBuilder: ApplicationBuilder;
let rendered: RenderResult;
beforeEach(async () => {
applicationBuilder = getApplicationBuilder();
rendered = await applicationBuilder.render();
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("does not show application preferences page yet", () => {
const actual = rendered.queryByTestId("application-preferences-page");
expect(actual).toBeNull();
});
describe("when navigating using tray", () => {
beforeEach(async () => {
await applicationBuilder.tray.click("open-preferences");
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("shows application preferences page", () => {
const actual = rendered.getByTestId("application-preferences-page");
expect(actual).not.toBeNull();
});
});
});

View File

@ -1,6 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`welcome - navigation using application menu renders 1`] = `<div />`;
exports[`welcome - navigation using application menu renders 1`] = `
<div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
exports[`welcome - navigation using application menu when navigating to welcome using application menu renders 1`] = `
<div>
@ -87,5 +93,8 @@ exports[`welcome - navigation using application menu when navigating to welcome
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;

View File

@ -6,16 +6,13 @@
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 isAutoUpdateEnabledInjectable from "../../main/is-auto-update-enabled.injectable";
describe("welcome - navigation using application menu", () => {
let applicationBuilder: ApplicationBuilder;
let rendered: RenderResult;
beforeEach(async () => {
applicationBuilder = getApplicationBuilder().beforeApplicationStart(({ mainDi }) => {
mainDi.override(isAutoUpdateEnabledInjectable, () => () => false);
});
applicationBuilder = getApplicationBuilder();
rendered = await applicationBuilder.render();
});

View File

@ -22,6 +22,9 @@ import getConfigurationFileModelInjectable from "../get-configuration-file-model
import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable";
import assert from "assert";
import directoryForTempInjectable from "../app-paths/directory-for-temp/directory-for-temp.injectable";
import kubectlBinaryNameInjectable from "../../main/kubectl/binary-name.injectable";
import kubectlDownloadingNormalizedArchInjectable from "../../main/kubectl/normalized-arch.injectable";
import normalizedPlatformInjectable from "../vars/normalized-platform.injectable";
console = new Console(stdout, stderr);
@ -84,6 +87,9 @@ describe("cluster-store", () => {
mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
mainDi.override(directoryForTempInjectable, () => "some-temp-directory");
mainDi.override(kubectlBinaryNameInjectable, () => "kubectl");
mainDi.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
mainDi.override(normalizedPlatformInjectable, () => "darwin");
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
mainDi.permitSideEffects(appVersionInjectable);
@ -363,6 +369,8 @@ users:
mockFs(mockOpts);
mainDi.override(appVersionInjectable, () => "3.6.0");
createCluster = mainDi.inject(createClusterInjectionToken);
clusterStore = mainDi.inject(clusterStoreInjectable);

View File

@ -21,7 +21,7 @@ jest.mock("electron", () => ({
},
}));
import { UserStore } from "../user-store";
import type { UserStore } from "../user-store";
import { Console } from "console";
import { SemVer } from "semver";
import electron from "electron";
@ -49,14 +49,15 @@ describe("user store tests", () => {
di.override(writeFileInjectable, () => () => Promise.resolve());
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(userStoreInjectable, () => UserStore.createInstance());
di.permitSideEffects(getConfigurationFileModelInjectable);
di.permitSideEffects(appVersionInjectable);
di.permitSideEffects(userStoreInjectable);
di.unoverride(userStoreInjectable);
});
afterEach(() => {
UserStore.resetInstance();
mockFs.restore();
});
@ -126,6 +127,8 @@ describe("user store tests", () => {
},
});
di.override(appVersionInjectable, () => "10.0.0");
userStore = di.inject(userStoreInjectable);
});

View File

@ -4,12 +4,9 @@
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { PathName } from "./app-path-names";
import { createChannel } from "../ipc-channel/create-channel/create-channel";
export type AppPaths = Record<PathName, string>;
export const appPathsInjectionToken = getInjectionToken<AppPaths>({ id: "app-paths-token" });
export const appPathsIpcChannel = createChannel<AppPaths>("app-paths");

View File

@ -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 type { AppPaths } from "./app-path-injection-token";
import type { RequestChannel } from "../utils/channel/request-channel-injection-token";
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
export type AppPathsChannel = RequestChannel<void, AppPaths>;
const appPathsChannelInjectable = getInjectable({
id: "app-paths-channel",
instantiate: (): AppPathsChannel => ({
id: "app-paths",
}),
injectionToken: messageChannelInjectionToken,
});
export default appPathsChannelInjectable;

View File

@ -1,13 +0,0 @@
/**
* 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 { baseBinariesDir } from "../../vars";
const directoryForBundledBinariesInjectable = getInjectable({
id: "directory-for-bundled-binaries",
instantiate: () => baseBinariesDir.get(),
});
export default directoryForBundledBinariesInjectable;

View File

@ -0,0 +1,29 @@
/**
* 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 type { MessageChannel } from "../utils/channel/message-channel-injection-token";
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
export type ApplicationUpdateStatusEventId =
| "checking-for-updates"
| "no-updates-available"
| "download-for-update-started"
| "download-for-update-failed";
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type ApplicationUpdateStatusChannelMessage = { eventId: ApplicationUpdateStatusEventId; version?: string };
export type ApplicationUpdateStatusChannel = MessageChannel<ApplicationUpdateStatusChannelMessage>;
const applicationUpdateStatusChannelInjectable = getInjectable({
id: "application-update-status-channel",
instantiate: (): ApplicationUpdateStatusChannel => ({
id: "application-update-status-channel",
}),
injectionToken: messageChannelInjectionToken,
});
export default applicationUpdateStatusChannelInjectable;

View File

@ -0,0 +1,28 @@
/**
* 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 createSyncBoxInjectable from "../../utils/sync-box/create-sync-box.injectable";
import type { UpdateChannel } from "../update-channels";
import { syncBoxInjectionToken } from "../../utils/sync-box/sync-box-injection-token";
const discoveredUpdateVersionInjectable = getInjectable({
id: "discovered-update-version",
instantiate: (di) => {
const createSyncBox = di.inject(createSyncBoxInjectable);
return createSyncBox<
| { version: string; updateChannel: UpdateChannel }
| null
>(
"discovered-update-version",
null,
);
},
injectionToken: syncBoxInjectionToken,
});
export default discoveredUpdateVersionInjectable;

View File

@ -0,0 +1,25 @@
/**
* 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 createSyncBoxInjectable from "../../utils/sync-box/create-sync-box.injectable";
import { syncBoxInjectionToken } from "../../utils/sync-box/sync-box-injection-token";
export interface ProgressOfDownload {
percentage: number;
}
const progressOfUpdateDownloadInjectable = getInjectable({
id: "progress-of-update-download-state",
instantiate: (di) => {
const createSyncBox = di.inject(createSyncBoxInjectable);
return createSyncBox<ProgressOfDownload>("progress-of-update-download", { percentage: 0 });
},
injectionToken: syncBoxInjectionToken,
});
export default progressOfUpdateDownloadInjectable;

View File

@ -0,0 +1,27 @@
/**
* 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 { SemVer } from "semver";
import appVersionInjectable from "../../get-configuration-file-model/app-version/app-version.injectable";
import type { UpdateChannelId } from "../update-channels";
import { updateChannels } from "../update-channels";
const defaultUpdateChannelInjectable = getInjectable({
id: "default-update-channel",
instantiate: (di) => {
const appVersion = di.inject(appVersionInjectable);
const currentReleaseChannel = new SemVer(appVersion).prerelease[0]?.toString() as UpdateChannelId;
if (currentReleaseChannel && updateChannels[currentReleaseChannel]) {
return updateChannels[currentReleaseChannel];
}
return updateChannels.latest;
},
});
export default defaultUpdateChannelInjectable;

View File

@ -0,0 +1,39 @@
/**
* 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 type { IComputedValue } from "mobx";
import { action, computed, observable } from "mobx";
import type { UpdateChannel, UpdateChannelId } from "../update-channels";
import { updateChannels } from "../update-channels";
import defaultUpdateChannelInjectable from "./default-update-channel.injectable";
export interface SelectedUpdateChannel {
value: IComputedValue<UpdateChannel>;
setValue: (channelId?: UpdateChannelId) => void;
}
const selectedUpdateChannelInjectable = getInjectable({
id: "selected-update-channel",
instantiate: (di): SelectedUpdateChannel => {
const defaultUpdateChannel = di.inject(defaultUpdateChannelInjectable);
const state = observable.box(defaultUpdateChannel);
return {
value: computed(() => state.get()),
setValue: action((channelId) => {
const targetUpdateChannel =
channelId && updateChannels[channelId]
? updateChannels[channelId]
: defaultUpdateChannel;
state.set(targetUpdateChannel);
}),
};
},
});
export default selectedUpdateChannelInjectable;

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export type UpdateChannelId = "alpha" | "beta" | "latest";
const latestChannel: UpdateChannel = {
id: "latest",
label: "Stable",
moreStableUpdateChannel: null,
};
const betaChannel: UpdateChannel = {
id: "beta",
label: "Beta",
moreStableUpdateChannel: latestChannel,
};
const alphaChannel: UpdateChannel = {
id: "alpha",
label: "Alpha",
moreStableUpdateChannel: betaChannel,
};
export const updateChannels: Record<UpdateChannelId, UpdateChannel> = {
latest: latestChannel,
beta: betaChannel,
alpha: alphaChannel,
};
export interface UpdateChannel {
readonly id: UpdateChannelId;
readonly label: string;
readonly moreStableUpdateChannel: UpdateChannel | null;
}

View File

@ -0,0 +1,21 @@
/**
* 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 createSyncBoxInjectable from "../../utils/sync-box/create-sync-box.injectable";
import { syncBoxInjectionToken } from "../../utils/sync-box/sync-box-injection-token";
const updateIsBeingDownloadedInjectable = getInjectable({
id: "update-is-being-downloaded",
instantiate: (di) => {
const createSyncBox = di.inject(createSyncBoxInjectable);
return createSyncBox("update-is-being-downloaded", false);
},
injectionToken: syncBoxInjectionToken,
});
export default updateIsBeingDownloadedInjectable;

View File

@ -0,0 +1,21 @@
/**
* 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 createSyncBoxInjectable from "../../utils/sync-box/create-sync-box.injectable";
import { syncBoxInjectionToken } from "../../utils/sync-box/sync-box-injection-token";
const updatesAreBeingDiscoveredInjectable = getInjectable({
id: "updates-are-being-discovered",
instantiate: (di) => {
const createSyncBox = di.inject(createSyncBoxInjectable);
return createSyncBox("updates-are-being-discovered", false);
},
injectionToken: syncBoxInjectionToken,
});
export default updatesAreBeingDiscoveredInjectable;

View File

@ -0,0 +1,21 @@
/**
* 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 type { MessageChannel } from "../utils/channel/message-channel-injection-token";
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
export type AskBooleanAnswerChannel = MessageChannel<{ id: string; value: boolean }>;
const askBooleanAnswerChannelInjectable = getInjectable({
id: "ask-boolean-answer-channel",
instantiate: (): AskBooleanAnswerChannel => ({
id: "ask-boolean-answer",
}),
injectionToken: messageChannelInjectionToken,
});
export default askBooleanAnswerChannelInjectable;

View File

@ -0,0 +1,23 @@
/**
* 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 type { MessageChannel } from "../utils/channel/message-channel-injection-token";
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type AskBooleanQuestionParameters = { id: string; title: string; question: string };
export type AskBooleanQuestionChannel = MessageChannel<AskBooleanQuestionParameters>;
const askBooleanQuestionChannelInjectable = getInjectable({
id: "ask-boolean-question-channel",
instantiate: (): AskBooleanQuestionChannel => ({
id: "ask-boolean-question",
}),
injectionToken: messageChannelInjectionToken,
});
export default askBooleanQuestionChannelInjectable;

View File

@ -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 { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
import type { MessageChannel } from "../utils/channel/message-channel-injection-token";
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
export type AppNavigationChannel = MessageChannel<string>;
const appNavigationChannelInjectable = getInjectable({
id: "app-navigation-channel",
instantiate: (): AppNavigationChannel => ({
id: IpcRendererNavigationEvents.NAVIGATE_IN_APP,
}),
injectionToken: messageChannelInjectionToken,
});
export default appNavigationChannelInjectable;

View File

@ -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 { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
import type { MessageChannel } from "../utils/channel/message-channel-injection-token";
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
export type ClusterFrameNavigationChannel = MessageChannel<string>;
const clusterFrameNavigationChannelInjectable = getInjectable({
id: "cluster-frame-navigation-channel",
instantiate: (): ClusterFrameNavigationChannel => ({
id: IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER,
}),
injectionToken: messageChannelInjectionToken,
});
export default clusterFrameNavigationChannelInjectable;

View File

@ -1,9 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { createChannel } from "../ipc-channel/create-channel/create-channel";
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
export const appNavigationIpcChannel = createChannel<string>(IpcRendererNavigationEvents.NAVIGATE_IN_APP);
export const clusterFrameNavigationIpcChannel = createChannel<string>(IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER);

View File

@ -3,12 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import packageInfo from "../../../../package.json";
import packageJsonInjectable from "../../vars/package-json.injectable";
const appVersionInjectable = getInjectable({
id: "app-version",
instantiate: () => packageInfo.version,
causesSideEffects: true,
instantiate: (di) => di.inject(packageJsonInjectable).version,
});
export default appVersionInjectable;

View File

@ -1,10 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Channel } from "../channel";
export const createChannel = <Message>(name: string): Channel<Message> => ({
name,
_template: null as never,
});

View File

@ -5,5 +5,4 @@
export * from "./ipc";
export * from "./invalid-kubeconfig";
export * from "./update-available";
export * from "./type-enforced-ipc";

View File

@ -1,53 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { UpdateInfo } from "electron-updater";
export const UpdateAvailableChannel = "update-available";
export const AutoUpdateChecking = "auto-update:checking";
export const AutoUpdateNoUpdateAvailable = "auto-update:no-update";
export const AutoUpdateQuitAndInstalledChannel = "auto-update:quit-and-install";
export const AutoUpdateLogPrefix = "[UPDATE-CHECKER]";
export type UpdateAvailableFromMain = [backChannel: string, updateInfo: UpdateInfo];
export function areArgsUpdateAvailableFromMain(args: unknown[]): args is UpdateAvailableFromMain {
if (args.length !== 2) {
return false;
}
if (typeof args[0] !== "string") {
return false;
}
if (typeof args[1] !== "object" || args[1] === null) {
// TODO: improve this checking
return false;
}
return true;
}
export type BackchannelArg = {
doUpdate: false;
} | {
doUpdate: true;
now: boolean;
};
export type UpdateAvailableToBackchannel = [updateDecision: BackchannelArg];
export function areArgsUpdateAvailableToBackchannel(args: unknown[]): args is UpdateAvailableToBackchannel {
if (args.length !== 1) {
return false;
}
if (typeof args[0] !== "object" || args[0] === null) {
// TODO: improve this checking
return false;
}
return true;
}

View File

@ -8,7 +8,61 @@ import { Node } from "../endpoints";
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
describe("Nodes tests", () => {
describe("Node tests", () => {
describe("isMasterNode()", () => {
it("given a master node labelled before kubernetes 1.20, should return true", () => {
const node = new Node({
apiVersion: "foo",
kind: "Node",
metadata: {
name: "bar",
resourceVersion: "1",
uid: "bat",
labels: {
"node-role.kubernetes.io/master": "NoSchedule",
},
selfLink: "/api/v1/nodes/bar",
},
});
expect(node.isMasterNode()).toBe(true);
});
it("given a master node labelled after kubernetes 1.20, should return true", () => {
const node = new Node({
apiVersion: "foo",
kind: "Node",
metadata: {
name: "bar",
resourceVersion: "1",
uid: "bat",
labels: {
"node-role.kubernetes.io/control-plane": "NoSchedule",
},
selfLink: "/api/v1/nodes/bar",
},
});
expect(node.isMasterNode()).toBe(true);
});
it("given a non master node, should return false", () => {
const node = new Node({
apiVersion: "foo",
kind: "Node",
metadata: {
name: "bar",
resourceVersion: "1",
uid: "bat",
labels: {},
selfLink: "/api/v1/nodes/bar",
},
});
expect(node.isMasterNode()).toBe(false);
});
});
describe("getRoleLabels()", () => {
it("should return empty string if labels is not present", () => {
const node = new Node({

View File

@ -10,12 +10,14 @@ import type { BaseKubeObjectCondition, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { DerivedKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { JSONSchemaProps } from "./types/json-schema-props";
interface AdditionalPrinterColumnsCommon {
name: string;
type: "integer" | "number" | "string" | "boolean" | "date";
priority: number;
description: string;
priority?: number;
format?: "int32" | "int64" | "float" | "double" | "byte" | "binary" | "date" | "date-time" | "password";
description?: string;
}
export type AdditionalPrinterColumnsV1 = AdditionalPrinterColumnsCommon & {
@ -26,11 +28,15 @@ type AdditionalPrinterColumnsV1Beta = AdditionalPrinterColumnsCommon & {
JSONPath: string;
};
export interface CustomResourceValidation {
openAPIV3Schema?: JSONSchemaProps;
}
export interface CustomResourceDefinitionVersion {
name: string;
served: boolean;
storage: boolean;
schema?: object; // required in v1 but not present in v1beta
schema?: CustomResourceValidation; // required in v1 but not present in v1beta
additionalPrinterColumns?: AdditionalPrinterColumnsV1[];
}

View File

@ -5,7 +5,7 @@
import type { BaseKubeObjectCondition, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import { cpuUnitsToNumber, unitsToBytes } from "../../../renderer/utils";
import { cpuUnitsToNumber, unitsToBytes, isObject } from "../../../renderer/utils";
import type { MetricData } from "./metrics.api";
import { metricsApi } from "./metrics.api";
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
@ -69,6 +69,17 @@ export interface NodeCondition extends BaseKubeObjectCondition {
lastHeartbeatTime?: string;
}
/**
* These role label prefixs are the ones that are for master nodes
*
* The `master` label has been deprecated in Kubernetes 1.20, and will be removed in 1.25 so we
* have to also use the newer `control-plane` label
*/
const masterNodeLabels = [
"master",
"control-plane",
];
/**
* This regex is used in the `getRoleLabels()` method bellow, but placed here
* as factoring out regexes is best practice.
@ -189,15 +200,19 @@ export class Node extends KubeObject<NodeStatus, NodeSpec, KubeObjectScope.Clust
return this.spec.taints || [];
}
getRoleLabels(): string {
isMasterNode(): boolean {
return this.getRoleLabelItems()
.some(roleLabel => masterNodeLabels.includes(roleLabel));
}
getRoleLabelItems(): string[] {
const { labels } = this.metadata;
if (!labels || typeof labels !== "object") {
return "";
}
const roleLabels: string[] = [];
if (!isObject(labels)) {
return roleLabels;
}
for (const labelKey of Object.keys(labels)) {
const match = nodeRoleLabelKeyMatcher.match(labelKey);
@ -214,7 +229,11 @@ export class Node extends KubeObject<NodeStatus, NodeSpec, KubeObjectScope.Clust
roleLabels.push(labels["node.kubernetes.io/role"]);
}
return roleLabels.join(", ");
return roleLabels;
}
getRoleLabels(): string {
return this.getRoleLabelItems().join(", ");
}
getCpuCapacity() {

View File

@ -294,8 +294,10 @@ export interface CephfsSource {
secretRef?: SecretReference;
/**
* Whether the filesystem is used as readOnly.
*
* @default false
*/
readOnly: boolean;
readOnly?: boolean;
}
export interface CinderSource {
@ -445,46 +447,62 @@ export interface PortworxVolumeSource {
readOnly?: boolean;
}
export interface KeyToPath {
key: string;
path: string;
mode?: number;
}
export interface ConfigMapProjection {
name: string;
items?: KeyToPath[];
optional?: boolean;
}
export interface ObjectFieldSelector {
fieldPath: string;
apiVersion?: string;
}
export interface ResourceFieldSelector {
resource: string;
containerName?: string;
divisor?: string;
}
export interface DownwardAPIVolumeFile {
path: string;
fieldRef?: ObjectFieldSelector;
resourceFieldRef?: ResourceFieldSelector;
mode?: number;
}
export interface DownwardAPIProjection {
items?: DownwardAPIVolumeFile[];
}
export interface SecretProjection {
name: string;
items?: KeyToPath[];
optional?: boolean;
}
export interface ServiceAccountTokenProjection {
audience?: string;
expirationSeconds?: number;
path: string;
}
export interface VolumeProjection {
secret?: SecretProjection;
downwardAPI?: DownwardAPIProjection;
configMap?: ConfigMapProjection;
serviceAccountToken?: ServiceAccountTokenProjection;
}
export interface ProjectedSource {
sources: {
secret?: {
name: string;
items?: {
key: string;
path: string;
mode?: number;
}[];
};
downwardAPI?: {
items?: {
path: string;
fieldRef?: {
fieldPath: string;
apiVersion?: string;
};
resourceFieldRef?: {
resource: string;
containerName?: string;
};
mode?: number;
}[];
};
configMap?: {
name: string;
items?: {
key: string;
path: string;
mode?: number;
}[];
optional?: boolean;
};
serviceAccountToken?: {
audience?: string;
expirationSeconds?: number;
path: string;
};
}[];
defaultMode: number;
sources?: VolumeProjection[];
defaultMode?: number;
}
export interface QuobyteSource {

View File

@ -0,0 +1,9 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface ExternalDocumentation {
description?: string;
url?: string;
}

View File

@ -0,0 +1,93 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { JsonValue } from "type-fest";
import type { ExternalDocumentation } from "./external-documentation";
export interface JSONSchemaProps {
$ref?: string;
$schema?: string;
additionalItems?: JSONSchemaProps | boolean;
additionalProperties?: JSONSchemaProps | boolean;
allOf?: JSONSchemaProps[];
anyOf?: JSONSchemaProps[];
/**
* default is a default value for undefined object fields.
* Defaulting is a beta feature under the CustomResourceDefaulting feature gate.
* Defaulting requires spec.preserveUnknownFields to be false.
*/
_default?: object;
definitions?: Partial<Record<string, JSONSchemaProps>>;
dependencies?: Partial<Record<string, object>>;
description?: string;
_enum?: object[];
example?: JsonValue;
exclusiveMaximum?: boolean;
exclusiveMinimum?: boolean;
externalDocs?: ExternalDocumentation;
/**
* format is an OpenAPI v3 format string.
* Unknown formats are ignored.
*
* The following formats are validated:
* - bsonobjectid: a bson object ID, i.e. a 24 characters hex string
* - uri: an URI as parsed by Golang net/url.ParseRequestURI
* - email: an email address as parsed by Golang net/mail.ParseAddress
* - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034].
* - ipv4: an IPv4 IP as parsed by Golang net.ParseIP
* - ipv6: an IPv6 IP as parsed by Golang net.ParseIP
* - cidr: a CIDR as parsed by Golang net.ParseCIDR
* - mac: a MAC address as parsed by Golang net.ParseMAC
* - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$
* - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$
* - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
* - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
* - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041"
* - isbn10: an ISBN10 number string like "0321751043"
* - isbn13: an ISBN13 number string like "978-0321751041"
* - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in
* - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$
* - hexcolor: an hexadecimal color code like "#FFFFFF: following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
* - rgbcolor: an RGB color code like rgb like "rgb(255,255,2559"
* - byte: base64 encoded binary data
* - password: any kind of string
* - date: a date string like "2006-01-02" as defined by full-date in RFC3339
* - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format
* - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339.
*/
format?: string;
id?: string;
items?: JSONSchemaProps | JSONSchemaProps[];
maxItems?: number;
maxLength?: number;
maxProperties?: number;
maximum?: number;
minItems?: number;
minLength?: number;
minProperties?: number;
minimum?: number;
multipleOf?: number;
not?: JSONSchemaProps;
nullable?: boolean;
oneOf?: JSONSchemaProps[];
pattern?: string;
patternProperties?: Partial<Record<string, JSONSchemaProps>>;
properties?: Partial<Record<string, JSONSchemaProps>>;
required?: Array<string>;
title?: string;
type?: string;
uniqueItems?: boolean;
x_kubernetes_embedded_resource?: boolean;
x_kubernetes_int_or_string?: boolean;
x_kubernetes_list_map_keys?: string[];
x_kubernetes_list_type?: string;
x_kubernetes_map_type?: string;
x_kubernetes_preserve_unknown_fields?: boolean;
}

View File

@ -169,6 +169,13 @@ export interface IRemoteKubeApiConfig {
clientCertificateData?: string;
clientKeyData?: string;
};
/**
* Custom instance of https.agent to use for the requests
*
* @remarks the custom agent replaced default agent, options skipTLSVerify,
* clientCertificateData, clientKeyData and caData are ignored.
*/
agent?: Agent;
}
export function forCluster<
@ -240,6 +247,10 @@ export function forRemoteCluster<
reqInit.agent = new Agent(agentOptions);
}
if (config.agent) {
reqInit.agent = config.agent;
}
const token = config.user.token;
const request = new KubeJsonApi({
serverAddress: config.cluster.server,

View File

@ -1,18 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// Register custom protocols
import { protocol } from "electron";
import path from "path";
export function registerFileProtocol(name: string, basePath: string) {
protocol.registerFileProtocol(name, (request, callback) => {
const filePath = request.url.replace(`${name}://`, "");
const absPath = path.resolve(basePath, filePath);
callback({ path: absPath });
});
}

View File

@ -0,0 +1,21 @@
/**
* 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 type { MessageChannel } from "../utils/channel/message-channel-injection-token";
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
export type RootFrameRenderedChannel = MessageChannel;
const rootFrameRenderedChannelInjectable = getInjectable({
id: "root-frame-rendered-channel",
instantiate: (): RootFrameRenderedChannel => ({
id: "root-frame-rendered",
}),
injectionToken: messageChannelInjectionToken,
});
export default rootFrameRenderedChannelInjectable;

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export enum TerminalChannels {
STDIN = "stdin",
STDOUT = "stdout",
CONNECTED = "connected",
RESIZE = "resize",
PING = "ping",
}
export type TerminalMessage = {
type: TerminalChannels.STDIN;
data: string;
} | {
type: TerminalChannels.STDOUT;
data: string;
} | {
type: TerminalChannels.CONNECTED;
} | {
type: TerminalChannels.RESIZE;
data: {
width: number;
height: number;
};
} | {
type: TerminalChannels.PING;
};

Some files were not shown because too many files have changed in this diff Show More