inferred-types 0.34.2 → 0.36.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.
Files changed (45) hide show
  1. package/README.md +2 -34
  2. package/dist/index.d.ts +1796 -1624
  3. package/dist/index.mjs +101 -18
  4. package/package.json +14 -13
  5. package/src/runtime/combinators/filter.ts +0 -1
  6. package/src/runtime/lists/asArray.ts +8 -4
  7. package/src/runtime/lists/createConverter.ts +62 -0
  8. package/src/runtime/lists/index.ts +1 -0
  9. package/src/runtime/literals/box.ts +41 -20
  10. package/src/runtime/literals/ensureLeading.ts +17 -0
  11. package/src/runtime/literals/ensureTrailing.ts +17 -0
  12. package/src/runtime/literals/index.ts +4 -0
  13. package/src/runtime/literals/pathJoin.ts +32 -0
  14. package/src/runtime/literals/stripLeading.ts +15 -0
  15. package/src/runtime/literals/stripTrailing.ts +15 -0
  16. package/src/runtime/literals/wide.ts +13 -0
  17. package/src/runtime/type-checks/ifSameType.ts +20 -0
  18. package/src/runtime/type-checks/index.ts +1 -0
  19. package/src/runtime/type-checks/isBoolean.ts +2 -1
  20. package/src/runtime/type-checks/isFunction.ts +2 -4
  21. package/src/runtime/type-checks/isObject.ts +10 -2
  22. package/src/runtime/type-checks/isString.ts +1 -1
  23. package/src/runtime/type-checks/isUndefined.ts +8 -0
  24. package/src/types/alphabetic/EnsureLeading.ts +24 -0
  25. package/src/types/alphabetic/EnsureTrailing.ts +24 -0
  26. package/src/types/alphabetic/PathJoin.ts +52 -0
  27. package/src/types/alphabetic/StripLeading.ts +23 -0
  28. package/src/types/alphabetic/StripTrailing.ts +23 -0
  29. package/src/types/alphabetic/index.ts +4 -0
  30. package/src/types/boolean-logic/HasParameters.ts +21 -0
  31. package/src/types/boolean-logic/array.ts +2 -2
  32. package/src/types/boolean-logic/boolean.ts +1 -1
  33. package/src/types/boolean-logic/equivalency.ts +2 -2
  34. package/src/types/boolean-logic/index.ts +1 -0
  35. package/src/types/dictionary/props.ts +5 -0
  36. package/src/types/lists/ConvertAndMap.ts +151 -0
  37. package/src/types/type-conversion/Widen.ts +15 -0
  38. package/tests/boolean-logic/HasParameters.ts +29 -0
  39. package/tests/lists/asArray.test.ts +19 -1
  40. package/tests/literals/EnsureStripLeadingTrailing.test.ts +79 -0
  41. package/tests/literals/PathJoin.test.ts +94 -0
  42. package/tests/literals/box.test.ts +31 -23
  43. package/tests/runtime/if-is.spec.ts +66 -5
  44. package/tests/runtime/map-and-convert.test.ts +31 -0
  45. package/dist/index.js +0 -852
@@ -23,3 +23,11 @@ export function ifUndefined<T extends Narrowable, IF extends Narrowable, ELSE ex
23
23
  ) {
24
24
  return (isUndefined(val) ? ifVal : elseVal) as IsUndefined<T> extends true ? IF : ELSE;
25
25
  }
26
+
27
+ export function ifDefined<T extends Narrowable, IF extends Narrowable, ELSE extends Narrowable>(
28
+ val: T,
29
+ ifVal: <V extends Exclude<T, undefined>>(v: V) => IF,
30
+ elseVal: ELSE
31
+ ) {
32
+ return (isUndefined(val) ? ifVal : elseVal) as IsUndefined<T> extends true ? IF : ELSE;
33
+ }
@@ -0,0 +1,24 @@
1
+ import { IfLiteral } from "../boolean-logic/IsLiteral";
2
+
3
+ /**
4
+ * **EnsureLeading**`<T, U>`
5
+ *
6
+ * Will ensure that `T` ends with the _substring_ `U` when
7
+ * both are string literals.
8
+ *
9
+ * ```ts
10
+ * type T = " World";
11
+ * type U = "Hello";
12
+ * // "Hello World"
13
+ * type R = EnsureLeading<T,U>;
14
+ * ```
15
+ */
16
+ export type EnsureLeading<T extends string, U extends string> = IfLiteral<
17
+ // can only operate on literal strings
18
+ T,
19
+ // this path represents successful strip opp
20
+ // but we must never accept `U` being wide
21
+ string extends U ? never : T extends `${U}${string}` ? T : `${U}${T}`,
22
+ // here we must stay wide
23
+ string
24
+ >;
@@ -0,0 +1,24 @@
1
+ import { IfLiteral } from "../boolean-logic/IsLiteral";
2
+
3
+ /**
4
+ * **EnsureTrailing**`<T, U>`
5
+ *
6
+ * Will ensure that `T` ends with the substring `U` when
7
+ * both are string literals.
8
+ *
9
+ * ```ts
10
+ * type T = "Hello";
11
+ * type U = " World";
12
+ * // "Hello World"
13
+ * type R = EnsureTrailing<T,U>;
14
+ * ```
15
+ */
16
+ export type EnsureTrailing<T extends string, U extends string> = IfLiteral<
17
+ // can only operate on literal strings
18
+ T,
19
+ // this path represents successful strip opp
20
+ // but we must never accept `U` being wide
21
+ string extends U ? never : T extends `${string}${U}` ? T : `${T}${U}`,
22
+ // here we must stay wide
23
+ string
24
+ >;
@@ -0,0 +1,52 @@
1
+ import { IfLiteral } from "../boolean-logic";
2
+ import { AfterFirst, First } from "../lists";
3
+ import { StripTrailing } from "./StripTrailing";
4
+ import { StripLeading } from "./StripLeading";
5
+
6
+ /**
7
+ * **PathJoin**`<T,U>`
8
+ *
9
+ * Type utility meant to bring 2 or more "path" strings together into
10
+ * a valid "path". Where a "path" is represented as nothing more than
11
+ * string characters delimited by a Posix `\` character.
12
+ *
13
+ * Note that the first part of the path will retain it's `\` if present
14
+ * and the last one will preserve it's `\` character if present. You can
15
+ * combine this utility with `EnsureTrailing<T>`, `StripTrailing<T>`,
16
+ * `EnsureStarting<T>`, and `StripStarting<T>` to further shape
17
+ * the type.
18
+ */
19
+ export type PathJoin<
20
+ // leading string
21
+ T extends string,
22
+ // trailing string or strings
23
+ U extends string | readonly string[]
24
+ > = U extends readonly string[]
25
+ ? // eslint-disable-next-line no-use-before-define
26
+ PathMultiJoin<PathJoin<T, "">, [...U]>
27
+ : U extends string
28
+ ? IfLiteral<
29
+ T,
30
+ // Literal T guaranteed
31
+ IfLiteral<
32
+ // conditional
33
+ U,
34
+ `${StripTrailing<T, "/">}/${StripLeading<U, "/">}`,
35
+ `${StripTrailing<T, "/">}/${string}`
36
+ >,
37
+ // wide `T` encountered
38
+ IfLiteral<U, `${string}${U}`, string>
39
+ >
40
+ : never;
41
+
42
+ type PathMultiJoin<
43
+ TProcessed extends string,
44
+ TRemaining extends readonly string[]
45
+ > = [] extends TRemaining
46
+ ? TProcessed
47
+ : PathMultiJoin<
48
+ // add to the TProcessed string
49
+ PathJoin<TProcessed, First<TRemaining>>,
50
+ // remaining elements after extracting head element
51
+ AfterFirst<TRemaining>
52
+ >;
@@ -0,0 +1,23 @@
1
+ import { IfLiteral } from "../boolean-logic/IsLiteral";
2
+
3
+ /**
4
+ * **StripStarting**`<T, U>`
5
+ *
6
+ * Will strip off of `T` the starting string defined by `U` when
7
+ * both are string literals.
8
+ * ```ts
9
+ * type T = "Hello World";
10
+ * type U = "Hello ";
11
+ * // "World"
12
+ * type R = StripStarting<T,U>;
13
+ * ```
14
+ */
15
+ export type StripLeading<T extends string, U extends string> = IfLiteral<
16
+ // can only operate on literal strings
17
+ T,
18
+ // this path represents successful strip opp
19
+ // but we must never accept `U` being wide
20
+ string extends U ? never : T extends `${U}${infer After}` ? After : T,
21
+ // here we must stay wide
22
+ string
23
+ >;
@@ -0,0 +1,23 @@
1
+ import { IfLiteral } from "../boolean-logic/IsLiteral";
2
+
3
+ /**
4
+ * **StripEnding**`<T, U>`
5
+ *
6
+ * Will strip off of `T` the ending defined by `U` when
7
+ * both are string literals.
8
+ * ```ts
9
+ * type T = "Hello World";
10
+ * type U = " World";
11
+ * // "Hello"
12
+ * type R = StripEnding<T,U>;
13
+ * ```
14
+ */
15
+ export type StripTrailing<T extends string, U extends string> = IfLiteral<
16
+ // can only operate on literal strings
17
+ T,
18
+ // this path represents successful strip opp
19
+ // but we must never accept `U` being wide
20
+ string extends U ? never : T extends `${infer Before}${U}` ? Before : T,
21
+ // here we must stay wide
22
+ string
23
+ >;
@@ -12,12 +12,16 @@ export * from "./DashUppercase";
12
12
  export * from "./Cardinality";
13
13
  export * from "./Dasherize";
14
14
  export * from "./HasUppercase";
15
+ export * from "./EnsureLeading";
16
+ export * from "./EnsureTrailing";
15
17
  export * from "./IsCapitalized";
16
18
  export * from "./KebabCase";
17
19
  export * from "./LowerAllCaps";
18
20
  export * from "./PascalCase";
19
21
  export * from "./Pluralize";
20
22
  export * from "./SnakeCase";
23
+ export * from "./StripLeading";
24
+ export * from "./StripTrailing";
21
25
  export * from "./alpha-characters";
22
26
  export * from "./Url";
23
27
 
@@ -0,0 +1,21 @@
1
+ import { AnyFunction } from "src/runtime";
2
+ import { Length } from "../Length";
3
+ import { Narrowable } from "../Narrowable";
4
+ import { IsEqual } from "./equivalency";
5
+
6
+ /**
7
+ * **HasParameters**`<T>`
8
+ *
9
+ * Type utility which detects if `T` is both a function and whether that
10
+ * function has at least one type parameter.
11
+ * ```ts
12
+ * const fn = (foo: string) => `${foo}bar`;
13
+ * // true
14
+ * type P = HasParameters<typeof fn>;
15
+ * ```
16
+ */
17
+ export type HasParameters<T extends Narrowable> = T extends AnyFunction
18
+ ? IsEqual<Length<Parameters<T>>, 0> extends true
19
+ ? false
20
+ : true
21
+ : false;
@@ -1,4 +1,4 @@
1
- import { Equal } from "./equivalency";
1
+ import { IsEqual } from "./equivalency";
2
2
  import { AfterFirst, First } from "../lists";
3
3
  import { Narrowable } from "../Narrowable";
4
4
 
@@ -47,7 +47,7 @@ export type Contains<T extends Narrowable, A extends readonly any[]> = First<A>
47
47
  * `true` if `T` _extends_ any element in `A` making it match widely against `A`. If you
48
48
  * prefer a wider match you can use `Contains<T,A>` instead.
49
49
  */
50
- export type NarrowlyContains<T extends Narrowable, A extends readonly any[]> = Equal<
50
+ export type NarrowlyContains<T extends Narrowable, A extends readonly any[]> = IsEqual<
51
51
  First<A>,
52
52
  T
53
53
  > extends true
@@ -1,6 +1,6 @@
1
1
  import { Narrowable } from "../Narrowable";
2
2
 
3
- export type IsBoolean<T> = T extends boolean ? true : false;
3
+ export type IsBoolean<T> = [T] extends [boolean] ? true : false;
4
4
 
5
5
  /**
6
6
  * Type utility which returns `true` or `false` based on
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Type utility which tests whether two types -- `X` and `Y` -- are exactly the same type
5
5
  */
6
- export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
6
+ export type IsEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
7
7
  ? true
8
8
  : false;
9
9
 
@@ -12,4 +12,4 @@ export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T exte
12
12
  *
13
13
  * Type utility which tests whether two types -- `X` and `Y` -- are _not_ exactly the same type
14
14
  */
15
- export type NotEqual<X, Y> = true extends Equal<X, Y> ? false : true;
15
+ export type NotEqual<X, Y> = true extends IsEqual<X, Y> ? false : true;
@@ -5,6 +5,7 @@ export * from "./IsScalar";
5
5
  export * from "./IsLiteral";
6
6
  export * from "./IsUndefined";
7
7
  export * from "./Extends";
8
+ export * from "./HasParameters";
8
9
  export * from "./TypeDefault";
9
10
  export * from "./object";
10
11
  export * from "./StartsWith";
@@ -128,6 +128,11 @@ export type WithValue<
128
128
  : // Exclude using E
129
129
  Omit<Pick<T, KeysWithValue<W, T>>, KeysWithValue<E, T>>;
130
130
 
131
+ export type DictionaryWithoutValue<TDict extends object, TWithoutValue> = Omit<
132
+ TDict,
133
+ KeysWithValue<TWithoutValue, TDict>
134
+ >;
135
+
131
136
  /**
132
137
  * Reduces an object to only the key/value pairs where the key is a
133
138
  * string.
@@ -0,0 +1,151 @@
1
+ import { TupleToUnion, UnionToTuple } from "../type-conversion";
2
+ import { Narrowable } from "../Narrowable";
3
+ import { First } from "./First";
4
+ import { AfterFirst } from "./AfterFirst";
5
+ import { Keys } from "../Keys";
6
+
7
+ import { DictionaryWithoutValue } from "../dictionary/props";
8
+
9
+ // [Mapped Tuple Types](https://github.com/Microsoft/TypeScript/issues/25947)
10
+
11
+ /**
12
+ * The basic shape of a `Converter`
13
+ */
14
+ export type ConverterShape<
15
+ S extends Narrowable,
16
+ N extends Narrowable,
17
+ B extends Narrowable,
18
+ O extends Narrowable
19
+ > = {
20
+ string: <T extends string>(v: T) => S;
21
+ number: <T extends number>(v: T) => N;
22
+ boolean: <T extends boolean>(v: T) => B;
23
+ object: <T extends Record<string, any>>(v: T) => O;
24
+ };
25
+
26
+ type ConverterKeys<S, N, B, O> = UnionToTuple<
27
+ Keys<
28
+ DictionaryWithoutValue<
29
+ {
30
+ string: S;
31
+ number: N;
32
+ boolean: B;
33
+ object: O;
34
+ },
35
+ undefined
36
+ >
37
+ >
38
+ >;
39
+
40
+ type ConverterInputType<T extends string> = T extends "string"
41
+ ? string
42
+ : T extends "number"
43
+ ? number
44
+ : T extends "boolean"
45
+ ? boolean
46
+ : T extends "object"
47
+ ? Record<string, any>
48
+ : unknown;
49
+
50
+ type ConverterInputUnion<
51
+ TConverted extends readonly any[],
52
+ TRemaining extends readonly string[]
53
+ > = [] extends TRemaining
54
+ ? // we're done iterating
55
+ TConverted
56
+ : // recurse
57
+ ConverterInputUnion<
58
+ [...TConverted, ConverterInputType<First<TRemaining>>],
59
+ AfterFirst<TRemaining>
60
+ >;
61
+
62
+ /**
63
+ * **AvailableConverters**
64
+ *
65
+ * Type utility which will produce the correct union type for a "converter"
66
+ */
67
+ export type AvailableConverters<S, N, B, O> = ConverterKeys<S, N, B, O> extends readonly string[]
68
+ ? TupleToUnion<ConverterInputUnion<[], ConverterKeys<S, N, B, O>>>
69
+ : never;
70
+
71
+ /**
72
+ * **Converter**
73
+ *
74
+ * A type converter coming from the `createConverter()` utility. It receives
75
+ * one or more broad types (e.g., number, string, etc.) -- what it can take
76
+ * is based on what is configured -- and then converts based on the value
77
+ * passed in.
78
+ *
79
+ * The primary goal is to preserve as many _narrow_ types as possible in this process.
80
+ */
81
+ export type Converter<
82
+ S extends Narrowable,
83
+ N extends Narrowable,
84
+ B extends Narrowable,
85
+ O extends Narrowable
86
+ > = <T extends AvailableConverters<S, N, B, O>>(input: T) => ConverterShape<S, N, B, O>;
87
+
88
+ export type Conversion<TInput extends Narrowable, TOutput extends Narrowable> = <T extends TInput>(
89
+ input: T
90
+ ) => TOutput;
91
+
92
+ export type ConverterCoverage = "string" | "number" | "boolean" | "object";
93
+
94
+ /**
95
+ * Extracts the coverage provided by a `StrongMap` as a Tuple
96
+ */
97
+ export type MapCoverage<T extends StrongMap<ConverterCoverage>> = T extends StrongMap<
98
+ infer Coverage
99
+ >
100
+ ? UnionToTuple<Coverage>
101
+ : never;
102
+
103
+ export type StrongMapTypes<K extends readonly any[]> = [] extends K
104
+ ? never
105
+ : TupleToUnion<
106
+ [
107
+ First<K> extends "string"
108
+ ? TupleToUnion<[string, ...UnionToTuple<StrongMapTypes<AfterFirst<K>>>]>
109
+ : First<K> extends "number"
110
+ ? TupleToUnion<[number, ...UnionToTuple<StrongMapTypes<AfterFirst<K>>>]>
111
+ : First<K> extends "boolean"
112
+ ? TupleToUnion<[boolean, ...UnionToTuple<StrongMapTypes<AfterFirst<K>>>]>
113
+ : First<K> extends "object"
114
+ ? TupleToUnion<[Record<string, any>, ...UnionToTuple<StrongMapTypes<AfterFirst<K>>>]>
115
+ : never,
116
+ ...UnionToTuple<StrongMapTypes<AfterFirst<K>>>
117
+ ]
118
+ >;
119
+
120
+ export type StrongMap<TDefined = ConverterCoverage> = {
121
+ string: TDefined extends "string" ? <T extends string, R extends any>(v: T) => R : never;
122
+ number: TDefined extends "number" ? <T extends number, R extends any>(v: T) => R : never;
123
+ boolean: TDefined extends "boolean" ? <T extends boolean, R extends any>(v: T) => R : never;
124
+ object: TDefined extends "object"
125
+ ? <T extends Record<string, any>, R extends any>(v: T) => R
126
+ : never;
127
+ };
128
+
129
+ export type StrongMapTransformer<M extends StrongMap> = <
130
+ T extends readonly StrongMapTypes<MapCoverage<M>>[]
131
+ >(
132
+ tuple: T
133
+ ) => {
134
+ [K in keyof T]: T[K] extends string
135
+ ? M["string"] extends (...args: any[]) => any
136
+ ? ReturnType<M["string"]>
137
+ : never
138
+ : T[K] extends number
139
+ ? M["number"] extends (...args: any[]) => any
140
+ ? ReturnType<M["number"]>
141
+ : never
142
+ : T[K] extends boolean
143
+ ? M["boolean"] extends (...args: any[]) => any
144
+ ? ReturnType<M["boolean"]>
145
+ : never
146
+ : T[K] extends Record<string, any>
147
+ ? M["object"] extends (...args: any[]) => any
148
+ ? ReturnType<M["object"]>
149
+ : never
150
+ : never;
151
+ }[keyof T];
@@ -1,7 +1,22 @@
1
+ import { AnyFunction } from "src/runtime";
2
+ import { TupleToUnion } from "./TupleToUnion";
3
+
1
4
  export type Widen<T> = T extends string
2
5
  ? string
3
6
  : T extends number
4
7
  ? number
5
8
  : T extends boolean
6
9
  ? boolean
10
+ : T extends readonly string[]
11
+ ? string[]
12
+ : T extends readonly number[]
13
+ ? number[]
14
+ : T extends readonly boolean[]
15
+ ? boolean[]
16
+ : T extends readonly AnyFunction[]
17
+ ? AnyFunction[]
18
+ : T extends readonly any[]
19
+ ? TupleToUnion<T>[]
20
+ : T extends {}
21
+ ? {}
7
22
  : T;
@@ -0,0 +1,29 @@
1
+ import { Equal, Expect } from "@type-challenges/utils";
2
+ import { HasParameters } from "src/types";
3
+ import { describe, it } from "vitest";
4
+
5
+ // Note: type tests fail visible inspection but pass from Vitest
6
+ // standpoint so always be sure to run `tsc --noEmit` over your test files to
7
+ // gain validation that no new type vulnerabilities have cropped up.
8
+
9
+ describe("HasParameters<T>", () => {
10
+ it("happy path", () => {
11
+ const fn1 = (foo: string) => `${foo}bar`;
12
+ const fn2 = (foo: string) => `${foo}bar` as const;
13
+ const fn3 = () => `hello world`;
14
+ const fn4 = () => `hello world` as const;
15
+ const fn5 = (r: string, g: string, b: string) => `${r},${g},${b}` as const;
16
+
17
+ type cases = [
18
+ // single
19
+ Expect<Equal<HasParameters<typeof fn1>, true>>,
20
+ Expect<Equal<HasParameters<typeof fn2>, true>>,
21
+ // none
22
+ Expect<Equal<HasParameters<typeof fn3>, false>>,
23
+ Expect<Equal<HasParameters<typeof fn4>, false>>,
24
+ // multiple
25
+ Expect<Equal<HasParameters<typeof fn5>, true>>
26
+ ];
27
+ const cases: cases = [true, true, true, true, true];
28
+ });
29
+ });
@@ -1,7 +1,25 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { asArray } from "src/runtime";
2
+ import { AsArray, asArray } from "src/runtime";
3
3
  import { Equal, Expect } from "@type-challenges/utils";
4
4
 
5
+ describe("AsArray<T>", () => {
6
+ it("happy path", () => {
7
+ type T1 = AsArray<4>;
8
+ type T2 = AsArray<4, true>;
9
+ type T3 = AsArray<[4, 5, 6]>;
10
+ type T4 = AsArray<[4, 5, 6], true>;
11
+
12
+ type cases = [
13
+ //
14
+ Expect<Equal<T1, 4[]>>,
15
+ Expect<Equal<T2, number[]>>,
16
+ Expect<Equal<T3, [4, 5, 6]>>,
17
+ Expect<Equal<T4, number[]>>
18
+ ];
19
+ const cases: cases = [true, true, true, true];
20
+ });
21
+ });
22
+
5
23
  describe("asArray() function", () => {
6
24
  it("non-array is returned as an array", () => {
7
25
  const i = "a";
@@ -0,0 +1,79 @@
1
+ import { Equal, Expect } from "@type-challenges/utils";
2
+ import { ensureLeading } from "src/runtime/literals/ensureLeading";
3
+ import { ensureTrailing } from "src/runtime/literals/ensureTrailing";
4
+ import { stripLeading } from "src/runtime/literals/stripLeading";
5
+ import { stripTrailing } from "src/runtime/literals/stripTrailing";
6
+ import { EnsureLeading, EnsureTrailing, StripLeading, StripTrailing } from "src/types/alphabetic";
7
+ import { describe, expect, it } from "vitest";
8
+
9
+ // Note: while type tests clearly fail visible inspection, they pass from Vitest
10
+ // standpoint so always be sure to run `tsc --noEmit` over your test files to
11
+ // gain validation that no new type vulnerabilities have cropped up.
12
+
13
+ describe("EnsureLeading", () => {
14
+ it("EnsureLeading<T,U>", () => {
15
+ type T1 = EnsureLeading<"bar", "foo">;
16
+ type T2 = EnsureLeading<"foobar", "foo">;
17
+
18
+ type cases = [Expect<Equal<T1, "foobar">>, Expect<Equal<T2, "foobar">>];
19
+ const cases: cases = [true, true];
20
+ });
21
+
22
+ it("ensureLeading()", () => {
23
+ const t1 = ensureLeading("bar", "foo");
24
+ const t2 = ensureLeading("foobar", "foo");
25
+ expect(t1).toBe("foobar");
26
+ expect(t2).toBe("foobar");
27
+ });
28
+ });
29
+
30
+ describe("EnsureTrailing", () => {
31
+ it("EnsureTrailing<T,U>", () => {
32
+ type T1 = EnsureTrailing<"foo", "bar">;
33
+ type T2 = EnsureTrailing<"foobar", "bar">;
34
+
35
+ type cases = [Expect<Equal<T1, "foobar">>, Expect<Equal<T2, "foobar">>];
36
+ const cases: cases = [true, true];
37
+ });
38
+
39
+ it("ensureTrailing()", () => {
40
+ const t1 = ensureTrailing("foo", "bar");
41
+ const t2 = ensureTrailing("foobar", "bar");
42
+ expect(t1).toBe("foobar");
43
+ expect(t2).toBe("foobar");
44
+ });
45
+ });
46
+
47
+ describe("StripLeading", () => {
48
+ it("StripLeading<T,U>", () => {
49
+ type T1 = StripLeading<"foobar", "foo">;
50
+ type T2 = StripLeading<"bar", "foo">;
51
+
52
+ type cases = [Expect<Equal<T1, "bar">>, Expect<Equal<T2, "bar">>];
53
+ const cases: cases = [true, true];
54
+ });
55
+
56
+ it("stripLeading()", () => {
57
+ const t1 = stripLeading("foobar", "foo");
58
+ const t2 = stripLeading("bar", "foo");
59
+ expect(t1).toBe("bar");
60
+ expect(t2).toBe("bar");
61
+ });
62
+ });
63
+
64
+ describe("StripTrailing", () => {
65
+ it("StripTrailing<T,U>", () => {
66
+ type T1 = StripTrailing<"foobar", "bar">;
67
+ type T2 = StripTrailing<"foo", "bar">;
68
+
69
+ type cases = [Expect<Equal<T1, "foo">>, Expect<Equal<T2, "foo">>];
70
+ const cases: cases = [true, true];
71
+ });
72
+
73
+ it("stripTrailing()", () => {
74
+ const t1 = stripTrailing("foobar", "bar");
75
+ const t2 = stripTrailing("foo", "bar");
76
+ expect(t1).toBe("foo");
77
+ expect(t2).toBe("foo");
78
+ });
79
+ });
@@ -0,0 +1,94 @@
1
+ import { Equal, Expect } from "@type-challenges/utils";
2
+ import { pathJoin } from "src/runtime/literals/pathJoin";
3
+ import { PathJoin } from "src/types/alphabetic/PathJoin";
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ // Note: type tests fail visible inspection but pass from Vitest
7
+ // standpoint so always be sure to run `tsc --noEmit` over your test files to
8
+ // gain validation that no new type vulnerabilities have cropped up.
9
+
10
+ describe("PathJoin<T,U>", () => {
11
+ it("only literals / happy path", () => {
12
+ type T1 = PathJoin<"foo", "bar">;
13
+ type T2 = PathJoin<"foo/", "bar">;
14
+ type T3 = PathJoin<"foo", "/bar">;
15
+ type T4 = PathJoin<"foo/", "/bar">;
16
+ type T5 = PathJoin<"/foo/", "/bar">;
17
+ type T6 = PathJoin<"foo/", "/bar/">;
18
+
19
+ type cases = [
20
+ // neither have divider
21
+ Expect<Equal<T1, "foo/bar">>,
22
+ // one has, one does not
23
+ Expect<Equal<T2, "foo/bar">>,
24
+ Expect<Equal<T3, "foo/bar">>,
25
+ // both have
26
+ Expect<Equal<T4, "foo/bar">>,
27
+ // leading slash
28
+ Expect<Equal<T5, "/foo/bar">>,
29
+ // trailing slash
30
+ Expect<Equal<T6, "foo/bar/">>
31
+ ];
32
+ const cases: cases = [true, true, true, true, true, true];
33
+ });
34
+
35
+ it("PathJoin<T,U> with U as array", () => {
36
+ type T1 = PathJoin<"foo", ["bar", "baz"]>;
37
+ type T2 = PathJoin<"/foo/", ["/bar/", "/baz/"]>;
38
+ type T3 = PathJoin<"/foo/", ["bar", "/baz"]>;
39
+
40
+ type cases = [
41
+ //
42
+ Expect<Equal<T1, "foo/bar/baz">>,
43
+ Expect<Equal<T2, "/foo/bar/baz/">>,
44
+ Expect<Equal<T3, "/foo/bar/baz">>
45
+ ];
46
+ const cases: cases = [true, true, true];
47
+ });
48
+
49
+ it("wide types mixed in", () => {
50
+ type T1 = PathJoin<"foo", string>;
51
+ type T2 = PathJoin<"foo/", string>;
52
+ type T3 = PathJoin<string, "/bar">;
53
+ type T4 = PathJoin<string, "bar">;
54
+
55
+ type cases = [
56
+ Expect<Equal<T1, `foo/${string}`>>,
57
+ Expect<Equal<T2, `foo/${string}`>>,
58
+ Expect<Equal<T3, `${string}/bar`>>,
59
+ Expect<Equal<T4, `${string}bar`>>
60
+ ];
61
+ const cases: cases = [true, true, true, true];
62
+ });
63
+ });
64
+
65
+ describe("pathJoin() runtime util", () => {
66
+ it("no leading or trailing slashes", () => {
67
+ const t1 = pathJoin("foo", "bar");
68
+ const t2 = pathJoin("foo/", "bar");
69
+ const t3 = pathJoin("foo", "/bar");
70
+ const t4 = pathJoin("foo/", "/bar");
71
+ // multi
72
+ const t5 = pathJoin("foo/", "bar", "/baz");
73
+ const t6 = pathJoin("foo/", "bar/", "/baz");
74
+
75
+ [t1, t2, t3, t4].forEach((test) =>
76
+ expect(test, "no leading or trailing slash").toBe("foo/bar")
77
+ );
78
+ [t5, t6].forEach((test) =>
79
+ expect(test, "no leading or trailing slash (with multi U)").toBe("foo/bar/baz")
80
+ );
81
+ });
82
+
83
+ it("leading and trailing slashes", () => {
84
+ const t1 = pathJoin("/foo", "bar");
85
+ const t2 = pathJoin("foo/", "bar/");
86
+ const t3 = pathJoin("/foo", "bar", "/baz");
87
+ const t4 = pathJoin("foo/", "/bar/", "/baz/");
88
+ // runtime tests
89
+ expect(t1).toBe("/foo/bar");
90
+ expect(t2).toBe("foo/bar/");
91
+ expect(t3).toBe("/foo/bar/baz");
92
+ expect(t4).toBe("foo/bar/baz/");
93
+ });
94
+ });