
;; GraphQL parser, executor, and hyperscript integration tests

;; ── Tokenizer ─────────────────────────────────────────────────────

(defsuite
  "gql-tokenizer"
  (deftest
    "tokenizes simple query"
    (let
      ((tokens (gql-tokenize "{ user { name } }")))
      (assert= "brace-open" (get (nth tokens 0) :type))
      (assert= "name" (get (nth tokens 1) :type))
      (assert= "user" (get (nth tokens 1) :value))
      (assert= "brace-open" (get (nth tokens 2) :type))
      (assert= "name" (get (nth tokens 3) :type))
      (assert= "name" (get (nth tokens 3) :value))
      (assert= "brace-close" (get (nth tokens 4) :type))
      (assert= "brace-close" (get (nth tokens 5) :type))
      (assert= "eof" (get (nth tokens 6) :type))))
  (deftest
    "tokenizes arguments"
    (let
      ((tokens (gql-tokenize "user(id: 42)")))
      (assert= "name" (get (nth tokens 0) :type))
      (assert= "paren-open" (get (nth tokens 1) :type))
      (assert= "name" (get (nth tokens 2) :type))
      (assert= "id" (get (nth tokens 2) :value))
      (assert= "colon" (get (nth tokens 3) :type))
      (assert= "number" (get (nth tokens 4) :type))
      (assert= 42 (get (nth tokens 4) :value))
      (assert= "paren-close" (get (nth tokens 5) :type))))
  (deftest
    "tokenizes string values"
    (let
      ((tokens (gql-tokenize "name: \"hello world\"")))
      (assert= "string" (get (nth tokens 2) :type))
      (assert= "hello world" (get (nth tokens 2) :value))))
  (deftest
    "tokenizes dollar sign for variables"
    (let
      ((tokens (gql-tokenize "$id")))
      (assert= "dollar" (get (nth tokens 0) :type))
      (assert= "name" (get (nth tokens 1) :type))
      (assert= "id" (get (nth tokens 1) :value))))
  (deftest
    "tokenizes bang for non-null"
    (let
      ((tokens (gql-tokenize "String!")))
      (assert= "name" (get (nth tokens 0) :type))
      (assert= "bang" (get (nth tokens 1) :type))))
  (deftest
    "tokenizes spread operator"
    (let
      ((tokens (gql-tokenize "...UserFields")))
      (assert= "spread" (get (nth tokens 0) :type))
      (assert= "name" (get (nth tokens 1) :type))
      (assert= "UserFields" (get (nth tokens 1) :value))))
  (deftest
    "tokenizes at sign for directives"
    (let
      ((tokens (gql-tokenize "@skip(if: true)")))
      (assert= "at" (get (nth tokens 0) :type))
      (assert= "name" (get (nth tokens 1) :type))
      (assert= "skip" (get (nth tokens 1) :value))))
  (deftest
    "skips comments"
    (let
      ((tokens (gql-tokenize "{ # this is a comment\n  user }")))
      (assert= "brace-open" (get (nth tokens 0) :type))
      (assert= "name" (get (nth tokens 1) :type))
      (assert= "user" (get (nth tokens 1) :value))
      (assert= "brace-close" (get (nth tokens 2) :type))))
  (deftest
    "skips commas"
    (let
      ((tokens (gql-tokenize "name, email, age")))
      (assert= 4 (length tokens))
      (assert= "name" (get (nth tokens 0) :value))
      (assert= "email" (get (nth tokens 1) :value))
      (assert= "age" (get (nth tokens 2) :value))))
  (deftest
    "tokenizes negative numbers"
    (let
      ((tokens (gql-tokenize "-42")))
      (assert= "number" (get (nth tokens 0) :type))
      (assert= -42 (get (nth tokens 0) :value))))
  (deftest
    "tokenizes float numbers"
    (let
      ((tokens (gql-tokenize "3.14")))
      (assert= "number" (get (nth tokens 0) :type))
      (assert= 3.14 (get (nth tokens 0) :value))))
  (deftest
    "tokenizes list brackets"
    (let
      ((tokens (gql-tokenize "[1, 2, 3]")))
      (assert= "bracket-open" (get (nth tokens 0) :type))
      (assert= "number" (get (nth tokens 1) :type))
      (assert= "bracket-close" (get (nth tokens 4) :type)))))

;; ── Parser: shorthand queries ─────────────────────────────────────

(defsuite
  "gql-parse-shorthand"
  (deftest
    "parses shorthand query"
    (let
      ((doc (gql-parse "{ user { name } }")))
      (assert= (quote gql-doc) (first doc))
      (let
        ((op (nth doc 1)))
        (assert= (quote gql-query) (first op))
        (assert= nil (gql-op-name op))
        (assert= 1 (length (gql-op-selections op)))
        (let
          ((field (first (gql-op-selections op))))
          (assert= (quote gql-field) (first field))
          (assert= "user" (gql-field-name field))
          (assert= 1 (length (gql-field-selections field)))
          (assert=
            "name"
            (gql-field-name (first (gql-field-selections field))))))))
  (deftest
    "parses multiple root fields"
    (let
      ((doc (gql-parse "{ user { name } posts { title } }")))
      (let
        ((sels (gql-op-selections (nth doc 1))))
        (assert= 2 (length sels))
        (assert= "user" (gql-field-name (first sels)))
        (assert= "posts" (gql-field-name (nth sels 1))))))
  (deftest
    "parses scalar field without sub-selections"
    (let
      ((doc (gql-parse "{ name email }")))
      (let
        ((sels (gql-op-selections (nth doc 1))))
        (assert= 2 (length sels))
        (assert= "name" (gql-field-name (first sels)))
        (assert= "email" (gql-field-name (nth sels 1)))
        (assert= 0 (length (gql-field-selections (first sels))))))))

;; ── Parser: named operations ──────────────────────────────────────

(defsuite
  "gql-parse-operations"
  (deftest
    "parses named query"
    (let
      ((doc (gql-parse "query GetUser { user { name } }")))
      (let
        ((op (nth doc 1)))
        (assert= (quote gql-query) (first op))
        (assert= "GetUser" (gql-op-name op)))))
  (deftest
    "parses mutation"
    (let
      ((doc (gql-parse "mutation CreatePost { createPost(title: \"Hello\") { id } }")))
      (let
        ((op (nth doc 1)))
        (assert= (quote gql-mutation) (first op))
        (assert= "CreatePost" (gql-op-name op))
        (let
          ((field (first (gql-op-selections op))))
          (assert= "createPost" (gql-field-name field))
          (assert= 1 (length (gql-field-args field)))
          (assert= "title" (first (first (gql-field-args field))))
          (assert= "Hello" (nth (first (gql-field-args field)) 1))))))
  (deftest
    "parses subscription"
    (let
      ((doc (gql-parse "subscription OnMessage { messageAdded { text } }")))
      (let
        ((op (nth doc 1)))
        (assert= (quote gql-subscription) (first op))
        (assert= "OnMessage" (gql-op-name op)))))
  (deftest
    "parses anonymous query keyword"
    (let
      ((doc (gql-parse "query { user { name } }")))
      (let
        ((op (nth doc 1)))
        (assert= (quote gql-query) (first op))
        (assert= nil (gql-op-name op))))))

;; ── Parser: arguments ─────────────────────────────────────────────

(defsuite
  "gql-parse-arguments"
  (deftest
    "parses integer argument"
    (let
      ((doc (gql-parse "{ user(id: 42) { name } }")))
      (let
        ((field (first (gql-op-selections (nth doc 1)))))
        (assert= 1 (length (gql-field-args field)))
        (assert= "id" (first (first (gql-field-args field))))
        (assert= 42 (nth (first (gql-field-args field)) 1)))))
  (deftest
    "parses string argument"
    (let
      ((doc (gql-parse "{ post(slug: \"hello-world\") { title } }")))
      (let
        ((field (first (gql-op-selections (nth doc 1)))))
        (assert= "slug" (first (first (gql-field-args field))))
        (assert= "hello-world" (nth (first (gql-field-args field)) 1)))))
  (deftest
    "parses boolean arguments"
    (let
      ((doc (gql-parse "{ posts(published: true) { title } }")))
      (let
        ((field (first (gql-op-selections (nth doc 1)))))
        (assert= true (nth (first (gql-field-args field)) 1)))))
  (deftest
    "parses null argument"
    (let
      ((doc (gql-parse "{ posts(cursor: null) { title } }")))
      (let
        ((field (first (gql-op-selections (nth doc 1)))))
        (assert= nil (nth (first (gql-field-args field)) 1)))))
  (deftest
    "parses multiple arguments"
    (let
      ((doc (gql-parse "{ posts(limit: 10, offset: 20) { title } }")))
      (let
        ((field (first (gql-op-selections (nth doc 1)))))
        (assert= 2 (length (gql-field-args field)))
        (assert= "limit" (first (first (gql-field-args field))))
        (assert= 10 (nth (first (gql-field-args field)) 1))
        (assert= "offset" (first (nth (gql-field-args field) 1)))
        (assert= 20 (nth (nth (gql-field-args field) 1) 1)))))
  (deftest
    "parses enum argument"
    (let
      ((doc (gql-parse "{ posts(status: PUBLISHED) { title } }")))
      (let
        ((field (first (gql-op-selections (nth doc 1)))))
        (assert= "PUBLISHED" (nth (first (gql-field-args field)) 1)))))
  (deftest
    "parses list argument"
    (let
      ((doc (gql-parse "{ posts(ids: [1, 2, 3]) { title } }")))
      (let
        ((field (first (gql-op-selections (nth doc 1)))))
        (let
          ((val (nth (first (gql-field-args field)) 1)))
          (assert= 3 (length val))
          (assert= 1 (first val))
          (assert= 3 (nth val 2))))))
  (deftest
    "parses object argument"
    (let
      ((doc (gql-parse "{ createPost(input: {title: \"Hi\", draft: true}) { id } }")))
      (let
        ((field (first (gql-op-selections (nth doc 1)))))
        (let
          ((val (nth (first (gql-field-args field)) 1)))
          (assert= "Hi" (get val :title))
          (assert= true (get val :draft)))))))

;; ── Parser: variables ─────────────────────────────────────────────

(defsuite
  "gql-parse-variables"
  (deftest
    "parses variable definitions"
    (let
      ((doc (gql-parse "query GetUser($id: ID!) { user(id: $id) { name } }")))
      (let
        ((op (nth doc 1)))
        (assert= 1 (length (gql-op-vars op)))
        (let
          ((vdef (first (gql-op-vars op))))
          (assert= (quote gql-var-def) (first vdef))
          (assert= "id" (nth vdef 1))
          (assert= (quote gql-non-null) (first (nth vdef 2)))))))
  (deftest
    "parses variable reference in arguments"
    (let
      ((doc (gql-parse "query($id: Int) { user(id: $id) { name } }")))
      (let
        ((field (first (gql-op-selections (nth doc 1)))))
        (let
          ((arg-val (nth (first (gql-field-args field)) 1)))
          (assert= (quote gql-var) (first arg-val))
          (assert= "id" (nth arg-val 1))))))
  (deftest
    "parses multiple variable definitions"
    (let
      ((doc (gql-parse "query($limit: Int = 10, $offset: Int) { posts { title } }")))
      (let
        ((vars (gql-op-vars (nth doc 1))))
        (assert= 2 (length vars))
        (assert= "limit" (nth (first vars) 1))
        (assert= 10 (nth (first vars) 3))
        (assert= "offset" (nth (nth vars 1) 1))
        (assert= nil (nth (nth vars 1) 3)))))
  (deftest
    "parses list type in variable"
    (let
      ((doc (gql-parse "query($ids: [Int!]!) { posts(ids: $ids) { title } }")))
      (let
        ((vdef (first (gql-op-vars (nth doc 1)))))
        (let
          ((typ (nth vdef 2)))
          (assert= (quote gql-non-null) (first typ))
          (assert= (quote gql-list-type) (first (nth typ 1))))))))

;; ── Parser: fragments ─────────────────────────────────────────────

(defsuite
  "gql-parse-fragments"
  (deftest
    "parses fragment definition"
    (let
      ((doc (gql-parse "fragment UserFields on User { name email }")))
      (let
        ((frag (nth doc 1)))
        (assert= (quote gql-fragment) (first frag))
        (assert= "UserFields" (gql-frag-name frag))
        (assert= "User" (gql-frag-type frag))
        (assert= 2 (length (gql-frag-selections frag))))))
  (deftest
    "parses fragment spread"
    (let
      ((doc (gql-parse "{ user { ...UserFields } }")))
      (let
        ((field (first (gql-op-selections (nth doc 1)))))
        (let
          ((spread (first (gql-field-selections field))))
          (assert= (quote gql-fragment-spread) (first spread))
          (assert= "UserFields" (nth spread 1))))))
  (deftest
    "parses inline fragment"
    (let
      ((doc (gql-parse "{ node { ... on User { name } ... on Post { title } } }")))
      (let
        ((sels (gql-field-selections (first (gql-op-selections (nth doc 1))))))
        (assert= 2 (length sels))
        (assert= (quote gql-inline-fragment) (first (first sels)))
        (assert= "User" (nth (first sels) 1))
        (assert= "Post" (nth (nth sels 1) 1)))))
  (deftest
    "parses multi-definition document"
    (let
      ((doc (gql-parse "query GetUser { user { ...UserFields } }  fragment UserFields on User { name email }")))
      (assert= 3 (length doc))
      (assert= (quote gql-query) (first (nth doc 1)))
      (assert= (quote gql-fragment) (first (nth doc 2))))))

;; ── Parser: directives ────────────────────────────────────────────

(defsuite
  "gql-parse-directives"
  (deftest
    "parses field directive"
    (let
      ((doc (gql-parse "{ user { name @uppercase } }")))
      (let
        ((field (first (gql-field-selections (first (gql-op-selections (nth doc 1)))))))
        (assert= 1 (length (gql-field-directives field)))
        (let
          ((dir (first (gql-field-directives field))))
          (assert= (quote gql-directive) (first dir))
          (assert= "uppercase" (nth dir 1))))))
  (deftest
    "parses directive with arguments"
    (let
      ((doc (gql-parse "{ user { avatar @resize(width: 100, height: 100) } }")))
      (let
        ((field (first (gql-field-selections (first (gql-op-selections (nth doc 1)))))))
        (let
          ((dir (first (gql-field-directives field))))
          (assert= "resize" (nth dir 1))
          (assert= 2 (length (nth dir 2))))))))

;; ── Parser: aliases ───────────────────────────────────────────────

(defsuite
  "gql-parse-aliases"
  (deftest
    "parses field alias"
    (let
      ((doc (gql-parse "{ smallPic: profilePic(size: 64) { url } }")))
      (let
        ((field (first (gql-op-selections (nth doc 1)))))
        (assert= "profilePic" (gql-field-name field))
        (assert= "smallPic" (gql-field-alias field)))))
  (deftest
    "parses multiple aliased fields"
    (let
      ((doc (gql-parse "{ small: pic(size: 64) { url } large: pic(size: 256) { url } }")))
      (let
        ((sels (gql-op-selections (nth doc 1))))
        (assert= 2 (length sels))
        (assert= "small" (gql-field-alias (first sels)))
        (assert= "large" (gql-field-alias (nth sels 1)))
        (assert= "pic" (gql-field-name (first sels)))
        (assert= "pic" (gql-field-name (nth sels 1)))))))

;; ── Serializer ────────────────────────────────────────────────────

(defsuite
  "gql-serialize"
  (deftest
    "round-trips shorthand query"
    (let
      ((src "query { user { name email } }"))
      (let
        ((doc (gql-parse src)) (out (gql-serialize doc)))
        (let
          ((doc2 (gql-parse out)))
          (assert= (quote gql-query) (first (nth doc2 1)))
          (let
            ((sels (gql-op-selections (nth doc2 1))))
            (assert= 1 (length sels))
            (assert= "user" (gql-field-name (first sels))))))))
  (deftest
    "serializes mutation"
    (let
      ((doc (gql-parse "mutation { createPost(title: \"Hi\") { id } }")))
      (let
        ((out (gql-serialize doc)))
        (assert (string-contains? out "mutation"))
        (assert (string-contains? out "createPost"))))))

;; ── Executor: projection ──────────────────────────────────────────

(defsuite
  "gql-exec-projection"
  (deftest
    "projects scalar fields"
    (let
      ((data {:internal-id 999 :email "alice@test.com" :age 30 :name "Alice"})
        (sels
          (list
            (list (quote gql-field) "name" (list) (list) (list))
            (list (quote gql-field) "email" (list) (list) (list)))))
      (let
        ((result (gql-project data sels {})))
        (assert= "Alice" (get result :name))
        (assert= "alice@test.com" (get result :email))
        (assert= nil (get result :age))
        (assert= nil (get result :internal-id)))))
  (deftest
    "projects nested fields"
    (let
      ((data {:user {:secret "hidden" :email "bob@test.com" :name "Bob"}})
        (sels
          (list
            (list
              (quote gql-field)
              "user"
              (list)
              (list)
              (list (list (quote gql-field) "name" (list) (list) (list)))))))
      (let
        ((result (gql-project data sels {})))
        (assert= "Bob" (get (get result :user) :name))
        (assert= nil (get (get result :user) :email)))))
  (deftest
    "projects over list results"
    (let
      ((data (list {:x 1 :name "A"} {:x 2 :name "B"}))
        (sels
          (list (list (quote gql-field) "name" (list) (list) (list)))))
      (let
        ((result (gql-project data sels {})))
        (assert= 2 (length result))
        (assert= "A" (get (first result) :name))
        (assert= nil (get (first result) :x)))))
  (deftest
    "handles nil data"
    (assert= nil (gql-project nil (list) {})))
  (deftest
    "empty selections returns full data"
    (let
      ((data {:age 30 :name "Alice"}))
      (assert= data (gql-project data (list) {})))))

;; ── Executor: full execution ──────────────────────────────────────

(defsuite
  "gql-exec-execute"
  (deftest
    "executes with custom resolver"
    (let
      ((doc (gql-parse "{ user(id: 1) { name email } }"))
        (resolver
          (fn
            (field-name args op-type)
            (if (= field-name "user") {:email "alice@test.com" :age 30 :name "Alice"} nil))))
      (let
        ((result (gql-execute doc {} resolver)))
        (assert= "Alice" (get (get (get result :data) :user) :name))
        (assert=
          "alice@test.com"
          (get (get (get result :data) :user) :email))
        (assert= nil (get (get (get result :data) :user) :age)))))
  (deftest
    "passes arguments to resolver"
    (let
      ((doc (gql-parse "{ post(slug: \"hello\") { title } }"))
        (resolver
          (fn
            (field-name args op-type)
            (if
              (and (= field-name "post") (= (get args :slug) "hello"))
              {:title "Hello World"}
              {:title "Not Found"}))))
      (let
        ((result (gql-execute doc {} resolver)))
        (assert= "Hello World" (get (get (get result :data) :post) :title)))))
  (deftest
    "reports operation type to resolver"
    (let
      ((doc (gql-parse "mutation { deletePost(id: 1) { success } }"))
        (seen-type nil)
        (resolver
          (fn (field-name args op-type) (set! seen-type op-type) {:success true})))
      (gql-execute doc {} resolver)
      (assert= (quote gql-mutation) seen-type)))
  (deftest
    "substitutes variables"
    (let
      ((doc (gql-parse "query($id: Int) { user(id: $id) { name } }"))
        (resolver (fn (field-name args op-type) {:name (str "User-" (get args :id))})))
      (let
        ((result (gql-execute doc {:id 42} resolver)))
        (assert= "User-42" (get (get (get result :data) :user) :name)))))
  (deftest
    "handles multiple root fields"
    (let
      ((doc (gql-parse "{ user { name } settings { theme } }"))
        (resolver
          (fn
            (field-name args op-type)
            (cond
              ((= field-name "user") {:name "Alice"})
              ((= field-name "settings") {:theme "dark"})
              (true nil)))))
      (let
        ((result (gql-execute doc {} resolver)))
        (assert= "Alice" (get (get (get result :data) :user) :name))
        (assert= "dark" (get (get (get result :data) :settings) :theme)))))
  (deftest
    "handles aliased fields"
    (let
      ((doc (gql-parse "{ me: user(id: 1) { name } them: user(id: 2) { name } }"))
        (resolver (fn (field-name args op-type) {:name (str "User-" (get args :id))})))
      (let
        ((result (gql-execute doc {} resolver)))
        (assert= "User-1" (get (get (get result :data) :me) :name))
        (assert= "User-2" (get (get (get result :data) :them) :name)))))
  (deftest
    "returns error when no operation"
    (let
      ((doc (gql-parse "fragment F on User { name }")))
      (let
        ((result (gql-execute doc {} (fn (f a t) nil))))
        (assert (not (nil? (get result :errors))))))))

;; ── Executor: fragments ───────────────────────────────────────────

(defsuite
  "gql-exec-fragments"
  (deftest
    "resolves fragment spread in projection"
    (let
      ((fragments {:UserFields (list (quote gql-fragment) "UserFields" "User" (list) (list (list (quote gql-field) "name" (list) (list) (list)) (list (quote gql-field) "email" (list) (list) (list))))})
        (data {:email "a@test.com" :age 30 :name "Alice"})
        (sels
          (list (list (quote gql-fragment-spread) "UserFields" (list)))))
      (let
        ((result (gql-project data sels fragments)))
        (assert= "Alice" (get result :name))
        (assert= "a@test.com" (get result :email))
        (assert= nil (get result :age)))))
  (deftest
    "collects fragments from document"
    (let
      ((doc (gql-parse "query { user { ...F } }  fragment F on User { name }")))
      (let
        ((frags (gql-collect-fragments doc)))
        (assert (not (nil? (get frags :F))))
        (assert= "User" (gql-frag-type (get frags :F)))))))

;; ── Executor: introspection helpers ───────────────────────────────

(defsuite
  "gql-exec-introspection"
  (deftest
    "extracts operation names"
    (let
      ((doc (gql-parse "query GetUser { user { name } }  mutation CreatePost { create { id } }")))
      (let
        ((names (gql-operation-names doc)))
        (assert= 2 (length names))
        (assert (some (fn (n) (= n "GetUser")) names))
        (assert (some (fn (n) (= n "CreatePost")) names)))))
  (deftest
    "extracts variable definitions"
    (let
      ((doc (gql-parse "query($id: ID!, $name: String) { user { name } }")))
      (let
        ((vars (gql-extract-variables doc)))
        (assert= 2 (length vars))
        (assert= "id" (nth (first vars) 1))
        (assert= "name" (nth (nth vars 1) 1))))))

;; ── Hyperscript integration: parser ───────────────────────────────

(defsuite
  "hs-fetch-gql-parse"
  (deftest
    "fetch gql with braces parses to fetch-gql AST"
    (let
      ((ast (hs-compile "fetch gql { user { name } }")))
      (assert= (quote fetch-gql) (first ast))
      (assert (string-contains? (nth ast 1) "user"))
      (assert (string-contains? (nth ast 1) "name"))
      (assert= nil (nth ast 2))))
  (deftest
    "fetch gql with from URL"
    (let
      ((ast (hs-compile "fetch gql { posts { title } } from \"/api/graphql\"")))
      (assert= (quote fetch-gql) (first ast))
      (assert (string-contains? (nth ast 1) "posts"))
      (assert= "/api/graphql" (nth ast 2))))
  (deftest
    "fetch gql query keyword"
    (let
      ((ast (hs-compile "fetch gql query { user { name } }")))
      (assert= (quote fetch-gql) (first ast))
      (assert (string-contains? (nth ast 1) "query"))
      (assert (string-contains? (nth ast 1) "user"))))
  (deftest
    "fetch gql mutation keyword"
    (let
      ((ast (hs-compile "fetch gql mutation { createPost { id } }")))
      (assert= (quote fetch-gql) (first ast))
      (assert (string-contains? (nth ast 1) "mutation"))
      (assert (string-contains? (nth ast 1) "createPost"))))
  (deftest
    "fetch gql with nested braces"
    (let
      ((ast (hs-compile "fetch gql { user(id: 1) { posts { title } } }")))
      (assert= (quote fetch-gql) (first ast))
      (assert (string-contains? (nth ast 1) "posts"))
      (assert (string-contains? (nth ast 1) "title"))))
  (deftest
    "regular fetch still works"
    (let
      ((ast (hs-compile "fetch \"/api/data\" as json")))
      (assert= (quote fetch) (first ast))
      (assert= "/api/data" (nth ast 1))
      (assert= "json" (nth ast 2)))))

;; ── Hyperscript integration: compiler ─────────────────────────────

(defsuite
  "hs-fetch-gql-compile"
  (deftest
    "compiles fetch gql to hs-fetch-gql call"
    (let
      ((sx (hs-to-sx-from-source "fetch gql { user { name } }")))
      (assert= (quote hs-fetch-gql) (first sx))
      (assert (string? (nth sx 1)))
      (assert= nil (nth sx 2))))
  (deftest
    "compiles fetch gql with from URL"
    (let
      ((sx (hs-to-sx-from-source "fetch gql { posts { title } } from \"/api\"")))
      (assert= (quote hs-fetch-gql) (first sx))
      (assert= "/api" (nth sx 2)))))
