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 "@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 fse from "fs-extra";
import React from "react"; import React from "react";
import { UserStore } from "../../../../common/user-store"; import { UserStore } from "../../../../common/user-store";
@ -97,43 +97,43 @@ describe("Extensions", () => {
it("disables uninstall and disable buttons while uninstalling", async () => { it("disables uninstall and disable buttons while uninstalling", async () => {
extensionDiscovery.isLoaded = true; extensionDiscovery.isLoaded = true;
const res = render(( render((
<> <>
<Extensions /> <Extensions />
<ConfirmDialog /> <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); assert(menuTrigger);
fireEvent.click(menuTrigger); fireEvent.click(menuTrigger);
expect(res.getByText("Disable")).toHaveAttribute("aria-disabled", "false"); expect(await screen.findByText("Disable")).toHaveAttribute("aria-disabled", "false");
expect(res.getByText("Uninstall")).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 // Approve confirm dialog
fireEvent.click(res.getByText("Yes")); fireEvent.click(await screen.findByText("Yes"));
await waitFor(() => { await waitFor(() => {
expect(extensionDiscovery.uninstallExtension).toHaveBeenCalled(); expect(extensionDiscovery.uninstallExtension).toHaveBeenCalled();
fireEvent.click(menuTrigger); fireEvent.click(menuTrigger);
expect(res.getByText("Disable")).toHaveAttribute("aria-disabled", "true"); expect(screen.getByText("Disable")).toHaveAttribute("aria-disabled", "true");
expect(res.getByText("Uninstall")).toHaveAttribute("aria-disabled", "true"); expect(screen.getByText("Uninstall")).toHaveAttribute("aria-disabled", "true");
}, { }, {
timeout: 30000, timeout: 30000,
}); });
}); });
it("disables install button while installing", async () => { it("disables install button while installing", async () => {
const res = render(<Extensions />); render(<Extensions />);
(fse.unlink as jest.MockedFunction<typeof fse.unlink>).mockReturnValue(Promise.resolve()); (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, exact: false,
}), { }), {
target: { target: {
@ -141,8 +141,8 @@ describe("Extensions", () => {
}, },
}); });
fireEvent.click(res.getByText("Install")); fireEvent.click(await screen.findByText("Install"));
expect(res.getByText("Install").closest("button")).toBeDisabled(); expect((await screen.findByText("Install")).closest("button")).toBeDisabled();
}); });
it("displays spinner while extensions are loading", () => { it("displays spinner while extensions are loading", () => {

View File

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

View File

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

View File

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