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

Refactor testing harness to use defaults

- Move tests into defaultly named test folders
- Use default test suffix of ".test" instead of "_test"
- Make cluster-store tests unit tests by adding more
    nesting, so that order of tests doesn't matter

Signed-off-by: Sebastian Malton <smalton@mirantis.com>
This commit is contained in:
Sebastian Malton 2020-09-04 14:06:35 -04:00 committed by Sebastian Malton
parent 14d3d88278
commit dc0c5dc9ea
18 changed files with 147 additions and 138 deletions

View File

@ -60,7 +60,6 @@
] ]
}, },
"jest": { "jest": {
"testRegex": ".*_(spec|test)\\.[jt]sx?$",
"collectCoverage": false, "collectCoverage": false,
"verbose": true, "verbose": true,
"testEnvironment": "node", "testEnvironment": "node",

View File

@ -1,9 +1,9 @@
import fs from "fs"; import fs from "fs";
import mockFs from "mock-fs"; import mockFs from "mock-fs";
import yaml from "js-yaml"; import yaml from "js-yaml";
import { Cluster } from "../main/cluster"; import { Cluster } from "../../main/cluster";
import { ClusterStore } from "./cluster-store"; import { ClusterStore } from "../cluster-store";
import { workspaceStore } from "./workspace-store"; import { workspaceStore } from "../workspace-store";
const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png") const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png")
@ -12,7 +12,7 @@ console.log("") // fix bug
let clusterStore: ClusterStore; let clusterStore: ClusterStore;
describe("empty config", () => { describe("empty config", () => {
beforeAll(() => { beforeEach(() => {
ClusterStore.resetInstance(); ClusterStore.resetInstance();
const mockOpts = { const mockOpts = {
'tmp': { 'tmp': {
@ -24,27 +24,32 @@ describe("empty config", () => {
return clusterStore.load(); return clusterStore.load();
}) })
afterAll(() => { afterEach(() => {
mockFs.restore(); mockFs.restore();
}) })
it("adds new cluster to store", async () => { describe("with foo cluster added", () => {
const cluster = new Cluster({ beforeEach(() => {
clusterStore.addCluster(
new Cluster({
id: "foo", id: "foo",
contextName: "minikube", contextName: "minikube",
preferences: { preferences: {
terminalCWD: "/tmp", terminalCWD: "/tmp",
icon: "data:;base64,iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5", icon: "data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5",
clusterName: "minikube" clusterName: "minikube"
}, },
kubeConfigPath: ClusterStore.embedCustomKubeConfig("foo", "fancy foo config"), kubeConfigPath: ClusterStore.embedCustomKubeConfig("foo", "fancy foo config"),
workspace: workspaceStore.currentWorkspaceId workspace: workspaceStore.currentWorkspaceId
}); })
clusterStore.addCluster(cluster); );
const storedCluster = clusterStore.getById(cluster.id); })
expect(storedCluster.id).toBe(cluster.id);
expect(storedCluster.preferences.terminalCWD).toBe(cluster.preferences.terminalCWD); it("adds new cluster to store", async () => {
expect(storedCluster.preferences.icon).toBe(cluster.preferences.icon); const storedCluster = clusterStore.getById("foo");
expect(storedCluster.id).toBe("foo");
expect(storedCluster.preferences.terminalCWD).toBe("/tmp");
expect(storedCluster.preferences.icon).toBe("data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5");
}) })
it("adds cluster to default workspace", () => { it("adds cluster to default workspace", () => {
@ -52,8 +57,21 @@ describe("empty config", () => {
expect(storedCluster.workspace).toBe("default"); expect(storedCluster.workspace).toBe("default");
}) })
it("check if store can contain multiple clusters", () => { it("removes cluster from store", async () => {
const prodCluster = new Cluster({ await clusterStore.removeById("foo");
expect(clusterStore.getById("foo")).toBeUndefined();
})
it("sets active cluster", () => {
clusterStore.setActive("foo");
expect(clusterStore.activeCluster.id).toBe("foo");
})
})
describe("with prod and dev clusters added", () => {
beforeEach(() => {
clusterStore.addCluster(
new Cluster({
id: "prod", id: "prod",
contextName: "prod", contextName: "prod",
preferences: { preferences: {
@ -61,8 +79,8 @@ describe("empty config", () => {
}, },
kubeConfigPath: ClusterStore.embedCustomKubeConfig("prod", "fancy config"), kubeConfigPath: ClusterStore.embedCustomKubeConfig("prod", "fancy config"),
workspace: "workstation" workspace: "workstation"
}); }),
const devCluster = new Cluster({ new Cluster({
id: "dev", id: "dev",
contextName: "dev", contextName: "dev",
preferences: { preferences: {
@ -70,27 +88,24 @@ describe("empty config", () => {
}, },
kubeConfigPath: ClusterStore.embedCustomKubeConfig("dev", "fancy config"), kubeConfigPath: ClusterStore.embedCustomKubeConfig("dev", "fancy config"),
workspace: "workstation" workspace: "workstation"
}); })
clusterStore.addCluster(prodCluster); )
clusterStore.addCluster(devCluster); })
it("check if store can contain multiple clusters", () => {
expect(clusterStore.hasClusters()).toBeTruthy(); expect(clusterStore.hasClusters()).toBeTruthy();
expect(clusterStore.clusters.size).toBe(3); expect(clusterStore.clusters.size).toBe(2);
}); });
it("gets clusters by workspaces", () => { it("gets clusters by workspaces", () => {
const wsClusters = clusterStore.getByWorkspaceId("workstation"); const wsClusters = clusterStore.getByWorkspaceId("workstation");
const defaultClusters = clusterStore.getByWorkspaceId("default"); const defaultClusters = clusterStore.getByWorkspaceId("default");
expect(defaultClusters.length).toBe(1); expect(defaultClusters.length).toBe(0);
expect(wsClusters.length).toBe(2); expect(wsClusters.length).toBe(2);
expect(wsClusters[0].id).toBe("prod"); expect(wsClusters[0].id).toBe("prod");
expect(wsClusters[1].id).toBe("dev"); expect(wsClusters[1].id).toBe("dev");
}) })
it("sets active cluster", () => {
clusterStore.setActive("foo");
expect(clusterStore.activeCluster.id).toBe("foo");
})
it("check if cluster's kubeconfig file saved", () => { it("check if cluster's kubeconfig file saved", () => {
const file = ClusterStore.embedCustomKubeConfig("boo", "kubeconfig"); const file = ClusterStore.embedCustomKubeConfig("boo", "kubeconfig");
expect(fs.readFileSync(file, "utf8")).toBe("kubeconfig"); expect(fs.readFileSync(file, "utf8")).toBe("kubeconfig");
@ -104,7 +119,7 @@ describe("empty config", () => {
expect(clusters[0].preferences.iconOrder).toBe(0) expect(clusters[0].preferences.iconOrder).toBe(0)
expect(clusters[1].id).toBe("dev") expect(clusters[1].id).toBe("dev")
expect(clusters[1].preferences.iconOrder).toBe(1) expect(clusters[1].preferences.iconOrder).toBe(1)
}); })
it("check if reorderring works for different from and to", () => { it("check if reorderring works for different from and to", () => {
clusterStore.swapIconOrders("workstation", 0, 1) clusterStore.swapIconOrders("workstation", 0, 1)
@ -114,19 +129,15 @@ describe("empty config", () => {
expect(clusters[0].preferences.iconOrder).toBe(0) expect(clusters[0].preferences.iconOrder).toBe(0)
expect(clusters[1].id).toBe("prod") expect(clusters[1].id).toBe("prod")
expect(clusters[1].preferences.iconOrder).toBe(1) expect(clusters[1].preferences.iconOrder).toBe(1)
}); })
it("check if after icon reordering, changing workspaces still works", () => { it("check if after icon reordering, changing workspaces still works", () => {
clusterStore.swapIconOrders("workstation", 1, 1) clusterStore.swapIconOrders("workstation", 1, 1)
clusterStore.getById("prod").workspace = "default" clusterStore.getById("prod").workspace = "default"
expect(clusterStore.getByWorkspaceId("workstation").length).toBe(1); expect(clusterStore.getByWorkspaceId("workstation").length).toBe(1);
expect(clusterStore.getByWorkspaceId("default").length).toBe(2); expect(clusterStore.getByWorkspaceId("default").length).toBe(1);
}); })
it("removes cluster from store", async () => {
await clusterStore.removeById("foo");
expect(clusterStore.getById("foo")).toBeUndefined();
}) })
}) })

View File

@ -10,7 +10,7 @@ jest.mock("electron", () => {
} }
}) })
import { UserStore } from "./user-store" import { UserStore } from "../user-store"
import { SemVer } from "semver" import { SemVer } from "semver"
import electron from "electron" import electron from "electron"

View File

@ -10,7 +10,7 @@ jest.mock("electron", () => {
} }
}) })
import { WorkspaceStore } from "./workspace-store" import { WorkspaceStore } from "../workspace-store"
describe("workspace store tests", () => { describe("workspace store tests", () => {
describe("for an empty config", () => { describe("for an empty config", () => {

View File

@ -1,4 +1,4 @@
import { splitArray } from "./splitArray"; import { splitArray } from "../splitArray";
describe("split array on element tests", () => { describe("split array on element tests", () => {
test("empty array", () => { test("empty array", () => {

View File

@ -1,11 +1,11 @@
import { IKubeApiParsed, parseKubeApi } from "./kube-api-parse"; import { IKubeApiParsed, parseKubeApi } from "../kube-api-parse";
interface KubeApi_Parse_Test { interface KubeApiParseTestData {
url: string; url: string;
expected: Required<IKubeApiParsed>; expected: Required<IKubeApiParsed>;
} }
const tests: KubeApi_Parse_Test[] = [ const tests: KubeApiParseTestData[] = [
{ {
url: "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com", url: "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com",
expected: { expected: {
@ -125,11 +125,10 @@ const tests: KubeApi_Parse_Test[] = [
}, },
]; ];
describe.only("parseApi unit tests", () => { describe("parseApi unit tests", () => {
for (const i in tests) { for (const { url, expected } of tests) {
const { url: tUrl, expected:tExpect} = tests[i]; test(`testing "${url}"`, () => {
test(`test #${parseInt(i)+1}`, () => { expect(parseKubeApi(url)).toStrictEqual(expected);
expect(parseKubeApi(tUrl)).toStrictEqual(tExpect);
}); });
} }
}); });

View File

@ -4,7 +4,7 @@ import { Input } from "../../input";
import { observable, autorun } from "mobx"; import { observable, autorun } from "mobx";
import { observer, disposeOnUnmount } from "mobx-react"; import { observer, disposeOnUnmount } from "mobx-react";
import { SubTitle } from "../../layout/sub-title"; import { SubTitle } from "../../layout/sub-title";
import { isRequired } from "../../input/input.validators"; import { isRequired } from "../../input/input_validators";
interface Props { interface Props {
cluster: Cluster; cluster: Cluster;
@ -33,7 +33,7 @@ export class ClusterNameSetting extends React.Component<Props> {
render() { render() {
return ( return (
<> <>
<SubTitle title="Cluster Name"/> <SubTitle title="Cluster Name" />
<p>Define cluster name.</p> <p>Define cluster name.</p>
<Input <Input
theme="round-black" theme="round-black"

View File

@ -3,7 +3,7 @@ import { observable, autorun } from "mobx";
import { observer, disposeOnUnmount } from "mobx-react"; import { observer, disposeOnUnmount } from "mobx-react";
import { Cluster } from "../../../../main/cluster"; import { Cluster } from "../../../../main/cluster";
import { Input } from "../../input"; import { Input } from "../../input";
import { isUrl } from "../../input/input.validators"; import { isUrl } from "../../input/input_validators";
import { SubTitle } from "../../layout/sub-title"; import { SubTitle } from "../../layout/sub-title";
interface Props { interface Props {
@ -33,7 +33,7 @@ export class ClusterProxySetting extends React.Component<Props> {
render() { render() {
return ( return (
<> <>
<SubTitle title="HTTP Proxy"/> <SubTitle title="HTTP Proxy" />
<p>HTTP Proxy server. Used for communicating with Kubernetes API.</p> <p>HTTP Proxy server. Used for communicating with Kubernetes API.</p>
<Input <Input
theme="round-black" theme="round-black"

View File

@ -8,7 +8,7 @@ import { _i18n } from "../../i18n";
import { Dialog, DialogProps } from "../dialog"; import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard"; import { Wizard, WizardStep } from "../wizard";
import { Input } from "../input"; import { Input } from "../input";
import { systemName } from "../input/input.validators"; import { systemName } from "../input/input_validators";
import { IResourceQuotaValues, resourceQuotaApi } from "../../api/endpoints/resource-quota.api"; import { IResourceQuotaValues, resourceQuotaApi } from "../../api/endpoints/resource-quota.api";
import { Select } from "../select"; import { Select } from "../select";
import { Icon } from "../icon"; import { Icon } from "../icon";
@ -73,7 +73,7 @@ export class AddQuotaDialog extends React.Component<Props> {
const isCount = quota.startsWith("count/"); const isCount = quota.startsWith("count/");
const icon = isCompute ? "memory" : isStorage ? "storage" : isCount ? "looks_one" : ""; const icon = isCompute ? "memory" : isStorage ? "storage" : isCount ? "looks_one" : "";
return { return {
label: icon ? <span className="nobr"><Icon material={icon}/> {quota}</span> : quota, label: icon ? <span className="nobr"><Icon material={icon} /> {quota}</span> : quota,
value: quota, value: quota,
}; };
}); });
@ -151,7 +151,7 @@ export class AddQuotaDialog extends React.Component<Props> {
/> />
</div> </div>
<SubTitle title={<Trans>Namespace</Trans>}/> <SubTitle title={<Trans>Namespace</Trans>} />
<NamespaceSelect <NamespaceSelect
value={this.namespace} value={this.namespace}
placeholder={_i18n._(t`Namespace`)} placeholder={_i18n._(t`Namespace`)}
@ -160,7 +160,7 @@ export class AddQuotaDialog extends React.Component<Props> {
onChange={({ value }) => this.namespace = value} onChange={({ value }) => this.namespace = value}
/> />
<SubTitle title={<Trans>Values</Trans>}/> <SubTitle title={<Trans>Values</Trans>} />
<div className="flex gaps align-center"> <div className="flex gaps align-center">
<Select <Select
className="quota-select" className="quota-select"
@ -191,7 +191,7 @@ export class AddQuotaDialog extends React.Component<Props> {
<div key={quota} className="quota flex gaps inline align-center"> <div key={quota} className="quota flex gaps inline align-center">
<div className="name">{quota}</div> <div className="name">{quota}</div>
<div className="value">{value}</div> <div className="value">{value}</div>
<Icon material="clear" onClick={() => this.quotas[quota] = ""}/> <Icon material="clear" onClick={() => this.quotas[quota] = ""} />
</div> </div>
) )
})} })}

View File

@ -8,7 +8,7 @@ import { _i18n } from "../../i18n";
import { Dialog, DialogProps } from "../dialog"; import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard"; import { Wizard, WizardStep } from "../wizard";
import { Input } from "../input"; import { Input } from "../input";
import { systemName } from "../input/input.validators"; import { systemName } from "../input/input_validators";
import { Secret, secretsApi, SecretType } from "../../api/endpoints"; import { Secret, secretsApi, SecretType } from "../../api/endpoints";
import { SubTitle } from "../layout/sub-title"; import { SubTitle } from "../layout/sub-title";
import { NamespaceSelect } from "../+namespaces/namespace-select"; import { NamespaceSelect } from "../+namespaces/namespace-select";
@ -184,7 +184,7 @@ export class AddSecretDialog extends React.Component<Props> {
<Wizard header={header} done={this.close}> <Wizard header={header} done={this.close}>
<WizardStep contentClass="flow column" nextLabel={<Trans>Create</Trans>} next={this.createSecret}> <WizardStep contentClass="flow column" nextLabel={<Trans>Create</Trans>} next={this.createSecret}>
<div className="secret-name"> <div className="secret-name">
<SubTitle title={<Trans>Secret name</Trans>}/> <SubTitle title={<Trans>Secret name</Trans>} />
<Input <Input
autoFocus required autoFocus required
placeholder={_i18n._(t`Name`)} placeholder={_i18n._(t`Name`)}
@ -194,7 +194,7 @@ export class AddSecretDialog extends React.Component<Props> {
</div> </div>
<div className="flex auto gaps"> <div className="flex auto gaps">
<div className="secret-namespace"> <div className="secret-namespace">
<SubTitle title={<Trans>Namespace</Trans>}/> <SubTitle title={<Trans>Namespace</Trans>} />
<NamespaceSelect <NamespaceSelect
themeName="light" themeName="light"
value={namespace} value={namespace}
@ -202,7 +202,7 @@ export class AddSecretDialog extends React.Component<Props> {
/> />
</div> </div>
<div className="secret-type"> <div className="secret-type">
<SubTitle title={<Trans>Secret type</Trans>}/> <SubTitle title={<Trans>Secret type</Trans>} />
<Select <Select
themeName="light" themeName="light"
options={this.types} options={this.types}

View File

@ -10,7 +10,7 @@ import { Wizard, WizardStep } from "../wizard";
import { namespaceStore } from "./namespace.store"; import { namespaceStore } from "./namespace.store";
import { Namespace } from "../../api/endpoints"; import { Namespace } from "../../api/endpoints";
import { Input } from "../input"; import { Input } from "../input";
import { systemName } from "../input/input.validators"; import { systemName } from "../input/input_validators";
import { Notifications } from "../notifications"; import { Notifications } from "../notifications";
interface Props extends DialogProps { interface Props extends DialogProps {

View File

@ -10,7 +10,7 @@ import { Wizard, WizardStep } from "../wizard";
import { SubTitle } from "../layout/sub-title"; import { SubTitle } from "../layout/sub-title";
import { serviceAccountsStore } from "./service-accounts.store"; import { serviceAccountsStore } from "./service-accounts.store";
import { Input } from "../input"; import { Input } from "../input";
import { systemName } from "../input/input.validators"; import { systemName } from "../input/input_validators";
import { NamespaceSelect } from "../+namespaces/namespace-select"; import { NamespaceSelect } from "../+namespaces/namespace-select";
import { Notifications } from "../notifications"; import { Notifications } from "../notifications";
import { showDetails } from "../../navigation"; import { showDetails } from "../../navigation";
@ -62,14 +62,14 @@ export class CreateServiceAccountDialog extends React.Component<Props> {
> >
<Wizard header={header} done={this.close}> <Wizard header={header} done={this.close}>
<WizardStep nextLabel={<Trans>Create</Trans>} next={this.createAccount}> <WizardStep nextLabel={<Trans>Create</Trans>} next={this.createAccount}>
<SubTitle title={<Trans>Account Name</Trans>}/> <SubTitle title={<Trans>Account Name</Trans>} />
<Input <Input
autoFocus required autoFocus required
placeholder={_i18n._(t`Enter a name`)} placeholder={_i18n._(t`Enter a name`)}
validators={systemName} validators={systemName}
value={name} onChange={v => this.name = v.toLowerCase()} value={name} onChange={v => this.name = v.toLowerCase()}
/> />
<SubTitle title={<Trans>Namespace</Trans>}/> <SubTitle title={<Trans>Namespace</Trans>} />
<NamespaceSelect <NamespaceSelect
themeName="light" themeName="light"
value={namespace} value={namespace}

View File

@ -10,7 +10,7 @@ import { CronJob, cronJobApi, jobApi, Job } from "../../api/endpoints";
import { Notifications } from "../notifications"; import { Notifications } from "../notifications";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { Input } from "../input"; import { Input } from "../input";
import { systemName, maxLength } from "../input/input.validators"; import { systemName, maxLength } from "../input/input_validators";
interface Props extends Partial<DialogProps> { interface Props extends Partial<DialogProps> {
} }

View File

@ -1,4 +1,4 @@
import { isEmail, systemName } from "./input.validators"; import { isEmail, systemName } from "../input_validators";
describe("input validation tests", () => { describe("input validation tests", () => {
describe("isEmail tests", () => { describe("isEmail tests", () => {

View File

@ -3,7 +3,7 @@ import "./input.scss";
import React, { DOMAttributes, InputHTMLAttributes, TextareaHTMLAttributes } from "react"; import React, { DOMAttributes, InputHTMLAttributes, TextareaHTMLAttributes } from "react";
import { autobind, cssNames, debouncePromise } from "../../utils"; import { autobind, cssNames, debouncePromise } from "../../utils";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { conditionalValidators, Validator } from "./input.validators"; import { conditionalValidators, Validator } from "./input_validators";
import isString from "lodash/isString" import isString from "lodash/isString"
import isFunction from "lodash/isFunction" import isFunction from "lodash/isFunction"
import isBoolean from "lodash/isBoolean" import isBoolean from "lodash/isBoolean"
@ -288,9 +288,9 @@ export class Input extends React.Component<InputProps, State> {
return ( return (
<div className={className}> <div className={className}>
<label className="input-area flex gaps align-center"> <label className="input-area flex gaps align-center">
{isString(iconLeft) ? <Icon material={iconLeft}/> : iconLeft} {isString(iconLeft) ? <Icon material={iconLeft} /> : iconLeft}
{multiLine ? <textarea {...inputProps as any}/> : <input {...inputProps as any}/>} {multiLine ? <textarea {...inputProps as any} /> : <input {...inputProps as any} />}
{isString(iconRight) ? <Icon material={iconRight}/> : iconRight} {isString(iconRight) ? <Icon material={iconRight} /> : iconRight}
</label> </label>
<div className="input-info flex gaps"> <div className="input-info flex gaps">
{!valid && dirty && ( {!valid && dirty && (

View File

@ -1,4 +1,4 @@
import { formatDuration } from "./formatDuration"; import { formatDuration } from "../formatDuration";
const second = 1000; const second = 1000;
const minute = 60 * second; const minute = 60 * second;

View File

@ -1,4 +1,4 @@
import { metricUnitsToNumber } from "./metricUnitsToNumber"; import { metricUnitsToNumber } from "../metricUnitsToNumber";
describe("metricUnitsToNumber tests", () => { describe("metricUnitsToNumber tests", () => {
test("plain number", () => { test("plain number", () => {