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

chore: Add test verifying new notification formatting behaviour

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-04-24 09:43:47 -04:00
parent bcf95a65f1
commit 1c8559013b
3 changed files with 833 additions and 3 deletions

View File

@ -26,6 +26,30 @@ export interface JsonApiError {
errors?: { id: string; title: string; status?: number }[]; errors?: { id: string; title: string; status?: number }[];
} }
export interface KubeJsonApiErrorCause {
reason: string;
message: string;
field: string;
}
export interface KubeJsonApiErrorDetails {
name: string;
group: string;
kind: string;
causes: KubeJsonApiErrorCause[];
}
export interface KubeJsonApiError {
kind: "Status";
apiVersion: "v1";
metadata: object;
status: string;
message: string;
reason: string;
details: KubeJsonApiErrorDetails;
code: number;
}
export interface JsonApiParams<D> { export interface JsonApiParams<D> {
data?: PartialDeep<D>; // request body data?: PartialDeep<D>; // request body
} }
@ -246,7 +270,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
export class JsonApiErrorParsed { export class JsonApiErrorParsed {
isUsedForNotification = false; isUsedForNotification = false;
constructor(private error: JsonApiError | DOMException, private messages: string[]) { constructor(private error: JsonApiError | DOMException | KubeJsonApiError, private messages: string[]) {
} }
get isAborted() { get isAborted() {

View File

@ -10334,6 +10334,758 @@ metadata:
</body> </body>
`; `;
exports[`cluster/namespaces - edit namespace from new tab when navigating to namespaces when namespaces resolve when clicking the context menu for a namespace when clicking to edit namespace when call for namespace resolves with namespace when selecting to save and close when saving failings with a JsonApiError renders 1`] = `
<body>
<div>
<div
class="Animate slide-right Drawer KubeObjectDetails flex column right enter leave"
style="--size: 725px; --enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="drawer-wrapper flex column"
>
<div
class="drawer-title flex align-center"
>
<div
class="drawer-title-text flex gaps align-center"
>
</div>
<i
class="Icon material interactive focusable"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
<div>
Close
</div>
</div>
<div
class="drawer-content flex column box grow"
/>
</div>
<div
class="ResizingAnchor horizontal leading"
/>
</div>
<div
class="Notifications flex column align-flex-end"
/>
<div
class="mainLayout"
style="--sidebar-width: 200px;"
>
<div
class="sidebar"
>
<div
class="flex flex-col"
data-testid="cluster-sidebar"
>
<div
class="SidebarCluster"
>
<div
class="Avatar rounded loadingAvatar"
style="width: 40px; height: 40px;"
>
??
</div>
<div
class="loadingClusterName"
/>
</div>
<div
class="sidebarNav sidebar-active-status"
>
<div
class="SidebarItem"
data-is-active-test="false"
data-testid="sidebar-item-workloads"
>
<a
class="navItem"
data-testid="sidebar-item-link-for-workloads"
href="/"
>
<i
class="Icon svg focusable"
>
<span
class="icon"
/>
</i>
<span>
Workloads
</span>
<i
class="Icon expandIcon material focusable"
>
<span
class="icon"
data-icon-name="keyboard_arrow_down"
>
keyboard_arrow_down
</span>
</i>
</a>
</div>
<div
class="SidebarItem"
data-is-active-test="false"
data-testid="sidebar-item-config"
>
<a
class="navItem"
data-testid="sidebar-item-link-for-config"
href="/"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="list"
>
list
</span>
</i>
<span>
Config
</span>
</a>
</div>
<div
class="SidebarItem"
data-is-active-test="false"
data-testid="sidebar-item-network"
>
<a
class="navItem"
data-testid="sidebar-item-link-for-network"
href="/"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="device_hub"
>
device_hub
</span>
</i>
<span>
Network
</span>
<i
class="Icon expandIcon material focusable"
>
<span
class="icon"
data-icon-name="keyboard_arrow_down"
>
keyboard_arrow_down
</span>
</i>
</a>
</div>
<div
class="SidebarItem"
data-is-active-test="false"
data-testid="sidebar-item-storage"
>
<a
class="navItem"
data-testid="sidebar-item-link-for-storage"
href="/"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="storage"
>
storage
</span>
</i>
<span>
Storage
</span>
</a>
</div>
<div
class="SidebarItem"
data-is-active-test="true"
data-testid="sidebar-item-namespaces"
>
<a
aria-current="page"
class="navItem active"
data-testid="sidebar-item-link-for-namespaces"
href="/"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
Namespaces
</span>
</a>
</div>
<div
class="SidebarItem"
data-is-active-test="false"
data-testid="sidebar-item-helm"
>
<a
class="navItem"
data-testid="sidebar-item-link-for-helm"
href="/"
>
<i
class="Icon svg focusable"
>
<span
class="icon"
/>
</i>
<span>
Helm
</span>
<i
class="Icon expandIcon material focusable"
>
<span
class="icon"
data-icon-name="keyboard_arrow_down"
>
keyboard_arrow_down
</span>
</i>
</a>
</div>
<div
class="SidebarItem"
data-is-active-test="false"
data-testid="sidebar-item-user-management"
>
<a
class="navItem"
data-testid="sidebar-item-link-for-user-management"
href="/"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="security"
>
security
</span>
</i>
<span>
Access Control
</span>
</a>
</div>
<div
class="SidebarItem"
data-is-active-test="false"
data-testid="sidebar-item-custom-resources"
>
<a
class="navItem"
data-testid="sidebar-item-link-for-custom-resources"
href="/"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="extension"
>
extension
</span>
</i>
<span>
Custom Resources
</span>
<i
class="Icon expandIcon material focusable"
>
<span
class="icon"
data-icon-name="keyboard_arrow_down"
>
keyboard_arrow_down
</span>
</i>
</a>
</div>
</div>
</div>
<div
class="ResizingAnchor horizontal trailing"
/>
</div>
<div
class="contents"
>
<div
class="TabLayout"
data-testid="tab-layout"
>
<main
class=""
>
<div
class="ItemListLayout flex column KubeObjectListLayout Namespaces"
>
<div
class="header flex gaps align-center"
>
<h5
class="title"
>
Namespaces
</h5>
<div
class="info-panel box grow"
>
0 items
</div>
<div
class="Input SearchInput"
>
<label
class="input-area flex gaps align-center"
id=""
>
<input
class="input box grow"
placeholder="Search Namespaces..."
spellcheck="false"
value=""
/>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="search"
>
search
</span>
</i>
</label>
<div
class="input-info flex gaps"
/>
</div>
</div>
<div
class="items box grow flex column"
>
<div
class="Table flex column KubeObjectListLayout Namespaces box grow dark selectable scrollable sortable autoSize virtual"
>
<div
class="TableHead sticky nowrap topLine"
>
<div
class="TableCell checkbox"
>
<label
class="Checkbox flex align-center"
>
<input
type="checkbox"
/>
<i
class="box flex align-center"
/>
</label>
</div>
<div
class="TableCell name nowrap sorting"
id="name"
>
<div
class="content"
>
Name
</div>
<i
class="Icon sortIcon material focusable"
>
<span
class="icon"
data-icon-name="arrow_drop_down"
>
arrow_drop_down
</span>
</i>
</div>
<div
class="TableCell warning nowrap"
>
<div
class="content"
/>
</div>
<div
class="TableCell labels scrollable nowrap sorting"
id="labels"
>
<div
class="content"
>
Labels
</div>
<i
class="Icon sortIcon material focusable"
>
<span
class="icon"
data-icon-name="arrow_drop_down"
>
arrow_drop_down
</span>
</i>
</div>
<div
class="TableCell age nowrap sorting"
id="age"
>
<div
class="content"
>
Age
</div>
<i
class="Icon sortIcon material focusable"
>
<span
class="icon"
data-icon-name="arrow_drop_down"
>
arrow_drop_down
</span>
</i>
</div>
<div
class="TableCell status nowrap sorting"
id="status"
>
<div
class="content"
>
Status
</div>
<i
class="Icon sortIcon material focusable"
>
<span
class="icon"
data-icon-name="arrow_drop_down"
>
arrow_drop_down
</span>
</i>
</div>
<div
class="TableCell menu nowrap"
>
<div
class="content"
>
<i
class="Icon material interactive focusable"
id="menu-actions-for-item-object-list-content"
tabindex="0"
>
<span
class="icon"
data-icon-name="more_vert"
>
more_vert
</span>
</i>
</div>
</div>
</div>
<div
class="NoItems flex box grow"
>
<div
class="box center"
>
Item list is empty
</div>
</div>
</div>
<div
class="AddRemoveButtons flex gaps"
>
<button
class="Button add-button primary big round"
type="button"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="add"
>
add
</span>
</i>
</button>
<div>
Add Namespace
</div>
</div>
</div>
</div>
</main>
</div>
</div>
<div
class="footer"
>
<div
class="Dock isOpen"
tabindex="-1"
>
<div
class="ResizingAnchor vertical leading"
/>
<div
class="tabs-container flex align-center"
>
<div
class="dockTabs"
role="tablist"
>
<div
class="Tabs tabs"
>
<div
class="Tab flex gaps align-center DockTab active"
data-testid="dock-tab-for-some-first-tab-id"
id="tab-some-first-tab-id"
role="tab"
tabindex="0"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="edit"
>
edit
</span>
</i>
<div
class="label"
>
<div
class="flex align-center"
>
<span
class="title"
>
Namespace: some-name
</span>
<div
class="close"
>
<i
class="Icon material interactive focusable small"
data-testid="dock-tab-close-for-some-first-tab-id"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
<div
data-testid="tooltip-content-for-dock-tab-close-for-some-first-tab-id"
>
Close ⌘+W
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="toolbar flex gaps align-center box grow"
>
<div
class="dock-menu box grow"
>
<i
class="Icon new-dock-tab material interactive focusable"
id="menu-actions-for-dock"
tabindex="0"
>
<span
class="icon"
data-icon-name="add"
>
add
</span>
</i>
<div>
New tab
</div>
</div>
<i
class="Icon material interactive focusable"
tabindex="0"
>
<span
class="icon"
data-icon-name="fullscreen"
>
fullscreen
</span>
</i>
<div>
Fit to window
</div>
<i
class="Icon material interactive focusable"
tabindex="0"
>
<span
class="icon"
data-icon-name="keyboard_arrow_down"
>
keyboard_arrow_down
</span>
</i>
<div>
Minimize
</div>
</div>
</div>
<div
class="tab-content edit-resource"
data-testid="dock-tab-content-for-some-first-tab-id"
style="flex-basis: 300px;"
>
<div
class="EditResource flex column"
>
<div
class="InfoPanel flex gaps align-center"
>
<div
class="controls"
>
<div
class="resource-info flex gaps align-center"
>
<span>
Kind:
</span>
<div
class="badge"
>
Namespace
</div>
<span>
Name:
</span>
<div
class="badge"
>
some-name
</div>
<span>
Namespace:
</span>
<div
class="badge"
>
default
</div>
</div>
</div>
<div
class="flex gaps align-center"
/>
<button
class="Button plain"
data-testid="cancel-edit-resource-from-tab-for-some-first-tab-id"
type="button"
>
Cancel
</button>
<button
class="Button active outlined"
data-testid="save-edit-resource-from-tab-for-some-first-tab-id"
type="button"
>
Save
</button>
<button
class="Button primary active"
data-testid="save-and-close-edit-resource-from-tab-for-some-first-tab-id"
type="button"
>
Save & Close
</button>
</div>
<textarea
data-testid="monaco-editor-for-some-first-tab-id"
>
apiVersion: some-api-version
kind: Namespace
metadata:
uid: some-uid
name: some-name
resourceVersion: some-resource-version
somePropertyToBeRemoved: some-value
somePropertyToBeChanged: some-old-value
selfLink: /apis/some-api-version/namespaces/some-uid
</textarea>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
`;
exports[`cluster/namespaces - edit namespace from new tab when navigating to namespaces when namespaces resolve when clicking the context menu for a namespace when clicking to edit namespace when call for namespace resolves with namespace when selecting to save and close when saving resolves with success renders 1`] = ` exports[`cluster/namespaces - edit namespace from new tab when navigating to namespaces when namespaces resolve when clicking the context menu for a namespace when clicking to edit namespace when call for namespace resolves with namespace when selecting to save and close when saving resolves with success renders 1`] = `
<body> <body>
<div> <div>

View File

@ -25,13 +25,16 @@ import apiKubePatchInjectable from "../../../renderer/k8s/api-kube-patch.injecta
import apiKubeGetInjectable from "../../../renderer/k8s/api-kube-get.injectable"; import apiKubeGetInjectable from "../../../renderer/k8s/api-kube-get.injectable";
import type { KubeJsonApiData } from "../../../common/k8s-api/kube-json-api"; import type { KubeJsonApiData } from "../../../common/k8s-api/kube-json-api";
import type { BaseKubeJsonApiObjectMetadata, KubeObjectScope } from "../../../common/k8s-api/kube-object"; import type { BaseKubeJsonApiObjectMetadata, KubeObjectScope } from "../../../common/k8s-api/kube-object";
import { JsonApiErrorParsed } from "../../../common/k8s-api/json-api";
import type { ShowNotification } from "../../../renderer/components/notifications";
import React from "react";
describe("cluster/namespaces - edit namespace from new tab", () => { describe("cluster/namespaces - edit namespace from new tab", () => {
let builder: ApplicationBuilder; let builder: ApplicationBuilder;
let apiKubePatchMock: AsyncFnMock<ApiKubePatch>; let apiKubePatchMock: AsyncFnMock<ApiKubePatch>;
let apiKubeGetMock: AsyncFnMock<ApiKubeGet>; let apiKubeGetMock: AsyncFnMock<ApiKubeGet>;
let showSuccessNotificationMock: jest.Mock; let showSuccessNotificationMock: jest.MockedFunction<ShowNotification>;
let showErrorNotificationMock: jest.Mock; let showErrorNotificationMock: jest.MockedFunction<ShowNotification>;
beforeEach(() => { beforeEach(() => {
builder = getApplicationBuilder(); builder = getApplicationBuilder();
@ -418,6 +421,57 @@ metadata:
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
}); });
describe("when saving failings with a JsonApiError", () => {
beforeEach(async () => {
await apiKubePatchMock.reject(new JsonApiErrorParsed(
{
kind: "Status",
apiVersion: "v1",
metadata: {},
status: "Failure",
message: "PodDisruptionBudget.policy \"frontend-pdb\" is invalid: spec.minAvailable: Invalid value: -10: must be greater than or equal to 0",
reason: "Invalid",
details: {
name: "frontend-pdb",
group: "policy",
kind: "PodDisruptionBudget",
causes: [
{
reason: "FieldValueInvalid",
message: "Invalid value: -10: must be greater than or equal to 0",
field: "spec.minAvailable",
},
],
},
code: 422,
},
[
"PodDisruptionBudget.policy \"frontend-pdb\" is invalid: spec.minAvailable: Invalid value: -10: must be greater than or equal to 0",
],
));
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("does not close the dock tab", () => {
expect(
rendered.getByTestId("dock-tab-for-some-first-tab-id"),
).toBeInTheDocument();
});
it("shows an error notification with a condensed message", () => {
expect(showErrorNotificationMock).toBeCalledWith(
<p>
{"Failed to save resource:"}
{" "}
{'PodDisruptionBudget.policy "frontend-pdb" is invalid: spec.minAvailable: Invalid value: -10: must be greater than or equal to 0'}
</p>,
);
});
});
}); });
describe("when selecting to cancel", () => { describe("when selecting to cancel", () => {