inferred-types 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,37 +1,124 @@
1
- import { MapToWithFiltering } from "src/types/dictionary";
1
+ import { MapDirectionVal, MapFn, MapFnInput, MapTo } from "src/types/dictionary";
2
+ import { OptRequired } from "src/types/literal-unions";
3
+ import { createFnWithProps } from "../createFnWithProps";
4
+
5
+ export interface MapConfig<
6
+ IR extends OptRequired = "req",
7
+ D extends MapDirectionVal = "I -> O[]",
8
+ OR extends OptRequired = "opt"
9
+ > {
10
+ input?: IR;
11
+ output?: OR;
12
+ direction?: D;
13
+ }
2
14
 
3
15
  /**
4
- * **mapTo**
5
- *
6
- * A utility function which maps one dictionary structure `I` to another `O`; which allows
7
- * the transform to:
8
- *
9
- * - _split_ inputs into multiple outputs
10
- * - _map_ 1:1 between input and output
11
- * - _filter_ inputs which don't meet certain criteria (by returning `null`)
16
+ * Provides a mapper function using the default configuration
17
+ */
18
+ export type MapperApiDefault = <I, O>(map: MapTo<I, O>) => MapFn<I, O>;
19
+
20
+ /**
21
+ * Provides opportunity to configure _input_ and _output_ relationships
22
+ * prior to providing a mapping function.
23
+ */
24
+ export type MapperApiConfigured = <
25
+ IR extends OptRequired = "req",
26
+ D extends MapDirectionVal = "I -> O[]",
27
+ OR extends OptRequired = "opt"
28
+ >(
29
+ config: MapConfig<IR, D, OR>
30
+ ) => <I, O>(map: MapTo<I, O, IR, D, OR>) => MapFn<I, O, IR, D, OR>;
31
+
32
+ /**
33
+ * The single implementation for all mapping
34
+ */
35
+ const mapper =
36
+ <
37
+ IR extends OptRequired = "req",
38
+ D extends MapDirectionVal = "I -> O[]",
39
+ OR extends OptRequired = "opt"
40
+ >(
41
+ config: MapConfig<IR, D, OR> = {}
42
+ ) =>
43
+ <I, O>(map: MapTo<I, O, IR, D, OR>) => {
44
+ // TODO
45
+ config = {
46
+ input: "req",
47
+ output: "opt",
48
+ direction: "I -> O[]",
49
+ ...config,
50
+ } as Required<MapConfig<IR, D, OR>>;
51
+
52
+ const fn = <S extends MapFnInput<I, IR, D>>(source?: S) => {
53
+ const isArray =
54
+ config.direction === "I -> O[]" && Array.isArray(source)
55
+ ? true
56
+ : config.direction === "I[] -> O" && Array.isArray(source) && Array.isArray(source[0])
57
+ ? true
58
+ : false;
59
+
60
+ if (isArray) {
61
+ return (source as any).flatMap(map);
62
+ } else {
63
+ return map(source as any);
64
+ }
65
+ };
66
+
67
+ return fn as unknown as MapFn<I, O, IR, D, OR>;
68
+ };
69
+
70
+ /**
71
+ * **mapTo** _utility_
12
72
  *
13
- * This higher order function first asks for the mapping criteria:
73
+ * This utility creates a strongly typed data mapper which maps from one
74
+ * known source `I` to another `O`:
14
75
  * ```ts
15
- * const mapper = mapTo<I,O>(i => i.name
16
- * ? [
17
- * { name: i.name, a: "static text", b: i.products.length }
18
- * ]
19
- * : null
20
- * );
76
+ * const mapper = mapTo<I, O>(i => [{
77
+ * foo: i.bar
78
+ * }]);
21
79
  * ```
80
+ */
81
+ export const mapToFn = <I, O>(map: MapTo<I, O>) => {
82
+ return mapper()<I, O>(map);
83
+ };
84
+
85
+ /**
86
+ * Provides a `config` method which allows the relationships between _inputs_
87
+ * and _outputs_ to be configured.
88
+ */
89
+ export const mapToDict = {
90
+ /** configure the relationship between the _inputs_ and _outputs_ */
91
+ config: <
92
+ IR extends OptRequired = "req",
93
+ D extends MapDirectionVal = "I -> O[]",
94
+ OR extends OptRequired = "opt"
95
+ >(
96
+ c: MapConfig<IR, D, OR> = { input: "req", output: "opt", direction: "I -> O[]" } as MapConfig<
97
+ IR,
98
+ D,
99
+ OR
100
+ >
101
+ ) => ({
102
+ /**
103
+ * create your mapping with your recently setup configuration:
104
+ * ```ts
105
+ * const mapper = mapTo
106
+ * .config({ ... })
107
+ * .map<I, O>(i => [{
108
+ * foo: i.bar
109
+ * }]);
110
+ * ```
111
+ */
112
+ map<I, O>(map: MapTo<I, O, IR, D, OR>): MapFn<I, O, IR, D, OR> {
113
+ return mapper(c)(map);
114
+ },
115
+ }),
116
+ };
117
+
118
+ /**
119
+ * **mapTo** _utility_
22
120
  *
23
- * and now you'll have a _mapper_ variable will be assigned as a mapping fn:
24
- * ```ts
25
- * function (source: I | I[]): O[]
26
- * ```
121
+ * This utility creates a strongly typed data mapper which maps from one
122
+ * known source `I` to another `O`.
27
123
  */
28
- export const mapTo =
29
- <I extends {}, O extends {}>(cb: MapToWithFiltering<I, O>) =>
30
- (source: I | I[]) => {
31
- if (Array.isArray(source)) {
32
- return source.flatMap((i) => cb(i)).filter((i) => i !== null) as O[];
33
- } else {
34
- const result = cb(source);
35
- return result ? result : ([] as O[]);
36
- }
37
- };
124
+ export const mapTo = createFnWithProps(mapToFn, mapToDict);
@@ -10,11 +10,11 @@ export * from "./keys";
10
10
  export * from "./ruleset";
11
11
  // directory exports
12
12
  export * from "./api/index";
13
+ export * from "./boolean-logic/index";
13
14
  export * from "./dictionary/index";
14
15
  export * from "./errors/index";
15
16
  export * from "./lists/index";
16
17
  export * from "./literals/index";
17
- export * from "./map-reduce/index";
18
18
  export * from "./modelling/index";
19
19
  export * from "./runtime/index";
20
20
  export * from "./state/index";
@@ -0,0 +1,34 @@
1
+ import { Narrowable, Widen } from "src/types";
2
+ import { isArray } from "../runtime";
3
+
4
+ /**
5
+ * Type utility which converts `undefined[]` to `unknown[]`
6
+ */
7
+ export type UndefinedArrayIsUnknown<T extends any[]> = undefined[] extends T ? unknown[] : T;
8
+
9
+ export type AsArray<T, W extends boolean = false> = T extends any[]
10
+ ? W extends true
11
+ ? Widen<T>
12
+ : T
13
+ : W extends true
14
+ ? UndefinedArrayIsUnknown<Widen<T>[]>
15
+ : UndefinedArrayIsUnknown<T[]>;
16
+
17
+ /**
18
+ * Ensures that any input passed in is passed back as an array:
19
+ *
20
+ * - if it was already an array than this just serves as an _identity_ function
21
+ * - if it was not then it wraps the element into a one element array of the
22
+ * given type
23
+ *
24
+ * Note: by default the _type_ of values will be intentionally widened so that the value "abc"
25
+ * is of type `string` not the literal `abc`. If you want to keep literal types then
26
+ * change the optional _widen_ parameter to _false_.
27
+ */
28
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
29
+ export const asArray = <T extends Narrowable, W extends boolean = true>(thing: T, _widen?: W) => {
30
+ return (isArray(thing) ? thing : typeof thing === "undefined" ? ([] as T[]) : [thing]) as AsArray<
31
+ T,
32
+ W
33
+ >;
34
+ };
@@ -5,6 +5,7 @@
5
5
  // hash-code: bf4a6791
6
6
 
7
7
  // file exports
8
+ export * from "./asArray";
8
9
  export * from "./groupBy";
9
10
 
10
11
  // #endregion auto-indexed files
@@ -0,0 +1,21 @@
1
+ import { describe, it, expect } from "vitest";
2
+ // import type { Equal, Expect } from "@type-challenges/utils";
3
+ import { and, filter } from "src/utility";
4
+
5
+ describe("boolean logic", () => {
6
+ it("AND operation", () => {
7
+ const a1 = and(
8
+ () => true,
9
+ () => false
10
+ );
11
+ const a2 = and(
12
+ () => true,
13
+ () => true
14
+ );
15
+ const md = and(filter({ endsWith: ".md" }), filter({ not: { startsWith: "_" } }));
16
+
17
+ expect(a1()).toBe(false);
18
+ expect(a2()).toBe(true);
19
+ expect(["foo.md", "bar.html", "_baz.md"].filter(md)).toEqual(["foo.md"]);
20
+ });
21
+ });
@@ -0,0 +1,52 @@
1
+ import { filter } from "src/utility";
2
+ import { Equal, Expect } from "@type-challenges/utils";
3
+ import { describe, it, expect } from "vitest";
4
+
5
+ describe("filter() utility function", () => {
6
+ it("string filter built and gives proper types", () => {
7
+ const f = filter({ startsWith: "th" });
8
+ type P = Parameters<typeof f>;
9
+ type R = ReturnType<typeof f>;
10
+ const notPrivate = filter({ not: { startsWith: ["_", "."] } });
11
+
12
+ // runtime
13
+ expect(typeof f).toEqual("function");
14
+ expect(["one", "two", "three"].filter(f)).toEqual(["three"]);
15
+ expect(["foo.md", "bar.html", "_private.md", ".bobs-your-uncle.txt"].filter(notPrivate)) //
16
+ .toEqual(["foo.md", "bar.html"]);
17
+
18
+ // design time
19
+ type cases = [
20
+ Expect<Equal<P[0], string | undefined>>, //
21
+ Expect<Equal<R, boolean>>
22
+ ];
23
+ const cases: cases = [true, true];
24
+ });
25
+
26
+ it("numeric filter built and gives proper types", () => {
27
+ const f = filter({ equals: 42 });
28
+ type P = Parameters<typeof f>;
29
+ type R = ReturnType<typeof f>;
30
+
31
+ // runtime
32
+ expect(typeof f).toEqual("function");
33
+ expect([1, 2, 3, 4, 5].filter(filter({ greaterThan: 3 }))) //
34
+ .toEqual([4, 5]);
35
+ expect([1, 2, 3, 4, 5].filter(filter({ not: { greaterThan: 3 } }))) //
36
+ .toEqual([1, 2, 3]);
37
+
38
+ // design time
39
+ type cases = [
40
+ Expect<Equal<P[0], number | undefined>>, //
41
+ Expect<Equal<R, boolean>>
42
+ ];
43
+ const cases: cases = [true, true];
44
+ });
45
+
46
+ it("string filter's startsWith", () => {
47
+ const f = filter({ startsWith: "." });
48
+ const remaining = ["foo", "bar", ".baz"].filter(f);
49
+
50
+ expect(remaining).toEqual([".baz"]);
51
+ });
52
+ });
@@ -5,6 +5,7 @@ describe("createFnWithProps()", () => {
5
5
  it("simple fn and prop are combined, type is retained", () => {
6
6
  const fn = () => "hi";
7
7
  const props = { foo: "bar" };
8
+ /** combo */
8
9
  const combo = createFnWithProps(fn, props);
9
10
 
10
11
  expect(combo.foo).toBe("bar");
@@ -1,4 +1,4 @@
1
- import { MapTo } from "src/types/dictionary";
1
+ import { MapDirection, MapFn, MapTo } from "src/types/dictionary";
2
2
  import { describe, expect, it } from "vitest";
3
3
 
4
4
  import type { Expect, Equal } from "@type-challenges/utils";
@@ -10,115 +10,182 @@ const i2: I = { title: "i2", color: "green", products: ["foo", "bar", "baz"] };
10
10
 
11
11
  type O = { title: string; count: number; unnecessary?: number };
12
12
 
13
- describe("MapTo<T> type util", () => {
14
- it("1:1 conversion", () => {
15
- const m: MapTo<I, O> = (i) => [
16
- {
17
- title: i.title,
18
- count: i.products.length,
19
- },
13
+ describe("MapTo<I,O> and MapToFn<I,O>", () => {
14
+ it("MapTo with 0:M, 0:M cardinality", () => {
15
+ type Fn = MapTo<I, O>;
16
+ type Fn2 = MapTo<I, O, "req", MapDirection.OneToMany, "opt">;
17
+ type WFn = MapFn<I, O>;
18
+ type WFn2 = MapFn<I, O, "req", MapDirection.OneToMany, "opt">;
19
+
20
+ /** userland mapping function */
21
+ type T = (source: I) => O[];
22
+ /** exposed via mapTo() util */
23
+ type WT = <S extends I | I[]>(source: S) => S extends I[] ? O[] : O[] | null;
24
+
25
+ type cases = [
26
+ Expect<Equal<Fn, Fn2>>, // implicit default = explicit values
27
+ Expect<Equal<WFn, WFn2>>, // implicit default = explicit values
28
+ Expect<Equal<Fn, T>>,
29
+ Expect<Equal<WFn, WT>>
20
30
  ];
21
- type M = typeof m;
22
- type cases = [Expect<Equal<M, MapTo<I, O>>>];
23
- const cases: cases = [true];
24
- expect(cases).toBe(cases);
31
+ const cases: cases = [true, true, true, true];
25
32
  });
26
33
 
27
- it("1:1 conversion with non-required", () => {
28
- const m: MapTo<I, O> = (i) => [
29
- {
30
- title: i.title,
31
- count: i.products.length,
32
- unnecessary: 42,
33
- },
34
+ it("MapTo<X> with I -> O[] variable config", () => {
35
+ type Fn0 = MapTo<I, O, "req", MapDirection.OneToMany, "opt">;
36
+ type Fn1 = MapTo<I, O, "req", MapDirection.OneToMany, "req">;
37
+ type Fn2 = MapTo<I, O, "opt", MapDirection.OneToMany, "opt">;
38
+ type Fn3 = MapTo<I, O, "opt", "I -> O[]", "req">;
39
+
40
+ type T0 = (source: I) => O[];
41
+ type T1 = (source: I) => [O, ...O[]];
42
+ type T2 = (source?: I) => O[];
43
+ type T3 = (source?: I) => [O, ...O[]];
44
+
45
+ type cases = [
46
+ Expect<Equal<Fn0, T0>>, //
47
+ Expect<Equal<Fn1, T1>>, //
48
+ Expect<Equal<Fn2, T2>>, //
49
+ Expect<Equal<Fn3, T3>> //
34
50
  ];
35
- type M = typeof m;
36
- type cases = [Expect<Equal<M, MapTo<I, O>>>];
37
- const cases: cases = [true];
38
- expect(cases).toBe(cases);
51
+ const cases: cases = [true, true, true, true];
39
52
  });
40
53
 
41
- it("1:M conversion", () => {
42
- const m: MapTo<I, O> = (i) => [
43
- {
44
- title: i.title,
45
- count: i.products.length,
46
- unnecessary: 1,
47
- },
48
- {
49
- title: i.title,
50
- count: i.products.length + 1,
51
- unnecessary: 2,
52
- },
54
+ it("MapTo<X> with I[] -> O variable config", () => {
55
+ type Fn0 = MapTo<I, O, "req", "I[] -> O", "opt">;
56
+ type Fn1 = MapTo<I, O, "req", "I[] -> O", "req">;
57
+ type Fn2 = MapTo<I, O, "opt", "I[] -> O", "opt">;
58
+ type Fn3 = MapTo<I, O, "opt", "I[] -> O", "req">;
59
+
60
+ type T0 = (source: I[]) => O | null;
61
+ type T1 = (source: I[]) => O;
62
+ type T2 = (source?: I[]) => O | null;
63
+ type T3 = (source?: I[]) => O;
64
+
65
+ type cases = [
66
+ Expect<Equal<Fn0, T0>>, //
67
+ Expect<Equal<Fn1, T1>>, //
68
+ Expect<Equal<Fn2, T2>>, //
69
+ Expect<Equal<Fn3, T3>> //
53
70
  ];
54
- type M = typeof m;
55
- type cases = [Expect<Equal<M, MapTo<I, O>>>];
56
- const cases: cases = [true];
57
- expect(cases).toBe(cases);
71
+ const cases: cases = [true, true, true, true];
58
72
  });
59
73
 
60
- it("filter input as 1:0 cardinality", () => {
61
- const m: MapTo<I, O> = (_i) => [];
62
- type M = typeof m;
63
- type cases = [Expect<Equal<M, MapTo<I, O>>>];
64
- const cases: cases = [true];
65
- expect(cases).toBe(cases);
74
+ it("MapFn<X> with I -> O[] variable config", () => {
75
+ type Fn0 = MapFn<I, O, "req", "I -> O[]", "opt">;
76
+ type Fn1 = MapFn<I, O, "req", "I -> O[]", "req">;
77
+ type Fn2 = MapFn<I, O, "opt", "I -> O[]", "opt">;
78
+ type Fn3 = MapFn<I, O, "opt", "I -> O[]", "req">;
79
+
80
+ type T0 = <S extends I | I[]>(source: S) => S extends I[] ? O[] : O[] | null;
81
+ type T1 = <S extends I | I[]>(source: S) => S extends I[] ? [O, ...O[]] : O[];
82
+ type T2 = <S extends I | I[] | undefined>(source?: S) => S extends I[] ? O[] : O[] | null;
83
+ type T3 = <S extends I | I[] | undefined>(source?: S) => S extends I[] ? [O, ...O[]] : O[];
84
+
85
+ type cases = [
86
+ Expect<Equal<Fn0, T0>>, //
87
+ Expect<Equal<Fn1, T1>>, //
88
+ Expect<Equal<Fn2, T2>>, //
89
+ Expect<Equal<Fn3, T3>> //
90
+ ];
91
+ const cases: cases = [true, true, true, true];
92
+ });
93
+
94
+ it("MapFn<X> with I[] -> O variable config", () => {
95
+ type Fn0 = MapFn<I, O, "req", "I[] -> O", "opt">;
96
+ type Fn1 = MapFn<I, O, "req", "I[] -> O", "req">;
97
+ type Fn2 = MapFn<I, O, "opt", "I[] -> O", "opt">;
98
+ type Fn3 = MapFn<I, O, "opt", "I[] -> O", "req">;
99
+
100
+ type T0 = <S extends I[] | I[][]>(source: S) => S extends I[][] ? O[] : O | null;
101
+ type T1 = <S extends I[] | I[][]>(source: S) => S extends I[][] ? [O, ...O[]] : O;
102
+ type T2 = <S extends I[] | I[][] | undefined>(source?: S) => S extends I[][] ? O[] : O | null;
103
+ type T3 = <S extends I[] | I[][] | undefined>(source?: S) => S extends I[][] ? [O, ...O[]] : O;
104
+
105
+ type cases = [
106
+ Expect<Equal<Fn0, T0>>, //
107
+ Expect<Equal<Fn1, T1>>, //
108
+ Expect<Equal<Fn2, T2>>, //
109
+ Expect<Equal<Fn3, T3>> //
110
+ ];
111
+ const cases: cases = [true, true, true, true];
112
+ });
113
+
114
+ it("walk through of mapTo types", () => {
115
+ const t1 = mapTo<I, O>((i) => [{ title: i.title, count: i.products.length }]);
116
+ type T1 = typeof t1;
117
+
118
+ const a1 = mapTo
119
+ .config({ input: "req" })
120
+ .map<I, O>((i) => [{ title: i.title, count: i.products.length }]);
121
+ type A1 = typeof a1;
122
+
123
+ type cases = [
124
+ Expect<Equal<T1, MapFn<I, O>>>, //
125
+ Expect<Equal<T1, A1>>
126
+ ];
127
+ const cases: cases = [true, true];
66
128
  });
67
129
  });
68
130
 
69
131
  describe("mapTo() utility function", () => {
70
- it("test 1:1 mapping", () => {
71
- const m = mapTo<I, O>((i) => [
72
- {
73
- title: i.title,
74
- count: i.products.length,
75
- },
76
- ]);
77
- type M = typeof m;
78
- type R = ReturnType<M>;
79
-
80
- // runtime
81
- expect(m(i)).toEqual([{ title: i.title, count: i.products.length }]);
82
- // types
83
- type cases = [Expect<Equal<R, O[]>>];
84
- const cases: cases = [true];
85
- expect(cases).toBe(cases);
132
+ it("M:1 conversion", () => {
133
+ const m = mapTo
134
+ .config({
135
+ direction: "I[] -> O",
136
+ output: "req",
137
+ })
138
+ .map<I, O>((i) => ({
139
+ title: "count of products",
140
+ count: i.reduce((acc, i) => (acc = acc + i.products.length), 0),
141
+ }));
142
+
143
+ const summary = m([i, i2]);
144
+ expect(summary.title).toBe("count of products");
145
+ expect(summary.count).toBe(6);
146
+ });
147
+
148
+ it("1:1 conversion", () => {
149
+ const m = mapTo
150
+ .config({ output: "req", direction: MapDirection.OneToOne })
151
+ .map<I, O>((i) => ({ title: i.title, count: i.products.length }));
152
+ const o = m(i);
153
+
154
+ expect(o?.title).toBe(i.title);
155
+ expect(o?.count).toBe(3);
86
156
  });
87
157
 
88
- it("test 1:1 mapping with extraneous prop", () => {
89
- const m = mapTo<I, O>((i) => [
90
- {
91
- title: i.title,
92
- count: i.products.length,
93
- extraneous: 42,
94
- },
95
- ]);
96
- type M = typeof m;
97
- type R = ReturnType<M>;
98
-
99
- // runtime
100
- // TODO: extraneous props are cut out of the type but still allows us to return `O` values with extraneous props!
101
- // expect(m(i)).toEqual([{ title: i.title, count: i.products.length }]);
102
- // types
103
- type cases = [Expect<Equal<R, O[]>>];
104
- const cases: cases = [true];
105
- expect(cases).toBe(cases);
158
+ it("1:1 with 1:M conversion setting", () => {
159
+ const m = mapTo
160
+ .config({ output: "req", direction: MapDirection.OneToMany })
161
+ .map<I, O>((i) => [{ title: i.title, count: i.products.length }]);
162
+ const o = m(i);
163
+
164
+ expect(o.length).toBe(1);
165
+ expect(o[0]?.title).toBe(i.title);
166
+ expect(o[0]?.count).toBe(3);
167
+ });
168
+
169
+ it("1:M conversion ", () => {
170
+ const m = mapTo
171
+ .config({ output: "req", direction: MapDirection.OneToMany })
172
+ .map<I, O>((i) => [{ title: i.title, count: i.products.length }]);
173
+ const o = m([i, i2]);
174
+
175
+ expect(o.length).toBe(2);
176
+ o.map((item) => expect("title" in item).toBeTruthy());
106
177
  });
107
178
 
108
- it("test input filtering", () => {
109
- const m = mapTo<I, O>((x) => {
110
- return x.title === "i2"
111
- ? null
112
- : [
113
- {
114
- title: i.title,
115
- count: i.products.length,
116
- },
117
- ];
179
+ it("M:1 conversion", () => {
180
+ const m = mapTo.config({ output: "req", direction: MapDirection.ManyToOne }).map<I, O>((i) => {
181
+ return {
182
+ title: "summary",
183
+ count: i.reduce((acc, i) => (acc = acc + i.products.length), 0),
184
+ };
118
185
  });
119
- const results = m([i, i2]);
186
+ const o = m([i, i2]);
120
187
 
121
- expect(results).toHaveLength(1);
122
- expect(results[0].title).toBe("Test");
188
+ expect(o.title).toBe("summary");
189
+ expect(o.count).toBe(6);
123
190
  });
124
191
  });
@@ -0,0 +1,91 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { asArray } from "src/utility";
3
+ import { Equal, Expect } from "@type-challenges/utils";
4
+
5
+ describe("asArray() function", () => {
6
+ it("non-array is returned as an array", () => {
7
+ const i = "a";
8
+ const o = asArray(i);
9
+ type O = typeof o;
10
+
11
+ // run-time
12
+ expect(o).toEqual(["a"]);
13
+ // design-time
14
+ type cases = [Expect<Equal<O, string[]>>];
15
+ const cases: cases = [true];
16
+ });
17
+
18
+ it("array is returned as an array", () => {
19
+ const i = ["a"];
20
+ const o = asArray(i);
21
+ type O = typeof o;
22
+
23
+ // run-time
24
+ expect(o).toEqual(["a"]);
25
+ // design-time
26
+ type cases = [Expect<Equal<O, string[]>>];
27
+ const cases: cases = [true];
28
+ });
29
+
30
+ it("non-array literal is returned as an array", () => {
31
+ const i = "a" as const;
32
+ const o = asArray(i, false);
33
+ const o2 = asArray(i, true);
34
+
35
+ type O = typeof o;
36
+ type O2 = typeof o2;
37
+
38
+ // run-time
39
+ expect(o).toEqual(["a"]);
40
+ // design-time
41
+ type cases = [
42
+ Expect<Equal<O, "a"[]>>, //
43
+ Expect<Equal<O2, string[]>>
44
+ ];
45
+ const cases: cases = [true, true];
46
+ });
47
+
48
+ it("handling non-array element which presents as undefined", () => {
49
+ type T = string | undefined;
50
+ const i = undefined;
51
+ const i2: T = undefined;
52
+ const o = asArray(i);
53
+ const o2 = asArray(i2 as T);
54
+ const o3 = asArray(i2 as T, false);
55
+ type O = typeof o;
56
+ type O2 = typeof o2;
57
+ type O3 = typeof o3;
58
+
59
+ // run-time
60
+ expect(o).toEqual([]);
61
+ expect(o2).toEqual([]);
62
+ // design-time
63
+ type cases = [
64
+ Expect<Equal<O, unknown[]>>, //
65
+ // TODO: would be nice to extract the unknown[] part of the union
66
+ Expect<Equal<O2, unknown[] | string[]>>,
67
+ Expect<Equal<O3, unknown[] | string[]>>
68
+ ];
69
+ const cases: cases = [true, true, true];
70
+ });
71
+
72
+ it("handling array element which contains undefined is unaffected", () => {
73
+ type T = string | undefined;
74
+ const i = [undefined, "foobar"];
75
+ const i2: T[] = [undefined, "foobar"];
76
+ const o = asArray(i);
77
+ const o2 = asArray(i2 as T[]);
78
+ type O = typeof o;
79
+ type O2 = typeof o2;
80
+
81
+ // run-time
82
+ expect(o).toEqual([undefined, "foobar"]);
83
+ expect(o2).toEqual([undefined, "foobar"]);
84
+ // design-time
85
+ type cases = [
86
+ Expect<Equal<O, (string | undefined)[]>>, //
87
+ Expect<Equal<O2, T[]>>
88
+ ];
89
+ const cases: cases = [true, true];
90
+ });
91
+ });