Fix test flakiness by removing side effects from userStore preferences

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton committed 2022-12-06 17:14:30 -05:00
1 parent a6d1b7a3a4
commit f95ed19a7d
24 files changed
+369 -385

No files matched your search

@@ -2524,12 +2524,40 @@ exports[`remove helm repository from list of active repositories in preferences
Synced Items
</div>
<div
class="loading-spinner"
>
<div>
<div
class="Spinner singleColor"
/>
class="item flex gaps align-center justify-space-between mt-3"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="folder"
>
folder
</span>
</i>
<div
class="flex-grow break-all"
>
/some-home-dir/.kube
</div>
<i
class="Icon material interactive focusable"
tabindex="0"
>
<span
class="icon"
data-icon-name="delete"
>
delete
</span>
</i>
<div>
Remove
</div>
</div>
</div>
</section>
</div>
@@ -8,12 +8,13 @@ import { withInjectables } from "@ogre-tools/injectable-react";
import type { UserStore } from "../../../../../../common/user-store";
import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable";
import { Select } from "../../../../../../renderer/components/select";
import { defaultLocaleTimezone } from "../../../../../../common/user-store/preferences-helpers";
import moment from "moment-timezone";
import { observer } from "mobx-react";
import currentTimezoneInjectable from "../../../../../../common/user-store/current-timezone.injectable";
interface Dependencies {
userStore: UserStore;
currentTimezone: string;
}
const timezoneOptions = moment.tz.names()
@@ -23,26 +24,26 @@ const timezoneOptions = moment.tz.names()
}));
const NonInjectedTimezone = observer(({ userStore }: Dependencies) => (
const NonInjectedTimezone = observer(({
userStore,
currentTimezone,
}: Dependencies) => (
<section id="locale">
<SubTitle title="Locale Timezone" />
<Select
id="timezone-input"
options={timezoneOptions}
value={userStore.localeTimezone}
onChange={value => userStore.localeTimezone = value?.value ?? defaultLocaleTimezone}
onChange={value => userStore.localeTimezone = value?.value ?? currentTimezone}
themeName="lens"
/>
</section>
));
export const Timezone = withInjectables<Dependencies>(
NonInjectedTimezone,
{
getProps: (di) => ({
userStore: di.inject(userStoreInjectable),
}),
},
);
export const Timezone = withInjectables<Dependencies>(NonInjectedTimezone, {
getProps: (di) => ({
userStore: di.inject(userStoreInjectable),
currentTimezone: di.inject(currentTimezoneInjectable),
}),
});
@@ -0,0 +1,20 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { SyncKind } from "./discover-sync-kind.injectable";
import discoverKubeconfigSyncKindInjectable from "./discover-sync-kind.injectable";
export type DiscoverAllKubeconfigSyncKinds = (filePaths: string[]) => Promise<[string, SyncKind][]>;
const discoverAllKubeconfigSyncKindsInjectable = getInjectable({
id: "discover-all-kubeconfig-sync-kinds",
instantiate: (di): DiscoverAllKubeconfigSyncKinds => {
const discoverKubeconfigSyncKind = di.inject(discoverKubeconfigSyncKindInjectable);
return filePaths => Promise.all(filePaths.map(discoverKubeconfigSyncKind));
},
});
export default discoverAllKubeconfigSyncKindsInjectable;
@@ -0,0 +1,46 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import statInjectable from "../../../../../../common/fs/stat.injectable";
import loggerInjectable from "../../../../../../common/logger.injectable";
export interface SyncKind {
type: "file" | "folder" | "unknown";
}
export type DiscoverKubeconfigSyncKind = (path: string) => Promise<[string, SyncKind]>;
const discoverKubeconfigSyncKindInjectable = getInjectable({
id: "discover-kubeconfig-sync-kind",
instantiate: (di): DiscoverKubeconfigSyncKind => {
const stat = di.inject(statInjectable);
const logger = di.inject(loggerInjectable);
return async (path) => {
try {
// stat follows the stat(2) linux syscall spec, namely it follows symlinks
const stats = await stat(path);
if (stats.isFile()) {
return [path, { type: "file" }];
}
if (stats.isDirectory()) {
return [path, { type: "folder" }];
}
logger.warn("[KUBECONFIG-SYNCS]: unknown stat entry", { stats });
return [path, { type: "unknown" }];
} catch (error) {
logger.warn(`[KUBECONFIG-SYNCS]: failed to stat entry: ${error}`, { error });
return [path, { type: "unknown" }];
}
};
},
});
export default discoverKubeconfigSyncKindInjectable;
@@ -3,12 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import fse from "fs-extra";
import { computed, makeObservable, observable, reaction } from "mobx";
import { action, computed, makeObservable, observable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import React from "react";
import { Notice } from "../../../../../../renderer/components/+extensions/notice";
import type { KubeconfigSyncEntry, KubeconfigSyncValue, UserStore } from "../../../../../../common/user-store";
import type { UserStore } from "../../../../../../common/user-store";
import { iter, tuple } from "../../../../../../renderer/utils";
import { SubTitle } from "../../../../../../renderer/components/layout/sub-title";
import { PathPicker } from "../../../../../../renderer/components/path-picker/path-picker";
@@ -18,56 +17,26 @@ import userStoreInjectable from "../../../../../../common/user-store/user-store.
import isWindowsInjectable from "../../../../../../common/vars/is-windows.injectable";
import loggerInjectable from "../../../../../../common/logger.injectable";
import type { Logger } from "../../../../../../common/logger";
import type { DiscoverAllKubeconfigSyncKinds } from "./discover-all-sync-kinds.injectable";
import type { DiscoverKubeconfigSyncKind, SyncKind } from "./discover-sync-kind.injectable";
import discoverKubeconfigSyncKindInjectable from "./discover-sync-kind.injectable";
import discoverAllKubeconfigSyncKindsInjectable from "./discover-all-sync-kinds.injectable";
interface SyncInfo {
type: "file" | "folder" | "unknown";
}
interface Entry extends Value {
interface Entry extends SyncKind {
filePath: string;
}
interface Value {
data: KubeconfigSyncValue;
info: SyncInfo;
}
async function getMapEntry({ filePath, ...data }: KubeconfigSyncEntry, logger: Logger): Promise<[string, Value]> {
try {
// stat follows the stat(2) linux syscall spec, namely it follows symlinks
const stats = await fse.stat(filePath);
if (stats.isFile()) {
return [filePath, { info: { type: "file" }, data }];
}
if (stats.isDirectory()) {
return [filePath, { info: { type: "folder" }, data }];
}
logger.warn("[KubeconfigSyncs]: unknown stat entry", { stats });
return [filePath, { info: { type: "unknown" }, data }];
} catch (error) {
logger.warn(`[KubeconfigSyncs]: failed to stat entry: ${error}`, { error });
return [filePath, { info: { type: "unknown" }, data }];
}
}
export async function getAllEntries(filePaths: string[], logger: Logger): Promise<[string, Value][]> {
return Promise.all(filePaths.map(filePath => getMapEntry({ filePath }, logger)));
}
interface Dependencies {
userStore: UserStore;
isWindows: boolean;
logger: Logger;
discoverAllKubeconfigSyncKinds: DiscoverAllKubeconfigSyncKinds;
discoverKubeconfigSyncKind: DiscoverKubeconfigSyncKind;
}
@observer
class NonInjectedKubeconfigSync extends React.Component<Dependencies> {
syncs = observable.map<string, Value>();
readonly syncs = observable.map<string, SyncKind>();
@observable loaded = false;
constructor(props: Dependencies) {
@@ -79,7 +48,7 @@ class NonInjectedKubeconfigSync extends React.Component<Dependencies> {
const mapEntries = await Promise.all(
iter.map(
this.props.userStore.syncKubeconfigEntries,
([filePath, ...value]) => getMapEntry({ filePath, ...value }, this.props.logger),
([filePath]) => this.props.discoverKubeconfigSyncKind(filePath),
),
);
@@ -88,9 +57,13 @@ class NonInjectedKubeconfigSync extends React.Component<Dependencies> {
disposeOnUnmount(this, [
reaction(
() => Array.from(this.syncs.entries(), ([filePath, { data }]) => tuple.from(filePath, data)),
() => Array.from(this.syncs.entries(), ([filePath, kind]) => tuple.from(filePath, kind)),
syncs => {
this.props.userStore.syncKubeconfigEntries.replace(syncs);
action(() => {
for (const [path] of syncs) {
this.props.userStore.syncKubeconfigEntries.set(path, {});
}
});
},
),
]);
@@ -105,11 +78,11 @@ class NonInjectedKubeconfigSync extends React.Component<Dependencies> {
}
onPick = async (filePaths: string[]) => {
this.syncs.merge(await getAllEntries(filePaths, this.props.logger));
this.syncs.merge(await this.props.discoverAllKubeconfigSyncKinds(filePaths));
};
getIconName(entry: Entry) {
switch (entry.info.type) {
switch (entry.type) {
case "file":
return "description";
case "folder":
@@ -206,14 +179,12 @@ class NonInjectedKubeconfigSync extends React.Component<Dependencies> {
}
}
export const KubeconfigSync = withInjectables<Dependencies>(
NonInjectedKubeconfigSync,
{
getProps: (di) => ({
userStore: di.inject(userStoreInjectable),
isWindows: di.inject(isWindowsInjectable),
logger: di.inject(loggerInjectable),
}),
},
);
export const KubeconfigSync = withInjectables<Dependencies>(NonInjectedKubeconfigSync, {
getProps: (di) => ({
userStore: di.inject(userStoreInjectable),
isWindows: di.inject(isWindowsInjectable),
logger: di.inject(loggerInjectable),
discoverAllKubeconfigSyncKinds: di.inject(discoverAllKubeconfigSyncKindsInjectable),
discoverKubeconfigSyncKind: di.inject(discoverKubeconfigSyncKindInjectable),
}),
});