Passed
Push — main ( c016a8...e3a98e )
by Andrii
02:31
created

bem.test.ts ➔ beming   B

Complexity

Conditions 8

Size

Total Lines 19
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 19
rs 7.3333
c 0
b 0
f 0
cc 8
1
import type { Ever0, Extends, PartDeep } from "../src/ts-swiss.types"
2
import type { CssModule } from "../src/definitions.types"
3
4
it("tree2classes", () => {
5
6
  type BemTree = {
7
    [block: string]: {
8
        // ""?: {[blockMod: string]: true}
9
        [element: string]: boolean | {[elMod: string]: boolean}
10
    }
11
  }
12
13
  type El2Classes<DMod extends string, S extends Record<string, boolean|Record<string, boolean>>> = {[K in string & keyof S]:
14
    K | (S[K] extends Record<string, boolean> ? `${K}${DMod}${string & keyof S[K]}` : never)
15
  }[string & keyof S]
16
17
  type BemTree2Classes<DEl extends string, DMod extends string, S extends BemTree> = {[K in string & keyof S]:
18
    K | `${K}${DEl}${El2Classes<DMod,S[K]>}`
19
  }[string & keyof S]
20
21
22
  const bem2 = {
23
    "Button": {
24
       "Container": true,
25
       "Icon": {
26
           "small": true,
27
           "big": true
28
       }
29
   },
30
   "Form": {
31
       "Container": true,
32
       "Button": true
33
   }
34
  }
35
36
  const suite: Record<BemTree2Classes<"__", "--", typeof bem2>, boolean> = {
37
    Button: true,
38
    Button__Icon: true,
39
    "Button__Icon--big": true,
40
    "Button__Icon--small": true,
41
    Button__Container: true,
42
    Form: true,
43
    Form__Button: true,
44
    Form__Container: true
45
   }
46
47
  expect(suite).toBeInstanceOf(Object)
48
})
49
50
type ClassNames = {
51
  "App": string
52
  "App--dark": string
53
  "App__Container": string
54
  "App__Container--loading": string
55
  "App__Container--status--loading": string
56
  "App__Container--status--error": string
57
  "App__Header": string
58
  "Btn": string
59
  "Btn--disabled": string
60
  "Btn--info--warning": string
61
  "Btn--info--error": string
62
  "Btn__Icon": string
63
  "Btn__Icon--big": string
64
  "Footer": string
65
}
66
67
it("take blocks", () => {
68
  // type digits = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'
69
  type smallLetters = 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'|'k'|'l'|'m'|'n'|'o'|'p'|'q'|'r'|'s'|'t'|'u'|'v'|'w'|'x'|'y'|'z'
70
  type bigLetters = 'A'|'B'|'C'|'D'|'E'|'F'|'G'|'H'|'I'|'J'|'K'|'L'|'M'|'N'|'O'|'P'|'Q'|'R'|'S'|'T'|'U'|'V'|'W'|'X'|'Y'|'Z'
71
  type letters = smallLetters | bigLetters
72
73
  type FirstWord<chars extends string, word extends string, stacked extends string = ""> = word extends `${chars}${infer K}`
74
  ? word extends `${infer F}${K}`
75
  ? FirstWord<chars, K, `${stacked}${F}`>
76
  : never
77
  : stacked
78
79
  type Blocks<T> = {[K in keyof T]: FirstWord<letters, K & string>}[keyof T]
80
81
  const suite: Record<Blocks<ClassNames>, Blocks<ClassNames>> = {
82
    App: "App",
83
    Btn: "Btn",
84
    Footer: "Footer"
85
  }
86
87
  expect(suite).toBeInstanceOf(Object)
88
})
89
90
describe("upon delimiter", () => {
91
  type Strip<Str extends string, Delimiter extends string> = Str extends `${infer Lead}${Delimiter}${string}` ? Lead : Str
92
  type StripFromObj<T, Delimiter extends string> = {[K in string & keyof T]: Strip<K,Delimiter>}[keyof T]
93
94
  it("args", () => {
95
    type GetMods<T, B extends string, E extends string|undefined> = {
96
      [K in string & keyof T]: E extends string
97
      ? K extends `${B}__${E}--${infer M}` ? M : never
98
      : K extends `${B}--${infer M}` ? M : never
99
    }[keyof T]
100
    type Bemer<ClassNames extends CssModule> = (
101
      <
102
        BE extends StripFromObj<ClassNames, "--">,
103
        Block extends Strip<BE, "__">,
104
        Element extends undefined|(BE extends `${Block}__${infer Element}` ? Element : undefined) = undefined,
105
        Modifier extends undefined|GetMods<ClassNames, Block, Element> = undefined
106
      >(
107
        block: Block,
108
        element?: Element,
109
        modifier?: Modifier
110
      ) => `${
111
        Block
112
      }${
113
        Element extends string ? ` ${Block}__${Element}` : ""
114
      }${
115
        Modifier extends string
116
        ? ` ${Block}${
117
          Element extends string ? `__${Element}` : ""
118
        }--${Modifier}`
119
        : ""
120
      }`
121
    )
122
123
124
    function beming<ClassNames extends CssModule>() {
125
      const host: Bemer<ClassNames> = ((block, element?, modifier?) => {
126
        const elemened = element ? `${block}__${element}` : ""
127
        const moded = modifier ? ` ${element ? elemened : block}--${modifier}` : ""
128
129
        return `${
130
          block
131
        }${
132
          element ! ? " " : ""
133
        }${
134
          elemened
135
        }${
136
          moded
137
        }`
138
      }) as Bemer<ClassNames>
139
140
      return host
141
    }
142
143
144
    const bemer = beming<ClassNames>()
145
    , $return = bemer("Btn", "Icon", "big")
146
    , typed: typeof $return = "Btn Btn__Icon Btn__Icon--big"
147
148
    expect($return).toBe(typed)
149
  })
150
151
  it("query", () => {
152
    // Can be used on #30
153
154
    type Elements<
155
        classes extends string,
156
        b extends string,
157
        delE extends string = "elementDelimiter" extends keyof ReactClassNaming.BemOptions
158
        ? ReactClassNaming.BemOptions["elementDelimiter"]
159
        : ReactClassNaming.BemOptions["$default"]["elementDelimiter"],
160
        delM extends string = "modDelimiter" extends keyof ReactClassNaming.BemOptions
161
        ? ReactClassNaming.BemOptions["modDelimiter"]
162
        : ReactClassNaming.BemOptions["$default"]["modDelimiter"],
163
        bModKey extends string = "$" /*"blockModKey" extends keyof ReactClassNaming.BemOptions
164
        ? ReactClassNaming.BemOptions["blockModKey"]
165
        : ReactClassNaming.BemOptions["$default"]["blockModKey"]*/
166
      > = classes extends `${b}${delE}${infer E}`
167
      ? Strip<E, delM>
168
      : classes extends `${b}${delM}${string}`
169
      ? bModKey
170
      : never
171
172
    type BemQuery<
173
      classes extends string,
174
      delE extends string = "elementDelimiter" extends keyof ReactClassNaming.BemOptions
175
      ? ReactClassNaming.BemOptions["elementDelimiter"]
176
      : ReactClassNaming.BemOptions["$default"]["elementDelimiter"],
177
      delM extends string = "modDelimiter" extends keyof ReactClassNaming.BemOptions
178
      ? ReactClassNaming.BemOptions["modDelimiter"]
179
      : ReactClassNaming.BemOptions["$default"]["modDelimiter"],
180
      bModKey extends string = "$" /*"blockModKey" extends keyof ReactClassNaming.BemOptions
181
      ? ReactClassNaming.BemOptions["blockModKey"]
182
      : ReactClassNaming.BemOptions["$default"]["blockModKey"]*/
183
    > = string extends classes ? BemInGeneral : PartDeep<{
184
      [b in Strip<Strip<classes, delM>, delE>]: boolean
185
      | Exclude<MVs<classes, b, bModKey>, `${string}${delM}${string}`>
186
      | (
187
        Extends<classes, `${b}${delE | delM}${string}`,
188
          {
189
            [e in Elements<classes, b>]: boolean
190
            | Exclude<MVs<classes, b, e>, `${string}${delM}${string}`>
191
            | (
192
              {[m in Strip<MVs<classes, b, e>, delM>]:
193
                false | (
194
                  Ever0<
195
                    classes extends `${b}${
196
                      e extends bModKey ? "" : `${delE}${e}`
197
                    }${delM}${m}${delM}${infer V}`
198
                    ? V : never,
199
                    true
200
                  >
201
                )
202
              }
203
            )
204
          }
205
        >
206
      )
207
    }>
208
209
    type MVs<
210
      classes extends string,
211
      b extends string,
212
      e extends string,
213
      delE extends string = "elementDelimiter" extends keyof ReactClassNaming.BemOptions
214
      ? ReactClassNaming.BemOptions["elementDelimiter"]
215
      : ReactClassNaming.BemOptions["$default"]["elementDelimiter"],
216
      delM extends string = "modDelimiter" extends keyof ReactClassNaming.BemOptions
217
      ? ReactClassNaming.BemOptions["modDelimiter"]
218
      : ReactClassNaming.BemOptions["$default"]["modDelimiter"],
219
      bModKey extends string = "$" /*"blockModKey" extends keyof ReactClassNaming.BemOptions
220
      ? ReactClassNaming.BemOptions["blockModKey"]
221
      : ReactClassNaming.BemOptions["$default"]["blockModKey"]*/
222
    > = classes extends `${b}${
223
      e extends bModKey ? "" : `${delE}${e}`
224
    }${delM}${infer MV}` ? MV : never
225
226
    type BemInGeneral = {
227
      [block: string]: undefined | boolean | string | {
228
        [el: string]: undefined | boolean | string
229
        | {
230
          [mod: string]: undefined | boolean | string
231
        }
232
      }
233
    }
234
235
    type BemQuerier<
236
      ClassNames extends CssModule,
237
      delE extends string = "__",
238
      delM extends string = "--",
239
      bModKey extends string = "$",
240
    > =
241
    <
242
      // classes extends keyof ClassNames,
243
      // BE extends StripFromObj<ClassNames, delM>,
244
      Q extends BemQuery<
245
        keyof ClassNames,
246
        delE,
247
        delM,
248
        bModKey
249
        // BE,
250
        // Strip<BE, delE>
251
      >
252
    >(arg: Q) => {[K in
253
      {[b in keyof Q]: Q[b] extends boolean ? b : never}[keyof Q]
254
      // | {[b in keyof Q]: Q[b] extends Primitive ? never : `${b}${delE}${keyof Q[b]}`}[keyof Q]
255
      // | {
256
      //   [b in keyof Q]: {[e in keyof Q[b]]: Q[b][e] extends Primitive ? never :
257
      //     {[m in keyof Q[b][e]]:
258
      //       Q[b][e][m] extends string
259
      //       ? `${b}${delE}${e}${delM}${m}${delM}${Q[b][e][m]}`
260
      //       : `${b}${delE}${e}${delM}${m}`
261
      //     }[keyof Q[b][e]]
262
      //   }[keyof Q[b]]
263
      // }[keyof Q]
264
      // {[b in keyof Q]:
265
      //   Q[b] extends boolean ? b : never
266
      // }[keyof Q]
267
    ]: boolean}
268
269
    //@ts-expect-error
270
    const q = x => x as unknown as BemQuerier<ClassNames>
271
    , res = q({
272
      "App": {
273
        "Header": false,
274
        "Container": {
275
          "loading": true,
276
          "status": "error"
277
        }
278
      },
279
      "Btn": {
280
        $: {
281
          info: "error",
282
          disabled: false
283
        },
284
        "Icon": {
285
          "big": true
286
        }
287
      },
288
      "Footer": false
289
    })
290
    , typeCheck: Record<string, typeof res> = {
291
      "exact": {
292
        //@ts-expect-error
293
        Footer: true,
294
        Btn: true,
295
        App: true,
296
      }
297
    }
298
299
    // expect(res).toStrictEqual({
300
    //   "App__Container": true,
301
    //   "App__Container--loading": true,
302
    //   "App__Container--status--error": true,
303
    //   "App__Header": false,
304
    //   "Btn": true,
305
    //   "Btn--disabled": false,
306
    //   "Btn--info--error": true,
307
    //   "Btn__Icon": true,
308
    //   "Btn__Icon--big": true,
309
    //   "Footer": false,
310
    // })
311
    expect(typeCheck).toBeInstanceOf(Object)
312
  })
313
})
314