All files / test / mod.ts

100.00% Branches 37/37
100.00% Lines 62/62
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
 
x30
 
 
 
x30
 
 
x30
x30
x90
x90
x90
x90
x90
x30
x30
 
 
x30
 
 
x30
x30
x30
x30
x30
x30
x5526
x5526
x34752
 
x11584
x11584
x14745
x14745
 
x11584
 
x12263
x13619
x13619
x13619
 
x12263
x12263
x11584
x11584
 
x13802
x59352
x14838
x14838
x14838
 
x18207
x20093
x20093
x20093
 
x17641
x17741
x17741
x17741
 
x17583
x158631
x17607
 
x18192
x18350
x18350
x13802
x11584
x11017
x5526
x30
 
 
x30
x30
x2310
 
 
x30


















































































// Imports
import { type Directive, type NonVoid, Phase } from "@mizu/internal/engine"
export type * from "@mizu/internal/engine"

/** Delta for {@linkcode Phase} to prevent collisions when {@linkcode Directive.multiple} is not enabled. */
export const PHASE_TESTING_DELTA = -0.5 as number

/** `~test` typings. */
export const typings = {
  modifiers: {
    text: { type: Boolean },
    comment: { type: Boolean },
    eval: { type: Boolean },
    throw: { type: Boolean },
    event: { type: String },
  },
} as const

/** `~test` directive. */
export const _test: Directive<{
  Name: RegExp
  Typings: typeof typings
}> = {
  name: /^~test/,
  phase: Phase.TESTING,
  typings,
  multiple: true,
  async execute(this: typeof _test, renderer, element, { attributes, ...options }) {
    const returned = {} as NonVoid<Awaited<ReturnType<NonNullable<Directive["execute"]>>>>
    for (const attribute of attributes) {
      const parsed = renderer.parseAttribute(attribute, this.typings, { modifiers: true })
      // Skip if tag does not match current phase
      parsed.tag ??= Phase[Phase.TESTING]
      if ((parsed.tag) && (this.phase !== (Phase[parsed.tag.toLocaleUpperCase() as keyof typeof Phase] + PHASE_TESTING_DELTA))) {
        continue
      }
      // Handle comment elements
      if (renderer.isComment(element)) {
        // Uncomment element on falsy `comment` modifier value
        if ((parsed.modifiers.comment) && (!await renderer.evaluate(element, attribute.value || "'true'", options))) {
          returned.element = renderer.uncomment(element)
          element = returned.element
        }
        // Set comment content on any `~test` directive
        renderer.setAttribute(element, attribute.name, attribute.value)
        continue
      } // Handle HTML elements
      else if (renderer.isHtmlElement(element)) {
        // Comment element on truthy `comment` modifier value
        if ((parsed.modifiers.comment) && (await renderer.evaluate(element, attribute.value || "'true'", options))) {
          returned.element = renderer.comment(element, { expression: attribute.value, directive: attribute.name })
          element = returned.element
          continue
        }
        // Set text content on `text` modifier value
        if (parsed.modifiers.text) {
          element.textContent = `${await renderer.evaluate(element, attribute.value, options)}`
          continue
        }
        // Evaluate attribute value on `eval` modifier value
        if (parsed.modifiers.eval) {
          await renderer.evaluate(element, attribute.value, options)
          continue
        }
        // Evaluate attribute value on `event` modifier value
        if (parsed.modifiers.event) {
          element.addEventListener(parsed.modifiers.event, (event) => renderer.evaluate(element, attribute.value, { context: options.context, state: { ...options.state, $event: event }, args: [event] }))
        }
        // Throw error on truthy `throw` modifier value
        if ((parsed.modifiers.throw) && (await renderer.evaluate(element, attribute.value || "'true'", options))) {
          throw new EvalError(`Expected error from: ${element.outerHTML.replace(element.innerHTML, "")}`)
        }
      }
    }
    return returned
  },
}

/** `~test` directives. */
const _tests = Object.entries(Phase)
  .filter(([, value]) => (Number.isFinite(value)) && (Number(value) > Phase.META))
  .map(([key, value]) => ({ ..._test, name: new RegExp(`${_test.name.source}(?<${key.toLocaleLowerCase()}>)`), phase: (value as number) + PHASE_TESTING_DELTA })) as Array<typeof _test>

/** Default exports. */
export default _tests