;; _hyperscript tokenizer tests

;; helper: get token types as a flat list
(define hs-types (fn (tokens) (map (fn (t) (get t "type")) tokens)))
(define hs-vals (fn (tokens) (map (fn (t) (get t "value")) tokens)))
(define hs-tok (fn (tokens n) (nth tokens n)))

(defsuite
  "hs-tokenize-basics"
  (deftest
    "empty input"
    (let
      ((tokens (hs-tokenize "")))
      (assert= 1 (len tokens))
      (assert= "eof" (get (first tokens) "type"))))
  (deftest
    "single keyword"
    (let
      ((tokens (hs-tokenize "on")))
      (assert= 2 (len tokens))
      (assert= "keyword" (get (first tokens) "type"))
      (assert= "on" (get (first tokens) "value"))))
  (deftest
    "identifier"
    (let
      ((tokens (hs-tokenize "myVar")))
      (assert= "ident" (get (first tokens) "type"))
      (assert= "myVar" (get (first tokens) "value"))))
  (deftest
    "keywords vs identifiers"
    (let
      ((tokens (hs-tokenize "on click add foo")))
      (assert= "keyword" (get (hs-tok tokens 0) "type"))
      (assert= "ident" (get (hs-tok tokens 1) "type"))
      (assert= "keyword" (get (hs-tok tokens 2) "type"))
      (assert= "ident" (get (hs-tok tokens 3) "type"))))
  (deftest
    "whitespace skipped"
    (let
      ((tokens (hs-tokenize "  on  click  ")))
      (assert= 3 (len tokens))
      (assert= "on" (get (first tokens) "value")))))

(defsuite
  "hs-tokenize-literals"
  (deftest
    "integer"
    (let
      ((t (first (hs-tokenize "42"))))
      (assert= "number" (get t "type"))
      (assert= "42" (get t "value"))))
  (deftest
    "decimal"
    (let
      ((t (first (hs-tokenize "3.14"))))
      (assert= "number" (get t "type"))
      (assert= "3.14" (get t "value"))))
  (deftest
    "number with ms unit"
    (let
      ((t (first (hs-tokenize "100ms"))))
      (assert= "number" (get t "type"))
      (assert= "100ms" (get t "value"))))
  (deftest
    "number with s unit"
    (let
      ((t (first (hs-tokenize "2s"))))
      (assert= "number" (get t "type"))
      (assert= "2s" (get t "value"))))
  (deftest
    "double-quoted string"
    (let
      ((t (first (hs-tokenize "\"hello world\""))))
      (assert= "string" (get t "type"))
      (assert= "hello world" (get t "value"))))
  (deftest
    "single-quoted string"
    (let
      ((t (first (hs-tokenize "'foo'"))))
      (assert= "string" (get t "type"))
      (assert= "foo" (get t "value"))))
  (deftest
    "string with escapes"
    (let
      ((t (first (hs-tokenize "\"a\\nb\""))))
      (assert= "string" (get t "type"))
      (assert= "a\nb" (get t "value"))))
  (deftest
    "template literal"
    (let
      ((t (first (hs-tokenize "`hello ${name}`"))))
      (assert= "template" (get t "type"))
      (assert= "hello ${name}" (get t "value")))))

(defsuite
  "hs-tokenize-dom-refs"
  (deftest
    "class literal"
    (let
      ((t (first (hs-tokenize ".foo"))))
      (assert= "class" (get t "type"))
      (assert= "foo" (get t "value"))))
  (deftest
    "class with dashes"
    (let
      ((t (first (hs-tokenize ".foo--bar"))))
      (assert= "class" (get t "type"))
      (assert= "foo--bar" (get t "value"))))
  (deftest
    "id literal"
    (let
      ((t (first (hs-tokenize "#bar"))))
      (assert= "id" (get t "type"))
      (assert= "bar" (get t "value"))))
  (deftest
    "attribute ref"
    (let
      ((t (first (hs-tokenize "@foo"))))
      (assert= "attr" (get t "type"))
      (assert= "foo" (get t "value"))))
  (deftest
    "style ref"
    (let
      ((t (first (hs-tokenize "*color"))))
      (assert= "style" (get t "type"))
      (assert= "color" (get t "value"))))
  (deftest
    "element-scoped local"
    (let
      ((t (first (hs-tokenize ":myVar"))))
      (assert= "local" (get t "type"))
      (assert= "myVar" (get t "value"))))
  (deftest
    "CSS selector"
    (let
      ((t (first (hs-tokenize "<p/>"))))
      (assert= "selector" (get t "type"))
      (assert= "p" (get t "value"))))
  (deftest
    "CSS selector complex"
    (let
      ((t (first (hs-tokenize "<div.foo/>"))))
      (assert= "selector" (get t "type"))
      (assert= "div.foo" (get t "value")))))

(defsuite
  "hs-tokenize-operators"
  (deftest
    "equals"
    (let
      ((t (first (hs-tokenize "=="))))
      (assert= "op" (get t "type"))
      (assert= "==" (get t "value"))))
  (deftest
    "not-equals"
    (let
      ((t (first (hs-tokenize "!="))))
      (assert= "op" (get t "type"))
      (assert= "!=" (get t "value"))))
  (deftest
    "plus"
    (let
      ((t (first (hs-tokenize "+"))))
      (assert= "op" (get t "type"))
      (assert= "+" (get t "value"))))
  (deftest
    "possessive"
    (let
      ((t (first (hs-tokenize "#d1's"))))
      (assert= "id" (get (hs-tok (hs-tokenize "#d1's") 0) "type"))
      (assert= "op" (get (hs-tok (hs-tokenize "#d1's") 1) "type"))
      (assert= "'s" (get (hs-tok (hs-tokenize "#d1's") 1) "value"))))
  (deftest
    "parens"
    (let
      ((tokens (hs-tokenize "(foo)")))
      (assert= "paren-open" (get (hs-tok tokens 0) "type"))
      (assert= "ident" (get (hs-tok tokens 1) "type"))
      (assert= "paren-close" (get (hs-tok tokens 2) "type"))))
  (deftest
    "brackets"
    (let
      ((tokens (hs-tokenize "[0]")))
      (assert= "bracket-open" (get (hs-tok tokens 0) "type"))
      (assert= "number" (get (hs-tok tokens 1) "type"))
      (assert= "bracket-close" (get (hs-tok tokens 2) "type")))))

(defsuite
  "hs-tokenize-arithmetic-ops"
  (deftest
    "multiply operator"
    (let
      ((toks (hs-tokenize "4 * 5")))
      (assert= "number" (get (nth toks 0) "type"))
      (assert= "op" (get (nth toks 1) "type"))
      (assert= "*" (get (nth toks 1) "value"))
      (assert= "number" (get (nth toks 2) "type"))))
  (deftest
    "modulo operator"
    (let
      ((toks (hs-tokenize "10 % 3")))
      (assert= "op" (get (nth toks 1) "type"))
      (assert= "%" (get (nth toks 1) "value"))))
  (deftest
    "star as style not operator"
    (let
      ((toks (hs-tokenize "*color")))
      (assert= "style" (get (nth toks 0) "type"))
      (assert= "color" (get (nth toks 0) "value"))))
  (deftest
    "division operator"
    (let
      ((toks (hs-tokenize "10 / 2")))
      (assert= "op" (get (nth toks 1) "type"))
      (assert= "/" (get (nth toks 1) "value"))))
  (deftest
    "mixed arithmetic"
    (let
      ((toks (hs-tokenize "1 + 2 * 3")))
      (assert= "op" (get (nth toks 1) "type"))
      (assert= "+" (get (nth toks 1) "value"))
      (assert= "op" (get (nth toks 3) "type"))
      (assert= "*" (get (nth toks 3) "value")))))

(defsuite
  "hs-tokenize-comments"
  (deftest
    "line comment skipped"
    (let
      ((tokens (hs-tokenize "on // this is a comment\nclick")))
      (assert= 3 (len tokens))
      (assert= "on" (get (hs-tok tokens 0) "value"))
      (assert= "click" (get (hs-tok tokens 1) "value")))))

(defsuite
  "hs-tokenize-full-expressions"
  (deftest
    "on click add .called"
    (let
      ((tokens (hs-tokenize "on click add .called")))
      (assert= 5 (len tokens))
      (assert= "keyword" (get (hs-tok tokens 0) "type"))
      (assert= "on" (get (hs-tok tokens 0) "value"))
      (assert= "ident" (get (hs-tok tokens 1) "type"))
      (assert= "click" (get (hs-tok tokens 1) "value"))
      (assert= "keyword" (get (hs-tok tokens 2) "type"))
      (assert= "add" (get (hs-tok tokens 2) "value"))
      (assert= "class" (get (hs-tok tokens 3) "type"))
      (assert= "called" (get (hs-tok tokens 3) "value"))
      (assert= "eof" (get (hs-tok tokens 4) "type"))))
  (deftest
    "set #d1.innerHTML to foo"
    (let
      ((tokens (hs-tokenize "set #d1.innerHTML to \"foo\"")))
      (assert= 6 (len tokens))
      (assert= "keyword" (get (hs-tok tokens 0) "type"))
      (assert= "id" (get (hs-tok tokens 1) "type"))
      (assert= "d1" (get (hs-tok tokens 1) "value"))
      (assert= "class" (get (hs-tok tokens 2) "type"))
      (assert= "innerHTML" (get (hs-tok tokens 2) "value"))
      (assert= "keyword" (get (hs-tok tokens 3) "type"))
      (assert= "to" (get (hs-tok tokens 3) "value"))
      (assert= "string" (get (hs-tok tokens 4) "type"))))
  (deftest
    "put \"Clicked\" into my.innerHTML"
    (let
      ((tokens (hs-tokenize "put \"Clicked\" into my.innerHTML")))
      (assert= 6 (len tokens))
      (assert= "keyword" (get (hs-tok tokens 0) "type"))
      (assert= "put" (get (hs-tok tokens 0) "value"))
      (assert= "string" (get (hs-tok tokens 1) "type"))
      (assert= "keyword" (get (hs-tok tokens 2) "type"))
      (assert= "into" (get (hs-tok tokens 2) "value"))
      (assert= "keyword" (get (hs-tok tokens 3) "type"))
      (assert= "my" (get (hs-tok tokens 3) "value"))))
  (deftest
    "on click send custom(foo:\"fromBar\") to #d2"
    (let
      ((tokens (hs-tokenize "on click send custom(foo:\"fromBar\") to #d2")))
      (assert= "keyword" (get (hs-tok tokens 0) "type"))
      (assert= "on" (get (hs-tok tokens 0) "value"))
      (assert= "ident" (get (hs-tok tokens 1) "type"))
      (assert= "click" (get (hs-tok tokens 1) "value"))
      (assert= "keyword" (get (hs-tok tokens 2) "type"))
      (assert= "send" (get (hs-tok tokens 2) "value"))))
  (deftest
    "toggle between .foo and .bar"
    (let
      ((tokens (hs-tokenize "toggle between .foo and .bar")))
      (assert= "keyword" (get (hs-tok tokens 0) "type"))
      (assert= "toggle" (get (hs-tok tokens 0) "value"))
      (assert= "keyword" (get (hs-tok tokens 1) "type"))
      (assert= "between" (get (hs-tok tokens 1) "value"))
      (assert= "class" (get (hs-tok tokens 2) "type"))
      (assert= "foo" (get (hs-tok tokens 2) "value"))
      (assert= "keyword" (get (hs-tok tokens 3) "type"))
      (assert= "and" (get (hs-tok tokens 3) "value"))
      (assert= "class" (get (hs-tok tokens 4) "type"))
      (assert= "bar" (get (hs-tok tokens 4) "value"))))
  (deftest
    "if true put \"foo\" into me.innerHTML else put \"bar\" into me.innerHTML end"
    (let
      ((tokens (hs-tokenize "if true put \"foo\" into me.innerHTML else put \"bar\" into me.innerHTML end")))
      (assert= "keyword" (get (hs-tok tokens 0) "type"))
      (assert= "if" (get (hs-tok tokens 0) "value"))
      (assert= "keyword" (get (hs-tok tokens 1) "type"))
      (assert= "true" (get (hs-tok tokens 1) "value"))))
  (deftest
    "on click[buttons==0] log event"
    (let
      ((tokens (hs-tokenize "on click[buttons==0] log event")))
      (assert= "keyword" (get (hs-tok tokens 0) "type"))
      (assert= "ident" (get (hs-tok tokens 1) "type"))
      (assert= "click" (get (hs-tok tokens 1) "value"))
      (assert= "bracket-open" (get (hs-tok tokens 2) "type"))
      (assert= "ident" (get (hs-tok tokens 3) "type"))
      (assert= "buttons" (get (hs-tok tokens 3) "value"))
      (assert= "op" (get (hs-tok tokens 4) "type"))
      (assert= "==" (get (hs-tok tokens 4) "value"))))
  (deftest
    "wait 100ms then add .done"
    (let
      ((tokens (hs-tokenize "wait 100ms then add .done")))
      (assert= "keyword" (get (hs-tok tokens 0) "type"))
      (assert= "wait" (get (hs-tok tokens 0) "value"))
      (assert= "number" (get (hs-tok tokens 1) "type"))
      (assert= "100ms" (get (hs-tok tokens 1) "value"))
      (assert= "keyword" (get (hs-tok tokens 2) "type"))
      (assert= "then" (get (hs-tok tokens 2) "value"))
      (assert= "keyword" (get (hs-tok tokens 3) "type"))
      (assert= "add" (get (hs-tok tokens 3) "value"))
      (assert= "class" (get (hs-tok tokens 4) "type"))
      (assert= "done" (get (hs-tok tokens 4) "value"))))
  (deftest
    "increment @count then put it into me"
    (let
      ((tokens (hs-tokenize "increment @count then put it into me")))
      (assert= "keyword" (get (hs-tok tokens 0) "type"))
      (assert= "increment" (get (hs-tok tokens 0) "value"))
      (assert= "attr" (get (hs-tok tokens 1) "type"))
      (assert= "count" (get (hs-tok tokens 1) "value"))
      (assert= "keyword" (get (hs-tok tokens 4) "type"))
      (assert= "it" (get (hs-tok tokens 4) "value"))))
  (deftest
    "on click from #bar add .clicked"
    (let
      ((tokens (hs-tokenize "on click from #bar add .clicked")))
      (assert= "keyword" (get (hs-tok tokens 2) "type"))
      (assert= "from" (get (hs-tok tokens 2) "value"))
      (assert= "id" (get (hs-tok tokens 3) "type"))
      (assert= "bar" (get (hs-tok tokens 3) "value")))))