Skip to content

Commit

Permalink
add new files
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinbarabash committed Feb 16, 2023
1 parent 1d8e550 commit 8ed2605
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/long-pears-laugh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/wonder-stuff-core": minor
---

Re-add entries, keys, and values wrappers
44 changes: 44 additions & 0 deletions packages/wonder-stuff-core/src/__tests__/entries.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// @flow
import {entries} from "../entries";

describe("#entries", () => {
it("should call Object.entries with the given object", () => {
// Arrange
const entriesSpy = jest.spyOn(Object, "entries");
const obj = {
a: 1,
b: "2",
c: [3, 4],
};

// Act
entries(obj);

// Assert
expect(entriesSpy).toHaveBeenCalledWith(obj);
});

it("should return the result of Object.entries", () => {
// Arrange
const obj = {
a: 1,
b: "2",
c: [3, 4],
};
jest.spyOn(Object, "entries").mockReturnValueOnce([
["e", 1],
["f", 2],
["g", 3],
]);

// Act
const result = entries(obj);

// Assert
expect(result).toEqual([
["e", 1],
["f", 2],
["g", 3],
]);
});
});
69 changes: 69 additions & 0 deletions packages/wonder-stuff-core/src/__tests__/entries.typestest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {entries} from "../entries";

{
// should type returned array element as tuples of keys as string subtype
const obj1 = {
a: 1,
b: "2",
c: [3, 4],
};

const entries1 = entries(obj1);

// This works because the keys are all strings
const [_]: [string, unknown] = entries1[0];
}

{
// should type returned array element as tuples supertype of keys
const obj2 = {
a: 1,
b: "2",
c: [3, 4],
} as const;
const entries2 = entries(obj2);

// It would be nice if this worked, but TypeScript's library definition
// defines the first item in the tuples returned by Object.entries() to
// be `string`s.
// @ts-expect-error: "a" | "b" | "c" is not assignable to string
const [_]: ["a" | "b" | "c", unknown] = entries2[0];

// This errors because we try to get a key of only one type.
// @ts-expect-error: "a" is not assignable to string
const [__]: ["a", unknown] = entries2[0];
}

{
// should type returned array element as tuples of values as supertype
const obj1 = {
a: 1,
b: "2",
c: [3, 4],
};

const entries1 = entries(obj1);

// This works because the keys are all strings, and the values are a
// supertype of all the value types in the object.
const [_, __]: [string, number | string | Array<number>] = entries1[0];

// @ts-expect-error: This errors because not all values are a number.
const [___, ____]: [string, number] = entries1[0];
}

{
// should work with class instances
class Foo {
a: string;
b: string;
constructor(a: string, b: string) {
this.a = a;
this.b = b;
}
}
const foo = new Foo("hello", "world");

// This should not be erroring.
const _ = entries(foo);
}
35 changes: 35 additions & 0 deletions packages/wonder-stuff-core/src/__tests__/keys.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {keys} from "../keys";

describe("#keys", () => {
it("should call Object.keys with the given object", () => {
// Arrange
const keysSpy = jest.spyOn(Object, "keys");
const obj = {
a: 1,
b: "2",
c: [3, 4],
};

// Act
keys(obj);

// Assert
expect(keysSpy).toHaveBeenCalledWith(obj);
});

it("should return the result of Object.keys", () => {
// Arrange
const obj = {
a: 1,
b: "2",
c: [3, 4],
};
jest.spyOn(Object, "keys").mockReturnValueOnce(["THE RESULT"]);

// Act
const result = keys(obj);

// Assert
expect(result).toEqual(["THE RESULT"]);
});
});
57 changes: 57 additions & 0 deletions packages/wonder-stuff-core/src/__tests__/keys.typestest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {keys} from "../keys";

{
// should type returned array element as subtype of string
const obj1 = {
a: 1,
b: "2",
};

const keys1 = keys(obj1);
const _: string = keys1[0];
}

{
// should type returned array element as supertype of keys
const obj2 = {
a: 1,
b: "2",
c: [3, 4],
} as const;

// It would be nice if this worked, but TypeScript's library definition
// defines the return type of Object.keys() to be Array<string>.
const keys2bad = keys(obj2);
// @ts-expect-error: string is not assignable to "a" | "b" | "c"
const _: "a" | "b" | "c" = keys2bad[0];

// @ts-expect-error: This errors because we try to get a key of only one type.
const __: "a" = keys2bad[0];
}

{
// should work with more specific object types
const obj3: Record<string, string> = {
a: "1",
b: "2",
};

// This should not be erroring.
const _ = keys(obj3);
}

{
// should work with class instances
class Foo {
a: string;
b: string;
constructor(a: string, b: string) {
this.a = a;
this.b = b;
}
}
const foo = new Foo("hello", "world");

// This should not be erroring.
const _ = keys(foo);
}
35 changes: 35 additions & 0 deletions packages/wonder-stuff-core/src/__tests__/values.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {values} from "../values";

describe("#values", () => {
it("should call Object.values with the given object", () => {
// Arrange
const valuesSpy = jest.spyOn(Object, "values");
const obj = {
a: 1,
b: "2",
c: [3, 4],
};

// Act
values(obj);

// Assert
expect(valuesSpy).toHaveBeenCalledWith(obj);
});

it("should return the result of Object.values", () => {
// Arrange
const obj = {
a: 1,
b: "2",
c: [3, 4],
};
jest.spyOn(Object, "values").mockReturnValue(["THE RESULT"]);

// Act
const result = values(obj);

// Assert
expect(result).toEqual(["THE RESULT"]);
});
});
71 changes: 71 additions & 0 deletions packages/wonder-stuff-core/src/__tests__/values.typestest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {values} from "../values";

{
// should type returned array element with union of value types of passed object
const obj1 = {
a: 1,
b: "2",
c: [3, 4],
};
const obj1Values = values(obj1);

// This works because the variable is typed to all the possible value types.
const _: number | string | Array<number> = obj1Values[0];

// @ts-expect-error: This errors because the variable is only typed to string, but
// the value could be a number, string, or array of numbers.
const __: string = obj1Values[1];
}

{
// should work with explicit object-as-map types
const map1: {[key: string]: number} = {
a: 1,
b: 2,
c: 3,
};
const map1Values = values(map1);

// This works because the variable is typed to number.
const _: number = map1Values[0];

// @ts-expect-error: This errors because the variable is typed to string, and that
// is not a number.
const __: string = map1Values[1];
}

{
// should return type Array<empty> for empty object
const emptyObj = {};
const _: Array<never> = values(emptyObj);
}

{
// should error if passed object values do not match parameterized type
const obj2 = {
a: 1,
b: "2",
};

// @ts-expect-error: This errors because the return type of values() is not Array<number>
const _: Array<number> = values(obj2);

// @ts-expect-error: This errors because the object does not have values that are all numbers.
const __ = values<number>(obj2);
}

{
// should work with class instances
class Foo {
a: string;
b: string;
constructor(a: string, b: string) {
this.a = a;
this.b = b;
}
}
const foo = new Foo("hello", "world");

// This should not be erroring.
const _ = values(foo);
}
19 changes: 19 additions & 0 deletions packages/wonder-stuff-core/src/entries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Return an array of key/value tuples for an object.
*
* @param {{[s: string]: T}} obj The object for which the values are
* to be returned.
* @returns {Array<[string, T]>} An array of key/value tuples for the object.
*/
// NOTE(kevinb): This type was copied from TypeScript's library definitions.
export const entries: {
<T>(
obj:
| {
[s: string]: T;
}
| ArrayLike<T>,
): [string, T][];
// eslint-disable-next-line @typescript-eslint/ban-types
(obj: {}): [string, any][];
} = (obj: any) => Object.entries(obj);
12 changes: 12 additions & 0 deletions packages/wonder-stuff-core/src/keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Return an array of the enumerable keys of an object.
*
* @param {$ReadOnly<interface {[string]: mixed}>} obj The object for which the values are
* to be returned.
* @returns {Array<$Keys<O>>} An array of the enumerable keys of an object.
*/
// NOTE(kevinb): This type was copied from TypeScript's library definitions.
// eslint-disable-next-line @typescript-eslint/ban-types
export function keys(obj: {}): string[] {
return Object.keys(obj);
}
19 changes: 19 additions & 0 deletions packages/wonder-stuff-core/src/values.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Return an array of the enumerable property values of an object.
*
* @param {$ReadOnly<interface {[mixed]: V}>} obj The object for which the values are
* to be returned.
* @returns {Array<V>} An array of the enumerable property values of the object.
*/
// NOTE(kevinb): This type was copied from TypeScript's library definitions.
export const values: {
<T>(
obj:
| {
[s: string]: T;
}
| ArrayLike<T>,
): T[];
// eslint-disable-next-line @typescript-eslint/ban-types
(obj: {}): any[];
} = (obj: any) => Object.values(obj);

0 comments on commit 8ed2605

Please sign in to comment.