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

chore: Add some behavioural tests for custom columns for custom resources

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-05-10 14:36:23 -04:00
parent 2ca77ecfe8
commit 978015510a
9 changed files with 815 additions and 87 deletions

View File

@ -0,0 +1,663 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Viewing Custom Resources with extra columns renders 1`] = `
<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="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="true"
data-testid="sidebar-item-custom-resources"
>
<a
aria-current="page"
class="navItem active"
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 CrdResources"
>
<div
class="header flex gaps align-center"
>
<h5
class="title"
>
SomeResource
</h5>
<div
class="info-panel box grow"
>
1 item
</div>
<div
class="Input SearchInput focused"
>
<label
class="input-area flex gaps align-center"
id=""
>
<input
class="input box grow"
placeholder="SomeResource search ..."
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 CrdResources 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 some-column nowrap sorting"
data-testid="custom-resource-column-title-some-column"
id="Some Column"
>
<div
class="content"
>
Some Column
</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 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="VirtualList KubeObjectListLayout CrdResources box grow dark"
>
<div>
<div
class="list"
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
>
<div
style="height: 33px; width: 100%;"
>
<div
style="position: absolute; left: 0px; top: 0px; height: 33px; width: 100%;"
>
<div
class="TableRow nowrap"
>
<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"
>
some-resource
</div>
<div
class="TableCell"
/>
<div
class="TableCell some-column"
data-testid="custom-resource-column-cell-some-column-for-some-resource"
>
{"foo":"bar"}
</div>
<div
class="TableCell age"
>
&lt;unknown&gt;
</div>
<div
class="TableCell menu"
>
<div>
<i
class="Icon material interactive focusable"
data-testid="icon-for-menu-actions-for-kube-object-menu-for-some-uid-2"
id="menu-actions-for-kube-object-menu-for-some-uid-2"
tabindex="0"
>
<span
class="icon"
data-icon-name="more_vert"
>
more_vert
</span>
</i>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="AddRemoveButtons flex gaps"
/>
</div>
</div>
</main>
</div>
</div>
<div
class="footer"
>
<div
class="Dock"
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 TerminalTab active"
data-testid="dock-tab-for-terminal"
id="tab-terminal"
role="tab"
tabindex="0"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="terminal"
>
terminal
</span>
</i>
<div
class="label"
>
<div
class="flex align-center"
>
<span
class="title"
>
Terminal
</span>
<div
class="close"
>
<i
class="Icon material interactive focusable small"
data-testid="dock-tab-close-for-terminal"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
<div
data-testid="tooltip-content-for-dock-tab-close-for-terminal"
>
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_up"
>
keyboard_arrow_up
</span>
</i>
<div>
Open
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,123 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope } from "@k8slens/kube-object";
import { KubeObject, CustomResourceDefinition } from "@k8slens/kube-object";
import type { RenderResult } from "@testing-library/react";
import navigateToCustomResourcesInjectable from "../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/navigate-to-custom-resources.injectable";
import apiManagerInjectable from "../../common/k8s-api/api-manager/manager.injectable";
import type { CustomResourceStore } from "../../common/k8s-api/api-manager/resource.store";
import type { CustomResourceDefinitionStore } from "../../renderer/components/custom-resources/definition.store";
import customResourceDefinitionStoreInjectable from "../../renderer/components/custom-resources/definition.store.injectable";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
describe("Viewing Custom Resources with extra columns", () => {
let builder: ApplicationBuilder;
let result: RenderResult;
let customResourceDefinitionStore: CustomResourceDefinitionStore;
let customResourceStore: CustomResourceStore<KubeObject<KubeObjectMetadata<KubeObjectScope.Cluster>, void, { someColumn: Record<string, unknown> }>>;
let customResource: CustomResourceDefinition;
beforeEach(async () => {
builder = getApplicationBuilder();
builder.setEnvironmentToClusterFrame();
builder.afterWindowStart(({ windowDi }) => {
const apiManager = windowDi.inject(apiManagerInjectable);
customResourceDefinitionStore = windowDi.inject(customResourceDefinitionStoreInjectable);
customResource = new CustomResourceDefinition({
apiVersion: "apiextensions.k8s.io/v1",
kind: "CustomResourceDefinition",
metadata: {
name: "some-crd",
selfLink: "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/some-crd",
uid: "some-uid",
resourceVersion: "1",
},
spec: {
group: "some-group",
scope: "Cluster",
names: {
kind: "SomeResource",
plural: "some-resources",
singular: "some-resource",
},
versions: [
{
storage: true,
name: "v1",
served: true,
additionalPrinterColumns: [
{
name: "Some Column",
type: "string",
description: "Some description",
jsonPath: ".spec.someColumn",
},
],
},
],
},
});
customResourceDefinitionStore.items.replace([
customResource,
]);
customResourceStore = apiManager.getStore(customResource.getResourceApiBase()) as typeof customResourceStore;
customResourceStore.items.replace([
new KubeObject({
apiVersion: "/some-group/v1",
kind: "SomeResource",
metadata: {
name: "some-resource",
selfLink: "/apis/some-group/v1/namespaces/default/some-resources/some-resource",
uid: "some-uid-2",
resourceVersion: "1",
},
spec: {
someColumn: {
"foo": "bar",
},
},
}),
]);
});
result = await builder.render();
builder.allowKubeResource({
group: "/apis/apiextensions.k8s.io/v1",
apiName: "customresourcedefinitions",
});
const windowDi = builder.applicationWindow.only.di;
const navigateToCustomResources = windowDi.inject(navigateToCustomResourcesInjectable);
navigateToCustomResources({
group: "some-group",
name: "some-resources",
});
});
it("renders", () => {
expect(result.container).toMatchSnapshot();
});
it("shows the table for the custom resource SomeResource", () => {
expect(result.getByText("SomeResource")).toBeInTheDocument();
});
it("shows the 'some-column' column", () => {
expect(result.getByTestId("custom-resource-column-title-some-column")).toBeInTheDocument();
});
it("shows some value for in the cells of 'some-column' column", () => {
expect(result.getByTestId("custom-resource-column-cell-some-column-for-some-resource")).toHaveTextContent(JSON.stringify({ foo: "bar" }));
});
});

View File

@ -3,9 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { TableCellProps } from "@k8slens/list-layout";
import type { StrictReactNode } from "@k8slens/utilities"; import type { StrictReactNode } from "@k8slens/utilities";
import type { CatalogEntity } from "../../../common/catalog"; import type { CatalogEntity } from "../../../common/catalog";
import type { TableCellProps } from "../table";
/** /**
* These are the supported props for the title cell * These are the supported props for the title cell

View File

@ -20,6 +20,7 @@ import type { CustomResourceDefinitionStore } from "./definition.store";
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable"; import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
import customResourceDefinitionStoreInjectable from "./definition.store.injectable"; import customResourceDefinitionStoreInjectable from "./definition.store.injectable";
import { NamespaceSelectBadge } from "../namespaces/namespace-select-badge"; import { NamespaceSelectBadge } from "../namespaces/namespace-select-badge";
import type { TableCellProps } from "@k8slens/list-layout";
enum columnId { enum columnId {
name = "name", name = "name",
@ -95,9 +96,10 @@ class NonInjectedCustomResources extends React.Component<Dependencies> {
: undefined, : undefined,
...extraColumns.map(({ name }) => ({ ...extraColumns.map(({ name }) => ({
title: name, title: name,
className: name.toLowerCase(), className: name.toLowerCase().replace(/\s+/g, "-"),
sortBy: name, sortBy: name,
id: name, id: name,
"data-testid": `custom-resource-column-title-${name.toLowerCase().replace(/\s+/g, "-")}`,
})), })),
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
@ -106,11 +108,10 @@ class NonInjectedCustomResources extends React.Component<Dependencies> {
isNamespaced && ( isNamespaced && (
<NamespaceSelectBadge namespace={customResource.getNs() as string} /> <NamespaceSelectBadge namespace={customResource.getNs() as string} />
), ),
...( ...extraColumns.map((column): TableCellProps => ({
extraColumns "data-testid": `custom-resource-column-cell-${column.name.toLowerCase().replace(/\s+/g, "-")}-for-${customResource.getScopedName()}`,
.map((column) => safeJSONPathValue(customResource, column.jsonPath)) title: formatJSONValue(safeJSONPathValue(customResource, column.jsonPath)),
.map(formatJSONValue) })),
),
<KubeObjectAge key="age" object={customResource} />, <KubeObjectAge key="age" object={customResource} />,
]} ]}
failedToLoadMessage={( failedToLoadMessage={(

View File

@ -10,7 +10,7 @@ import type { IComputedValue } from "mobx";
import { computed, makeObservable } from "mobx"; import { computed, makeObservable } from "mobx";
import { Observer, observer } from "mobx-react"; import { Observer, observer } from "mobx-react";
import type { ConfirmDialogParams } from "../confirm-dialog"; import type { ConfirmDialogParams } from "../confirm-dialog";
import type { TableCellProps, TableProps, TableRowProps, TableSortCallbacks } from "../table"; import type { TableProps, TableRowProps, TableSortCallbacks } from "../table";
import { Table, TableCell, TableHead, TableRow } from "../table"; import { Table, TableCell, TableHead, TableRow } from "../table";
import type { IClassName, StrictReactNode } from "@k8slens/utilities"; import type { IClassName, StrictReactNode } from "@k8slens/utilities";
import { cssNames, isDefined, isReactNode, noop, prevDefault, stopPropagation } from "@k8slens/utilities"; import { cssNames, isDefined, isReactNode, noop, prevDefault, stopPropagation } from "@k8slens/utilities";
@ -18,7 +18,7 @@ import type { AddRemoveButtonsProps } from "../add-remove-buttons";
import { AddRemoveButtons } from "../add-remove-buttons"; import { AddRemoveButtons } from "../add-remove-buttons";
import { NoItems } from "../no-items"; import { NoItems } from "../no-items";
import { Spinner } from "../spinner"; import { Spinner } from "../spinner";
import type { ItemObject } from "@k8slens/list-layout"; import type { ItemObject, TableCellProps } from "@k8slens/list-layout";
import type { Filter, PageFiltersStore } from "./page-filters/store"; import type { Filter, PageFiltersStore } from "./page-filters/store";
import type { LensTheme } from "../../themes/lens-theme"; import type { LensTheme } from "../../themes/lens-theme";
import { MenuActions } from "../menu/menu-actions"; import { MenuActions } from "../menu/menu-actions";

View File

@ -9,11 +9,11 @@ import React from "react";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import { computed, makeObservable, untracked } from "mobx"; import { computed, makeObservable, untracked } from "mobx";
import type { ConfirmDialogParams } from "../confirm-dialog"; import type { ConfirmDialogParams } from "../confirm-dialog";
import type { TableCellProps, TableProps, TableRowProps, TableSortCallbacks } from "../table"; import type { TableProps, TableRowProps, TableSortCallbacks } from "../table";
import type { IClassName, StrictReactNode, SingleOrMany } from "@k8slens/utilities"; import type { IClassName, StrictReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames, noop } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities";
import type { AddRemoveButtonsProps } from "../add-remove-buttons"; import type { AddRemoveButtonsProps } from "../add-remove-buttons";
import type { ItemObject } from "@k8slens/list-layout"; import type { ItemObject, TableCellProps } from "@k8slens/list-layout";
import type { SearchInputUrlProps } from "../input"; import type { SearchInputUrlProps } from "../input";
import type { PageFiltersStore } from "./page-filters/store"; import type { PageFiltersStore } from "./page-filters/store";
import { FilterType } from "./page-filters/store"; import { FilterType } from "./page-filters/store";

View File

@ -4,80 +4,16 @@
*/ */
import "./table-cell.scss"; import "./table-cell.scss";
import type { TableSortBy, TableSortParams } from "./table";
import React from "react"; import React from "react";
import type { StrictReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { Checkbox } from "../checkbox"; import { Checkbox } from "../checkbox";
import autoBindReact from "auto-bind/react"; import autoBindReact from "auto-bind/react";
import type { TableCellProps } from "@k8slens/list-layout";
export type TableCellElem = React.ReactElement<TableCellProps>; export type TableCellElem = React.ReactElement<TableCellProps>;
export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
/**
* used for configuration visibility of columns
*/
id?: string;
/**
* Any css class names for this table cell. Only used if `title` is a "simple" react node
*/
className?: string;
/**
* The actual value of the cell
*/
title?: StrictReactNode;
/**
* content inside could be scrolled
*/
scrollable?: boolean;
/**
* render cell with a checkbox
*/
checkbox?: boolean;
/**
* mark checkbox as checked or not
*/
isChecked?: boolean;
/**
* column name, must be same as key in sortable object `<Table sortable={}></Table>`
*/
sortBy?: TableSortBy;
/**
* id of the column which follow same visibility rules
*/
showWithColumn?: string;
/**
* @internal
*/
_sorting?: Partial<TableSortParams>;
/**
* @internal
*/
_sort?(sortBy: TableSortBy): void;
/**
* @internal
* indicator, might come from parent <TableHead>, don't use this prop outside (!)
*/
_nowrap?: boolean;
/**
* For passing in the testid
*/
"data-testid"?: string;
}
export class TableCell extends React.Component<TableCellProps> { export class TableCell extends React.Component<TableCellProps> {
constructor(props: TableCellProps) { constructor(props: TableCellProps) {
super(props); super(props);

View File

@ -141,13 +141,6 @@ export class CustomResourceDefinition extends KubeObject<
} }
getPreferredVersion(): CustomResourceDefinitionVersion { getPreferredVersion(): CustomResourceDefinitionVersion {
return this.getPreferedVersion();
}
/**
* @deprecated Switch to using {@link getPreferredVersion} instead (which fixes the is a typo)
*/
getPreferedVersion(): CustomResourceDefinitionVersion {
const { apiVersion } = this; const { apiVersion } = this;
switch (apiVersion) { switch (apiVersion) {
@ -179,8 +172,15 @@ export class CustomResourceDefinition extends KubeObject<
); );
} }
/**
* @deprecated Switch to using {@link getPreferredVersion} instead (which fixes the is a typo)
*/
getPreferedVersion(): CustomResourceDefinitionVersion {
return this.getPreferredVersion();
}
getVersion() { getVersion() {
return this.getPreferedVersion().name; return this.getPreferredVersion().name;
} }
isNamespaced() { isNamespaced() {
@ -200,13 +200,13 @@ export class CustomResourceDefinition extends KubeObject<
} }
getPrinterColumns(ignorePriority = true): AdditionalPrinterColumnsV1[] { getPrinterColumns(ignorePriority = true): AdditionalPrinterColumnsV1[] {
const columns = this.getPreferedVersion().additionalPrinterColumns ?? []; const columns = this.getPreferredVersion().additionalPrinterColumns ?? [];
return columns.filter((column) => column.name.toLowerCase() !== "age" && (ignorePriority || !column.priority)); return columns.filter((column) => column.name.toLowerCase() !== "age" && (ignorePriority || !column.priority));
} }
getValidation() { getValidation() {
return JSON.stringify(this.getPreferedVersion().schema, null, 2); return JSON.stringify(this.getPreferredVersion().schema, null, 2);
} }
getConditions() { getConditions() {

View File

@ -54,7 +54,7 @@ export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
isChecked?: boolean; isChecked?: boolean;
/** /**
* column name, must be same as key in sortable object <Table sortable={}/> * column name, must be same as key in sortable object `<Table sortable={}></Table>`
*/ */
sortBy?: TableSortBy; sortBy?: TableSortBy;
@ -78,4 +78,9 @@ export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
* indicator, might come from parent <TableHead>, don't use this prop outside (!) * indicator, might come from parent <TableHead>, don't use this prop outside (!)
*/ */
_nowrap?: boolean; _nowrap?: boolean;
/**
* For passing in the testid
*/
"data-testid"?: string;
} }