Replace all internal uses of utilities with new packages

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton committed 2023-03-02 13:30:57 -05:00
1 parent 3a2e436933
commit ef32f2b72b
410 files changed
+1222 -825

No files matched your search

@@ -1,3 +1,5 @@
export * from "./src/flush-promises";
export * from "./src/get-global-override-for-function";
export * from "./src/get-global-override";
export * from "./src/get-promise-status";
export * from "./src/use-fake-time";
@@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Injectable } from "@ogre-tools/injectable";
import { getGlobalOverride } from "./get-global-override";
import { camelCase } from "lodash/fp";
export const getGlobalOverrideForFunction = (
injectable: Injectable<Function, any, any>,
) =>
getGlobalOverride(injectable, () => (...args: any[]) => {
console.warn(
`Tried to invoke a function "${injectable.id}" without override. The args were:`,
args,
);
throw new Error(
`Tried to invoke a function "${
injectable.id
}" without override. Add eg. "di.override(${camelCase(
injectable.id,
)}Mock)" to the unit test interested in this.`,
);
});
@@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Injectable } from "@ogre-tools/injectable";
export interface GlobalOverride {
injectable: Injectable<any, any, any>;
overridingInstantiate: any;
}
export const getGlobalOverride = <T extends Injectable<any, any, any>>(
injectable: T,
overridingInstantiate: T["instantiate"],
) => ({
injectable,
overridingInstantiate,
});
+5 -5
View File
@@ -1,5 +1,8 @@
export * from "./src/abort-controller";
export * from "./src/array";
export * from "./src/backoff-caller";
export * from "./src/base64";
export * from "./src/buildUrl";
export * from "./src/camelCase";
export * from "./src/collection-functions";
export * from "./src/computed-or";
@@ -17,11 +20,13 @@ export * from "./src/interval";
export * from "./src/is-node-falsy";
export * from "./src/isMiddleClick";
export * from "./src/isReactNode";
export * from "./src/iter";
export * from "./src/json";
export * from "./src/jsonPath";
export * from "./src/metricUnitsToNumber";
export * from "./src/name-parts";
export * from "./src/noop";
export * from "./src/object";
export * from "./src/observable-crate";
export * from "./src/on-keyboard-shortcut";
export * from "./src/prevDefault";
@@ -38,8 +43,3 @@ export * from "./src/types";
export * from "./src/union-env-path";
export * from "./src/wait";
export * from "./src/with-concurrency-limit";
export * as iter from "./src/iter";
export * as array from "./src/array";
export * as object from "./src/object";
export * as base64 from "./src/base64";
@@ -8,7 +8,7 @@
* @param length The number of entries
* @param value The value of each of the indices
*/
export function filled<T>(length: number, value: T): T[] {
function filled<T>(length: number, value: T): T[] {
return Array(length).fill(value);
}
@@ -23,7 +23,7 @@ export function filled<T>(length: number, value: T): T[] {
* @returns the left and right sub-arrays which when conjoined with `element`
* is the same as `array`, and `true`
*/
export function split<T>(src: T[], element: T): [T[], T[], boolean] {
function split<T>(src: T[], element: T): [T[], T[], boolean] {
const index = src.indexOf(element);
if (index < 0) {
@@ -33,7 +33,7 @@ export function split<T>(src: T[], element: T): [T[], T[], boolean] {
return [src.slice(0, index), src.slice(index + 1, src.length), true];
}
export function bifurcate<T>(src: T[], condition: (item: T) => any): [falses: T[], trues: T[]] {
function bifurcate<T>(src: T[], condition: (item: T) => any): [falses: T[], trues: T[]] {
const trues: T[] = [];
const falses: T[] = [];
@@ -47,3 +47,9 @@ export function bifurcate<T>(src: T[], condition: (item: T) => any): [falses: T[
return [falses, trues];
}
export const array = {
bifurcate,
filled,
split,
};
@@ -12,7 +12,7 @@ import * as Utf8 from "crypto-js/enc-utf8";
* @param data A Base64 encoded string
* @returns The original utf-8 string
*/
export function decode(data: string): string {
function decode(data: string): string {
return Base64.parse(data).toString(Utf8);
}
@@ -21,6 +21,11 @@ export function decode(data: string): string {
* @param data A normal string
* @returns A base64 encoded version
*/
export function encode(data: string): string {
function encode(data: string): string {
return Utf8.parse(data).toString(Base64);
}
export const base64 = {
encode,
decode,
};
@@ -0,0 +1,58 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { compile } from "path-to-regexp";
import type { RouteProps } from "react-router";
import { isDefined } from "@k8slens/utilities";
export interface UrlRouteProps extends RouteProps {
path: string;
}
export interface URLParams<P extends object = {}, Q extends object = {}> {
params?: P;
query?: Q;
fragment?: string;
}
export function buildURL<P extends object = {}, Q extends object = {}>(path: string, { params, query, fragment }: URLParams<P, Q> = {}) {
const pathBuilder = compile(String(path));
const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : "";
const parts = [
pathBuilder(params),
queryParams && `?${queryParams}`,
fragment && `#${fragment}`,
];
return parts.filter(isDefined).join("");
}
export function buildURLPositional<P extends object = {}, Q extends object = {}>(path: string) {
return function (params?: P, query?: Q, fragment?: string): string {
return buildURL(path, { params, query, fragment });
};
}
export type UrlParamsFor<Pathname extends string> =
Pathname extends `${string}/:${infer A}?/${infer Tail}`
? Partial<Record<A, string>> & UrlParamsFor<`/${Tail}`>
: Pathname extends `${string}/:${infer A}/${infer Tail}`
? Record<A, string> & UrlParamsFor<`/${Tail}`>
: Pathname extends `${string}/:${infer A}?`
? Partial<Record<A, string>>
: Pathname extends `${string}/:${infer A}`
? Record<A, string>
: {};
export interface UrlBuilder<Pathname extends string> {
compile(params: UrlParamsFor<Pathname>, query?: object, fragment?: string): string;
}
export function urlBuilderFor<Pathname extends string>(pathname: Pathname): UrlBuilder<Pathname> {
return {
compile: buildURLPositional(pathname),
};
}
@@ -0,0 +1,128 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { bytesToUnits, unitsToBytes } from "./convertMemory";
describe("unitsToBytes", () => {
it("without any units, just parse as float", () => {
expect(unitsToBytes("1234")).toBe(1234);
});
it("should parse with B suffix", () => {
expect(unitsToBytes("1234B")).toBe(1234);
});
it("should parse with Ki suffix", () => {
expect(unitsToBytes("1234Ki")).toBe(1234 * (1024));
});
it("should parse with Ki suffix as the same as KiB", () => {
expect(unitsToBytes("1234Ki")).toBe(unitsToBytes("1234KiB"));
});
it("should parse with Mi suffix", () => {
expect(unitsToBytes("1234Mi")).toBe(1234 * (1024 ** 2));
});
it("should parse with Mi suffix as the same as MiB", () => {
expect(unitsToBytes("1234Mi")).toBe(unitsToBytes("1234MiB"));
});
it("should parse with Gi suffix", () => {
expect(unitsToBytes("1234Gi")).toBe(1234 * (1024 ** 3));
});
it("should parse with Gi suffix as the same as GiB", () => {
expect(unitsToBytes("1234Gi")).toBe(unitsToBytes("1234GiB"));
});
it("should parse with Ti suffix", () => {
expect(unitsToBytes("1234Ti")).toBe(1234 * (1024 ** 4));
});
it("should parse with Ti suffix as the same as TiB", () => {
expect(unitsToBytes("1234Ti")).toBe(unitsToBytes("1234TiB"));
});
it("should parse with Pi suffix", () => {
expect(unitsToBytes("1234Pi")).toBe(1234 * (1024 ** 5));
});
it("should parse with Pi suffix as the same as PiB", () => {
expect(unitsToBytes("1234Pi")).toBe(unitsToBytes("1234PiB"));
});
it("given unrelated data, return NaN", () => {
expect(unitsToBytes("I am not a number")).toBeNaN();
});
it("given unrelated data, but has number, return that", () => {
expect(unitsToBytes("I am not a number, but this is 0.1")).toBe(0.1);
});
});
describe("bytesToUnits", () => {
it("should return N/A for invalid bytes", () => {
expect(bytesToUnits(-1)).toBe("N/A");
expect(bytesToUnits(Infinity)).toBe("N/A");
expect(bytesToUnits(NaN)).toBe("N/A");
});
it("given a number within the magnitude of 0..124, format with B", () => {
expect(bytesToUnits(100)).toBe("100.0B");
});
it("given a number within the magnitude of 1024..1024^2, format with KiB", () => {
expect(bytesToUnits(1024)).toBe("1.0KiB");
expect(bytesToUnits(2048)).toBe("2.0KiB");
expect(bytesToUnits(1900)).toBe("1.9KiB");
expect(bytesToUnits(50*1024 + 1)).toBe("50.0KiB");
});
it("given a number within the magnitude of 1024^2..1024^3, format with MiB", () => {
expect(bytesToUnits(1024**2)).toBe("1.0MiB");
expect(bytesToUnits(2048**2)).toBe("4.0MiB");
expect(bytesToUnits(1900 * 1024)).toBe("1.9MiB");
expect(bytesToUnits(50*(1024 ** 2) + 1)).toBe("50.0MiB");
});
it("given a number within the magnitude of 1024^3..1024^4, format with GiB", () => {
expect(bytesToUnits(1024**3)).toBe("1.0GiB");
expect(bytesToUnits(2048**3)).toBe("8.0GiB");
expect(bytesToUnits(1900 * 1024 ** 2)).toBe("1.9GiB");
expect(bytesToUnits(50*(1024 ** 3) + 1)).toBe("50.0GiB");
});
it("given a number within the magnitude of 1024^4..1024^5, format with TiB", () => {
expect(bytesToUnits(1024**4)).toBe("1.0TiB");
expect(bytesToUnits(2048**4)).toBe("16.0TiB");
expect(bytesToUnits(1900 * 1024 ** 3)).toBe("1.9TiB");
expect(bytesToUnits(50*(1024 ** 4) + 1)).toBe("50.0TiB");
});
it("given a number within the magnitude of 1024^5..1024^6, format with PiB", () => {
expect(bytesToUnits(1024**5)).toBe("1.0PiB");
expect(bytesToUnits(2048**5)).toBe("32.0PiB");
expect(bytesToUnits(1900 * 1024 ** 4)).toBe("1.9PiB");
expect(bytesToUnits(50*(1024 ** 5) + 1)).toBe("50.0PiB");
});
});
describe("bytesToUnits -> unitsToBytes", () => {
it("given an input, round trip to the same value, given enough precision", () => {
expect(unitsToBytes(bytesToUnits(123))).toBe(123);
expect(unitsToBytes(bytesToUnits(1024**0 + 1, { precision: 2 }))).toBe(1024**0 + 1);
expect(unitsToBytes(bytesToUnits(1024**1 + 2, { precision: 3 }))).toBe(1024**1 + 2);
expect(unitsToBytes(bytesToUnits(1024**2 + 3, { precision: 6 }))).toBe(1024**2 + 3);
expect(unitsToBytes(bytesToUnits(1024**3 + 4, { precision: 9 }))).toBe(1024**3 + 4);
expect(unitsToBytes(bytesToUnits(1024**4 + 5, { precision: 12 }))).toBe(1024**4 + 5);
expect(unitsToBytes(bytesToUnits(1024**5 + 6, { precision: 16 }))).toBe(1024**5 + 6);
expect(unitsToBytes(bytesToUnits(1024**6 + 7, { precision: 20 }))).toBe(1024**6 + 7);
});
it("given an invalid input, round trips to NaN", () => {
expect(unitsToBytes(bytesToUnits(-1))).toBeNaN();
});
});
@@ -0,0 +1,94 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import moment from "moment";
import { formatDuration } from "./formatDuration";
const second = 1000;
const minute = 60 * second;
const hour = 60 * minute;
const day = 24 * hour;
const year = 365 * day;
describe("human format durations", () => {
test("small duration should output something", () => {
expect(formatDuration(1)).toBe("0s");
expect(formatDuration(3)).toBe("0s");
});
test("returns seconds for duration under 1 min", () => {
const res = formatDuration(8 * second);
expect(res).toBe("8s");
});
test("zero duration should output something", () => {
expect(formatDuration(0)).toBe("0s");
});
describe("when compact is true", () => {
test("duration under 3 hours return minutes", () => {
const res = formatDuration(1 * hour + 35 * minute);
expect(res).toBe("95m");
});
test("duration under 8 hours return hours and minutes", () => {
const res = formatDuration(6 * hour + 15 * minute + 20 * second);
expect(res).toBe("6h15m");
});
test("duration under 48 hours return hours", () => {
const res = formatDuration(1 * day + 4 * hour + 15 * minute);
expect(res).toBe("28h");
});
test("duration under 2 years return days", () => {
const res = formatDuration(400 * day + 4 * hour + 15 * minute);
expect(res).toBe("400d");
});
test("durations less than 8 years returns years and days", () => {
const timeValue = new Date(2020, 0, 10, 12, 0, 0, 0).getTime() - new Date(2018, 0, 4, 12, 0, 0, 0).getTime();
const res = formatDuration(timeValue);
expect(res).toBe("2y5d");
});
test("durations more than 8 years returns years", () => {
const timeValue = Date.now() - new Date(moment().subtract(9, "years").subtract(5, "days").toDate()).getTime();
const res = formatDuration(timeValue);
expect(res).toBe("9y");
});
test("durations more than 8 years returns years", () => {
const res = formatDuration(10 * year + 25 * day);
expect(res).toBe("10y");
});
test("durations shouldn't include zero magnitude parts", () => {
const zeroSeconds = formatDuration(8 * minute);
expect(zeroSeconds).toBe("8m");
const zeroMinutes = formatDuration(8 * hour + 15 * minute);
expect(zeroMinutes).toBe("8h");
const zeroHours = formatDuration(6 * day + 2 * minute);
expect(zeroHours).toBe("6d");
});
});
});
@@ -0,0 +1,512 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { HashSet, ObservableHashSet } from "./hash-set";
describe("ObservableHashSet<T>", () => {
it("should not throw on creation", () => {
expect(() => new ObservableHashSet<{ a: number }>([], item => item.a.toString())).not.toThrowError();
});
it("should be initialized", () => {
const res = new ObservableHashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.size).toBe(4);
});
it("has should work as expected", () => {
const res = new ObservableHashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.has({ a: 1 })).toBe(true);
expect(res.has({ a: 5 })).toBe(false);
});
it("forEach should work as expected", () => {
const res = new ObservableHashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
let a = 1;
res.forEach((item) => {
expect(item.a).toEqual(a++);
});
});
it("delete should work as expected", () => {
const res = new ObservableHashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.has({ a: 1 })).toBe(true);
expect(res.delete({ a: 1 })).toBe(true);
expect(res.has({ a: 1 })).toBe(false);
expect(res.has({ a: 5 })).toBe(false);
expect(res.delete({ a: 5 })).toBe(false);
expect(res.has({ a: 5 })).toBe(false);
});
it("toggle should work as expected", () => {
const res = new ObservableHashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.has({ a: 1 })).toBe(true);
res.toggle({ a: 1 });
expect(res.has({ a: 1 })).toBe(false);
expect(res.has({ a: 6 })).toBe(false);
res.toggle({ a: 6 });
expect(res.has({ a: 6 })).toBe(true);
});
it("add should work as expected", () => {
const res = new ObservableHashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.has({ a: 6 })).toBe(false);
res.add({ a: 6 });
expect(res.has({ a: 6 })).toBe(true);
});
it("add should treat the hash to be the same as equality", () => {
const res = new ObservableHashSet([
{ a: 1, foobar: "hello" },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.has({ a: 1 })).toBe(true);
res.add({ a: 1, foobar: "goodbye" });
expect(res.has({ a: 1 })).toBe(true);
});
it("clear should work as expected", () => {
const res = new ObservableHashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.size).toBe(4);
res.clear();
expect(res.size).toBe(0);
});
it("replace should work as expected", () => {
const res = new ObservableHashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.size).toBe(4);
res.replace([{ a: 13 }]);
expect(res.size).toBe(1);
expect(res.has({ a: 1 })).toBe(false);
expect(res.has({ a: 13 })).toBe(true);
});
it("toJSON should work as expected", () => {
const res = new ObservableHashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.toJSON()).toStrictEqual([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
]);
});
it("values should work as expected", () => {
const res = new ObservableHashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
const iter = res.values();
expect(iter.next()).toStrictEqual({
value: { a: 1 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: { a: 2 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: { a: 3 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: { a: 4 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: undefined,
done: true,
});
});
it("keys should work as expected", () => {
const res = new ObservableHashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
const iter = res.keys();
expect(iter.next()).toStrictEqual({
value: { a: 1 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: { a: 2 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: { a: 3 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: { a: 4 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: undefined,
done: true,
});
});
it("entries should work as expected", () => {
const res = new ObservableHashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
const iter = res.entries();
expect(iter.next()).toStrictEqual({
value: [{ a: 1 }, { a: 1 }],
done: false,
});
expect(iter.next()).toStrictEqual({
value: [{ a: 2 }, { a: 2 }],
done: false,
});
expect(iter.next()).toStrictEqual({
value: [{ a: 3 }, { a: 3 }],
done: false,
});
expect(iter.next()).toStrictEqual({
value: [{ a: 4 }, { a: 4 }],
done: false,
});
expect(iter.next()).toStrictEqual({
value: undefined,
done: true,
});
});
});
describe("HashSet<T>", () => {
it("should not throw on creation", () => {
expect(() => new HashSet<{ a: number }>([], item => item.a.toString())).not.toThrowError();
});
it("should be initialized", () => {
const res = new HashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.size).toBe(4);
});
it("has should work as expected", () => {
const res = new HashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.has({ a: 1 })).toBe(true);
expect(res.has({ a: 5 })).toBe(false);
});
it("forEach should work as expected", () => {
const res = new HashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
let a = 1;
res.forEach((item) => {
expect(item.a).toEqual(a++);
});
});
it("delete should work as expected", () => {
const res = new HashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.has({ a: 1 })).toBe(true);
expect(res.delete({ a: 1 })).toBe(true);
expect(res.has({ a: 1 })).toBe(false);
expect(res.has({ a: 5 })).toBe(false);
expect(res.delete({ a: 5 })).toBe(false);
expect(res.has({ a: 5 })).toBe(false);
});
it("toggle should work as expected", () => {
const res = new HashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.has({ a: 1 })).toBe(true);
res.toggle({ a: 1 });
expect(res.has({ a: 1 })).toBe(false);
expect(res.has({ a: 6 })).toBe(false);
res.toggle({ a: 6 });
expect(res.has({ a: 6 })).toBe(true);
});
it("add should work as expected", () => {
const res = new HashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.has({ a: 6 })).toBe(false);
res.add({ a: 6 });
expect(res.has({ a: 6 })).toBe(true);
});
it("add should treat the hash to be the same as equality", () => {
const res = new HashSet([
{ a: 1, foobar: "hello" },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.has({ a: 1 })).toBe(true);
res.add({ a: 1, foobar: "goodbye" });
expect(res.has({ a: 1 })).toBe(true);
});
it("clear should work as expected", () => {
const res = new HashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.size).toBe(4);
res.clear();
expect(res.size).toBe(0);
});
it("replace should work as expected", () => {
const res = new HashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.size).toBe(4);
res.replace([{ a: 13 }]);
expect(res.size).toBe(1);
expect(res.has({ a: 1 })).toBe(false);
expect(res.has({ a: 13 })).toBe(true);
});
it("toJSON should work as expected", () => {
const res = new HashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
expect(res.toJSON()).toStrictEqual([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
]);
});
it("values should work as expected", () => {
const res = new HashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
const iter = res.values();
expect(iter.next()).toStrictEqual({
value: { a: 1 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: { a: 2 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: { a: 3 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: { a: 4 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: undefined,
done: true,
});
});
it("keys should work as expected", () => {
const res = new HashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
const iter = res.keys();
expect(iter.next()).toStrictEqual({
value: { a: 1 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: { a: 2 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: { a: 3 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: { a: 4 },
done: false,
});
expect(iter.next()).toStrictEqual({
value: undefined,
done: true,
});
});
it("entries should work as expected", () => {
const res = new HashSet([
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
], item => item.a.toString());
const iter = res.entries();
expect(iter.next()).toStrictEqual({
value: [{ a: 1 }, { a: 1 }],
done: false,
});
expect(iter.next()).toStrictEqual({
value: [{ a: 2 }, { a: 2 }],
done: false,
});
expect(iter.next()).toStrictEqual({
value: [{ a: 3 }, { a: 3 }],
done: false,
});
expect(iter.next()).toStrictEqual({
value: [{ a: 4 }, { a: 4 }],
done: false,
});
expect(iter.next()).toStrictEqual({
value: undefined,
done: true,
});
});
});
@@ -6,7 +6,7 @@
// Type guard for checking valid react node to use in render
import type { ReactNode } from "react";
import React from "react";
import { isObject } from "../../common/utils";
import { isObject } from "@k8slens/utilities";
export function isReactNode(node: unknown): node is ReactNode {
return (isObject(node) && React.isValidElement(node))
@@ -0,0 +1,101 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { iter } from "./iter";
describe("iter", () => {
describe("reduce", () => {
it("can reduce a value", () => {
expect(iter.reduce([1, 2, 3], (acc: number[], current: number) => [current, ...acc], [0])).toEqual([3, 2, 1, 0]);
});
it("can reduce an empty iterable", () => {
expect(iter.reduce([], (acc: number[], current: number) => [acc[0] + current], [])).toEqual([]);
});
});
describe("join", () => {
it("should not prefix the output by the separator", () => {
expect(iter.join(["a", "b", "c"].values(), " ")).toBe("a b c");
});
it("should return empty string if iterator is empty", () => {
expect(iter.join([].values(), " ")).toBe("");
});
it("should return just first entry if iterator is of size 1", () => {
expect(iter.join(["d"].values(), " ")).toBe("d");
});
});
describe("nth", () => {
it("should return undefined past the end of the iterator", () => {
expect(iter.nth(["a"], 123)).toBeUndefined();
});
it("should by 0-indexing the index", () => {
expect(iter.nth(["a", "b"], 0)).toBe("a");
});
});
describe("concat", () => {
it("should yield undefined for empty args", () => {
const i = iter.concat();
expect(i.next()).toEqual({ done: true });
});
it("should yield undefined for only empty args", () => {
const i = iter.concat([].values(), [].values(), [].values(), [].values());
expect(i.next()).toEqual({ done: true });
});
it("should yield all of the first and then all of the second", () => {
const i = iter.concat([1, 2, 3].values(), [4, 5, 6].values());
expect(i.next()).toEqual({ done: false, value: 1 });
expect(i.next()).toEqual({ done: false, value: 2 });
expect(i.next()).toEqual({ done: false, value: 3 });
expect(i.next()).toEqual({ done: false, value: 4 });
expect(i.next()).toEqual({ done: false, value: 5 });
expect(i.next()).toEqual({ done: false, value: 6 });
expect(i.next()).toEqual({ done: true });
});
});
describe("nFircate", () => {
it("should produce an empty array if no parts are provided", () => {
expect(iter.nFircate([{ a: 1 }, { a: 2 }], "a", []).length).toBe(0);
});
it("should ignore non-matching parts", () => {
const res = iter.nFircate([{ a: 1 }, { a: 2 }], "a", [1]);
expect(res.length).toBe(1);
expect(res[0].length).toBe(1);
});
it("should include all matching parts in each type", () => {
const res = iter.nFircate([{ a: 1, b: "a" }, { a: 2, b: "b" }, { a: 1, b: "c" }], "a", [1, 2]);
expect(res.length).toBe(2);
expect(res[0].length).toBe(2);
expect(res[0][0].b).toBe("a");
expect(res[0][1].b).toBe("c");
expect(res[1].length).toBe(1);
expect(res[1][0].b).toBe("b");
});
it("should throw a type error if the same part is provided more than once", () => {
try {
iter.nFircate([{ a: 1, b: "a" }, { a: 2, b: "b" }, { a: 1, b: "c" }], "a", [1, 2, 1]);
fail("Expected error");
} catch (error) {
expect(error).toBeInstanceOf(TypeError);
}
});
});
});
+42 -23
View File
@@ -16,7 +16,7 @@ interface Iterator<T> extends Iterable<T> {
join(sep?: string): string;
}
export function chain<T>(src: IterableIterator<T>): Iterator<T> {
function chain<T>(src: IterableIterator<T>): Iterator<T> {
return {
filter: (fn) => chain(filter(src, fn)),
filterMap: (fn) => chain(filterMap(src, fn)),
@@ -34,7 +34,7 @@ export function chain<T>(src: IterableIterator<T>): Iterator<T> {
* Create a new type safe empty Iterable
* @returns An `Iterable` that yields 0 items
*/
export function* newEmpty<T>(): IterableIterator<T> {}
function* newEmpty<T>(): IterableIterator<T> {}
/**
* Creates a new `Iterable` that yields at most n items from src.
@@ -42,7 +42,7 @@ export function* newEmpty<T>(): IterableIterator<T> {}
* @param src An initial iterator
* @param n The maximum number of elements to take from src. Yields up to the floor of `n` and 0 items if n < 0
*/
export function* take<T>(src: Iterable<T>, n: number): IterableIterator<T> {
function* take<T>(src: Iterable<T>, n: number): IterableIterator<T> {
outer: for (let i = 0; i < n; i += 1) {
for (const item of src) {
yield item;
@@ -60,7 +60,7 @@ export function* take<T>(src: Iterable<T>, n: number): IterableIterator<T> {
* @param src A type that can be iterated over
* @param fn The function that is called for each value
*/
export function* map<T, U>(src: Iterable<T>, fn: (from: T) => U): IterableIterator<U> {
function* map<T, U>(src: Iterable<T>, fn: (from: T) => U): IterableIterator<U> {
for (const from of src) {
yield fn(from);
}
@@ -71,7 +71,7 @@ export function* map<T, U>(src: Iterable<T>, fn: (from: T) => U): IterableIterat
* @param src A type that can be iterated over
* @param fn The function that returns either an iterable over items that should be filtered out or a `Falsy` value indicating that it should be ignored
*/
export function* filterFlatMap<T, U>(src: Iterable<T>, fn: (from: T) => Iterable<U | Falsy> | Falsy): IterableIterator<U> {
function* filterFlatMap<T, U>(src: Iterable<T>, fn: (from: T) => Iterable<U | Falsy> | Falsy): IterableIterator<U> {
for (const from of src) {
if (!from) {
continue;
@@ -96,7 +96,7 @@ export function* filterFlatMap<T, U>(src: Iterable<T>, fn: (from: T) => Iterable
* @param src A type that can be iterated over
* @param fn A function that returns an iterator
*/
export function* flatMap<T, U>(src: Iterable<T>, fn: (from: T) => Iterable<U>): IterableIterator<U> {
function* flatMap<T, U>(src: Iterable<T>, fn: (from: T) => Iterable<U>): IterableIterator<U> {
for (const from of src) {
yield* fn(from);
}
@@ -108,7 +108,7 @@ export function* flatMap<T, U>(src: Iterable<T>, fn: (from: T) => Iterable<U>):
* @param src A type that can be iterated over
* @param fn The function that is called for each value
*/
export function* filter<T>(src: Iterable<T>, fn: (from: T) => any): IterableIterator<T> {
function* filter<T>(src: Iterable<T>, fn: (from: T) => any): IterableIterator<T> {
for (const from of src) {
if (fn(from)) {
yield from;
@@ -122,7 +122,7 @@ export function* filter<T>(src: Iterable<T>, fn: (from: T) => any): IterableIter
* @param src A type that can be iterated over
* @param fn The function that is called for each value
*/
export function* filterMap<T, U>(src: Iterable<T>, fn: (from: T) => U | Falsy): IterableIterator<U> {
function* filterMap<T, U>(src: Iterable<T>, fn: (from: T) => U | Falsy): IterableIterator<U> {
for (const from of src) {
const res = fn(from);
@@ -138,7 +138,7 @@ export function* filterMap<T, U>(src: Iterable<T>, fn: (from: T) => U | Falsy):
* @param src A type that can be iterated over
* @param fn The function that is called for each value
*/
export function* filterMapStrict<T, U>(src: Iterable<T>, fn: (from: T) => U | null | undefined): IterableIterator<U> {
function* filterMapStrict<T, U>(src: Iterable<T>, fn: (from: T) => U | null | undefined): IterableIterator<U> {
for (const from of src) {
const res = fn(from);
@@ -154,7 +154,7 @@ export function* filterMapStrict<T, U>(src: Iterable<T>, fn: (from: T) => U | nu
* @param match A function that should return a truthy value for the item that you want to find
* @returns The first entry that `match` returns a truthy value for, or `undefined`
*/
export function find<T>(src: Iterable<T>, match: (i: T) => any): T | undefined {
function find<T>(src: Iterable<T>, match: (i: T) => any): T | undefined {
for (const from of src) {
if (match(from)) {
return from;
@@ -171,9 +171,9 @@ export function find<T>(src: Iterable<T>, match: (i: T) => any): T | undefined {
* @param reducer A function for producing the next item from an accumulation and the current item
* @param initial The initial value for the iteration
*/
export function reduce<T, R extends Iterable<any>>(src: Iterable<T>, reducer: (acc: R, cur: T) => R, initial: R): R;
function reduce<T, R extends Iterable<any>>(src: Iterable<T>, reducer: (acc: R, cur: T) => R, initial: R): R;
export function reduce<T, R = T>(src: Iterable<T>, reducer: (acc: R, cur: T) => R, initial: R): R {
function reduce<T, R = T>(src: Iterable<T>, reducer: (acc: R, cur: T) => R, initial: R): R {
let acc = initial;
for (const item of src) {
@@ -189,7 +189,7 @@ export function reduce<T, R = T>(src: Iterable<T>, reducer: (acc: R, cur: T) =>
* @param connector The string value to intersperse between the yielded values
* @returns The concatenated entries of `src` interspersed with copies of `connector`
*/
export function join(src: IterableIterator<unknown>, connector = ","): string {
function join(src: IterableIterator<unknown>, connector = ","): string {
const iterSrc = src[Symbol.iterator]();
const first = iterSrc.next();
@@ -205,7 +205,7 @@ export function join(src: IterableIterator<unknown>, connector = ","): string {
* @param src The value to iterate over
* @param n The zero-index value for the item to return to.
*/
export function nth<T>(src: Iterable<T>, n: number): T | undefined {
function nth<T>(src: Iterable<T>, n: number): T | undefined {
const iterator = src[Symbol.iterator]();
while (n --> 0) {
@@ -219,7 +219,7 @@ export function nth<T>(src: Iterable<T>, n: number): T | undefined {
* A convenience function to get the first item of an iterator
* @param src The value to iterate over
*/
export function first<T>(src: Iterable<T>): T | undefined {
function first<T>(src: Iterable<T>): T | undefined {
return nth(src, 0);
}
@@ -229,7 +229,7 @@ export function first<T>(src: Iterable<T>): T | undefined {
* @param src The type to be iterated over
* @param fn A function to check each iteration
*/
export function every<T>(src: Iterable<T>, fn: (val: T) => any): boolean {
function every<T>(src: Iterable<T>, fn: (val: T) => any): boolean {
for (const val of src) {
if (!fn(val)) {
return false;
@@ -239,7 +239,7 @@ export function every<T>(src: Iterable<T>, fn: (val: T) => any): boolean {
return true;
}
export function* concat<T>(...sources: IterableIterator<T>[]): IterableIterator<T> {
function* concat<T>(...sources: IterableIterator<T>[]): IterableIterator<T> {
for (const source of sources) {
for (const val of source) {
yield val;
@@ -254,12 +254,11 @@ export function* concat<T>(...sources: IterableIterator<T>[]): IterableIterator<
* @param parts What each array will be filtered to
* @returns A `parts.length` tuple of `T[]` where each array has matching `field` values
*/
export function nFircate<T>(from: Iterable<T>, field: keyof T, parts: []): [];
export function nFircate<T>(from: Iterable<T>, field: keyof T, parts: [T[typeof field]]): [T[]];
export function nFircate<T>(from: Iterable<T>, field: keyof T, parts: [T[typeof field], T[typeof field]]): [T[], T[]];
export function nFircate<T>(from: Iterable<T>, field: keyof T, parts: [T[typeof field], T[typeof field], T[typeof field]]): [T[], T[], T[]];
export function nFircate<T>(from: Iterable<T>, field: keyof T, parts: T[typeof field][]): T[][] {
function nFircate<T>(from: Iterable<T>, field: keyof T, parts: []): [];
function nFircate<T>(from: Iterable<T>, field: keyof T, parts: [T[typeof field]]): [T[]];
function nFircate<T>(from: Iterable<T>, field: keyof T, parts: [T[typeof field], T[typeof field]]): [T[], T[]];
function nFircate<T>(from: Iterable<T>, field: keyof T, parts: [T[typeof field], T[typeof field], T[typeof field]]): [T[], T[], T[]];
function nFircate<T>(from: Iterable<T>, field: keyof T, parts: T[typeof field][]): T[][] {
if (new Set(parts).size !== parts.length) {
throw new TypeError("Duplicate parts entries");
}
@@ -278,3 +277,23 @@ export function nFircate<T>(from: Iterable<T>, field: keyof T, parts: T[typeof f
return res;
}
export const iter = {
chain,
concat,
every,
filter,
filterFlatMap,
filterMap,
filterMapStrict,
find,
first,
flatMap,
join,
map,
nFircate,
newEmpty,
nth,
reduce,
take,
};
@@ -7,22 +7,28 @@
* A better typed version of `Object.fromEntries` where the keys are known to
* be a specific subset
*/
export function fromEntries<T, Key extends string>(entries: Iterable<readonly [Key, T]>): Record<Key, T> {
function fromEntries<T, Key extends string>(entries: Iterable<readonly [Key, T]>): Record<Key, T> {
return Object.fromEntries(entries) as Record<Key, T>;
}
export function keys<K extends keyof any>(obj: Partial<Record<K, any>>): K[];
export function keys<K extends keyof any>(obj: Record<K, any>): K[] {
function keys<K extends keyof any>(obj: Partial<Record<K, any>>): K[];
function keys<K extends keyof any>(obj: Record<K, any>): K[] {
return Object.keys(obj) as K[];
}
export function entries<K extends string, V>(obj: Partial<Record<K, V>> | null | undefined): [K, V][];
export function entries<K extends string | number | symbol, V>(obj: Partial<Record<K, V>> | null | undefined): [K, V][];
export function entries<K extends string | number | symbol, V>(obj: Record<K, V> | null | undefined): [K, V][];
export function entries<K extends string | number | symbol, V>(obj: Record<K, V> | null | undefined): [K, V][] {
function entries<K extends string, V>(obj: Partial<Record<K, V>> | null | undefined): [K, V][];
function entries<K extends string | number | symbol, V>(obj: Partial<Record<K, V>> | null | undefined): [K, V][];
function entries<K extends string | number | symbol, V>(obj: Record<K, V> | null | undefined): [K, V][];
function entries<K extends string | number | symbol, V>(obj: Record<K, V> | null | undefined): [K, V][] {
if (obj && typeof obj == "object") {
return Object.entries(obj) as never;
}
return [] as never;
}
export const object = {
entries,
fromEntries,
keys,
};
@@ -46,12 +46,12 @@ export function sortCompare<T>(left: T, right: T): Ordering {
}
/**
* This function sorts of list of items that have what should be a semver version formated string
* as the field `version` but if it is not loosely coercable to semver falls back to sorting them
* This function sorts of list of items that have what should be a semver version formatted string
* as the field `version` but if it is not loosely coercible to semver falls back to sorting them
* alphanumerically
*/
export function sortBySemverVersion<T extends { version: string }>(versioneds: T[]): T[] {
return versioneds
export function sortBySemverVersion<T extends { version: string }>(versioned: T[]): T[] {
return versioned
.map(versioned => ({
__version: coerce(versioned.version, { loose: true }),
raw: versioned,
@@ -0,0 +1,66 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { bifurcateArray, splitArray } from "../splitArray";
describe("split array on element tests", () => {
it("empty array", () => {
expect(splitArray([], 10)).toStrictEqual([[], [], false]);
});
it("one element, not in array", () => {
expect(splitArray([1], 10)).toStrictEqual([[1], [], false]);
});
it("ten elements, not in array", () => {
expect(splitArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 10)).toStrictEqual([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [], false]);
});
it("one elements, in array", () => {
expect(splitArray([1], 1)).toStrictEqual([[], [], true]);
});
it("ten elements, in front array", () => {
expect(splitArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0)).toStrictEqual([[], [1, 2, 3, 4, 5, 6, 7, 8, 9], true]);
});
it("ten elements, in middle array", () => {
expect(splitArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 4)).toStrictEqual([[0, 1, 2, 3], [5, 6, 7, 8, 9], true]);
});
it("ten elements, in end array", () => {
expect(splitArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 9)).toStrictEqual([[0, 1, 2, 3, 4, 5, 6, 7, 8], [], true]);
});
});
describe("bifurcateArray", () => {
it("should return tuple of empty arrays from empty array", () => {
const [left, right] = bifurcateArray([], () => true);
expect(left).toStrictEqual([]);
expect(right).toStrictEqual([]);
});
it("should return all true condition returning items in the right array", () => {
const [left, right] = bifurcateArray([1, 2, 3], () => true);
expect(left).toStrictEqual([]);
expect(right).toStrictEqual([1, 2, 3]);
});
it("should return all false condition returning items in the right array", () => {
const [left, right] = bifurcateArray([1, 2, 3], () => false);
expect(left).toStrictEqual([1, 2, 3]);
expect(right).toStrictEqual([]);
});
it("should split array as specified", () => {
const [left, right] = bifurcateArray([1, 2, 3], (i) => Boolean(i % 2));
expect(left).toStrictEqual([2]);
expect(right).toStrictEqual([1, 3]);
});
});
@@ -0,0 +1,43 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { tuple } from "./tuple";
describe("tuple tests", () => {
describe("zip()", () => {
it("should yield 0 times and return 1 tuple of empty arrays when given empty array", () => {
expect(tuple.zip([]).next()).toEqual({
done: true,
value: [[]],
});
});
it("should yield 1 times and return 2 tuple of empty arrays when given one element array tuples", () => {
const i = tuple.zip([1], [2]);
expect(i.next()).toEqual({
done: false,
value: [1, 2],
});
expect(i.next()).toEqual({
done: true,
value: [[], []],
});
});
it("should yield 1 times and return 2 tuple of partial arrays when given one element array tuples", () => {
const i = tuple.zip([1], [2, 3]);
expect(i.next()).toEqual({
done: false,
value: [1, 2],
});
expect(i.next()).toEqual({
done: true,
value: [[], [3]],
});
});
});
});
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import * as array from "./array";
import { array } from "./array";
/**
* A strict N-tuple of type T
@@ -24,11 +24,10 @@ type TupleOfImpl<T, N extends number, R extends unknown[]> = R["length"] extends
* @yields A tuple of the next element from each of the sources
* @returns The tuple of all the sources as soon as at least one of the sources is exausted
*/
export function zip<T>(...sources: Tuple<T[], 0>): Iterator<Tuple<T, 0>, Tuple<T[], 0>>;
export function zip<T>(...sources: Tuple<T[], 1>): Iterator<Tuple<T, 1>, Tuple<T[], 1>>;
export function zip<T>(...sources: Tuple<T[], 2>): Iterator<Tuple<T, 2>, Tuple<T[], 2>>;
export function* zip<T, N extends number>(...sources: Tuple<T[], N>): Iterator<Tuple<T, N>, Tuple<T[], N>> {
function zip<T>(...sources: Tuple<T[], 0>): Iterator<Tuple<T, 0>, Tuple<T[], 0>>;
function zip<T>(...sources: Tuple<T[], 1>): Iterator<Tuple<T, 1>, Tuple<T[], 1>>;
function zip<T>(...sources: Tuple<T[], 2>): Iterator<Tuple<T, 2>, Tuple<T[], 2>>;
function* zip<T, N extends number>(...sources: Tuple<T[], N>): Iterator<Tuple<T, N>, Tuple<T[], N>> {
const maxSafeLength = Math.min(...sources.map(source => source.length));
for (let i = 0; i < maxSafeLength; i += 1) {
@@ -43,13 +42,19 @@ export function* zip<T, N extends number>(...sources: Tuple<T[], N>): Iterator<T
* @param length The size of the tuple
* @param value The value for each of the tuple entries
*/
export function filled<T, L extends number>(length: L, value: T): Tuple<T, L> {
function filled<T, L extends number>(length: L, value: T): Tuple<T, L> {
return array.filled(length, value) as Tuple<T, L>;
}
/**
* A function for converting an explicit array to a tuple but without the `readonly` typing
*/
export function from<T extends any[]>(...args: T): [...T] {
function from<T extends any[]>(...args: T): [...T] {
return args;
}
export const tuple = {
zip,
filled,
from,
};
@@ -0,0 +1,32 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import path from "path";
import { unionPATHs } from "../union-env-path";
describe("unionPATHs", () => {
it("return the same path if given only one with no double delimiters", () => {
expect(unionPATHs(`/bin/bar${path.delimiter}/usr/bin`)).toBe(`/bin/bar${path.delimiter}/usr/bin`);
});
it("return equivalent path if given only one with no double delimiters", () => {
expect(unionPATHs(`/bin/bar${path.delimiter}${path.delimiter}/usr/bin`)).toBe(`/bin/bar${path.delimiter}/usr/bin`);
});
it("should remove duplicate entries, appending non duplicates in order received", () => {
expect(unionPATHs(
`/bin/bar${path.delimiter}/usr/bin`,
`/bin/bar${path.delimiter}/usr/lens/bat`,
)).toBe(`/bin/bar${path.delimiter}/usr/bin${path.delimiter}/usr/lens/bat`);
});
it("should remove duplicate entries, appending non duplicates in order received, 3", () => {
expect(unionPATHs(
`/bin/bar${path.delimiter}/usr/bin`,
`/bin/bar${path.delimiter}/usr/lens/bat`,
`/usr/local/lens${path.delimiter}/usr/bin`,
)).toBe(`/bin/bar${path.delimiter}/usr/bin${path.delimiter}/usr/lens/bat${path.delimiter}/usr/local/lens`);
});
});