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

extensions.test.tsx

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-04-21 12:48:02 -04:00
parent 955aa28bc8
commit 3c8b1d9a7b
4 changed files with 88 additions and 85 deletions

View File

@ -4,7 +4,7 @@
*/
import "@testing-library/jest-dom/extend-expect";
import { fireEvent, waitFor } from "@testing-library/react";
import { fireEvent, screen, waitFor } from "@testing-library/react";
import fse from "fs-extra";
import React from "react";
import { UserStore } from "../../../../common/user-store";
@ -97,43 +97,43 @@ describe("Extensions", () => {
it("disables uninstall and disable buttons while uninstalling", async () => {
extensionDiscovery.isLoaded = true;
const res = render((
render((
<>
<Extensions />
<ConfirmDialog />
</>
));
const table = res.getByTestId("extensions-table");
const menuTrigger = table.querySelector("div[role=row]:first-of-type .actions .Icon");
const table = await screen.findByTestId("extensions-table");
const menuTrigger = table.querySelector(".table div[role='rowgroup'] .actions .Icon");
assert(menuTrigger);
fireEvent.click(menuTrigger);
expect(res.getByText("Disable")).toHaveAttribute("aria-disabled", "false");
expect(res.getByText("Uninstall")).toHaveAttribute("aria-disabled", "false");
expect(await screen.findByText("Disable")).toHaveAttribute("aria-disabled", "false");
expect(await screen.findByText("Uninstall")).toHaveAttribute("aria-disabled", "false");
fireEvent.click(res.getByText("Uninstall"));
fireEvent.click(await screen.findByText("Uninstall"));
// Approve confirm dialog
fireEvent.click(res.getByText("Yes"));
fireEvent.click(await screen.findByText("Yes"));
await waitFor(() => {
expect(extensionDiscovery.uninstallExtension).toHaveBeenCalled();
fireEvent.click(menuTrigger);
expect(res.getByText("Disable")).toHaveAttribute("aria-disabled", "true");
expect(res.getByText("Uninstall")).toHaveAttribute("aria-disabled", "true");
expect(screen.getByText("Disable")).toHaveAttribute("aria-disabled", "true");
expect(screen.getByText("Uninstall")).toHaveAttribute("aria-disabled", "true");
}, {
timeout: 30000,
});
});
it("disables install button while installing", async () => {
const res = render(<Extensions />);
render(<Extensions />);
(fse.unlink as jest.MockedFunction<typeof fse.unlink>).mockReturnValue(Promise.resolve());
fireEvent.change(res.getByPlaceholderText("File path or URL", {
fireEvent.change(await screen.findByPlaceholderText("File path or URL", {
exact: false,
}), {
target: {
@ -141,8 +141,8 @@ describe("Extensions", () => {
},
});
fireEvent.click(res.getByText("Install"));
expect(res.getByText("Install").closest("button")).toBeDisabled();
fireEvent.click(await screen.findByText("Install"));
expect((await screen.findByText("Install")).closest("button")).toBeDisabled();
});
it("displays spinner while extensions are loading", () => {

View File

@ -81,63 +81,61 @@ const NonInjectedInstalledExtensions = observer(({ extensionDiscovery, extension
);
const data = useMemo(
() => {
return extensions.map(extension => {
const { id, isEnabled, isCompatible, manifest } = extension;
const { name, description, version } = manifest;
const isUninstalling = extensionInstallationStateStore.isExtensionUninstalling(id);
() => extensions.map(extension => {
const { id, isEnabled, isCompatible, manifest } = extension;
const { name, description, version } = manifest;
const isUninstalling = extensionInstallationStateStore.isExtensionUninstalling(id);
return {
extension: (
<div className={"flex items-start"}>
<div>
<div className={styles.extensionName}>{name}</div>
<div className={styles.extensionDescription}>{description}</div>
</div>
return {
extension: (
<div className={"flex items-start"}>
<div>
<div className={styles.extensionName}>{name}</div>
<div className={styles.extensionDescription}>{description}</div>
</div>
),
version,
status: (
<div className={cssNames({ [styles.enabled]: isEnabled, [styles.invalid]: !isCompatible })}>
{getStatus(extension)}
</div>
),
actions: (
<MenuActions usePortal toolbar={false}>
{ isCompatible && (
<>
{isEnabled ? (
<MenuItem
disabled={isUninstalling}
onClick={() => disable(id)}
>
<Icon material="unpublished"/>
<span className="title" aria-disabled={isUninstalling}>Disable</span>
</MenuItem>
) : (
<MenuItem
disabled={isUninstalling}
onClick={() => enable(id)}
>
<Icon material="check_circle"/>
<span className="title" aria-disabled={isUninstalling}>Enable</span>
</MenuItem>
)}
</>
)}
</div>
),
version,
status: (
<div className={cssNames({ [styles.enabled]: isEnabled, [styles.invalid]: !isCompatible })}>
{getStatus(extension)}
</div>
),
actions: (
<MenuActions usePortal toolbar={false}>
{isCompatible && (
<>
{isEnabled ? (
<MenuItem
disabled={isUninstalling}
onClick={() => disable(id)}
>
<Icon material="unpublished" />
<span className="title" aria-disabled={isUninstalling}>Disable</span>
</MenuItem>
) : (
<MenuItem
disabled={isUninstalling}
onClick={() => enable(id)}
>
<Icon material="check_circle" />
<span className="title" aria-disabled={isUninstalling}>Enable</span>
</MenuItem>
)}
</>
)}
<MenuItem
disabled={isUninstalling}
onClick={() => uninstall(extension)}
>
<Icon material="delete"/>
<span className="title" aria-disabled={isUninstalling}>Uninstall</span>
</MenuItem>
</MenuActions>
),
};
});
}, [extensions, extensionInstallationStateStore.anyUninstalling],
<MenuItem
disabled={isUninstalling}
onClick={() => uninstall(extension)}
>
<Icon material="delete" />
<span className="title" aria-disabled={isUninstalling}>Uninstall</span>
</MenuItem>
</MenuActions>
),
};
}), [extensions, extensionInstallationStateStore.anyUninstalling],
);
if (!extensionDiscovery.isLoaded) {

View File

@ -10,11 +10,11 @@ import { useFlexLayout, useSortBy, useTable } from "react-table";
import { Icon } from "../icon";
import { cssNames } from "../../utils";
export interface ReactTableProps extends UseTableOptions<any> {
export interface ReactTableProps<Data extends object> extends UseTableOptions<Data> {
headless?: boolean;
}
export function ReactTable({ columns, data, headless }: ReactTableProps) {
export function ReactTable<Data extends object>({ columns, data, headless }: ReactTableProps<Data>) {
const defaultColumn = useMemo(
() => ({
minWidth: 20,
@ -40,11 +40,11 @@ export function ReactTable({ columns, data, headless }: ReactTableProps) {
);
const renderRow = useCallback(
(row: Row<object>) => {
(row: Row<Data>) => {
prepareRow(row);
return (
<div className={styles.tr}>
<div className={styles.tr} key={row.id}>
{row.cells.map((cell, index) => (
<div
{...cell.getCellProps()}

View File

@ -5,22 +5,27 @@
// Helper for combining css classes inside components
export type IClassName = string | string[] | IClassNameMap | undefined | false;
export type IClassNameMap = Record<string, any>;
export type IgnoredClassNames = number | symbol | Function;
export function cssNames(...args: IClassName[]): string {
const map: IClassNameMap = {};
export type IClassName = string | string[] | IClassNameMap | undefined | null | false | IgnoredClassNames;
export type IClassNameMap = Record<string, unknown>;
args.forEach(className => {
if (typeof className === "string" || Array.isArray(className)) {
[className].flat().forEach(name => map[name] = true);
export function cssNames(...classNames: IClassName[]): string {
const classNamesEnabled: IClassNameMap = {};
for (const className of classNames) {
if (typeof className === "string") {
classNamesEnabled[className] = true;
} else if (Array.isArray(className)) {
for (const name of className) {
classNamesEnabled[name] = true;
}
} else if (className && typeof className === "object") {
Object.assign(classNamesEnabled, className);
}
else {
Object.assign(map, className);
}
});
}
return Object.entries(map)
return Object.entries(classNamesEnabled)
.filter(([, isActive]) => !!isActive)
.map(([className]) => className.trim())
.join(" ");