<|start_filename|>test/unit/hologram/template/transformers/component_transformer_test.exs<|end_filename|>
defmodule Hologram.Template.ComponentTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.Context

  alias Hologram.Compiler.IR.{
    AdditionOperator,
    AliasDirective,
    IntegerType,
    ModuleAttributeOperator,
    ModuleDefinition,
    TupleType
  }

  alias Hologram.Template.ComponentTransformer
  alias Hologram.Template.VDOM.{Component, Expression, TextNode}

  @attrs []
  @children :children_stub
  @context %Context{}
  @module Hologram.Test.Fixtures.PlaceholderComponent
  @module_name "Hologram.Test.Fixtures.PlaceholderComponent"

  test "non-aliased component" do
    result = ComponentTransformer.transform(@module_name, @attrs, @children, @context)

    assert %ModuleDefinition{} = result.module_def

    expected = %Component{
      module: @module,
      module_def: result.module_def,
      props: %{},
      children: @children
    }

    assert result == expected
  end

  test "aliased component" do
    module_name = "Bcd"

    context = %Context{
      aliases: [%AliasDirective{module: @module, as: [:Bcd]}]
    }

    result = ComponentTransformer.transform(module_name, @attrs, @children, context)

    assert %ModuleDefinition{} = result.module_def

    expected = %Component{
      children: @children,
      module: @module,
      module_def: result.module_def,
      props: %{}
    }

    assert result == expected
  end

  test "props" do
    props = [
      {:literal, "attr_1", "text"},
      {:expression, "attr_2", "1 + 2"},
      {:literal, "attr_3", "abc{@k}xyz"}
    ]

    result = ComponentTransformer.transform(@module_name, props, @children, @context)

    assert %ModuleDefinition{} = result.module_def

    expected = %Component{
      children: @children,
      module: @module,
      module_def: result.module_def,
      props: %{
        attr_1: [%TextNode{content: "text"}],
        attr_2: [
          %Expression{
            ir: %TupleType{
              data: [
                %AdditionOperator{
                  left: %IntegerType{value: 1},
                  right: %IntegerType{value: 2}
                }
              ]
            }
          }
        ],
        attr_3: [
          %TextNode{content: "abc"},
          %Expression{
            ir: %TupleType{
              data: [%ModuleAttributeOperator{name: :k}]
            }
          },
          %TextNode{content: "xyz"}
        ]
      }
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_21.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module21 do
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module22

  def test_21a do
    Module22.test_22a()
  end
end


<|start_filename|>lib/hologram/runtime/controller.ex<|end_filename|>
# DEFER: refactor & test
defmodule Hologram.Runtime.Controller do
  use Phoenix.Controller

  alias Hologram.Conn
  alias Hologram.Template.Renderer

  def index(phx_conn, params) do
    module = phx_conn.private.hologram_page
    holo_conn = %Conn{params: params}
    bindings = %{}

    output = Renderer.render(module, holo_conn, bindings)
    html(phx_conn, output)
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_61.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module61 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module62

  route "/test-route-61"

  def template do
    ~H"""
    """
  end

  def action do
    Module62.test_fun_62a()
  end
end


<|start_filename|>test/unit/fixtures/template/renderers/component_renderer/module_3.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.ComponentRenderer.Module3 do
  use Hologram.Component

  def init(props) do
    %{
      test_state: props.test_prop
    }
  end

  def template do
    ~H"""
    abc.{@test_state}.xyz
    """
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/page_5.ex<|end_filename|>
defmodule HologramE2E.Page5 do
  use Hologram.Page

  route "/e2e/page-5"

  def init(_params, _conn) do
    %{
      text: ""
    }
  end

  def template do
    ~H"""
    <h1>Page 5</h1>

    <button id="page-5-update-text-button" on:click="update_text">Update text</button>
    <button id="page-5-forward-button">Forward</button>
    <br />

    <div id="page-5-text">{@text}</div><br />

    <a id="page-2-link" href={HologramE2E.Page2.route()} on:click.command={:__redirect__, page: HologramE2E.Page2}>Page 2</a><br />

    <script>
      document.getElementById("page-5-forward-button")
        .addEventListener("click", () => \{ history.forward() \})
    </script>
    """
  end

  def action(:update_text, _params, state) do
    put(state, :text, "text updated by page 5 update button")
  end
end


<|start_filename|>test/unit/hologram/template/evaluators/boolean_type_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.BooleanTypeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.BooleanType
  alias Hologram.Template.Evaluator

  test "evaluate/2" do
    ir = %BooleanType{value: true}

    result = Evaluator.evaluate(ir, %{})
    expected = true

    assert result == expected
  end
end


<|start_filename|>assets/js/hologram/events/click_event.js<|end_filename|>
"use strict";

import Type from "../type"

export default class ClickEvent {
  // TODO: implement & test (return boxed map)
  static buildEventData(_event, _tag) {
    return Type.map()
  }

  // DEFER: test
  // see: https://stackoverflow.com/a/20087506/13040586
  static shouldHandleEvent(event) {
    if (event.ctrlKey || event.metaKey || event.shiftKey || event.button === 1) {
      return false
    }
    
    return true
  }
}

<|start_filename|>lib/hologram/compiler/transformers/struct_type_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.StructTypeTransformer do
  alias Hologram.Compiler.{Context, Resolver, Transformer}
  alias Hologram.Compiler.IR.StructType

  def transform({:%, _, [{_, _, module_segs}, data]}, %Context{} = context) do
    module = Resolver.resolve(module_segs, context)
    data = Transformer.transform(data, context).data

    %StructType{module: module, data: data}
  end
end


<|start_filename|>lib/hologram/template/evaluators/dot_operator.ex<|end_filename|>
alias Hologram.Compiler.IR.DotOperator
alias Hologram.Template.Evaluator

defimpl Evaluator, for: DotOperator do
  def evaluate(%{left: left, right: right}, bindings) do
    left = Evaluator.evaluate(left, bindings)
    right = Evaluator.evaluate(right, bindings)

    Map.get(left, right)
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/float_type.ex<|end_filename|>
alias Hologram.Compiler.IR.FloatType
alias Hologram.Compiler.JSEncoder

defimpl JSEncoder, for: FloatType do
  use Hologram.Commons.Encoder

  def encode(%{value: value}, _, _) do
    encode_primitive_type(:float, "#{value}")
  end
end


<|start_filename|>assets/js/hologram/target.js<|end_filename|>
"use strict";

import Runtime from "./runtime";
import Type from "./type";

export default class Target {
  static get TYPE() {
    return {
      layout: "layout",
      page: "page"
    }
  }

  constructor(id) {
    this.id = id
    this.class = Runtime.getComponentClass(id)
    this.module = Type.module(this.class.name)
  }
}

<|start_filename|>test/unit/hologram/compiler/call_graph_builder/case_expression_test.exs<|end_filename|>
defmodule Hologram.Compiler.CallGraphBuilder.CaseExpressionTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{CallGraph, CallGraphBuilder}
  alias Hologram.Compiler.IR.{Block, CaseExpression, IntegerType, ModuleType}

  alias Hologram.Test.Fixtures.{
    PlaceholderModule1,
    PlaceholderModule2,
    PlaceholderModule3,
    PlaceholderModule4,
    PlaceholderModule5,
    PlaceholderModule6
  }

  @module_defs %{}
  @templates %{}

  setup do
    CallGraph.run()
    :ok
  end

  test "build/4" do
    ir = %CaseExpression{
      clauses: [
        %{
          bindings: [],
          body: %Block{expressions: [
            %ModuleType{module: PlaceholderModule3},
            %ModuleType{module: PlaceholderModule4}
          ]},
          pattern: %IntegerType{value: 1}
        },
        %{
          bindings: [],
          body: %Block{expressions: [
            %ModuleType{module: PlaceholderModule5},
            %ModuleType{module: PlaceholderModule6}
          ]},
          pattern: %IntegerType{value: 2}
        }
      ],
      condition: %ModuleType{module: PlaceholderModule2}
    }

    from_vertex = PlaceholderModule1
    CallGraphBuilder.build(ir, @module_defs, @templates, from_vertex)

    assert CallGraph.has_edge?(PlaceholderModule1, PlaceholderModule2)
    assert CallGraph.has_edge?(PlaceholderModule1, PlaceholderModule3)
    assert CallGraph.has_edge?(PlaceholderModule1, PlaceholderModule4)
    assert CallGraph.has_edge?(PlaceholderModule1, PlaceholderModule5)
    assert CallGraph.has_edge?(PlaceholderModule1, PlaceholderModule6)
  end
end


<|start_filename|>lib/hologram/compiler/map_key_encoders/integer_type.ex<|end_filename|>
alias Hologram.Compiler.IR.IntegerType
alias Hologram.Compiler.MapKeyEncoder

defimpl MapKeyEncoder, for: IntegerType do
  use Hologram.Commons.Encoder

  def encode(%{value: value}, _, _) do
    encode_primitive_key(:integer, value)
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/anonymous_function_call.ex<|end_filename|>
alias Hologram.Compiler.Context
alias Hologram.Compiler.IR.AnonymousFunctionCall
alias Hologram.Compiler.JSEncoder
alias Hologram.Compiler.Opts

defimpl JSEncoder, for: AnonymousFunctionCall do
  use Hologram.Commons.Encoder

  def encode(%{name: name, args: args}, %Context{} = context, %Opts{} = opts) do
    var = encode_identifier(name)
    args = encode_args(args, context, opts)

    "#{var}.callback(#{args})"
  end
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/element_node/module_3.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ElementNode.Module3 do
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/module_type.ex<|end_filename|>
alias Hologram.Compiler.{CallGraph, CallGraphBuilder, Reflection}
alias Hologram.Compiler.IR.ModuleType

defimpl CallGraphBuilder, for: ModuleType do
  def build(%{module: module}, module_defs, templates, from_vertex) do
    unless Reflection.standard_lib?(module) do
      CallGraphBuilder.build(module_defs[module], module_defs, templates, nil)
      CallGraph.add_edge(from_vertex, module)
    end
  end
end


<|start_filename|>lib/hologram/compiler/decoders/tuple_type_decoder.ex<|end_filename|>
defmodule Hologram.Compiler.TupleTypeDecoder do
  alias Hologram.Compiler.Decoder

  def decode(%{"data" => data}) do
    Enum.map(data, &Decoder.decode/1)
    |> List.to_tuple()
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_15.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module15 do
  use Hologram.Page

  route "/test-route-15"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/not_supported_expression.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.NotSupportedExpression
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: NotSupportedExpression do
  require Logger

  def aggregate(ir) do
    """
    Not supported expression (compiler ignored it)
    #{inspect(ir)}
    """
    |> Logger.notice()
  end
end


<|start_filename|>test/unit/hologram/compiler/module_def_aggregators/component_test.exs<|end_filename|>
defmodule Hologram.Compiler.ModuleDefAggregator.ComponentTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{ModuleDefAggregator, ModuleDefStore}
  alias Hologram.Compiler.IR.{ModuleDefinition, ModuleType}
  alias Hologram.Template.VDOM.Component
  alias Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.Component.{Module1, Module2, Module3}

  setup do
    ModuleDefStore.run()
    :ok
  end

  test "aggregation from module body" do
    ir = %Component{module: Module1, props: %{}, children: []}

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(Module1)
    assert %ModuleDefinition{} = ModuleDefStore.get!(Module2)
  end

  test "aggregation from props" do
    ir = %Component{
      module: Module2,
      props: %{
        prop_1: %ModuleType{module: Module3}
      },
      children: []
    }

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(Module2)
    assert %ModuleDefinition{} = ModuleDefStore.get!(Module3)
  end

  test "aggregation from children" do
    ir = %Component{
      module: Module2,
      props: %{},
      children: [%Component{module: Module3}]
    }

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(Module2)
    assert %ModuleDefinition{} = ModuleDefStore.get!(Module3)
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/float_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.FloatType
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: FloatType do
  def aggregate(_), do: nil
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/types/float_page.ex<|end_filename|>
defmodule HologramE2E.Types.FloatPage do
  use Hologram.Page

  route "/e2e/types/float"

  def init(_params, _conn) do
    %{
      result_encoding: 0,
      result_decoding: 0
    }
  end

  def template do
    ~H"""
    <button id="button_test_encoding" on:click="test_encoding">Test encoding</button>
    <button id="button_test_decoding" on:click.command={:test_decoding, client_param: 2.34}>Test decoding</button>
    <div id="text_encoding_result">Result encoding = {@result_encoding}</div>
    <div id="text_decoding_result">Result decoding = {@result_decoding}</div>
    """
  end

  def action(:test_encoding, _params, state) do
    Map.put(state, :result_encoding, 1.23)
  end

  def action(:dipslay_test_decoding_result, params, state) do
    Map.put(state, :result_decoding, params.server_result)
  end

  def command(:test_decoding, params) do
    {:dipslay_test_decoding_result, server_result: params.client_param + 10}
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/operators/unary_positive_page.ex<|end_filename|>
defmodule HologramE2E.Operators.UnaryPositivePage do
  use Hologram.Page

  route "/e2e/operators/unary-positive"

  def init(_params, _conn) do
    %{
      value: 123,
      result: 0
    }
  end

  def template do
    ~H"""
    <button id="button" on:click="calculate">Calculate</button>
    <div id="text">Result = {@result}</div>
    """
  end

  def action(:calculate, _params, state) do
    Map.put(state, :result, +state.value)
  end
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/module_type/module_6.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ModuleType.Module6 do
  use Hologram.Component
  alias Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ModuleType.Module7, warn: false

  def template do
    ~H"""
      <Module7 />
    """
  end
end


<|start_filename|>test/unit/hologram/compiler/module_def_aggregators/module_type_test.exs<|end_filename|>
defmodule Hologram.Compiler.ModuleDefAggregator.ModuleTypeTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{ModuleDefAggregator, ModuleDefStore}
  alias Hologram.Compiler.IR.{ModuleDefinition, ModuleType}
  alias Hologram.Runtime.{Commons, TemplateStore}

  alias Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ModuleType.{
    Module1,
    Module2,
    Module6,
    Module7
  }

  alias Hologram.Test.Fixtures.PlaceholderModule1

  setup do
    [
      app_path: @fixtures_path <> "/compiler/module_def_aggregators/module_type",
      templatables: [Hologram.Test.Fixtures.App.DefaultLayout]
    ]
    |> compile()

    ModuleDefStore.run()
    TemplateStore.run()

    :ok
  end

  test "non standard lib module that isn't in the IR store yet is added" do
    ir = %ModuleType{module: PlaceholderModule1}
    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(PlaceholderModule1)
  end

  test "module that is in the IR store already is ignored" do
    ir = %ModuleType{module: PlaceholderModule1}

    ModuleDefAggregator.aggregate(ir)
    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(PlaceholderModule1)
  end

  test "standard lib module is ignored" do
    ir = %ModuleType{module: Kernel}
    ModuleDefAggregator.aggregate(ir)

    assert ModuleDefStore.get(PlaceholderModule1) == :error
  end

  test "handles ignored modules" do
    ir = %ModuleType{module: Ecto.Changeset}
    ModuleDefAggregator.aggregate(ir)

    assert ModuleDefStore.get(Ecto.Changeset) == :error
  end

  test "module that belongs to a namespace that is in the @ignored_namespaces is ignored" do
    ir = %ModuleType{module: Hologram.Commons.Encoder}
    ModuleDefAggregator.aggregate(ir)

    assert ModuleDefStore.get(Hologram.Commons.Encoder) == :error
  end

  test "module functions are traversed" do
    ir = %ModuleType{module: Module1}
    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(Module1)
    assert %ModuleDefinition{} = ModuleDefStore.get!(Module2)
  end

  test "page default layout is added" do
    page = Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ModuleType.Module3
    layout = Hologram.Test.Fixtures.App.DefaultLayout
    ir = %ModuleType{module: page}

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(layout)
  end

  test "page custom layout is added" do
    page = Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ModuleType.Module4
    layout = Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ModuleType.Module5
    ir = %ModuleType{module: page}

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(layout)
  end

  test "aggregation from templetable module's template" do
    ir = %ModuleType{module: Module6}

    ModuleDefAggregator.aggregate(ir)

    # Hologram.Runtime.Commons module is added because templates use Hologram.Runtime.Commons.sigil_H/2
    assert %ModuleDefinition{} = ModuleDefStore.get!(Commons)
    assert %ModuleDefinition{} = ModuleDefStore.get!(Module6)
    assert %ModuleDefinition{} = ModuleDefStore.get!(Module7)
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/variable.ex<|end_filename|>
alias Hologram.Compiler.{Context, JSEncoder, Opts}
alias Hologram.Compiler.IR.Variable

defimpl JSEncoder, for: Variable do
  def encode(_, %Context{}, %Opts{placeholder: true}) do
    "{ type: 'placeholder' }"
  end

  def encode(%Variable{name: name}, %Context{}, %Opts{}) do
    "#{name}"
  end
end


<|start_filename|>test/unit/hologram/compiler/pattern_deconstructors/fallback_test.exs<|end_filename|>
defmodule Hologram.Compiler.PatternDeconstructor.FallbackTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.IR.IntegerType
  alias Hologram.Compiler.PatternDeconstructor

  test "deconstruct/2" do
    ir = %IntegerType{value: 1}

    assert PatternDeconstructor.deconstruct(ir) == []
  end
end


<|start_filename|>lib/hologram/compiler/transformers/block_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.BlockTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.Block

  def transform({:__block__, _, ast}, %Context{} = context) do
    ir = Enum.map(ast, &Transformer.transform(&1, context))
    %Block{expressions: ir}
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/binary_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.BinaryType

defimpl CallGraphBuilder, for: BinaryType do
  def build(_, _, _, _), do: nil
end


<|start_filename|>assets/js/hologram/elixir/keyword.js<|end_filename|>
"use strict";

import Type from "../type";
import Utils from "../utils";

export default class Keyword {
  static delete(keywords, key) {
    const newElems = keywords.data.filter((elem) => {
      return !Utils.isEqual(elem.data[0], key)
    })

    return Utils.freeze(Type.list(newElems))
  }

  static get(keywords, key, default_value = Type.nil()) {
    if (Keyword.has_key$question(keywords, key).value) {
      const matchingItems = keywords.data.filter(item =>
        item.data[0].value === key.value
      )

      if (matchingItems.length > 0) {
        return matchingItems[0].data[1]
      } else {
        return default_value
      }
      
    } else {
      return default_value
    }
  }

  static has_key$question(keywords, key) {
    if (keywords.data.find(el => Utils.isEqual(el.data[0], key))) {
      return Type.boolean(true)
    } else {
      return Type.boolean(false)
    }
  }

  static put(keywords, key, value) {
    const keywordsWithKeyDeleted = Keyword.delete(keywords, key)
    const newElems = Utils.clone(keywordsWithKeyDeleted.data)
    newElems.unshift(Type.tuple([key, value]))

    return Type.list(newElems)
  }
}

<|start_filename|>lib/hologram/compiler/call_graph_builder/module_definition.ex<|end_filename|>
alias Hologram.Compiler.{CallGraph, CallGraphBuilder}
alias Hologram.Compiler.IR.ModuleDefinition

defimpl CallGraphBuilder, for: ModuleDefinition do
  def build(%{module: module, functions: functions}, module_defs, templates, _) do
    unless CallGraph.has_vertex?(module) do
      CallGraph.add_vertex(module)
      CallGraphBuilder.build(functions, module_defs, templates, module)
      build_from_template(module, module_defs, templates)
    end
  end

  defp build_from_template(module, module_defs, templates) do
    if module_defs[module].templatable? do
      CallGraph.add_edge(module, {module, :template})

      templates[module]
      |> CallGraphBuilder.build(module_defs, templates, {module, :template})
    end
  end
end


<|start_filename|>test/e2e/config/test.exs<|end_filename|>
import Config

config :hologram_e2e, HologramE2EWeb.Endpoint,
  http: [ip: {127, 0, 0, 1}, port: 4002],
  server: true

config :logger, level: :warn

config :phoenix, :plug_init_mode, :runtime

config :wallaby,
  otp_app: :hologram_e2e,
  driver: Wallaby.Chrome,
  screenshot_dir: "./tmp/screenshots"


<|start_filename|>lib/hologram/compiler/module_def_aggregators/case_expression.ex<|end_filename|>
alias Hologram.Compiler.IR.CaseExpression
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: CaseExpression do
  def aggregate(%{condition: condition, clauses: clauses}) do
    ModuleDefAggregator.aggregate(condition)
    Enum.map(clauses, &ModuleDefAggregator.aggregate(&1.body))
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/list_concatenation_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.ListConcatenationOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, ListConcatenationOperatorTransformer}
  alias Hologram.Compiler.IR.{IntegerType, ListConcatenationOperator, ListType}

  test "transform/3" do
    code = "[1, 2] ++ [3, 4]"
    ast = ast(code)

    result = ListConcatenationOperatorTransformer.transform(ast, %Context{})

    expected = %ListConcatenationOperator{
      left: %ListType{
        data: [
          %IntegerType{value: 1},
          %IntegerType{value: 2}
        ]
      },
      right: %ListType{
        data: [
          %IntegerType{value: 3},
          %IntegerType{value: 4}
        ]
      }
    }

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/boolean_type.ex<|end_filename|>
alias Hologram.Compiler.IR.BooleanType
alias Hologram.Compiler.JSEncoder

defimpl JSEncoder, for: BooleanType do
  use Hologram.Commons.Encoder

  def encode(%{value: value}, _, _) do
    encode_primitive_type(:boolean, "#{value}")
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/list_subtraction_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.ListSubtractionOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, ListSubtractionOperatorTransformer}
  alias Hologram.Compiler.IR.{IntegerType, ListSubtractionOperator, ListType}

  test "transform/3" do
    code = "[1, 2] -- [3, 2]"
    ast = ast(code)

    result = ListSubtractionOperatorTransformer.transform(ast, %Context{})

    expected = %ListSubtractionOperator{
      left: %ListType{
        data: [
          %IntegerType{value: 1},
          %IntegerType{value: 2}
        ]
      },
      right: %ListType{
        data: [
          %IntegerType{value: 3},
          %IntegerType{value: 2}
        ]
      }
    }

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/anonymous_function_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.AnonymousFunctionType

defimpl CallGraphBuilder, for: AnonymousFunctionType do
  def build(%{params: params, body: body}, module_defs, templates, from_vertex) do
    CallGraphBuilder.build(params, module_defs, templates, from_vertex)
    CallGraphBuilder.build(body, module_defs, templates, from_vertex)
  end
end


<|start_filename|>lib/hologram/template/evaluators/addition_operator.ex<|end_filename|>
alias Hologram.Compiler.IR.AdditionOperator
alias Hologram.Template.Evaluator

defimpl Evaluator, for: AdditionOperator do
  def evaluate(%{left: left, right: right}, bindings) do
    Evaluator.evaluate(left, bindings) + Evaluator.evaluate(right, bindings)
  end
end


<|start_filename|>test/unit/fixtures/template/renderers/component_renderer/module_6.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.ComponentRenderer.Module6 do
  use Hologram.Component
  alias Hologram.Test.Fixtures.Template.ComponentRenderer.Module7, warn: false

  def init(_props) do
    %{
      parent_component_state_key: "parent_component_state_value"
    }
  end

  def template do
    ~H"""
    parent_head.<Module7 id="child_component" />.parent_tail
    """
  end
end


<|start_filename|>test/unit/hologram/template/evaluators/text_node_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.TextNodeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Template.Evaluator
  alias Hologram.Template.VDOM.TextNode

  test "evaluate/2" do
    node = %TextNode{content: "abc"}

    result = Evaluator.evaluate(node, %{})
    expected = "abc"

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/if_expression.ex<|end_filename|>
alias Hologram.Compiler.IR.IfExpression
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: IfExpression do
  def aggregate(%{condition: condition, do: do_clause, else: else_clause}) do
    ModuleDefAggregator.aggregate(condition)
    ModuleDefAggregator.aggregate(do_clause)
    ModuleDefAggregator.aggregate(else_clause)
  end
end


<|start_filename|>test/unit/fixtures/runtime/channel/module_8.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Runtime.Channel.Module8 do
  use Hologram.Layout

  def init(_conn), do: %{}

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/unit/hologram/template/evaluators/node_list_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.NodeListTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.{IntegerType, TupleType}
  alias Hologram.Template.Evaluator
  alias Hologram.Template.VDOM.{Expression, TextNode}

  describe "evaluate/2" do
    test "no nodes" do
      assert Evaluator.evaluate([], %{}) == ""
    end

    test "single node" do
      nodes = [
        %Expression{
          ir: %TupleType{
            data: [
              %IntegerType{value: 9}
            ]
          }
        }
      ]

      result = Evaluator.evaluate(nodes, %{})
      expected = "9"

      assert result == expected
    end

    test "multiple nodes" do
      nodes = [
        %TextNode{content: "abc"},
        %Expression{
          ir: %TupleType{
            data: [
              %IntegerType{value: 1}
            ]
          }
        },
        %TextNode{content: "kmn"},
        %Expression{
          ir: %TupleType{
            data: [
              %IntegerType{value: 9}
            ]
          }
        },
        %TextNode{content: "xyz"}
      ]

      result = Evaluator.evaluate(nodes, %{})
      expected = "abc1kmn9xyz"

      assert result == expected
    end
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_58.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module58 do
  def test_fun_58a do
    :ok
  end

  def test_fun_58b do
    :ok
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_11.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module11 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module12, warn: false

  route "/test-route-11"

  def template do
    ~H"""
      <Module12 />
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/transformers/struct_type_transformer/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.StructTypeTransformer.Module1 do
  defstruct a: nil
end


<|start_filename|>lib/hologram/template/renderers/text_node.ex<|end_filename|>
alias Hologram.Template.VDOM.TextNode
alias Hologram.Template.Renderer

defimpl Renderer, for: TextNode do
  def render(%{content: content}, _conn, _bindings, _slots) do
    html =
      content
      |> String.replace("\\{", "{")
      |> String.replace("\\}", "}")

    {html, %{}}
  end
end


<|start_filename|>test/unit/fixtures/runtime/page/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Runtime.Page.Module1 do
  use Hologram.Page

  route "/test-route-1"
end


<|start_filename|>test/unit/hologram/template/renderers/component_test.exs<|end_filename|>
defmodule Hologram.Template.Renderer.ComponentTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.IR.AdditionOperator
  alias Hologram.Compiler.IR.IntegerType
  alias Hologram.Compiler.IR.ModuleAttributeOperator
  alias Hologram.Compiler.IR.TupleType
  alias Hologram.Conn
  alias Hologram.Runtime
  alias Hologram.Template.Renderer
  alias Hologram.Template.VDOM.Component
  alias Hologram.Template.VDOM.Expression
  alias Hologram.Template.VDOM.TextNode
  alias Hologram.Test.Fixtures.Template.ComponentRenderer.Module1
  alias Hologram.Test.Fixtures.Template.ComponentRenderer.Module2
  alias Hologram.Test.Fixtures.Template.ComponentRenderer.Module3
  alias Hologram.Test.Fixtures.Template.ComponentRenderer.Module4
  alias Hologram.Test.Fixtures.Template.ComponentRenderer.Module5
  alias Hologram.Test.Fixtures.Template.ComponentRenderer.Module6
  alias Hologram.Test.Fixtures.Template.ComponentRenderer.Module8

  @bindings %{__context__: %{}, test_outer_binding: 123}
  @conn %Conn{}

  setup do
    [app_path: "#{@fixtures_path}/template/renderers/component_renderer"]
    |> compile()

    Runtime.run()

    :ok
  end

  describe "props" do
    test "text prop" do
      component = %Component{
        module: Module1,
        props: %{
          test_prop: [%TextNode{content: "test_prop_value"}]
        }
      }

      result = Renderer.render(component, @conn, @bindings)
      expected = {"abc.test_prop_value.xyz", %{}}

      assert result == expected
    end

    test "expression prop" do
      component = %Component{
        module: Module1,
        props: %{
          test_prop: [
            %Expression{
              ir: %TupleType{
                data: [
                  %AdditionOperator{
                    left: %IntegerType{value: 10},
                    right: %ModuleAttributeOperator{name: :test_outer_binding}
                  }
                ]
              }
            }
          ]
        }
      }

      result = Renderer.render(component, @conn, @bindings)
      expected = {"abc.133.xyz", %{}}

      assert result == expected
    end

    test "multiple parts prop" do
      component = %Component{
        module: Module1,
        props: %{
          test_prop: [
            %TextNode{content: "test_prop_value"},
            %Expression{
              ir: %TupleType{
                data: [
                  %AdditionOperator{
                    left: %IntegerType{value: 10},
                    right: %ModuleAttributeOperator{name: :test_outer_binding}
                  }
                ]
              }
            }
          ]
        }
      }

      result = Renderer.render(component, @conn, @bindings)
      expected = {"abc.test_prop_value133.xyz", %{}}

      assert result == expected
    end
  end

  describe "bindings" do
    test "bindings are combined from props and initial state" do
      component = %Component{
        module: Module2,
        props: %{
          id: [
            %TextNode{content: "component_2"},
          ],
          test_prop: [
            %TextNode{content: "test_prop_value"},
          ]
        }
      }

      result = Renderer.render(component, @conn, @bindings)
      expected = {"1.test_prop_value", %{component_2: %{test_state: 1}}}

      assert result == expected
    end

    test "initial state has precendence over props in bindings" do
      component = %Component{
        module: Module2,
        props: %{
          id: [
            %TextNode{content: "component_2"},
          ],
          test_state: [
            %TextNode{content: "try_to_override_test_state"}
          ],
          test_prop: [
            %TextNode{content: "test_prop_value"},
          ]
        }
      }

      result = Renderer.render(component, @conn, @bindings)
      expected = {"1.test_prop_value", %{component_2: %{test_state: 1}}}

      assert result == expected
    end
  end

  describe "state initialization" do
    test "init/1" do
      component = %Component{
        module: Module3,
        props: %{
          id: [
            %TextNode{content: "component_3"},
          ],
          test_prop: [
            %TextNode{content: "test_prop_value"},
          ]
        }
      }

      result = Renderer.render(component, @conn, @bindings)
      expected = {"abc.test_prop_value.xyz", %{component_3: %{test_state: "test_prop_value"}}}

      assert result == expected
    end

    test "init/2" do
      component = %Component{
        module: Module4,
        props: %{
          id: [
            %TextNode{content: "component_3"},
          ],
          test_prop: [
            %TextNode{content: "test_prop_value"},
          ]
        }
      }

      conn = %Conn{
        session: %{
          test_session_key: "test_session_value"
        }
      }

      result = Renderer.render(component, conn, @bindings)

      expected = {
        "test_prop_value.test_session_value",
        %{
          component_3: %{
            test_state_1: "test_prop_value",
            test_state_2: "test_session_value"
          }
        }
      }

      assert result == expected
    end

    test "nested state" do
      component = %Component{
        module: Module6,
        props: %{
          id: [
            %TextNode{content: "parent_component"},
          ],
        }
      }

      result = Renderer.render(component, @conn, @bindings)

      expected_html = "parent_head.child.parent_tail"

      expected_state = %{
        child_component: %{child_component_state_key: "child_component_state_value"},
        parent_component: %{parent_component_state_key: "parent_component_state_value"}
      }

      expected = {expected_html, expected_state}

      assert result == expected
    end
  end

  test "default slot" do
    component = %Component{
      module: Module5,
      children: [
        %TextNode{content: "abc."},
        %Expression{
          ir: %TupleType{
            data: [
              %AdditionOperator{
                left: %IntegerType{value: 10},
                right: %ModuleAttributeOperator{name: :test_outer_binding}
              }
            ]
          }
        },
        %TextNode{content: ".xyz"},
      ]
    }

    result = Renderer.render(component, @conn, @bindings)
    expected = {"<span>abc.133.xyz</span>", %{}}

    assert result == expected
  end

  test "context passing" do
    component = %Component{
      module: Module8,
      props: %{
        id: [
          %TextNode{content: "component_8_id"},
        ],
      }
    }

    context = %{test_context_key: "test_context_value"}
    bindings = Map.put(@bindings, :__context__, context)

    result = Renderer.render(component, @conn, bindings)

    expected_initial_state = %{
      component_8_id: %{component_8_state_key: "component_8_state_value"},
      component_9_id: %{component_9_state_key: "component_9_state_value"}
    }

    expected_html = "abc(in component 9: test_context_value)xyz"
    expected = {expected_html, expected_initial_state}

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/access_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.AccessOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, AccessOperatorTransformer}
  alias Hologram.Compiler.IR.{AccessOperator, AtomType, IntegerType, MapType, Variable}

  test "data is variable" do
    code = "a[:b]"
    ast = ast(code)

    result = AccessOperatorTransformer.transform(ast, %Context{})
    expected = %AccessOperator{data: %Variable{name: :a}, key: %AtomType{value: :b}}

    assert result == expected
  end

  test "data is literal" do
    code = "%{a: 1, b: 2}[:b]"
    ast = ast(code)

    result = AccessOperatorTransformer.transform(ast, %Context{})

    expected = %AccessOperator{
      data: %MapType{
        data: [
          {%AtomType{value: :a}, %IntegerType{value: 1}},
          {%AtomType{value: :b}, %IntegerType{value: 2}}
        ]
      },
      key: %AtomType{value: :b}
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/compiler/builder_test.exs<|end_filename|>
defmodule Hologram.Compiler.BuilderTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{
    Builder,
    CallGraph,
    CallGraphBuilder
  }

  @module Hologram.Test.Fixtures.Compiler.Builder.Module1

  setup do
    opts = [
      app_path: @fixtures_path <> "/compiler/builder",
      templatables: [Hologram.Test.Fixtures.App.DefaultLayout]
    ]

    compile(opts)
  end

  test "build/1", %{module_defs: module_defs, templates: templates} do
    from_vertex = nil

    CallGraph.run()
    CallGraphBuilder.build(@module, module_defs, templates, from_vertex)
    call_graph = CallGraph.get()

    result = Builder.build(@module, module_defs, call_graph)

    assert result =~ ~r/class Elixir_Hologram_Test_Fixtures_Compiler_Builder_Module1/
    assert result =~ ~r/class Elixir_Hologram_Test_Fixtures_Compiler_Builder_Module3/

    refute result =~ ~r/Elixir_Hologram_Test_Fixtures_Compiler_Builder_Module2/
  end
end


<|start_filename|>test/js/type_test.js<|end_filename|>
"use strict";

import { assert, assertFrozen, assertNotFrozen, cleanup } from "./support/commons";
beforeEach(() => cleanup())

import { HologramNotImplementedError } from "../../assets/js/hologram/errors";
import Type from "../../assets/js/hologram/type";

describe("atom()", () => {
  it("returns boxed atom value", () => {
    const expected = {type: "atom", value: "test"}
    const result = Type.atom("test")

    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    const result = Type.atom("test")
    assertFrozen(result)
  })
})

describe("atomKey()", () => {
  it("returns serialized boxed atom value", () => {
    const result = Type.atomKey("test")
    assert.equal(result, "~atom[test]")
  })
})

describe("binary()", () => {
  let elems, result;

  beforeEach(() => {
    elems = [Type.string("abc"), Type.string("xyz")]
    result = Type.binary(elems)
  })

  it("returns boxed binary value", () => {
    const expected = {type: "binary", data: elems}
    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    assertFrozen(result)
  })
})

describe("boolean()", () => {
  it("returns boxed boolean value", () => {
    const expected = {type: "boolean", value: true}
    const result = Type.boolean(true)

    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    const result = Type.boolean(true)
    assertFrozen(result)
  })
})

describe("componentNode()", () => {
  const result = Type.componentNode("test_class_name", "test_props", "test_children")

  it("builds a component node", () => {
    const expected = {type: "component", className: "test_class_name", props: "test_props", children: "test_children"}
    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    assertFrozen(result)
  })
})

describe("consOperatorPattern()", () => {
  let head, tail, result;

  beforeEach(() => {
    head = Type.integer(1)
    tail = Type.list([Type.integer(2), Type.integer(3)])
    
    result = Type.consOperatorPattern(head, tail)
  })

  it("returns boxed cons_operator_pattern value", () => {
    const expected = {type: "cons_operator_pattern", head: head, tail: tail}
    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    assertFrozen(result)
  })
})

describe("decodeKey()", () => {
  it("decodes encoded atom key", () => {
    const boxedValue = Type.atom("test")
    const encodedKey = Type.encodedKey(boxedValue)

    const result = Type.decodeKey(encodedKey)
    assert.deepStrictEqual(result, boxedValue)
  })

  it("decodes encoded string key", () => {
    const boxedValue = Type.string("test")
    const encodedKey = Type.encodedKey(boxedValue)

    const result = Type.decodeKey(encodedKey)
    assert.deepStrictEqual(result, boxedValue)
  })

  it("throws an error for not implemented types", () => {
    const arg = "~invalid[123]"
    const expectedMessage = 'Type.decodeKey(): key = "~invalid[123]"'
    
    assert.throw(() => { Type.decodeKey(arg) }, HologramNotImplementedError, expectedMessage);
  })

  it("returns frozen object", () => {
    const result = Type.decodeKey(Type.atomKey("test"))
    assertFrozen(result)
  })
})

describe("elementNode()", () => {
  const result = Type.elementNode("div", "test_attrs", "test_children")

  it("builds an element node", () => {
    const expected = {type: "element", tag: "div", attrs: "test_attrs", children: "test_children"}
    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    assertFrozen(result)
  })
})

describe("encodedKey()", () => {
  it("serializes boxed atom value for use as a boxed map key", () => {
    const arg = Type.atom("test")
    const result = Type.encodedKey(arg)

    assert.match(result, /atom/)
  })

  it("serializes boxed string value for use as a boxed map key", () => {
    const arg = Type.string("test")
    const result = Type.encodedKey(arg)

    assert.match(result, /string/)
  })

  it("throws an error for not implemented types", () => {
    const arg = {type: "not implemented", value: "test"}
    const expectedMessage = 'Type.encodedKey(): boxedValue = {"type":"not implemented","value":"test"}'
    
    assert.throw(() => { Type.encodedKey(arg) }, HologramNotImplementedError, expectedMessage);
  })
})

describe("float()", () => {
  it("returns boxed float value", () => {
    const expected = {type: "float", value: 1.0}
    const result = Type.float(1.0)

    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    const result = Type.float(1.0)
    assertFrozen(result)
  })
})

describe("integer()", () => {
  it("returns boxed integer value", () => {
    const expected = {type: "integer", value: 1}
    const result = Type.integer(1)

    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    const result = Type.integer(1)
    assertFrozen(result)
  })
})

describe("isAtom()", () => {
  it("returns true for boxed atom value", () => {
    const arg = Type.atom("test")
    const result = Type.isAtom(arg)

    assert.isTrue(result)
  })

  it("returns false for values of type other than boxed atom", () => {
    const arg = Type.boolean(false)
    const result = Type.isAtom(arg)
    
    assert.isFalse(result)
  })
})

describe("isExpressionNode()", () => {
  it("returns true if the arg is an expression node", () => {
    const arg = Type.expressionNode("test_callback")
    const result = Type.isExpressionNode(arg)

    assert.isTrue(result)
  })

  it("returns false if the arg is not an expression node", () => {
    const arg = Type.textNode("test_content")
    const result = Type.isExpressionNode(arg)
    
    assert.isFalse(result)
  })
})

describe("isFalse()", () => {
  it("returns true for boxed false value", () => {
    const arg = Type.boolean(false)
    const result = Type.isFalse(arg)

    assert.isTrue(result)
  })

  it("returns false for boxed true value", () => {
    const arg = Type.boolean(true)
    const result = Type.isFalse(arg)

    assert.isFalse(result)
  })

  it("returns false for values of type other than boxed boolean", () => {
    const arg = Type.string("false")
    const result = Type.isFalse(arg)
    
    assert.isFalse(result)
  })
})

describe("isFalsy()", () => {
  it("returns true for boxed false value", () => {
    const arg = Type.boolean(false)
    const result = Type.isFalsy(arg)

    assert.isTrue(result)
  })

  it("returns true for boxed nil value", () => {
    const arg = Type.nil()
    const result = Type.isFalsy(arg)
    
    assert.isTrue(result)
  })

  it("returns false for values other than boxed false or boxed nil values", () => {
    const arg = Type.integer(0)
    const result = Type.isFalsy(arg)

    assert.isFalse(result)
  })
})

describe("isList()", () => {
  it("returns true for boxed list value", () => {
    const arg = Type.list()
    const result = Type.isList(arg)

    assert.isTrue(result)
  })

  it("returns false for values other than boxed list value", () => {
    const arg = Type.boolean(false)
    const result = Type.isList(arg)
    
    assert.isFalse(result)
  })
})

describe("isMap()", () => {
  it("returns true for boxed map value", () => {
    const arg = Type.map()
    const result = Type.isMap(arg)

    assert.isTrue(result)
  })

  it("returns false for values other than boxed map value", () => {
    const arg = Type.boolean(false)
    const result = Type.isMap(arg)
    
    assert.isFalse(result)
  })
})

describe("isNil()", () => {
  it("returns true for boxed nil value", () => {
    const arg = Type.nil()
    const result = Type.isNil(arg)

    assert.isTrue(result)
  })

  it("returns false for values other than boxed nil value", () => {
    const arg = Type.boolean(false)
    const result = Type.isNil(arg)
    
    assert.isFalse(result)
  })
})

describe("isNumber()", () => {
  it("returns true for boxed floats", () => {
    const arg = Type.float(1.0)
    const result = Type.isNumber(arg)

    assert.isTrue(result)
  })

  it("returns true for boxed integers", () => {
    const arg = Type.integer(1)
    const result = Type.isNumber(arg)

    assert.isTrue(result)
  })

  it("returns false for boxed types other than float or integer", () => {
    const arg = Type.string("1")
    const result = Type.isNumber(arg)

    assert.isFalse(result)
  })
})

describe("isString()", () => {
  it("returns true for boxed string value", () => {
    const arg = Type.string("test")
    const result = Type.isString(arg)

    assert.isTrue(result)
  })

  it("returns false for values other than boxed string value", () => {
    const arg = Type.boolean(false)
    const result = Type.isString(arg)
    
    assert.isFalse(result)
  })
})

describe("isTrue()", () => {
  it("returns true for boxed true value", () => {
    const arg = Type.boolean(true)
    const result = Type.isTrue(arg)

    assert.isTrue(result)
  })

  it("returns false for boxed false value", () => {
    const arg = Type.boolean(false)
    const result = Type.isTrue(arg)

    assert.isFalse(result)
  })

  it("returns false for values of types other than boxed boolean", () => {
    const arg = Type.string("true")
    const result = Type.isTrue(arg)
    
    assert.isFalse(result)
  })
})

describe("isTruthy()", () => {
  it("returns false for boxed false value", () => {
    const arg = Type.boolean(false)
    const result = Type.isTruthy(arg)

    assert.isFalse(result)
  })

  it("returns false for boxed nil value", () => {
    const arg = Type.nil()
    const result = Type.isTruthy(arg)
    
    assert.isFalse(result)
  })

  it("returns true for values other than boxed false or boxed nil values", () => {
    const arg = Type.integer(0)
    const result = Type.isTruthy(arg)

    assert.isTrue(result)
  })
})

describe("isTuple()", () => {
  it("returns true for boxed tuple value", () => {
    const arg = Type.tuple()
    const result = Type.isTuple(arg)

    assert.isTrue(result)
  })

  it("returns false for values other than boxed tuple value", () => {
    const arg = Type.boolean(false)
    const result = Type.isTuple(arg)
    
    assert.isFalse(result)
  })
})

describe("keywordToMap()", () => {
  it("converts empty boxed keyword list to boxed map", () => {
    const keyword = Type.list()
    const result = Type.keywordToMap(keyword)
    const expected = Type.map()
    
    assert.deepStrictEqual(result, expected) 
  })

  it("converts non-empty boxed keyword list to boxed map ", () => {
    const keywordElems = [
      Type.tuple([Type.atom("a"), Type.integer(1)]),
      Type.tuple([Type.atom("b"), Type.integer(2)])
    ]

    const keyword = Type.list(keywordElems)
    const result = Type.keywordToMap(keyword)

    const mapElems = {}
    mapElems[Type.atomKey("a")] = Type.integer(1)
    mapElems[Type.atomKey("b")] = Type.integer(2)
    const expected = Type.map(mapElems)
    
    assert.deepStrictEqual(result, expected) 
  })

  it("overwrites the same keys", () => {
    const keywordElems = [
      Type.tuple([Type.atom("a"), Type.integer(1)]),
      Type.tuple([Type.atom("b"), Type.integer(2)]),
      Type.tuple([Type.atom("a"), Type.integer(9)])
    ]

    const keyword = Type.list(keywordElems)
    const result = Type.keywordToMap(keyword)

    const mapElems = {}
    mapElems[Type.atomKey("a")] = Type.integer(9)
    mapElems[Type.atomKey("b")] = Type.integer(2)
    const expected = Type.map(mapElems)
    
    assert.deepStrictEqual(result, expected) 
  })

  it("returns frozen object", () => {
    const arg = Type.list()
    const result = Type.keywordToMap(arg)

    assertFrozen(result)
  })
})

describe("list()", () => {
  let elems, expected, result;

  beforeEach(() => {
    elems = [Type.integer(1), Type.integer(2)]
    expected = {type: "list", data: elems}
    result = Type.list(elems)
  })

  it("returns boxed list value", () => {
    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    assertFrozen(result)
  })
})

describe("map()", () => {
  let elems, result;

  beforeEach(() => {
    elems = {}
    elems[Type.atomKey("a")] = Type.integer(1)
    elems[Type.atomKey("b")] = Type.integer(2)
    result = Type.map(elems)
  })

  it("returns boxed map value", () => {
    const expected = {type: "map", data: elems}
    assert.deepStrictEqual(result, expected)
  })

  it("returns immutable object by default", () => {
    assertFrozen(result)
  })

  it("returns mutable object if immutable arg is set to false", () => {
    result = Type.map(elems, false)
    assertNotFrozen(result)
  })
})

describe("module()", () => {
  it("returns boxed module value", () => {
    const expected = {type: "module", className: "Elixir_ClassStub"}
    const result = Type.module("Elixir_ClassStub")

    assert.deepStrictEqual(result, expected)
  })


  it("returns frozen object", () => {
    const result = Type.module("Elixir_ClassStub")
    assertFrozen(result)
  })
})

describe("nil()", () => {
  it("returns boxed nil value", () => {
    const expected = {type: "nil"}
    const result = Type.nil()

    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    const result = Type.nil()
    assertFrozen(result)
  })
})

describe("placeholder()", () => {
  it("returns boxed placeholder value", () => {
    const expected = {type: "placeholder"}
    const result = Type.placeholder()

    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    const result = Type.placeholder()
    assertFrozen(result)
  })
})

describe("string()", () => {
  it("returns boxed string value", () => {
    const expected = {type: "string", value: "test"}
    const result = Type.string("test")

    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    const result = Type.string("test")
    assertFrozen(result)
  })
})

describe("stringKey()", () => {
  it("returns serialized boxed string value", () => {
    const result = Type.stringKey("test")
    assert.equal(result, "~string[test]")
  })
})

describe("struct()", () => {
  let elems, result;

  beforeEach(() => {
    elems = {}
    elems[Type.atomKey("a")] = Type.integer(1)
    elems[Type.atomKey("b")] = Type.integer(2)
    result = Type.struct("TestClass", elems)
  })

  it("returns boxed map value", () => {
    const expected = {type: "struct", className: "TestClass", data: elems}
    assert.deepStrictEqual(result, expected)
  })

  it("returns immutable object", () => {
    assertFrozen(result)
  })
})

describe("textNode()", () => {
  it("builds a text node", () => {
    const expected = {type: "text", content: "test"}
    const result = Type.textNode("test")

    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    const result = Type.textNode("test")
    assertFrozen(result)
  })
})

describe("tuple()", () => {
  let elems, expected, result;

  beforeEach(() => {
    elems = [Type.integer(1), Type.integer(2)]
    expected = {type: "tuple", data: elems}
    result = Type.tuple(elems)
  })

  it("returns boxed tuple value", () => {
    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    assertFrozen(result)
  })
})

<|start_filename|>lib/hologram/compiler/js_encoder.ex<|end_filename|>
defprotocol Hologram.Compiler.JSEncoder do
  def encode(ir, context, opts)
end


<|start_filename|>lib/hologram/compiler/js_encoders/type_operator.ex<|end_filename|>
alias Hologram.Compiler.{Context, JSEncoder, Opts}
alias Hologram.Compiler.IR.TypeOperator

defimpl JSEncoder, for: TypeOperator do
  def encode(%{left: left, right: :binary}, %Context{} = context, %Opts{} = opts) do
    value = JSEncoder.encode(left, context, opts)
    "Hologram.Interpreter.$type_operator(#{value}, 'binary')"
  end
end


<|start_filename|>test/js/command_test.js<|end_filename|>
"use strict";

import { assert, cleanup } from "./support/commons";
beforeEach(() => cleanup())

import Command from "../../assets/js/hologram/command"
import Operation from "../../assets/js/hologram/operation"
import Runtime from "../../assets/js/hologram/runtime"
import Target from "../../assets/js/hologram/target"
import Type from "../../assets/js/hologram/type"

describe("buildMessagePayload()", () => {
  it("builds command message payload", () => {
    const targetId = "test_target_id"
    const TestComponentClass = class {}
    Runtime.registerComponentClass(targetId, TestComponentClass)
    const target = new Target(targetId)

    const sourceId = "test_source_id"
    const commandName = Type.atom("test_command_name")
    const params = Type.list()

    const operation = new Operation(sourceId, target, commandName, params)
    const result = Command.buildMessagePayload(operation)

    const expected = {
      target_module: Type.module("TestComponentClass"),
      target_id: Type.atom(targetId),
      command: commandName,
      params: params,
    }

    assert.deepStrictEqual(result, expected)
  })
})

<|start_filename|>lib/hologram/compiler/module_def_aggregators/elixir_atom.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.{ModuleDefAggregator, Reflection}
alias Hologram.Compiler.IR.ModuleType

defimpl ModuleDefAggregator, for: Atom do
  def aggregate(module) do
    if Reflection.is_module?(module) do
      %ModuleType{module: module}
      |> ModuleDefAggregator.aggregate()
    end
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/module_definition.ex<|end_filename|>
alias Hologram.Compiler.{Context, Formatter, Helpers, JSEncoder, Opts}
alias Hologram.Compiler.IR.{ModuleDefinition, NotSupportedExpression}

defimpl JSEncoder, for: ModuleDefinition do
  def encode(
        %{module: module, attributes: attrs, functions: function_defs} = ir,
        %Context{},
        %Opts{} = opts
      ) do
    class_name = Helpers.class_name(module)
    context = struct(Context, Map.from_struct(ir))

    attrs =
      attrs
      |> Enum.reject(&match?(%NotSupportedExpression{}, &1))
      |> Enum.map(&JSEncoder.encode(&1, context, opts))
      |> Enum.join("\n")

    functions =
      Helpers.aggregate_function_def_variants(function_defs)
      |> Map.values()
      |> Enum.map(&JSEncoder.encode(&1, context, opts))
      |> Enum.join("\n")

    "window.#{class_name} = class #{class_name} {"
    |> Formatter.maybe_append_new_section(attrs)
    |> Formatter.maybe_append_new_section(functions)
    |> Formatter.maybe_append_new_line("}")
    |> Formatter.append_line_break()
  end
end


<|start_filename|>test/unit/hologram/compiler/pattern_deconstructors/list_type_test.exs<|end_filename|>
defmodule Hologram.Compiler.PatternDeconstructor.ListTypeTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.IR.IntegerType
  alias Hologram.Compiler.IR.ListIndexAccess
  alias Hologram.Compiler.IR.ListType
  alias Hologram.Compiler.IR.Variable
  alias Hologram.Compiler.PatternDeconstructor

  test "non-nested list without vars" do
    ir = %ListType{
      data: [
        %IntegerType{value: 1},
        %IntegerType{value: 2}
      ]
    }

    assert PatternDeconstructor.deconstruct(ir) == []
  end

  test "non-nested list with single var" do
    ir = %ListType{
      data: [
        %IntegerType{value: 1},
        %Variable{name: :x}
      ]
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %ListIndexAccess{index: 1},
        %Variable{name: :x}
      ]
    ]

    assert result == expected
  end

  test "non-nested list with multiple vars" do
    ir = %ListType{
      data: [
        %Variable{name: :x},
        %IntegerType{value: 2},
        %Variable{name: :y}
      ]
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %ListIndexAccess{index: 0},
        %Variable{name: :x}
      ],
      [
        %ListIndexAccess{index: 2},
        %Variable{name: :y}
      ]
    ]

    assert result == expected
  end

  test "nested list without vars" do
    ir = %ListType{
      data: [
        %IntegerType{value: 1},
        %IntegerType{value: 2},
        %ListType{
          data: [
            %IntegerType{value: 3},
            %IntegerType{value: 4}
          ]
        }
      ]
    }

    assert PatternDeconstructor.deconstruct(ir) == []
  end

  test "nested list with single var" do
    ir = %ListType{
      data: [
        %IntegerType{value: 1},
        %ListType{
          data: [
            %Variable{name: :x},
            %IntegerType{value: 2}
          ]
        }
      ]
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %ListIndexAccess{index: 1},
        %ListIndexAccess{index: 0},
        %Variable{name: :x}
      ]
    ]

    assert result == expected
  end

  test "nested list with multiple vars" do
    ir = %ListType{
      data: [
        %IntegerType{value: 1},
        %Variable{name: :x},
        %ListType{
          data: [
            %Variable{name: :y},
            %IntegerType{value: 2}
          ]
        }
      ]
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %ListIndexAccess{index: 1},
        %Variable{name: :x}
      ],
      [
        %ListIndexAccess{index: 2},
        %ListIndexAccess{index: 0},
        %Variable{name: :y}
      ]
    ]

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/block.ex<|end_filename|>
alias Hologram.Compiler.{Config, Context, JSEncoder, Opts}
alias Hologram.Compiler.IR.{Block, MatchOperator}

defimpl JSEncoder, for: Block do
  def encode(%{expressions: exprs}, %Context{} = context, %Opts{} = opts) do
    expr_count = Enum.count(exprs)

    exprs
    |> Enum.with_index()
    |> Enum.map_reduce(context, fn {expr, idx}, context ->
      encode_expr(expr, idx, expr_count, context, opts)
    end)
    |> elem(0)
    |> Enum.join("\n")
  end

  defp build_output(%MatchOperator{} = expr, idx, expr_count, context, opts) do
    output = JSEncoder.encode(expr, context, opts)

    if idx == expr_count - 1 do
      output <> "\nreturn #{Config.match_access_js()};"
    else
      output
    end
  end

  defp build_output(expr, idx, expr_count, context, opts) do
    return_statement = if idx == expr_count - 1, do: "return ", else: ""
    "#{return_statement}#{JSEncoder.encode(expr, context, opts)};"
  end

  defp encode_expr(expr, idx, expr_count, context, opts) do
    output = build_output(expr, idx, expr_count, context, opts)
    block_bindings = context.block_bindings ++ get_expr_bindings(expr)
    context = %{context | block_bindings: block_bindings}

    {output, context}
  end

  defp get_expr_bindings(%MatchOperator{bindings: bindings}) do
    Enum.map(bindings, fn %{name: name} -> name end)
  end

  defp get_expr_bindings(_), do: []
end


<|start_filename|>test/e2e/test/support/test_case.ex<|end_filename|>
defmodule HologramE2E.TestCase do
  use ExUnit.CaseTemplate
  alias Hologram.Runtime.{PageDigestStore, RouterBuilder, RouterMatcher, TemplateStore}

  using do
    quote do
      use Wallaby.Feature
      import Wallaby.Query

      @fixtures_path "#{File.cwd!()}/test/fixtures"

      setup_all do
        Mix.Tasks.Compile.Hologram.run()

        PageDigestStore.populate_table()
        TemplateStore.populate_table()

        :code.delete(RouterMatcher)
        RouterBuilder.create_matcher_module()

        :ok
      end
    end
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/anonymous_function_type_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.AnonymousFunctionTypeTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Compiler.{Config, Context, JSEncoder, Opts}

  test "no vars / single expression" do
    code = "fn -> 1 end"
    ir = ir(code)
    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    { type: 'anonymous_function', callback: function() {
    return { type: 'integer', value: 1 };
    }
    }\
    """

    assert result == expected
  end

  test "single var" do
    code = "fn x -> 1 end"
    ir = ir(code)
    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    { type: 'anonymous_function', callback: function() {
    let x = arguments[0];
    return { type: 'integer', value: 1 };
    }
    }\
    """

    assert result == expected
  end

  test "multiple vars" do
    code = "fn x, y -> 1 end"
    ir = ir(code)
    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    { type: 'anonymous_function', callback: function() {
    let x = arguments[0];
    let y = arguments[1];
    return { type: 'integer', value: 1 };
    }
    }\
    """

    assert result == expected
  end

  test "multiple expressions" do
    code = """
    fn ->
      1
      2
    end
    """

    ir = ir(code)
    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    { type: 'anonymous_function', callback: function() {
    { type: 'integer', value: 1 };
    return { type: 'integer', value: 2 };
    }
    }\
    """

    assert result == expected
  end

  test "bindings from args are taken into account in block scope" do
    code = """
    fn x ->
      x = 2
      3
    end
    """

    ir = ir(code)
    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    match_access_js = Config.match_access_js()

    expected = """
    { type: 'anonymous_function', callback: function() {
    let x = arguments[0];
    #{match_access_js} = { type: 'integer', value: 2 };
    x = #{match_access_js};
    return { type: 'integer', value: 3 };
    }
    }\
    """

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/module_type/module_7.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ModuleType.Module7 do
  use Hologram.Component

  def template do
    ~H"""
    """
  end
end


<|start_filename|>lib/hologram/compiler/decoder.ex<|end_filename|>
defmodule Hologram.Compiler.Decoder do
  alias Hologram.Compiler.{ListTypeDecoder, MapTypeDecoder, ModuleTypeDecoder, TupleTypeDecoder}

  def decode(%{"type" => "atom", "value" => value}) do
    String.to_atom(value)
  end

  def decode(%{"type" => "boolean", "value" => value}) do
    value
  end

  def decode(%{"type" => "float", "value" => value}) do
    value
  end

  def decode(%{"type" => "integer", "value" => value}) do
    value
  end

  def decode(%{"type" => "list"} = value) do
    ListTypeDecoder.decode(value)
  end

  def decode(%{"type" => "map"} = value) do
    MapTypeDecoder.decode(value)
  end

  def decode(%{"type" => "module"} = value) do
    ModuleTypeDecoder.decode(value)
  end

  def decode(%{"type" => "string", "value" => value}) do
    value
  end

  def decode(%{"type" => "tuple"} = value) do
    TupleTypeDecoder.decode(value)
  end
end


<|start_filename|>lib/hologram/compiler/transformers/function_call_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.FunctionCallTransformer do
  alias Hologram.Compiler.{Context, Helpers, Resolver, Transformer}
  alias Hologram.Compiler.IR.{FunctionCall, ListType, NotSupportedExpression}

  def transform(
        {{:., _, [{:__aliases__, _, module_segs}, function]}, _, args},
        %Context{} = context
      ) do
    build_function_call(module_segs, function, args, context)
  end

  def transform({{:., _, [Kernel, :to_string]}, _, args}, %Context{} = context) do
    build_function_call([:Kernel], :to_string, args, context)
  end

  def transform({{:., _, [{:__MODULE__, _, _}, function]}, _, args}, %Context{} = context) do
    Helpers.module_name_segments(context.module)
    |> build_function_call(function, args, context)
  end

  def transform({{:., _, [erlang_module, _]}, _, _} = ast, _) when is_atom(erlang_module) do
    %NotSupportedExpression{ast: ast, type: :erlang_function_call}
  end

  def transform({{:., _, [module_expr, function]}, _, args}, %Context{} = context) do
    %FunctionCall{
      module: Kernel,
      function: :apply,
      args: [
        Transformer.transform(module_expr, context),
        Transformer.transform(function, context),
        %ListType{data: build_args(args, context)}
      ]
    }
  end

  def transform({function, _, args}, %Context{} = context) do
    build_function_call([], function, args, context)
  end

  defp build_function_call(module_segs, function, args, %Context{} = context) do
    args = build_args(args, context)
    arity = Enum.count(args)

    module =
      Resolver.resolve(
        module_segs,
        function,
        arity,
        context
      )

    %FunctionCall{module: module, function: function, args: args}
  end

  defp build_args(args, context) do
    args = if is_list(args), do: args, else: []
    Enum.map(args, &Transformer.transform(&1, context))
  end
end


<|start_filename|>test/e2e/test/ui_test.exs<|end_filename|>
defmodule HologramE2E.UITest do
  use HologramE2E.TestCase, async: false

  describe "link" do
    feature "redirects to another page", %{session: session} do
      session
      |> visit("/e2e/page-6")
      |> click(css("#test-id"))
      |> assert_has(css("h1", text: "Page 7"))

      assert current_path(session) == "/e2e/page-7"
    end

    # DEFER: test @id and @class optional props
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/atom_type_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.AtomTyperTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}
  alias Hologram.Compiler.IR.AtomType

  test "encode/3" do
    ir = %AtomType{value: :test}

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "{ type: 'atom', value: 'test' }"

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/unary_negative_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.UnaryNegativeOperator
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: UnaryNegativeOperator do
  def aggregate(%{value: value}) do
    ModuleDefAggregator.aggregate(value)
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/function_call_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.FunctionCallTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}

  alias Hologram.Compiler.IR.{
    BinaryType,
    FunctionCall,
    IntegerType,
    ListType,
    StringType
  }

  test "sigilH" do
    ir = %FunctionCall{
      function: :sigil_H,
      module: Hologram.Runtime.Commons,
      args: [
        %BinaryType{
          parts: [
            %StringType{value: "\n<div>Hello World {@counter}</div>\n"}
          ]
        },
        %ListType{data: []}
      ]
    }

    expected =
      "[ { type: 'element', tag: 'div', attrs: {}, children: [ { type: 'text', content: 'Hello World ' }, { type: 'expression', callback: ($bindings) => { return { type: 'tuple', data: [ $bindings.data['~atom[counter]'] ] } } } ] } ]"

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    assert result == expected
  end

  test "function name" do
    ir = %FunctionCall{
      function: :abc?,
      module: Test,
      args: []
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "Elixir_Test.abc$question()"

    assert result == expected
  end

  test "args" do
    ir = %FunctionCall{
      function: :abc,
      module: Test,
      args: [%IntegerType{value: 1}, %IntegerType{value: 2}]
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "Elixir_Test.abc({ type: 'integer', value: 1 }, { type: 'integer', value: 2 })"

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_10.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module10 do
  def test_fun_10a do
    :ok
  end
end


<|start_filename|>lib/hologram/compiler/expander.ex<|end_filename|>
defmodule Hologram.Compiler.Expander do
  alias Hologram.Compiler.{Helpers, Normalizer, Reflection}
  alias Hologram.Compiler.IR.{MacroDefinition, RequireDirective}

  def expand_macro(%MacroDefinition{module: module, name: name}, args) do
    expand_macro(module, name, args)
  end

  def expand_macro(module, name, args) do
    expanded =
      apply(module, :"MACRO-#{name}", [__ENV__] ++ args)
      |> Normalizer.normalize()

    case expanded do
      {:__block__, [], exprs} ->
        exprs

      _ ->
        [expanded]
    end
  end

  def expand_macros(ast, requires) do
    expand_with_fun(ast, &expand_macros_in_expression(&1, requires))
  end

  defp expand_macros_in_expression({name, _, args} = expr, requires) do
    args = if args, do: args, else: []
    macro_def = find_macro_definition(name, args, requires)

    if macro_def do
      expand_macro(macro_def, args)
    else
      expr
    end
  end

  defp expand_macros_in_expression(expr, _), do: expr

  def expand_module_pseudo_variable(
        {:defmodule, ast_1,
         [{:__aliases__, ast_2, module_segs}, [do: {:__block__, ast_3, exprs}]]}
      ) do
    exprs =
      Enum.reduce(exprs, [], fn expr, acc ->
        acc ++ [expand_module_pseudo_variable(expr, module_segs)]
      end)

    {:defmodule, ast_1, [{:__aliases__, ast_2, module_segs}, [do: {:__block__, ast_3, exprs}]]}
  end

  defp expand_module_pseudo_variable({:__MODULE__, line, _}, module_segs) do
    {:__aliases__, line, module_segs}
  end

  defp expand_module_pseudo_variable(ast, module_segs) when is_tuple(ast) do
    Tuple.to_list(ast)
    |> expand_module_pseudo_variable(module_segs)
    |> List.to_tuple()
  end

  defp expand_module_pseudo_variable(ast, module_segs) when is_list(ast) do
    Enum.map(ast, &expand_module_pseudo_variable(&1, module_segs))
  end

  defp expand_module_pseudo_variable(ast, _), do: ast

  defp expand_use_directive({:use, _, [{:__aliases__, _, module_segs}]}) do
    Helpers.module(module_segs)
    |> expand_macro(:__using__, [nil])
  end

  defp expand_use_directive(ast), do: ast

  def expand_use_directives(ast) do
    expand_with_fun(ast, &expand_use_directive/1)
  end

  defp expand_with_fun({:defmodule, line, [aliases, [do: {:__block__, _, exprs}]]}, fun) do
    expanded =
      Enum.reduce(exprs, [], fn expr, acc ->
        case fun.(expr) do
          # expanded expression is returned wrapped in a list
          expr when is_list(expr) ->
            acc ++ expr

          # non-expandable expression
          expr ->
            acc ++ [expr]
        end
      end)

    {:defmodule, line, [aliases, [do: {:__block__, [], expanded}]]}
  end

  defp find_macro_definition(name, args, requires) do
    require_directive =
      Enum.find(requires, fn %RequireDirective{module: module} ->
        Reflection.has_macro?(module, name, Enum.count(args))
      end)

    if require_directive do
      Reflection.macro_definition(require_directive.module, name, args)
    else
      nil
    end
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/component.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.ModuleType
alias Hologram.Template.VDOM.Component

defimpl CallGraphBuilder, for: Component do
  def build(
        %{module: module, props: props, children: children},
        module_defs,
        templates,
        from_vertex
      ) do
    CallGraphBuilder.build(%ModuleType{module: module}, module_defs, templates, from_vertex)
    CallGraphBuilder.build(props, module_defs, templates, from_vertex)
    CallGraphBuilder.build(children, module_defs, templates, from_vertex)
  end
end


<|start_filename|>test/unit/hologram/compiler/call_graph_builder/function_call_test.exs<|end_filename|>
defmodule Hologram.Compiler.CallGraphBuilder.FunctionCallTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{CallGraph, CallGraphBuilder, Reflection}
  alias Hologram.Compiler.IR.FunctionCall

  alias Hologram.Test.Fixtures.{
    PlaceholderModule1,
    PlaceholderModule2,
    PlaceholderModule3,
    PlaceholderModule4
  }

  @from_vertex {PlaceholderModule4, :test_fun_4a}

  @module_defs %{
    PlaceholderModule1 => Reflection.module_definition(PlaceholderModule1),
    PlaceholderModule2 => Reflection.module_definition(PlaceholderModule2),
    PlaceholderModule3 => Reflection.module_definition(PlaceholderModule3),
    PlaceholderModule4 => Reflection.module_definition(PlaceholderModule4)
  }

  @templates %{}

  setup do
    CallGraph.run()
    :ok
  end

  test "non-standard lib module" do
    ir = %FunctionCall{module: PlaceholderModule2, function: :test_fun_2a}
    CallGraphBuilder.build(ir, @module_defs, @templates, @from_vertex)

    assert CallGraph.has_edge?(@from_vertex, {PlaceholderModule2, :test_fun_2a})
  end

  test "standard lib module" do
    ir = %FunctionCall{module: Kernel, function: :get_in}
    CallGraphBuilder.build(ir, @module_defs, @templates, @from_vertex)

    refute CallGraph.has_edge?(@from_vertex, {Kernel, :get_in})
  end

  test "args recursion" do
    args = [
      %FunctionCall{module: PlaceholderModule2, function: :test_fun_2a},
      %FunctionCall{module: PlaceholderModule3, function: :test_fun_3a}
    ]

    ir = %FunctionCall{module: PlaceholderModule1, function: :test_fun_1a, args: args}
    CallGraphBuilder.build(ir, @module_defs, @templates, @from_vertex)

    assert CallGraph.has_edge?(@from_vertex, {PlaceholderModule2, :test_fun_2a})
    assert CallGraph.has_edge?(@from_vertex, {PlaceholderModule3, :test_fun_3a})
  end
end


<|start_filename|>assets/js/hologram.js<|end_filename|>
"use strict";

import Interpreter from "./hologram/interpreter";
import Runtime from "./hologram/runtime";

// transpiled Elixir standard library

import Elixir_Access from "./hologram/elixir/access";
import Elixir_Ecto_Changeset from "./hologram/elixir/ecto/changeset";
import Elixir_Enum from "./hologram/elixir/enum";
import Elixir_IO from "./hologram/elixir/io";
import Elixir_Kernel from "./hologram/elixir/kernel";
import Elixir_Keyword from "./hologram/elixir/keyword";
import Elixir_Map from "./hologram/elixir/map";
import Elixir_String from "./hologram/elixir/string";

window.Elixir_Access = Elixir_Access;
window.Elixir_Ecto_Changeset = Elixir_Ecto_Changeset;
window.Elixir_Enum = Elixir_Enum;
window.Elixir_IO = Elixir_IO;
window.Elixir_Kernel = Elixir_Kernel;
window.Elixir_Keyword = Elixir_Keyword;
window.Elixir_Map = Elixir_Map;
window.Elixir_String = Elixir_String;

// transpiled Hologram runtime

import Elixir_Hologram_Router from "./hologram/elixir/hologram/router";
import Elixir_Hologram_Runtime_JS from "./hologram/elixir/hologram/runtime/js";

window.Elixir_Hologram_Router = Elixir_Hologram_Router;
window.Elixir_Hologram_Runtime_JS = Elixir_Hologram_Runtime_JS;

export default class Hologram {
  static Interpreter = Interpreter;

  // Covered implicitely in E2E tests.
  static onReady(document, callback) {
    if (
      document.readyState === "interactive" ||
      document.readyState === "complete"
    ) {
      callback();
    } else {
      document.addEventListener("DOMContentLoaded", function listener() {
        document.removeEventListener("DOMContentLoaded", listener);
        callback();
      });
    }
  }

  // Covered implicitely in E2E tests.
  static run() {
    const window = globalThis.window;

    Hologram.onReady(window.document, () => {
      if (!Runtime.isInitiated) {
        Runtime.init(window);
      }

      const args = window.hologramArgs
      const storeSnapshot = window.hologramStoreSnapshot
      const state = storeSnapshot ? storeSnapshot : args.state

      Runtime.mountPage(args.class, args.digest, state);
    });
  }
}

window.Hologram = Hologram;
window.hologramRuntimeScriptLoaded = true;

if (
  window.hologramRuntimeScriptLoaded &&
  window.hologramPageScriptLoaded &&
  !window.hologramPageMounted
) {
  window.hologramPageMounted = true;
  Hologram.run();
}


<|start_filename|>lib/hologram/compiler/module_def_aggregators/relaxed_boolean_and_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.RelaxedBooleanAndOperator
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: RelaxedBooleanAndOperator do
  def aggregate(%{left: left, right: right}) do
    ModuleDefAggregator.aggregate(left)
    ModuleDefAggregator.aggregate(right)
  end
end


<|start_filename|>assets/js/hologram/elixir/ecto/changeset.js<|end_filename|>
"use strict";

import Enum from "../../elixir/enum";
import Keyword from "../../elixir/keyword";
import Map from "../../elixir/map";
import Type from "../../type";

export default class Changeset {
  // DEFER: implement other arg types
  static cast(arg, params, permitted, opts = Type.list()) {
    if (Type.isTuple(arg)) {
      return Changeset._castSchemaless(arg.data[0], arg.data[1], params, permitted, opts)
    }
  }

  static _appendAcceptanceError(errors, field) {
    const errorInfo = Type.tuple([
      Type.string("must be accepted"),
      Type.list([
        Type.tuple([Type.atom("validation"), Type.atom("acceptance")]),
      ])
    ])

    const error = Type.tuple([field, errorInfo])

    return Enum.concat(errors, Type.list([error]))
  }

  static _appendCastError(errors, key, type) {
    const errorInfo = Type.tuple([
      Type.string("is invalid"),
      Type.list([
        Type.tuple([Type.atom("type"), Type.atom(type.value)]),
        Type.tuple([Type.atom("validation"), Type.atom("cast")]),
      ])
    ])

    const error = Type.tuple([key, errorInfo])

    return Enum.concat(errors, Type.list([error]))
  }

  static _castSchemaless(data, types, params, permitted, _opts) {
    let changeset = Type.struct("Elixir_Ecto_Changeset")
    changeset = Map.put(changeset, Type.atom("action"), Type.nil())
    changeset = Map.put(changeset, Type.atom("data"), data)
    changeset = Changeset._putErrorsField(changeset, types, params, permitted)
    changeset = Changeset._putValidField(changeset)
    changeset = Changeset._putChangesField(changeset, params, permitted)

    return changeset
  }

  static _putChangesField(changeset, params, permitted) {
    let changes = Type.map()

    Map.keys(params).data.forEach(key => {
      if (Enum.member$question(permitted, key).value) {
        const errors = Map.get(changeset, Type.atom("errors"))
        if (!Keyword.has_key$question(errors, key).value) {
          const value = Map.get(params, key)
          changes = Map.put(changes, key, value)
        }
      }
    })

    return Map.put(changeset, Type.atom("changes"), changes)
  }

  static _putErrorsField(changeset, types, params, permitted) {
    let errors = Type.list()

    Map.keys(params).data.forEach(key => {
      if (Enum.member$question(permitted, key).value) {
        const value = Map.get(params, key)
        const type = Map.get(types, key)

        if (value.type !== type.value) {
          errors = Changeset._appendCastError(errors, key, type)
        }
      }
    })

    return Map.put(changeset, Type.atom("errors"), errors)
  }

  static _putValidField(changeset) {
    const errors = Map.get(changeset, Type.atom("errors"))
    const value = Type.boolean(errors.data.length === 0)

    return Map.put(changeset, Type.atom("valid?"), value)
  }

  // DEFER: handle opts param
  static validate_acceptance(changeset, field, _opts = Type.list()) {
    let errors = Map.get(changeset, Type.atom("errors"))
    const changes = Map.get(changeset, Type.atom("changes"))

    const boxedVal = Map.get(changes, field)
    if (boxedVal.type !== "boolean" || boxedVal.value !== true) {
      errors = Changeset._appendAcceptanceError(errors, field)
    }

    changeset = Map.put(changeset, Type.atom("errors"), errors)
    const isValid = Type.boolean(errors.data.length === 0)

    return Map.put(changeset, Type.atom("valid?"), isValid)
  }

  // DEFER: handle atom fields param (at the moment only list fields param is handled)
  static validate_required(changeset, fields) {
    let errors = Map.get(changeset, Type.atom("errors"))
    const changes = Map.get(changeset, Type.atom("changes"))

    fields.data.forEach(key => {
      const boxedVal = Map.get(changes, key)
      if (boxedVal.type === "nil" || (boxedVal.type === "string" && boxedVal.value === "")) {
        errors = Changeset._appendRequiredError(errors, key)
      }
    })

    changeset = Map.put(changeset, Type.atom("errors"), errors)
    const isValid = Type.boolean(errors.data.length === 0)

    return Map.put(changeset, Type.atom("valid?"), isValid)
  }

  static _appendRequiredError(errors, key) {
    const errorInfo = Type.tuple([
      Type.string("can't be blank"),
      Type.list([
        Type.tuple([Type.atom("validation"), Type.atom("required")]),
      ])
    ])

    const error = Type.tuple([key, errorInfo])

    return Enum.concat(errors, Type.list([error]))
  }
}

<|start_filename|>lib/hologram/compiler/transformers/anonymous_function_call_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.AnonymousFunctionCallTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.AnonymousFunctionCall

  def transform({{:., _, [{name, _, nil}]}, _, args}, %Context{} = context) do
    %AnonymousFunctionCall{
      name: name,
      args: Enum.map(args, &Transformer.transform(&1, context))
    }
  end
end


<|start_filename|>test/unit/fixtures/compiler/transformer/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Transformer.Module2 do
  defstruct a: nil
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_20.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module20 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module21, warn: false

  route "/test-route-20"

  def template do
    ~H"""
      {Module21.test_21a()}
    """
  end
end


<|start_filename|>test/unit/fixtures/placeholder_module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.PlaceholderModule1 do
  def test_fun_1a, do: nil
  def test_fun_1b, do: nil
end


<|start_filename|>test/unit/hologram/compiler/module_def_aggregators/if_expression_test.exs<|end_filename|>
defmodule Hologram.Compiler.ModuleDefAggregator.IfExpressionTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{ModuleDefAggregator, ModuleDefStore}
  alias Hologram.Compiler.IR.{ModuleDefinition, ModuleType, IfExpression, NilType}
  alias Hologram.Test.Fixtures.PlaceholderModule1

  setup do
    ModuleDefStore.run()
    :ok
  end

  test "condition is traversed" do
    ir = %IfExpression{
      condition: %ModuleType{module: PlaceholderModule1},
      do: %NilType{},
      else: %NilType{}
    }

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(PlaceholderModule1)
  end

  test "do clause is traversed" do
    ir = %IfExpression{
      condition: %NilType{},
      do: %ModuleType{module: PlaceholderModule1},
      else: %NilType{}
    }

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(PlaceholderModule1)
  end

  test "else clause is traversed" do
    ir = %IfExpression{
      condition: %NilType{},
      do: %NilType{},
      else: %ModuleType{module: PlaceholderModule1}
    }

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(PlaceholderModule1)
  end
end


<|start_filename|>test/e2e/test/runtime/actions_test.exs<|end_filename|>
defmodule HologramE2E.Runtime.ActionsTest do
  use HologramE2E.TestCase, async: false

  @page_path "/e2e/runtime/actions"

  describe "action spec" do
    feature "action spec without target and params specified as text", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-1"))
      |> assert_has(css("#text", text: "text updated by action_1, state.value = p1"))
    end

    feature "action spec without target and params specified as 1-element tuple", %{
      session: session
    } do
      session
      |> visit(@page_path)
      |> click(css("#button-2"))
      |> assert_has(css("#text", text: "text updated by action_2, state.value = p1"))
    end

    feature "action spec with params", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-3"))
      |> assert_has(
        css("#text",
          text: "text updated by action_3, params.a = 5, params.b = 6, state.value = p1"
        )
      )
    end

    feature "action spec with target ID", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-4"))
      |> assert_has(
        css("#text-component-3", text: "text updated by component_3_action_1, state.value = c3")
      )
    end

    feature "action spec with targetID and params", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-5"))
      |> assert_has(
        css("#text-component-3",
          text:
            "text updated by component_3_action_2, params.a = 5, params.b = 6, state.value = c3"
        )
      )
    end
  end

  describe "action result" do
    feature "action returning state only, not wrapped in tuple", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-11"))
      |> assert_has(css("#text", text: "text updated by action_9, state.value = p1"))
    end

    feature "action returning state only, wrapped in tuple", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-6"))
      |> assert_has(css("#text", text: "text updated by action_4, state.value = p1"))
    end

    feature "action returning state and command", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-7"))
      |> assert_has(
        css("#text",
          text: "text updated by action_5_b triggered by command_1, state.value = p1"
        )
      )
    end

    feature "action returning state, command and params", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-8"))
      |> assert_has(
        css("#text",
          text:
            "text updated by action_6_b triggered by command_2, params.a = 10, params.b = 20, state.value = p1"
        )
      )
    end

    feature "action returning state, target ID and command", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-9"))
      |> assert_has(
        css("#text",
          text: "text updated by action_7_b triggered by component_3_command_1, state.value = p1"
        )
      )
    end

    feature "action returning state, target ID, command and params", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-10"))
      |> assert_has(
        css("#text",
          text:
            "text updated by action_8_b triggered by component_3_command_2, params.a = 10, params.b = 20, state.value = p1"
        )
      )
    end
  end

  describe "target" do
    feature "default target", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-12"))
      |> assert_has(css("#text", text: "text updated by action_10, state.value = p1"))
    end

    feature "component target", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-4"))
      |> assert_has(
        css("#text-component-3", text: "text updated by component_3_action_1, state.value = c3")
      )
    end

    feature "page target", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-14"))
      |> assert_has(css("#text", text: "text updated by action_11, state.value = p1"))
    end

    feature "layout target", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-15"))
      |> assert_has(
        css("#text-default-layout",
          text: "text updated by default_layout_action_1, state.value = dl"
        )
      )
    end
  end

  describe "action trigger" do
    feature "action triggered by event", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-16"))
      |> assert_has(css("#text", text: "text updated by action_12, state.value = p1"))
    end

    feature "action trigerred by command", %{session: session} do
      session
      |> visit(@page_path)
      |> click(css("#button-17"))
      |> assert_has(css("#text", text: "text updated by action_13_b, state.value = p1"))
    end
  end
end


<|start_filename|>lib/hologram/compiler/transformers/match_operator_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.MatchOperatorTransformer do
  alias Hologram.Compiler.{Context, PatternDeconstructor, Transformer}
  alias Hologram.Compiler.IR.{Binding, MatchAccess, MatchOperator}

  def transform({:=, _, [left, right]}, %Context{} = context) do
    left = Transformer.transform(left, context)

    bindings =
      PatternDeconstructor.deconstruct(left)
      |> Enum.map(fn path ->
        [head | tail] = Enum.reverse(path)
        access_path = [%MatchAccess{} | Enum.reverse(tail)]
        %Binding{name: head.name, access_path: access_path}
      end)

    %MatchOperator{
      bindings: bindings,
      left: left,
      right: Transformer.transform(right, context)
    }
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_3.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module3 do
  use Hologram.Layout

  def template do
    ~H"""
    """
  end

  def action(:test_3a, _params, _state) do
    :ok
  end

  def action(:test_3b, _a, _b, _c) do
    :ok
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/integer_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.IntegerType

defimpl CallGraphBuilder, for: IntegerType do
  def build(_, _, _, _), do: nil
end


<|start_filename|>lib/hologram/template/renderers/expression.ex<|end_filename|>
alias Hologram.Template.{Evaluator, Renderer}
alias Hologram.Template.VDOM.Expression

defimpl Renderer, for: Expression do
  def render(%{ir: ir}, _conn, bindings, _) do
    tuple_first_elem_ir = hd(ir.data)

    html =
      Evaluator.evaluate(tuple_first_elem_ir, bindings)
      |> to_string()

    {html, %{}}
  end
end


<|start_filename|>test/unit/fixtures/compiler/transformers/module_definition_transformer/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1 do
end


<|start_filename|>test/unit/fixtures/template/renderers/component_renderer/module_5.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.ComponentRenderer.Module5 do
  use Hologram.Component

  def template do
    ~H"""
    <span><slot /></span>
    """
  end
end


<|start_filename|>assets/js/hologram/type.js<|end_filename|>
"use strict";

// TODO: test

import { HologramNotImplementedError } from "./errors";
import Utils from "./utils"

export default class Type {
  static anonymousFunction(callback) {
    return Utils.freeze({type: "anonymous_function", callback: callback})
  }

  static atom(value) {
    return Utils.freeze({type: "atom", value: value})
  }

  static atomKey(key) {
    return `~atom[${key}]`
  }

  static binary(elems) {
    return Utils.freeze({type: "binary", data: elems})
  }

  static boolean(value) {
    return Utils.freeze({type: "boolean", value: value})
  }

  static componentNode(className, props, children) {
    return Utils.freeze({type: "component", className: className, props: props, children: children})
  }

  static consOperatorPattern(head, tail) {
    return Utils.freeze({type: "cons_operator_pattern", head: head, tail: tail})
  }

  static decodeKey(key) {
    const regex = /^~([a-z]+)\[(.*)\]$/
    const matches = regex.exec(key)
    const type = matches[1]
    const value = matches[2]

    switch (type) {
      case "atom":
        return Type.atom(value)

      case "string":
        return Type.string(value)

      default:
        const message = `Type.decodeKey(): key = ${JSON.stringify(key)}`
        throw new HologramNotImplementedError(message)        
    }
  }

  static elementNode(tag, attrs = {}, children = []) {
    return Utils.freeze({type: "element", tag: tag, attrs: attrs, children: children})
  }

  static encodedKey(boxedValue) {
    switch (boxedValue.type) {
      case "atom":
        return Type.atomKey(boxedValue.value)

      case "string":
        return Type.stringKey(boxedValue.value)
        
      default:
        const message = `Type.encodedKey(): boxedValue = ${JSON.stringify(boxedValue)}`
        throw new HologramNotImplementedError(message)
    }
  }

  // DEFER: test
  static expressionNode(callback) {
    return Utils.freeze({type: "expression", callback: callback})
  }

  static float(value) {
    return Utils.freeze({type: "float", value: value})
  }

  static integer(value) {
    return Utils.freeze({type: "integer", value: value})
  }

  static isAnonymousFunction(boxedValue) {
    return boxedValue.type === "anonymous_function"
  }

  static isAtom(boxedValue) {
    return boxedValue.type === "atom"
  }

  static isExpressionNode(node) {
    return node.type === "expression"
  }

  static isFalse(boxedValue) {
    return boxedValue.type === "boolean" && boxedValue.value === false
  }

  static isFalsy(boxedValue) {
    return Type.isFalse(boxedValue) || Type.isNil(boxedValue)
  }

  static isList(boxedValue) {
    return boxedValue.type === "list"
  }

  static isMap(boxedValue) {
    return boxedValue.type === "map"
  }

  static isNil(boxedValue) {
    return boxedValue.type === "nil"
  }

  static isNumber(boxedValue) {
    return boxedValue.type === "float" || boxedValue.type === "integer"
  }

  static isString(boxedValue) {
    return boxedValue.type === "string"
  }

  static isTrue(boxedValue) {
    return boxedValue.type === "boolean" && boxedValue.value === true
  }

  static isTruthy(boxedValue) {
    return !Type.isFalsy(boxedValue)
  }

  static isTuple(boxedValue) {
    return boxedValue.type === "tuple"
  }

  static keywordToMap(keyword) {
    const result = keyword.data.reduce((acc, elem) => {
      const key = Type.encodedKey(elem.data[0])
      acc.data[key] = elem.data[1]
      return acc
    }, {type: "map", data: {}})

    return Utils.freeze(result)
  }

  static list(elems = []) {
    return Utils.freeze({type: "list", data: elems})
  }

  static map(elems = {}, immutable = true) {
    const result = {type: "map", data: elems}
    return immutable ? Utils.freeze(result) : result
  }

  static module(className) {
    return Utils.freeze({type: "module", className: className})
  }

  static nil() {
    return Utils.freeze({type: "nil"})
  }

  static placeholder() {
    return Utils.freeze({type: "placeholder"})
  }

  static string(value) {
    return Utils.freeze({type: "string", value: value})
  }

  static stringKey(key) {
    return `~string[${key}]`
  }

  static struct(className, elems = {}) {
    return Utils.freeze({type: "struct", className: className, data: elems})
  }

  static textNode(content) {
    return Utils.freeze({type: "text", content: content})
  }

  static tuple(elems = []) {
    return Utils.freeze({type: "tuple", data: elems})
  }
}

<|start_filename|>lib/hologram/compiler/decoders/map_type_decoder.ex<|end_filename|>
defmodule Hologram.Compiler.MapTypeDecoder do
  alias Hologram.Compiler.Decoder

  def decode(%{"data" => data}) do
    Enum.map(data, fn {key, value} -> {decode_key(key), Decoder.decode(value)} end)
    |> Enum.into(%{})
  end

  defp decode_key(key) do
    [_, type, value] =
      ~r/~(\w+)\[(.+)\]/
      |> Regex.run(key)

    case type do
      "atom" ->
        String.to_atom(value)

      "string" ->
        value
    end
  end
end


<|start_filename|>test/e2e/test/operators_test.exs<|end_filename|>
defmodule HologramE2E.OperatorsTest do
  use HologramE2E.TestCase, async: false

  feature "addition", %{session: session} do
    session
    |> visit("/e2e/operators/addition")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = 3"))
  end

  feature "cons", %{session: session} do
    session
    |> visit("/e2e/operators/cons")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = [1, 2, 3]"))
  end

  feature "division", %{session: session} do
    session
    |> visit("/e2e/operators/division")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = 2"))
  end

  feature "equal to", %{session: session} do
    session
    |> visit("/e2e/operators/equal-to")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = true"))
  end

  feature "less than", %{session: session} do
    session
    |> visit("/e2e/operators/less-than")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = true"))
  end

  feature "list concatenation", %{session: session} do
    session
    |> visit("/e2e/operators/list-concatenation")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = [1, 2, 3, 4]"))
  end

  feature "list subtraction", %{session: session} do
    session
    |> visit("/e2e/operators/list-subtraction")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = [2, 1, 2, 1]"))
  end

  feature "membership", %{session: session} do
    session
    |> visit("/e2e/operators/membership")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = true"))
  end

  feature "module attribute", %{session: session} do
    session
    |> visit("/e2e/operators/module-attribute")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = test_value"))
  end

  feature "multiplication", %{session: session} do
    session
    |> visit("/e2e/operators/multiplication")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = 6"))
  end

  feature "not equal to", %{session: session} do
    session
    |> visit("/e2e/operators/not-equal-to")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = true"))
  end

  feature "relaxed boolean and", %{session: session} do
    session
    |> visit("/e2e/operators/relaxed-boolean-and")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = true"))
  end

  feature "relaxed boolean not", %{session: session} do
    session
    |> visit("/e2e/operators/relaxed-boolean-not")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = true"))
  end

  feature "relaxed boolean or", %{session: session} do
    session
    |> visit("/e2e/operators/relaxed-boolean-or")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = 2"))
  end

  feature "subtraction", %{session: session} do
    session
    |> visit("/e2e/operators/subtraction")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = 4"))
  end

  feature "unary negative", %{session: session} do
    session
    |> visit("/e2e/operators/unary-negative")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = -123"))
  end

  feature "unary positive", %{session: session} do
    session
    |> visit("/e2e/operators/unary-positive")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = 123"))
  end
end


<|start_filename|>test/unit/fixtures/utils/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Utils.Module1 do
  defstruct key_1: nil, key_2: nil
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_65.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module65 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module66

  route "/test-route-65"

  def template do
    ~H"""
    """
  end

  def action do
    Module66
  end
end


<|start_filename|>lib/hologram/ui/link.ex<|end_filename|>
defmodule Hologram.UI.Link do
  use Hologram.Component

  def init(_props) do
    %{}
  end

  def template do
    ~H"""
    <a href={@to.route()} on:click.command={:__redirect__, page: @to} id={if @bindings[:id] do @bindings[:id] else false end} class={if @bindings[:class] do @bindings[:class] else false end}><slot /></a>
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/expander/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Expander.Module1 do
end


<|start_filename|>test/unit/hologram/template/evaluators/expression_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.ExpressionTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.{IntegerType, TupleType}
  alias Hologram.Template.VDOM.Expression
  alias Hologram.Template.Evaluator

  test "evaluate/2" do
    ir = %Expression{
      ir: %TupleType{
        data: [
          %IntegerType{value: 1},
          %IntegerType{value: 2}
        ]
      }
    }

    result = Evaluator.evaluate(ir, %{})
    expected = 1

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/compiler/helpers_test.exs<|end_filename|>
defmodule Hologram.Compiler.HelpersTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, Helpers, Transformer}

  alias Hologram.Compiler.IR.{
    AtomType,
    Binding,
    Block,
    FunctionDefinition,
    FunctionDefinitionVariants,
    IntegerType,
    MapAccess,
    ModuleDefinition,
    ParamAccess,
    UseDirective,
    Variable
  }

  test "aggregate_bindings_from_expression/1" do
    result =
      "%{a: x, b: y}"
      |> ast()
      |> Transformer.transform(%Context{})
      |> Helpers.aggregate_bindings_from_expression()

    expected = [
      %Binding{
        access_path: [
          %MapAccess{
            key: %AtomType{value: :a}
          }
        ],
        name: :x
      },
      %Binding{
        access_path: [
          %MapAccess{
            key: %AtomType{value: :b}
          }
        ],
        name: :y
      }
    ]

    assert result == expected
  end

  describe "aggregate_bindings_from_params/1" do
    test "no bindings" do
      # def test(1, 2) do
      # end

      params_ast = [1, 2]
      params = Helpers.transform_params(params_ast, %Context{})
      result = Helpers.aggregate_bindings_from_params(params)

      assert result == []
    end

    test "single binding in single param" do
      # def test(1, %{a: x}) do
      # end

      params_ast = [1, {:%{}, [line: 2], [a: {:x, [line: 2], nil}]}]
      params = Helpers.transform_params(params_ast, %Context{})
      result = Helpers.aggregate_bindings_from_params(params)

      expected = [
        %Binding{
          name: :x,
          access_path: [
            %ParamAccess{index: 1},
            %MapAccess{key: %AtomType{value: :a}}
          ]
        }
      ]

      assert result == expected
    end

    test "multiple bindings in single param" do
      # def test(1, %{a: x, b: y}) do
      # end

      params_ast = [1, {:%{}, [line: 2], [a: {:x, [line: 2], nil}, b: {:y, [line: 2], nil}]}]
      params = Helpers.transform_params(params_ast, %Context{})
      result = Helpers.aggregate_bindings_from_params(params)

      expected = [
        %Binding{
          name: :x,
          access_path: [
            %ParamAccess{index: 1},
            %MapAccess{key: %AtomType{value: :a}}
          ]
        },
        %Binding{
          name: :y,
          access_path: [
            %ParamAccess{index: 1},
            %MapAccess{key: %AtomType{value: :b}}
          ]
        }
      ]

      assert result == expected
    end

    test "multiple bindings in multiple params" do
      # def test(1, %{a: k, b: m}, 2, %{c: s, d: t}) do
      # end

      params_ast = [
        1,
        {:%{}, [line: 2], [a: {:k, [line: 2], nil}, b: {:m, [line: 2], nil}]},
        2,
        {:%{}, [line: 2], [c: {:s, [line: 2], nil}, d: {:t, [line: 2], nil}]}
      ]

      params = Helpers.transform_params(params_ast, %Context{})
      result = Helpers.aggregate_bindings_from_params(params)

      expected = [
        %Binding{
          name: :k,
          access_path: [
            %ParamAccess{index: 1},
            %MapAccess{key: %AtomType{value: :a}}
          ]
        },
        %Binding{
          name: :m,
          access_path: [
            %ParamAccess{index: 1},
            %MapAccess{key: %AtomType{value: :b}}
          ]
        },
        %Binding{
          name: :s,
          access_path: [
            %ParamAccess{index: 3},
            %MapAccess{key: %AtomType{value: :c}}
          ]
        },
        %Binding{
          name: :t,
          access_path: [
            %ParamAccess{index: 3},
            %MapAccess{key: %AtomType{value: :d}}
          ]
        }
      ]

      assert result == expected
    end

    test "sorting" do
      # def test(y, z) do
      # end

      params_ast = [{:y, [line: 2], nil}, {:x, [line: 2], nil}]
      params = Helpers.transform_params(params_ast, %Context{})
      result = Helpers.aggregate_bindings_from_params(params)

      expected = [
        %Binding{
          name: :x,
          access_path: [
            %ParamAccess{index: 1}
          ]
        },
        %Binding{
          name: :y,
          access_path: [
            %ParamAccess{index: 0}
          ]
        }
      ]

      assert result == expected
    end
  end

  describe "aggregate_function_def_variants/1" do
    test "single function with single variant" do
      function_defs = [
        %FunctionDefinition{
          bindings: [
            %Binding{name: :a, access_path: [%ParamAccess{index: 0}]}
          ],
          body: %Block{expressions: [
            %IntegerType{value: 1}
          ]},
          name: :test,
          params: [
            %Variable{name: :a}
          ]
        }
      ]

      result = Helpers.aggregate_function_def_variants(function_defs)

      expected = %{test: %FunctionDefinitionVariants{name: :test, variants: function_defs}}

      assert result == expected
    end

    test "single function with multiple variants" do
      function_def_1 = %FunctionDefinition{
        bindings: [
          %Binding{name: :a, access_path: [%ParamAccess{index: 0}]}
        ],
        body: %Block{expressions: [
          %IntegerType{value: 1}
        ]},
        name: :test,
        params: [
          %Variable{name: :a}
        ]
      }

      function_def_2 = %FunctionDefinition{
        bindings: [
          %Binding{name: :a, access_path: [%ParamAccess{index: 0}]},
          %Binding{name: :b, access_path: [%ParamAccess{index: 1}]}
        ],
        body: %Block{expressions: [
          %IntegerType{value: 2}
        ]},
        name: :test,
        params: [
          %Variable{name: :a},
          %Variable{name: :b}
        ]
      }

      function_defs = [function_def_1, function_def_2]
      result = Helpers.aggregate_function_def_variants(function_defs)

      expected = %{test: %FunctionDefinitionVariants{name: :test, variants: function_defs}}

      assert result == expected
    end

    test "multiple functions with single variant" do
      function_def_1 = %FunctionDefinition{
        bindings: [
          %Binding{name: :a, access_path: [%ParamAccess{index: 0}]}
        ],
        body: %Block{expressions: [
          %IntegerType{value: 1}
        ]},
        name: :test_1,
        params: [
          %Variable{name: :a}
        ]
      }

      function_def_2 = %FunctionDefinition{
        bindings: [
          %Binding{name: :a, access_path: [%ParamAccess{index: 0}]}
        ],
        body: %Block{expressions: [
          %IntegerType{value: 2}
        ]},
        name: :test_2,
        params: [
          %Variable{name: :a}
        ]
      }

      function_defs = [function_def_1, function_def_2]
      result = Helpers.aggregate_function_def_variants(function_defs)

      expected = %{
        test_1: %FunctionDefinitionVariants{name: :test_1, variants: [function_def_1]},
        test_2: %FunctionDefinitionVariants{name: :test_2, variants: [function_def_2]}
      }

      assert result == expected
    end

    test "multiple functions with multiple variants" do
      function_def_1 = %FunctionDefinition{
        bindings: [
          %Binding{name: :a, access_path: [%ParamAccess{index: 0}]}
        ],
        body: %Block{expressions: [
          %IntegerType{value: 1}
        ]},
        name: :test_1,
        params: [
          %Variable{name: :a}
        ]
      }

      function_def_2 = %FunctionDefinition{
        bindings: [
          %Binding{name: :a, access_path: [%ParamAccess{index: 0}]},
          %Binding{name: :b, access_path: [%ParamAccess{index: 1}]}
        ],
        body: %Block{expressions: [
          %IntegerType{value: 2}
        ]},
        name: :test_1,
        params: [
          %Variable{name: :a},
          %Variable{name: :b}
        ]
      }

      function_def_3 = %FunctionDefinition{
        bindings: [
          %Binding{name: :a, access_path: [%ParamAccess{index: 0}]}
        ],
        body: %Block{expressions: [
          %IntegerType{value: 3}
        ]},
        name: :test_2,
        params: [
          %Variable{name: :a}
        ]
      }

      function_defs = [function_def_1, function_def_2, function_def_3]
      result = Helpers.aggregate_function_def_variants(function_defs)

      expected = %{
        test_1: %FunctionDefinitionVariants{
          name: :test_1,
          variants: [function_def_1, function_def_2]
        },
        test_2: %FunctionDefinitionVariants{name: :test_2, variants: [function_def_3]}
      }

      assert result == expected
    end
  end

  test "class_name/1" do
    assert Helpers.class_name(Abc.Bcd) == "Elixir_Abc_Bcd"
  end

  test "get_components/1" do
    module_def_1 = %ModuleDefinition{
      module: Bcd.Cde,
      component?: true,
      uses: [
        %UseDirective{module: Hologram.Component}
      ]
    }

    module_def_2 = %ModuleDefinition{
      module: Def.Efg,
      component?: true,
      uses: [
        %UseDirective{module: Hologram.Component}
      ]
    }

    module_defs_map = %{
      Abc.Bcd => %ModuleDefinition{uses: []},
      Bcd.Cde => module_def_1,
      Cde.Def => %ModuleDefinition{uses: []},
      Def.Efg => module_def_2
    }

    result = Helpers.get_components(module_defs_map)
    expected = [module_def_1, module_def_2]

    assert result == expected
  end

  test "get_pages/1" do
    module_def_1 = %ModuleDefinition{
      module: Bcd.Cde,
      page?: true,
      uses: [
        %UseDirective{module: Hologram.Page}
      ]
    }

    module_def_2 = %ModuleDefinition{
      module: Def.Efg,
      page?: true,
      uses: [
        %UseDirective{module: Hologram.Page}
      ]
    }

    module_defs_map = %{
      Abc.Bcd => %ModuleDefinition{uses: []},
      Bcd.Cde => module_def_1,
      Cde.Def => %ModuleDefinition{uses: []},
      Def.Efg => module_def_2
    }

    result = Helpers.get_pages(module_defs_map)
    expected = [module_def_1, module_def_2]

    assert result == expected
  end

  test "module/1" do
    result = Helpers.module([:Hologram, :Compiler, :HelpersTest])
    expected = Elixir.Hologram.Compiler.HelpersTest
    assert result == expected
  end

  test "module_name/1" do
    assert Helpers.module_name(Abc.Bcd) == "Abc.Bcd"
  end

  describe "module_name_segments/1" do
    test "module" do
      assert Helpers.module_name_segments(Abc.Bcd) == [:Abc, :Bcd]
    end

    test "string" do
      assert Helpers.module_name_segments("Abc.Bcd") == [:Abc, :Bcd]
    end
  end

  describe "transform_params/2" do
    test "no params" do
      # def test do
      # end

      params = nil
      result = Helpers.transform_params(params, %Context{})

      assert result == []
    end

    test "vars" do
      # def test(a, b) do
      # end

      params = [{:a, [line: 1], nil}, {:b, [line: 1], nil}]
      result = Helpers.transform_params(params, %Context{})

      expected = [
        %Variable{name: :a},
        %Variable{name: :b}
      ]

      assert result == expected
    end

    test "primitive types" do
      # def test(:a, 2) do
      # end

      params = [:a, 2]
      result = Helpers.transform_params(params, %Context{})

      expected = [
        %AtomType{value: :a},
        %IntegerType{value: 2}
      ]

      assert result == expected
    end
  end

  describe "uses_module?/2" do
    @used_module Hologram.Commons.Parser

    test "true" do
      user_module = %ModuleDefinition{
        uses: [
          %UseDirective{
            module: @used_module
          }
        ]
      }

      assert Helpers.uses_module?(user_module, @used_module)
    end

    test "false" do
      user_module = %ModuleDefinition{uses: []}
      refute Helpers.uses_module?(user_module, @used_module)
    end
  end
end


<|start_filename|>test/unit/hologram/compiler/map_key_encoders/atom_type_test.exs<|end_filename|>
defmodule Hologram.Compiler.MapKeyEncoder.AtomTypeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, MapKeyEncoder, Opts}
  alias Hologram.Compiler.IR.AtomType

  test "encode/3" do
    ir = %AtomType{value: :test}

    result = MapKeyEncoder.encode(ir, %Context{}, %Opts{})
    expected = "~atom[test]"

    assert result == expected
  end
end


<|start_filename|>assets/js/hologram/interpreter.js<|end_filename|>
"use strict";

import { HologramNotImplementedError } from "./errors";
import Enum from "./elixir/enum"
import Kernel from "./elixir/kernel"
import List from "./elixir/list";
import Map from "./elixir/map"
import Type from "./type"
import Utils from "./utils"

export default class Interpreter {
  static $addition_operator(left, right) {
    const type = left.type === "integer" && right.type === "integer" ? "integer" : "float"
    const result = left.value + right.value
    return Utils.freeze({type: type, value: result})
  }

  static _areNumbersEqual(num1, num2) {
    if (Type.isNumber(num1) && Type.isNumber(num2)) {
      return num1.value == num2.value
    } else {
      return false
    }
  }

  static $case_expression(condition, clausesAnonFun) {
    const result = clausesAnonFun(condition)
    return Utils.freeze(result)
  }

  static $cons_operator(head, tail) {
    return List.insert_at(tail, 0, head)
  }

  // TODO: raise ArithmeticError if second argument is 0 or 0.0
  // see: https://github.com/bartblast/hologram/issues/67
  static $division_operator(left, right) {
    const result = Type.float(left.value / right.value)
    return Utils.freeze(result)
  }

  static $dot_operator(left, right) {
    return left.data[Type.encodedKey(right)]
  }

  static $equal_to_operator(left, right) {
    let value;

    switch (left.type) {        
      case "float":
      case "integer":
        value = Interpreter._areNumbersEqual(left, right)
        break;

      default:
        value = left.type === right.type && left.value === right.value
        break;
    }

    return Type.boolean(value)
  }

  static $if_expression(condition, doClause, elseClause) {
    let result;

    if (Type.isTruthy(condition())) {
      result = doClause()
    } else {
      result = elseClause()
    }

    return Utils.freeze(result)
  }

  static isConsOperatorPatternMatched(left, right) {
    if (right.type !== 'list') {
      return false
    }

    if (right.data.length === 0) {
      return false
    }

    if (!Interpreter.isPatternMatched(left.head, Kernel.hd(right))) {
      return false
    }

    if (!Interpreter.isPatternMatched(left.tail, Kernel.tl(right))) {
      return false
    }

    return true
  }

  static isEnumPatternMatched(left, right) {
    if (left.data.length !== right.data.length) {
      return false;
    }

    for (let i = 0; i < left.data.length; ++i) {
      if (!Interpreter.isPatternMatched(left.data[i], right.data[i])) {
        return false
      }
    }

    return true
  }

  static isFunctionArgsPatternMatched(params, args) {
    if (args.length !== params.length) {
      return false;
    }

    for (let i = 0; i < args.length; ++ i) {
      if (!Interpreter.isPatternMatched(params[i], args[i])) {
        return false;
      }
    }

    return true;
  }

  static isMapPatternMatched(left, right) {
    for (const key of Map.keys(left).data) {
      if (Map.has_key$question(right, key)) {
        if (!Interpreter.isPatternMatched(Map.get(left, key), Map.get(right, key))) {
          return false
        }
      } else {
        return false
      }
    }

    return true 
  }

  static isPatternMatched(left, right) {
    const lType = left.type;
    const rType = right.type;

    if (lType === "placeholder") {
      return true;
    }

    if (lType === 'cons_operator_pattern') {
      return Interpreter.isConsOperatorPatternMatched(left, right)
    }

    if (lType !== rType) {
      return false;
    }

    switch (lType) {
      case "atom":
      case "integer":
        return left.value === right.value;

      case "list":
      case "tuple":
        return Interpreter.isEnumPatternMatched(left, right)        

      case "map":
        return Interpreter.isMapPatternMatched(left, right)

      default:
        const message = `Interpreter.isPatternMatched(): left = ${JSON.stringify(left)}`
        throw new HologramNotImplementedError(message)
    }
  }

  // TODO: take into account other types (for now it works for integers and floats only)
  static $less_than_operator(left, right) {
    if (!Type.isNumber(left)) {
      const message = `Interpreter.$less_than_operator(): left = ${JSON.stringify(left)}`
      throw new HologramNotImplementedError(message)
    }

    if (!Type.isNumber(right)) {
      const message = `Interpreter.$less_than_operator(): right = ${JSON.stringify(right)}`
      throw new HologramNotImplementedError(message)
    }

    return Type.boolean(left.value < right.value)
  }

  static $list_concatenation_operator(left, right) {
    const result = Type.list(left.data.concat(right.data))
    return Utils.freeze(result)
  }

  static $list_subtraction_operator(left, right) {
    const rightElems = Utils.clone(right.data)
    const resultElems = []

    for (let leftElem of left.data) {
      let isLeftElemPreserved = true
      let rightElemIndex = rightElems.findIndex(rightElem => Utils.isEqual(rightElem, leftElem))

      if (rightElemIndex != -1) {
        isLeftElemPreserved = false
        rightElems.splice(rightElemIndex, 1)
      }

      if (isLeftElemPreserved) {
        resultElems.push(leftElem)
      }
    }

    return Type.list(resultElems)
  }

  static $membership_operator(left, right) {
    return Enum.member$question(right, left)
  }

  static $multiplication_operator(left, right) {
    const type = left.type === "integer" && right.type === "integer" ? "integer" : "float"
    const result = left.value * right.value
    return Utils.freeze({type: type, value: result})
  }

  static $not_equal_to_operator(left, right) {
    const isEqualTo = Interpreter.$equal_to_operator(left, right)
    return Type.boolean(!isEqualTo.value)
  }

  static $relaxed_boolean_and_operator(left, right) {
    if (Type.isTruthy(left)) {
      return right
    } else {
      return left
    }
  }

  static $relaxed_boolean_not_operator(value) {
    if (Type.isFalsy(value)) {
      return Type.boolean(true)
    } else {
      return Type.boolean(false)
    }
  }

  static $relaxed_boolean_or_operator(left, right) {
    if (Type.isTruthy(left)) {
      return left
    } else {
      return right
    }
  }

  static $subtraction_operator(left, right) {
    const type = left.type === "integer" && right.type === "integer" ? "integer" : "float"
    const result = left.value - right.value
    return Utils.freeze({type: type, value: result})
  }

  static $type_operator(value, type) {
    if (Type.isString(value) && type === "binary") {
      return value
    } else {
      const message = `Interpreter.$type_operator(): value = ${JSON.stringify(value)}, type = ${JSON.stringify(type)}`
      throw new HologramNotImplementedError(message)
    }
  }

  static $unary_negative_operator(boxedValue) {
    return Utils.freeze({type: boxedValue.type, value: -boxedValue.value})
  }
}

<|start_filename|>lib/hologram/compiler/js_encoders/unary_negative_operator.ex<|end_filename|>
alias Hologram.Compiler.{Context, JSEncoder, Opts}
alias Hologram.Compiler.IR.UnaryNegativeOperator

defimpl JSEncoder, for: UnaryNegativeOperator do
  def encode(%{value: value}, %Context{} = context, %Opts{} = opts) do
    value = JSEncoder.encode(value, context, opts)
    "Hologram.Interpreter.$unary_negative_operator(#{value})"
  end
end


<|start_filename|>test/unit/hologram/compiler/evaluator_test.exs<|end_filename|>
defmodule Hologram.Compiler.EvaluatorTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Compiler.Evaluator

  @bindings %{a: 1, b: 2, c: 3}

  test "evaluates addition expression" do
    code = "1 + @a + @c"
    ast = ast(code)
    result = Evaluator.evaluate(ast, @bindings)

    assert result == 5
  end

  test "evaluated if expression" do
    code = "if @a, do: @b, else: @c"
    ast = ast(code)
    result = Evaluator.evaluate(ast, @bindings)

    assert result == 2
  end

  test "injects bindings key into bindings" do
    code = "1 + @bindings.c"
    ast = ast(code)
    result = Evaluator.evaluate(ast, @bindings)

    assert result == 4
  end
end


<|start_filename|>test/unit/fixtures/commons/memory_store.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Commons.MemoryStore do
  use Hologram.Commons.MemoryStore

  @dump_path "#{File.cwd!()}/tmp/test_fixture_memory_store.bin"

  @impl true
  def table_name, do: :test_fixture_memory_store

  @impl true
  def populate_table do
    populate_table_from_file(@dump_path)
  end

  def dump_path, do: @dump_path
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/function_definition.ex<|end_filename|>
alias Hologram.Compiler.IR.FunctionDefinition
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: FunctionDefinition do
  def aggregate(%{body: body}) do
    ModuleDefAggregator.aggregate(body)
  end
end


<|start_filename|>test/unit/hologram/compiler/map_key_encoders/boolean_type_test.exs<|end_filename|>
defmodule Hologram.Compiler.MapKeyEncoder.BooleanTypeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, MapKeyEncoder, Opts}
  alias Hologram.Compiler.IR.BooleanType

  test "encode/3" do
    ir = %BooleanType{value: true}

    result = MapKeyEncoder.encode(ir, %Context{}, %Opts{})
    expected = "~boolean[true]"

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/block_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.BlockTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Config, Context, JSEncoder, Opts}

  alias Hologram.Compiler.IR.{
    AtomType,
    Binding,
    Block,
    IntegerType,
    MatchAccess,
    MatchOperator,
    Variable
  }

  @match_access_js Config.match_access_js()

  test "single expression" do
    ir = %Block{expressions: [%AtomType{value: :a}]}

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "return { type: 'atom', value: 'a' };"

    assert result == expected
  end

  test "multiple expressions / block ending with expression other than match operator" do
    ir = %Block{
      expressions: [
        %AtomType{value: :a},
        %AtomType{value: :b}
      ]
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    { type: 'atom', value: 'a' };
    return { type: 'atom', value: 'b' };\
    """

    assert result == expected
  end

  test "bindings in block scope are kept track of" do
    # code:
    # x = 1
    # x = 2
    # 3

    ir = %Block{
      expressions: [
        %MatchOperator{
          bindings: [
            %Binding{name: :x, access_path: [%MatchAccess{}]}
          ],
          left: %Variable{name: :x},
          right: %IntegerType{value: 1}
        },
        %MatchOperator{
          bindings: [
            %Binding{name: :x, access_path: [%MatchAccess{}]}
          ],
          left: %Variable{name: :x},
          right: %IntegerType{value: 2}
        },
        %IntegerType{value: 3}
      ]
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    #{@match_access_js} = { type: 'integer', value: 1 };
    let x = #{@match_access_js};
    #{@match_access_js} = { type: 'integer', value: 2 };
    x = #{@match_access_js};
    return { type: 'integer', value: 3 };\
    """

    assert result == expected
  end

  test "block ending with match operator expression" do
    ir = %Block{
      expressions: [
        %AtomType{value: :a},
        %MatchOperator{
          bindings: [
            %Binding{name: :x, access_path: [%MatchAccess{}]}
          ],
          left: %Variable{name: :x},
          right: %IntegerType{value: 1}
        }
      ]
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    { type: 'atom', value: 'a' };
    #{@match_access_js} = { type: 'integer', value: 1 };
    let x = #{@match_access_js};
    return #{@match_access_js};\
    """

    assert result == expected
  end
end


<|start_filename|>assets/js/hologram/elixir/enum.js<|end_filename|>
"use strict";

import { HologramNotImplementedError } from "../errors";
import Map from "./map"
import Type from "../type";
import Utils from "../utils";

export default class Enum {
  static concat(left, right) {
    const leftList = Enum.to_list(left)
    const rightList = Enum.to_list(right)

    return Type.list(leftList.data.concat(rightList.data))
  }

  // TODO: support Enum.count/2 and any data type that supports Enumerable protocol
  static count(enumerable) {
    return Type.integer(enumerable.data.length)
  }

  static member$question(enumerable, elem) {
    switch (enumerable.type) {
      case "list":
        if (enumerable.data.find(el => Utils.isEqual(el, elem))) {
          return Type.boolean(true)
        } else {
          return Type.boolean(false)
        }

      default: 
        const message = `Enum.member$question(): enumerable = ${JSON.stringify(enumerable)}, elem = ${JSON.stringify(elem)}`
        throw new HologramNotImplementedError(message)
    }
  }

  static reduce(enumerable, initialAcc, fun) {
    const reducer = (acc, elem) => fun.callback(elem, acc)
    return enumerable.data.reduce(reducer, initialAcc);
  }
  
  static to_list(enumerable) {
    switch (enumerable.type) {
      case "list":
        return enumerable

      case "map":
        const data = Map.keys(enumerable).data.reduce((acc, key) => {
          acc.push(Type.tuple([key, Map.get(enumerable, key)]))
          return acc
        }, [])
        return Type.list(data)

      default: 
        const message = `Enum.to_list(): enumerable = ${JSON.stringify(enumerable)}`
        throw new HologramNotImplementedError(message)      
    }
  }
}

<|start_filename|>lib/hologram/compiler/transformers/list_type_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.ListTypeTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.ListType

  def transform(ast, %Context{} = context) do
    data = Enum.map(ast, &Transformer.transform(&1, context))
    %ListType{data: data}
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/elixir_map.ex<|end_filename|>
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: Map do
  def aggregate(map) do
    map
    |> Map.to_list()
    |> ModuleDefAggregator.aggregate()
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/module_definition_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.ModuleDefinitionTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.{
    AdditionOperator,
    AliasDirective,
    AtomType,
    Binding,
    Block,
    FunctionDefinition,
    ImportDirective,
    IntegerType,
    ListType,
    MacroDefinition,
    ModuleDefinition,
    ModuleAttributeDefinition,
    ModuleType,
    ParamAccess,
    Quote,
    RequireDirective,
    TupleType,
    UseDirective,
    Variable
  }

  alias Hologram.Compiler.ModuleDefinitionTransformer

  @module_1 Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1
  @module_2 Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module2
  @module_4 Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module4

  test "module" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1 do
    end
    """

    ast = ast(code)

    assert %ModuleDefinition{module: module} = ModuleDefinitionTransformer.transform(ast)
    assert module == @module_1
  end

  test "use directive expansion" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module3 do
      use Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module2
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    expected = [
      %ImportDirective{
        module: @module_1,
        only: []
      }
    ]

    assert result.imports == expected
  end

  test "macro expansion" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module3 do
      require Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module5
      test_macro(123)
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    expected = %FunctionDefinition{
      arity: 1,
      bindings: [
        %Binding{name: :b, access_path: [%ParamAccess{index: 0}]}
      ],
      body: %Block{expressions: [
        %AdditionOperator{
          left: %IntegerType{value: 123},
          right: %Variable{name: :b}
        }
      ]},
      module: Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module3,
      name: :test_function,
      params: [%Variable{name: :b}],
      visibility: :public
    }

    assert Enum.member?(result.functions, expected)
  end

  test "__MODULE__ pseudo-variable expansion" do
    code = """
    defmodule Abc.Bcd do
      def test do
        __MODULE__
      end
    end
    """

    ast = ast(code)
    result = ModuleDefinitionTransformer.transform(ast)

    expected = %FunctionDefinition{
      arity: 0,
      bindings: [],
      body: %Block{expressions: [
        %ModuleType{module: Abc.Bcd}
      ]},
      module: Abc.Bcd,
      name: :test,
      params: [],
      visibility: :public
    }

    assert Enum.member?(result.functions, expected)
  end

  test "uses" do
    code = """
    defmodule Hologram.Test.Fixtures.PlaceholderModule1 do
      use Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module2
      use Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module4
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    expected = [
      %UseDirective{module: @module_2},
      %UseDirective{module: @module_4}
    ]

    assert result.uses == expected
  end

  test "imports" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module3 do
      import Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1
      import Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module2
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    expected = [
      %ImportDirective{module: @module_1, only: []},
      %ImportDirective{module: @module_2, only: []}
    ]

    assert result.imports == expected
  end

  test "requires" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module3 do
      require Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1
      require Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module2
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    expected = [
      %RequireDirective{module: @module_1},
      %RequireDirective{module: @module_2}
    ]

    assert result.requires == expected
  end

  test "aliases" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module3 do
      alias Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1
      alias Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module2
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    expected = [
      %AliasDirective{module: @module_1, as: [:Module1]},
      %AliasDirective{module: @module_2, as: [:Module2]}
    ]

    assert result.aliases == expected
  end

  test "multi-aliases" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module3 do
      alias Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.{Module1, Module2}
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    expected = [
      %AliasDirective{module: @module_1, as: [:Module1]},
      %AliasDirective{module: @module_2, as: [:Module2]}
    ]

    assert result.aliases == expected
  end

  test "attributes" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1 do
      @x 1
      @y 2
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    expected = [
      %ModuleAttributeDefinition{
        name: :x,
        value: %IntegerType{value: 1}
      },
      %ModuleAttributeDefinition{
        name: :y,
        value: %IntegerType{value: 2}
      }
    ]

    assert result.attributes == expected
  end

  test "public functions" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1 do
      def test_1 do
        1
      end

      def test_2 do
        2
      end
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    expected_1 = %FunctionDefinition{
      arity: 0,
      bindings: [],
      body: %Block{expressions: [
        %IntegerType{value: 1}
      ]},
      module: Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1,
      name: :test_1,
      params: [],
      visibility: :public
    }

    expected_2 = %FunctionDefinition{
      arity: 0,
      bindings: [],
      body: %Block{expressions: [
        %IntegerType{value: 2}
      ]},
      module: Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1,
      name: :test_2,
      params: [],
      visibility: :public
    }

    assert Enum.count(result.functions) == 3
    assert Enum.member?(result.functions, expected_1)
    assert Enum.member?(result.functions, expected_2)
  end

  test "private functions" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1 do
      defp test_1 do
        1
      end

      defp test_2 do
        2
      end
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    expected_1 = %FunctionDefinition{
      arity: 0,
      bindings: [],
      body: %Block{expressions: [
        %IntegerType{value: 1}
      ]},
      module: Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1,
      name: :test_1,
      params: [],
      visibility: :private
    }

    expected_2 = %FunctionDefinition{
      arity: 0,
      bindings: [],
      body: %Block{expressions: [
        %IntegerType{value: 2}
      ]},
      module: Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1,
      name: :test_2,
      params: [],
      visibility: :private
    }

    assert Enum.count(result.functions) == 3
    assert Enum.member?(result.functions, expected_1)
    assert Enum.member?(result.functions, expected_2)
  end

  test "function heads" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1 do
      def test_1(a)

      def test_1(a) do
        1
      end

      defp test_2(b)

      defp test_2(b) do
        2
      end
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    assert Enum.count(result.functions) == 3
  end

  test "__info__/1 module callback injection" do
    code = """
    defmodule Hologram.Test.Fixtures.PlaceholderModule1 do
      def test_1, do: 1
      def test_2(9), do: 9
      def test_2(8), do: 8
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    expected = %FunctionDefinition{
      arity: 1,
      bindings: [],
      body: %Block{expressions: [
        %ListType{
          data: [
            %TupleType{
              data: [
                %AtomType{value: :test_1},
                %IntegerType{value: 0}
              ]
            },
            %TupleType{
              data: [
                %AtomType{value: :test_2},
                %IntegerType{value: 1}
              ]
            }
          ]
        }
      ]},
      module: Hologram.Test.Fixtures.PlaceholderModule1,
      name: :__info__,
      params: [%AtomType{value: :functions}],
      visibility: :public
    }

    assert Enum.member?(result.functions, expected)
  end

  test "macros" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1 do
      defmacro test_1 do
        quote do
          1
        end
      end

      defmacro test_2 do
        quote do
          2
        end
      end
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    expected = [
      %MacroDefinition{
        module: @module_1,
        name: :test_1,
        arity: 0,
        body: %Block{expressions: [
          %Quote{body: %Block{expressions: [
            %IntegerType{value: 1}
          ]}}
        ]}
      },
      %MacroDefinition{
        module: @module_1,
        name: :test_2,
        arity: 0,
        body: %Block{expressions: [
          %Quote{body: %Block{expressions: [
            %IntegerType{value: 2}
          ]}}
        ]}
      }
    ]

    assert result.macros == expected
  end

  test "component module" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1 do
      use Hologram.Component
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    assert result.component?
    refute result.layout?
    refute result.page?
    assert result.templatable?
  end

  test "layout module" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1 do
      use Hologram.Layout
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    refute result.component?
    assert result.layout?
    refute result.page?
    assert result.templatable?
  end

  test "page module" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1 do
      use Hologram.Page
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    refute result.component?
    refute result.layout?
    assert result.page?
    assert result.templatable?
  end

  test "regular module" do
    code = """
    defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module1 do
    end
    """

    ast = ast(code)
    assert %ModuleDefinition{} = result = ModuleDefinitionTransformer.transform(ast)

    refute result.component?
    refute result.layout?
    refute result.page?
    refute result.templatable?
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/module_type.ex<|end_filename|>
alias Hologram.Compiler.IR.ModuleType
alias Hologram.Compiler.{ModuleDefAggregator, ModuleDefStore}

defimpl ModuleDefAggregator, for: ModuleType do
  alias Hologram.Compiler.Reflection
  alias Hologram.Runtime.TemplateStore
  alias Hologram.Utils

  def aggregate(%{module: module}) do
    if module_def = maybe_get_module_def(module) do
      aggregate_from_function_defs(module_def.functions)
      |> maybe_aggregate_from_template(module_def)
      |> Utils.await_tasks()
    end
  end

  defp aggregate_from_function_defs(function_defs) do
    function_defs
    |> Enum.map(&Task.async(fn -> ModuleDefAggregator.aggregate(&1) end))
  end

  defp maybe_aggregate_from_template(tasks, module_def) do
    if module_def.templatable? do
      task =
        Task.async(fn ->
          TemplateStore.get!(module_def.module)
          |> ModuleDefAggregator.aggregate()
        end)

      [task | tasks]
    else
      tasks
    end
  end

  defp maybe_get_module_def(module) do
    if Reflection.is_ignored_module?(module) || Reflection.standard_lib?(module) do
      nil
    else
      ModuleDefStore.get_if_not_exists(module)
    end
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/module_type_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.ModuleTypeTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, ModuleTypeTransformer}
  alias Hologram.Compiler.IR.{AliasDirective, ModuleType}

  test "non-aliased module segments" do
    code = "Abc.Bcd"
    ast = ast(code)

    result = ModuleTypeTransformer.transform(ast, %Context{})
    expected = %ModuleType{module: Abc.Bcd}

    assert result == expected
  end

  test "aliased module segments" do
    aliases = [%AliasDirective{module: Abc.Bcd, as: [:Bcd]}]
    context = %Context{aliases: aliases}

    code = "Bcd"
    ast = ast(code)

    result = ModuleTypeTransformer.transform(ast, context)
    expected = %ModuleType{module: Abc.Bcd}

    assert result == expected
  end

  test "module atom" do
    result = ModuleTypeTransformer.transform(Abc.Bcd, %Context{})
    expected = %ModuleType{module: Abc.Bcd}

    assert result == expected
  end
end


<|start_filename|>test/e2e/test/templates_test.exs<|end_filename|>
defmodule HologramE2E.TemplatesTest do
  use HologramE2E.TestCase, async: false

  describe "if directive" do
    feature "element node", %{session: session} do
      session
      |> visit("/e2e/page-9")
      |> assert_has(css("#div-1", text: "Element displayed"))
      |> refute_has(css("#div-2", text: "Element not displayed"))
    end
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/atom_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.AtomType

defimpl CallGraphBuilder, for: AtomType do
  def build(_, _, _, _), do: nil
end


<|start_filename|>test/js/elixir/list_test.js<|end_filename|>
"use strict";

import { assert, cleanup } from "../support/commons";
beforeEach(() => cleanup());

import List from "../../../assets/js/hologram/elixir/list";
import Type from "../../../assets/js/hologram/type";

describe("insert_at()", () => {
  let list;

  beforeEach(() => {
    list = Type.list([Type.integer(1), Type.integer(2)]);
  });

  it("inserts at the beginning of the list", () => {
    const result = List.insert_at(list, 0, Type.integer(3))
    const expected = Type.list([Type.integer(3), Type.integer(1), Type.integer(2)])

    assert.deepStrictEqual(result, expected);
  })

  it("inserts in the middle of the list", () => {
    const result = List.insert_at(list, 1, Type.integer(3))
    const expected = Type.list([Type.integer(1), Type.integer(3), Type.integer(2)])

    assert.deepStrictEqual(result, expected);
  })

  it("inserts at the end of the list when index is equal to the lenght of the list", () => {
    const result = List.insert_at(list, 2, Type.integer(3))
    const expected = Type.list([Type.integer(1), Type.integer(2), Type.integer(3)])

    assert.deepStrictEqual(result, expected);
  })

  it("inserts at the end of the list when index is set to -1", () => {
    const result = List.insert_at(list, -1, Type.integer(3))
    const expected = Type.list([Type.integer(1), Type.integer(2), Type.integer(3)])

    assert.deepStrictEqual(result, expected);
  })

  it("caps the index at the list end", () => {
    const result = List.insert_at(list, 5, Type.integer(3))
    const expected = Type.list([Type.integer(1), Type.integer(2), Type.integer(3)])

    assert.deepStrictEqual(result, expected);
  })

  it("works with negative indices", () => {
    const result = List.insert_at(list, -2, Type.integer(3))
    const expected = Type.list([Type.integer(1),  Type.integer(3), Type.integer(2)])

    assert.deepStrictEqual(result, expected);
  })
})

<|start_filename|>test/unit/fixtures/compiler/resolver/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Resolver.Module2 do
  def test(_a, _b) do
    :ok
  end
end


<|start_filename|>test/unit/fixtures/compiler/call_graph_builder/module_definition/module_5.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.CallGraphBuilder.ModuleDefinition.Module5 do
  use Hologram.Component

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_26.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module26 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module27, warn: false

  route "/test-route-26"

  def template do
    ~H"""
    <Module27 />
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_42.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module42 do
  def test_fun_42a do
    :ok
  end
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/module_type/module_5.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ModuleType.Module5 do
end


<|start_filename|>test/e2e/test/navigation_test.exs<|end_filename|>
defmodule HologramE2E.NavigationTest do
  use HologramE2E.TestCase, async: false

  feature "anchor", %{session: session} do
    session
    |> visit("/e2e/page-5")
    |> click(css("#page-2-link"))
    |> assert_has(css("h1", text: "Page 2"))
    |> click(css("#page-2-update-text-button"))
    |> assert_has(css("#page-2-text", text: "text updated by page 2 update button"))

    assert current_path(session) == "/e2e/page-2"
  end

  feature "back button", %{session: session} do
    session
    |> visit("/e2e/page-5")
    |> click(css("#page-2-link"))
    |> click(css("#page-2-back-button"))
    |> assert_has(css("h1", text: "Page 5"))
    |> click(css("#page-5-update-text-button"))
    |> assert_has(css("#page-5-text", text: "text updated by page 5 update button"))

    assert current_path(session) == "/e2e/page-5"
  end

  feature "forward button", %{session: session} do
    session
    |> visit("/e2e/page-5")
    |> click(css("#page-2-link"))
    |> click(css("#page-2-back-button"))
    |> click(css("#page-5-forward-button"))
    |> assert_has(css("h1", text: "Page 2"))
    |> click(css("#page-2-update-text-button"))
    |> assert_has(css("#page-2-text", text: "text updated by page 2 update button"))

    assert current_path(session) == "/e2e/page-2"
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_9.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module9 do
  use Hologram.Layout
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module10

  def template do
    ~H"""
    """
  end

  def action(:test_9a, _params, _state) do
    Module10.test_fun_10a()
  end
end


<|start_filename|>test/unit/hologram/template/parser_test.exs<|end_filename|>
defmodule Hologram.Template.ParserTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Template.{Parser, SyntaxError}

  defp assert_syntax_error(markup, message) do
    message = "\n#{message}" |> String.replace_trailing("\n", "")

    assert_raise SyntaxError, message, fn ->
      Parser.parse!(markup)
    end
  end

  test "text node" do
    markup = "abc"

    result = Parser.parse!(markup)
    expected = [{:text, "abc"}]

    assert result == expected
  end

  test "regular element node" do
    markup = "<div></div>"

    result = Parser.parse!(markup)
    expected = [{:element, "div", [], []}]

    assert result == expected
  end

  test "regular component node" do
    markup = "<Abc.Bcd></Abc.Bcd>"

    result = Parser.parse!(markup)
    expected = [{:component, "Abc.Bcd", [], []}]

    assert result == expected
  end

  test "closed void element node" do
    markup = "<br />"

    result = Parser.parse!(markup)
    expected = [{:element, "br", [], []}]

    assert result == expected
  end

  test "closed svg element node" do
    markup = "<path />"

    result = Parser.parse!(markup)
    expected = [{:element, "path", [], []}]

    assert result == expected
  end

  test "closed slot element node" do
    markup = "<slot />"

    result = Parser.parse!(markup)
    expected = [{:element, "slot", [], []}]

    assert result == expected
  end

  test "closed void component node" do
    markup = "<Abc.Bcd />"

    result = Parser.parse!(markup)
    expected = [{:component, "Abc.Bcd", [], []}]

    assert result == expected
  end

  test "unclosed void element node" do
    markup = "<br>"

    result = Parser.parse!(markup)
    expected = [{:element, "br", [], []}]

    assert result == expected
  end

  test "unclosed svg element node" do
    markup = "<path>"

    result = Parser.parse!(markup)
    expected = [{:element, "path", [], []}]

    assert result == expected
  end

  test "unclosed slot element node" do
    markup = "<slot>"

    result = Parser.parse!(markup)
    expected = [{:element, "slot", [], []}]

    assert result == expected
  end

  test "unclosed non-void element node" do
    markup = "<div>"

    assert_raise SyntaxError, "div tag is unclosed", fn ->
      Parser.parse!(markup)
    end
  end

  test "unclosed component node" do
    markup = "<Abc.Bcd>"

    assert_raise SyntaxError, "Abc.Bcd tag is unclosed", fn ->
      Parser.parse!(markup)
    end
  end

  test "multiple nodes" do
    markup = "<div></div>abc<Abc.Bcd />"

    result = Parser.parse!(markup)
    expected = [{:element, "div", [], []}, {:text, "abc"}, {:component, "Abc.Bcd", [], []}]

    assert result == expected
  end

  test "nested nodes" do
    markup = "<div><span><Abc.Bcd>abc</Abc.Bcd></span></div>"
    result = Parser.parse!(markup)

    expected = [
      {:element, "div", [],
       [
         {:element, "span", [],
          [
            {:component, "Abc.Bcd", [],
             [
               {:text, "abc"}
             ]}
          ]}
       ]}
    ]

    assert result == expected
  end

  test "single attribute with literal value" do
    markup = "<div class=\"abc\"></div>"

    result = Parser.parse!(markup)
    expected = [{:element, "div", [{:literal, "class", "abc"}], []}]

    assert result == expected
  end

  test "multiple attributes with literal value" do
    markup = "<div class=\"abc\" id=\"xyz\"></div>"
    result = Parser.parse!(markup)

    expected = [
      {:element, "div",
       [
         {:literal, "class", "abc"},
         {:literal, "id", "xyz"}
       ], []}
    ]

    assert result == expected
  end

  test "single attribute with expression value" do
    markup = "<div class={abc}></div>"

    result = Parser.parse!(markup)
    expected = [{:element, "div", [{:expression, "class", "abc"}], []}]

    assert result == expected
  end

  test "multiple attributes with expression value" do
    markup = "<div class={abc} id={xyz}></div>"
    result = Parser.parse!(markup)

    expected = [
      {:element, "div",
       [
         {:expression, "class", "abc"},
         {:expression, "id", "xyz"}
       ], []}
    ]

    assert result == expected
  end

  test "attribute with literal value followed by attribute with expression value" do
    markup = "<div class=\"abc\" id={xyz}></div>"
    result = Parser.parse!(markup)

    expected = [
      {:element, "div",
       [
         {:literal, "class", "abc"},
         {:expression, "id", "xyz"}
       ], []}
    ]

    assert result == expected
  end

  test "attribute with expression value followed by attribute with literal value" do
    markup = "<div class={abc} id=\"xyz\"></div>"
    result = Parser.parse!(markup)

    expected = [
      {:element, "div",
       [
         {:expression, "class", "abc"},
         {:literal, "id", "xyz"}
       ], []}
    ]

    assert result == expected
  end

  test "special characters in attribute with literal value" do
    attr_value = "abc \n \r \t </ /> < > / = { }"
    markup = "<div class=\"#{attr_value}\"></div>"

    result = Parser.parse!(markup)
    expected = [{:element, "div", [{:literal, "class", attr_value}], []}]

    assert result == expected
  end

  test "special characters in attribute with expression value" do
    attr_value = "abc \n \r \t </ /> < > / = \" { }"
    markup = "<div class={#{attr_value}}></div>"

    result = Parser.parse!(markup)
    expected = [{:element, "div", [{:expression, "class", attr_value}], []}]

    assert result == expected
  end

  test "special characters in text node" do
    markup = "abc \n \r \t < > / = \" { }"

    result = Parser.parse!(markup)
    expected = [{:text, markup}]

    assert result == expected
  end

  test "syntax error message" do
    markup = "<div class=\"test\" abcdefghij= 1234567890123456789012345"

    message = """
    s="test" abcdefghij= 12345678901234567890
                        ^
    """

    assert_syntax_error(markup, message)
  end

  test "removes doctype" do
    markup = "<!DoCtYpE html test_1 test_2>content"

    result = Parser.parse!(markup)
    expected = [{:text, "content"}]

    assert result == expected
  end

  test "removes comments" do
    special_chars = "abc \n \r \t < > / = \" { } ! -"
    markup = "aaa<!-- #{special_chars} -->bbb<!-- #{special_chars} -->ccc"

    result = Parser.parse!(markup)
    expected = [{:text, "aaabbbccc"}]

    assert result == expected
  end

  test "handles escaped JS code" do
    js_code = """
    function isPositiveNumber(param) \\{
      if (param > 0) \\{
        return true;
      \\}
      if (param <= 0) \\{
        return false;
      \\}
    \\}
    """

    markup = "<script>#{js_code}</script>"

    result = Parser.parse!(markup)
    expected = [{:element, "script", [], [{:text, js_code}]}]

    assert result == expected
  end

  test "trims leading and trailing whitespaces" do
    markup = "\n\t content \t\n"

    result = Parser.parse!(markup)
    expected = [{:text, "content"}]

    assert result == expected
  end
end


<|start_filename|>lib/hologram/template/builder.ex<|end_filename|>
defmodule Hologram.Template.Builder do
  alias Hologram.Compiler.{Context, Reflection}
  alias Hologram.Template.{Parser, Transformer}
  alias Hologram.Typespecs, as: T
  alias Hologram.Utils

  @doc """
  Returns module's VDOM template.

  ## Examples
      iex> build(MyApp.Homepage)
      [
        %ElementNode{tag: "h1", children: [%TextNode{content: "Homepage Title"}]},
        %TextNode{content: "Footer content"}
      ]
  """
  @spec build(module()) :: list(T.vdom_node())

  def build(module) do
    # DEFER: consider - use module def store, see: https://github.com/segmetric/hologram/issues/7
    module_def = Reflection.module_definition(module)

    context = %Context{
      aliases: module_def.aliases,
      imports: module_def.imports
    }

    module.template()
    |> Parser.parse!()
    |> Transformer.transform(context)
  end

  # DEFER: test
  def build_all(modules) do
    modules
    |> Utils.map_async(&{&1, build(&1)})
    |> Utils.await_tasks()
    |> Enum.into(%{})
  end
end


<|start_filename|>test/unit/fixtures/compiler/transformers/module_definition_transformer/module_5.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module5 do
  defmacro test_macro(a) do
    quote do
      def test_function(b) do
        unquote(a) + b
      end
    end
  end
end


<|start_filename|>assets/js/hologram/elixir/string.js<|end_filename|>
"use strict";

import Type from "../type";

export default class String {
  static to_atom(boxedString) {
    return Type.atom(boxedString.value)
  }
}

<|start_filename|>test/unit/hologram/template/builder_test.exs<|end_filename|>
defmodule Hologram.Template.BuilderTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Template.Builder
  alias Hologram.Template.VDOM.{Component, ElementNode, TextNode}

  @module_1 Hologram.Test.Fixtures.Template.Builder.Module1
  @module_2 Hologram.Test.Fixtures.Template.Builder.Module2

  test "build/1" do
    result = Builder.build(@module_1)
    assert [%ElementNode{}, %TextNode{}, %Component{module: @module_2}] = result
  end
end


<|start_filename|>test/unit/support/unit_case.ex<|end_filename|>
defmodule Hologram.Test.UnitCase do
  use ExUnit.CaseTemplate

  using do
    quote do
      import Hologram.Compiler.Reflection, only: [ast: 1, ir: 1]
      import Hologram.Test.Helpers

      alias Hologram.Utils

      @fixtures_path "#{File.cwd!()}/test/unit/fixtures"
    end
  end
end


<|start_filename|>test/unit/hologram/template/evaluators/addition_operator_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.AdditionOperatorTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.{AdditionOperator, IntegerType}
  alias Hologram.Template.Evaluator

  test "evaluate/2" do
    ir = %AdditionOperator{
      left: %IntegerType{value: 1},
      right: %IntegerType{value: 2}
    }

    result = Evaluator.evaluate(ir, %{})
    assert result == 3
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_34.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module34 do
  use Hologram.Component

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/unit/hologram/template/encoders/expression_test.exs<|end_filename|>
defmodule Hologram.Template.Encoder.ExpressionTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.{AtomType, TupleType}
  alias Hologram.Template.VDOM.Expression
  alias Hologram.Template.Encoder

  test "encode/1" do
    expression = %Expression{
      ir: %TupleType{
        data: [%AtomType{value: "x"}]
      }
    }

    result = Encoder.encode(expression)

    expected =
      "{ type: 'expression', callback: ($bindings) => { return { type: 'tuple', data: [ { type: 'atom', value: 'x' } ] } } }"

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/map_key_encoders/boolean_type.ex<|end_filename|>
alias Hologram.Compiler.IR.BooleanType
alias Hologram.Compiler.MapKeyEncoder

defimpl MapKeyEncoder, for: BooleanType do
  use Hologram.Commons.Encoder

  def encode(%{value: value}, _, _) do
    encode_primitive_key(:boolean, value)
  end
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/module_type/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ModuleType.Module1 do
  alias Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ModuleType.Module2

  def test_fun do
    Module2
  end
end


<|start_filename|>test/unit/fixtures/placeholder_module_6.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.PlaceholderModule6 do
  def test_fun_6a, do: nil
  def test_fun_6b, do: nil
end


<|start_filename|>test/unit/fixtures/template/renderers/component_renderer/module_4.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.ComponentRenderer.Module4 do
  use Hologram.Component

  def init(props, conn) do
    %{
      test_state_1: props.test_prop,
      test_state_2: conn.session.test_session_key
    }
  end

  def template do
    ~H"""
    {@test_state_1}.{@test_state_2}
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/builder/module_3.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Builder.Module3 do
  def test_3, do: nil
end


<|start_filename|>test/unit/hologram/template/evaluators/map_type_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.MapTypeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.{AtomType, IntegerType, MapType}
  alias Hologram.Template.Evaluator

  test "evaluate/2" do
    ir = %MapType{
      data: [
        {%AtomType{value: :a}, %IntegerType{value: 1}},
        {%AtomType{value: :b}, %IntegerType{value: 2}}
      ]
    }

    result = Evaluator.evaluate(ir, %{})
    expected = %{a: 1, b: 2}

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_49.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module49 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module50

  route "/test-route-49"

  def template do
    ~H"""
    """
  end

  def action do
    Module50
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/cons_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.ConsOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{ConsOperatorTransformer, Context}
  alias Hologram.Compiler.IR.{ConsOperator, Variable}

  test "transform/2" do
    code = "[h | t]"
    ast = ast(code)

    result = ConsOperatorTransformer.transform(ast, %Context{})

    expected = %ConsOperator{
      head: %Variable{name: :h},
      tail: %Variable{name: :t}
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/reflection/module_4.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Reflection.Module4 do
  def test_fun(_a, _b), do: nil
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/anonymous_function_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.AnonymousFunctionType
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: AnonymousFunctionType do
  def aggregate(%{params: params, body: body}) do
    ModuleDefAggregator.aggregate(params)
    ModuleDefAggregator.aggregate(body)
  end
end


<|start_filename|>test/unit/fixtures/runtime/page/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Runtime.Page.Module2 do
  use Hologram.Page

  layout :test_layout

  route "/test-route-2"
end


<|start_filename|>test/unit/hologram/compiler/call_graph_builder/block_test.exs<|end_filename|>
defmodule Hologram.Compiler.CallGraphBuilder.BlockTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{CallGraph, CallGraphBuilder}
  alias Hologram.Compiler.IR.{Block, ModuleType}
  alias Hologram.Test.Fixtures.{PlaceholderModule1, PlaceholderModule2, PlaceholderModule3}

  @module_defs %{}
  @templates %{}

  setup do
    CallGraph.run()
    :ok
  end

  test "build/4" do
    ir = %Block{
      expressions: [
        %ModuleType{module: PlaceholderModule2},
        %ModuleType{module: PlaceholderModule3}
      ]
    }

    from_vertex = PlaceholderModule1
    CallGraphBuilder.build(ir, @module_defs, @templates, from_vertex)

    assert CallGraph.has_edge?(PlaceholderModule1, PlaceholderModule2)
    assert CallGraph.has_edge?(PlaceholderModule1, PlaceholderModule3)
  end
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/element_node/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ElementNode.Module1 do
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/anonymous_function_call_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.AnonymousFunctionCallTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.Context
  alias Hologram.Compiler.IR.AnonymousFunctionCall
  alias Hologram.Compiler.IR.IntegerType
  alias Hologram.Compiler.JSEncoder
  alias Hologram.Compiler.Opts

  test "var name" do
    ir = %AnonymousFunctionCall{
      name: :abc?,
      args: []
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "abc$question.callback()"

    assert result == expected
  end

  test "args" do
    ir = %AnonymousFunctionCall{
      name: :abc,
      args: [%IntegerType{value: 1}, %IntegerType{value: 2}]
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "abc.callback({ type: 'integer', value: 1 }, { type: 'integer', value: 2 })"

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/template/renderers/node_list_test.exs<|end_filename|>
defmodule Hologram.Template.Renderer.NodeListTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Conn
  alias Hologram.Runtime
  alias Hologram.Template.Renderer
  alias Hologram.Template.VDOM.Component
  alias Hologram.Template.VDOM.TextNode
  alias Hologram.Test.Fixtures.Template.NodeListRenderer.Module1
  alias Hologram.Test.Fixtures.Template.NodeListRenderer.Module2

  setup do
    [app_path: "#{@fixtures_path}/template/renderers/node_list_renderer"]
    |> compile()

    Runtime.run()

    :ok
  end

  test "render/4" do
    component_1 = %Component{
      module: Module1,
      props: %{
        id: [
          %TextNode{content: "component_1_id"},
        ]
      }
    }

    component_2 = %Component{
      module: Module2,
      props: %{
        id: [
          %TextNode{content: "component_2_id"},
        ]
      }
    }

    nodes = [
      %TextNode{content: "test_1"},
      component_1,
      %TextNode{content: "test_2"},
      component_2
    ]

    bindings = %{__context__: %{}}
    result = Renderer.render(nodes, %Conn{}, bindings)

    expected_initial_state = %{
      component_1_id: %{component_1_state_key: "component_1_state_value"},
      component_2_id: %{component_2_state_key: "component_2_state_value"},
    }

    expected_html = "test_1(in component 1)test_2(in component 2)"
    expected = {expected_html, expected_initial_state}

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/template/renderers/component_renderer/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.ComponentRenderer.Module1 do
  use Hologram.Component

  def template do
    ~H"""
    abc.{@test_prop}.xyz
    """
  end
end


<|start_filename|>lib/hologram/compiler/transformers/alias_directive_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.AliasDirectiveTransformer do
  alias Hologram.Compiler.Helpers
  alias Hologram.Compiler.IR.AliasDirective

  def transform({:alias, _, [{{_, _, [{_, _, module_segs}, _]}, _, aliases}, _]}) do
    transform_multi_alias(module_segs, aliases)
  end

  def transform({:alias, _, [{{_, _, [{_, _, module_segs}, _]}, _, aliases}]}) do
    transform_multi_alias(module_segs, aliases)
  end

  def transform({:alias, _, [{_, _, module_segs}]}) do
    module = Helpers.module(module_segs)
    %AliasDirective{module: module, as: [List.last(module_segs)]}
  end

  def transform({:alias, _, [{_, _, module_segs}, opts]}) do
    module = Helpers.module(module_segs)

    as =
      if Keyword.has_key?(opts, :as) do
        elem(opts[:as], 2)
      else
        [List.last(module_segs)]
      end

    %AliasDirective{module: module, as: as}
  end

  defp transform_multi_alias(module_segs, aliases) do
    Enum.map(aliases, fn {:__aliases__, _, as} ->
      module = Helpers.module(module_segs ++ as)
      %AliasDirective{module: module, as: as}
    end)
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/module_type_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.ModuleTypeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}
  alias Hologram.Compiler.IR.ModuleType

  test "encode/3" do
    ir = %ModuleType{module: Abc.Bcd}

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "{ type: 'module', className: 'Elixir_Abc_Bcd' }"

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/template/renderers/page_renderer/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.PageRenderer.Module2 do
  use Hologram.Layout

  def init(conn) do
    %{
      b: 987,
      e: conn.session.e
    }
  end

  def template do
    ~H"""
    <!DOCTYPE html>
    <html>
      <head>
        <Hologram.UI.Runtime />
      </head>
      <body>
        layout template assign {@b}, layout template conn session {@e}
        <slot />
      </body>
    </html>
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_63.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module63 do
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module62

  def test_fun_63a do
    Module62.test_fun_62a()
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/anonymous_function_call.ex<|end_filename|>
alias Hologram.Compiler.IR.AnonymousFunctionCall
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: AnonymousFunctionCall do
  def aggregate(%{args: args}) do
    ModuleDefAggregator.aggregate(args)
  end
end


<|start_filename|>assets/js/hologram/command.js<|end_filename|>
"use strict";

import Action from "./action";
import Client from "./client";
import Operation from "./operation";
import Runtime from "./runtime";
import Target from "./target";
import Type from "./type";
import Utils from "./utils";

export default class Command {
  static buildMessagePayload(operation) {
    return {
      target_module: operation.target.module,
      target_id: Type.atom(operation.target.id),
      command: operation.name,
      params: operation.params,
    }
  }

  // Covered implicitely in E2E tests.
  static execute(operation) {
    const payload = Command.buildMessagePayload(operation)
    const callback = (commandResult) => Command.handleResult(commandResult, operation.target.id)
    Client.pushMessage("command", payload, callback)
  }

  // Covered implicitely in E2E tests.
  static handleResult(commandResult, sourceId) {
    const {data: [targetId, actionName, params]} = Utils.eval(commandResult)

    if (actionName.value === "__redirect__") {
      Runtime.redirect(params)

    } else {
      const target = new Target(targetId.value)
      const operation = new Operation(sourceId, target, actionName, params)
      Action.execute(operation)
    }
  }
}

<|start_filename|>lib/hologram/compiler/call_graph_builder/elixir_tuple.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder

defimpl CallGraphBuilder, for: Tuple do
  def build(tuple, module_defs, templates, from_vertex) do
    tuple
    |> Tuple.to_list()
    |> CallGraphBuilder.build(module_defs, templates, from_vertex)
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_66.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module66 do
  use Hologram.Page

  route "/test-route-66"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/map_type.ex<|end_filename|>
alias Hologram.Compiler.{Context, JSEncoder, Opts}
alias Hologram.Compiler.IR.MapType

defimpl JSEncoder, for: MapType do
  use Hologram.Commons.Encoder

  def encode(%{data: data}, %Context{} = context, %Opts{} = opts) do
    "{ type: 'map', data: #{encode_map_data(data, context, opts)} }"
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/control_flow/case_expression_page.ex<|end_filename|>
defmodule HologramE2E.ControlFlow.CaseExpressionPage do
  use Hologram.Page

  route "/e2e/control-flow/case-expression"

  def init(_params, _conn) do
    %{
      condition: 2,
      result: 0
    }
  end

  def template do
    ~H"""
    <button id="button" on:click="calculate">Calculate</button>
    <div id="text">Result = {@result}</div>
    """
  end

  def action(:calculate, _params, state) do
    result =
      case state.condition do
        1 ->
          11

        2 ->
          22
      end

    Map.put(state, :result, result)
  end
end


<|start_filename|>test/unit/hologram/template/encoders/node_list_test.exs<|end_filename|>
defmodule Hologram.Template.Encoder.NodeListTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Template.Encoder
  alias Hologram.Template.VDOM.TextNode

  describe "encode/1" do
    test "non-empty" do
      nodes = [
        %TextNode{content: "test_1"},
        %TextNode{content: "test_2"}
      ]

      result = Encoder.encode(nodes)
      expected = "[ { type: 'text', content: 'test_1' }, { type: 'text', content: 'test_2' } ]"

      assert result == expected
    end

    test "empty" do
      assert Encoder.encode([]) == "[]"
    end
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/cons_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.ConsOperator

defimpl CallGraphBuilder, for: ConsOperator do
  def build(%{head: head, tail: tail}, module_defs, templates, from_vertex) do
    CallGraphBuilder.build(head, module_defs, templates, from_vertex)
    CallGraphBuilder.build(tail, module_defs, templates, from_vertex)
  end
end


<|start_filename|>test/unit/hologram/compiler/resolver_test.exs<|end_filename|>
defmodule Hologram.Compiler.ResolverTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, Resolver}
  alias Hologram.Compiler.IR.{AliasDirective, ImportDirective}

  @calling_module Hologram.Compiler.ResolverTest

  describe "resolve/5" do
    test "imported module" do
      imported_module = Hologram.Test.Fixtures.Compiler.Resolver.Module1
      imports = [%ImportDirective{module: imported_module}]
      context = %Context{module: @calling_module, imports: imports}

      result = Resolver.resolve([], :test, 2, context)
      assert result == imported_module
    end

    test "calling module public function" do
      context = %Context{module: @calling_module}

      result = Resolver.resolve([], :test, 2, context)
      assert result == @calling_module
    end

    test "calling module private function" do
      calling_module = Hologram.Test.Fixtures.Compiler.Resolver.Module2
      context = %Context{module: calling_module}

      result = Resolver.resolve([], :test, 2, context)
      assert result == calling_module
    end

    test "Kernel function" do
      context = %Context{module: @calling_module}
      result = Resolver.resolve([], :max, 2, context)

      assert result == Kernel
    end

    test "Kernel macro" do
      context = %Context{module: @calling_module}
      result = Resolver.resolve([], :to_string, 1, context)

      assert result == Kernel
    end

    test "aliased module" do
      aliased_module = Hologram.Test.Fixtures.Compiler.Resolver.Module1
      aliases = [%AliasDirective{module: aliased_module, as: [:Module1]}]
      context = %Context{module: @calling_module, aliases: aliases}

      result = Resolver.resolve([:Module1], :test, 2, context)

      assert result == aliased_module
    end

    test "vertbatim module" do
      context = %Context{module: @calling_module}
      verbatim_module = [:Hologram, :Test, :Fixtures, :Compiler, :Resolver, :Module1]
      result = Resolver.resolve(verbatim_module, :test, 2, context)

      assert result == Hologram.Test.Fixtures.Compiler.Resolver.Module1
    end
  end

  test "resolve/2" do
    aliased_module = Hologram.Test.Fixtures.Compiler.Resolver.Module1
    aliases = [%AliasDirective{module: aliased_module, as: [:Module1]}]
    context = %Context{aliases: aliases}
    result = Resolver.resolve([:Module1], context)

    assert result == aliased_module
  end
end


<|start_filename|>test/js/interpreter_test.js<|end_filename|>
"use strict";

import { assert, assertBoxedFalse, assertBoxedTrue, assertFrozen, cleanup } from "./support/commons";
beforeEach(() => cleanup())

import { HologramNotImplementedError } from "../../assets/js/hologram/errors";
import Interpreter from "../../assets/js/hologram/interpreter"
import Map from "../../assets/js/hologram/elixir/map"
import Type from "../../assets/js/hologram/type"

describe("$addition_operator()", () => {
  it("adds integer and integer", () => {
    const left = Type.integer(1);
    const right = Type.integer(2);

    const result = Interpreter.$addition_operator(left, right);
    const expected = Type.integer(3);

    assert.deepStrictEqual(result, expected);
  });

  it("adds integer and float", () => {
    const left = Type.integer(1);
    const right = Type.float(2.0);

    const result = Interpreter.$addition_operator(left, right);
    const expected = Type.float(3.0);

    assert.deepStrictEqual(result, expected);
  });

  it("adds float and integer", () => {
    const left = Type.float(1.0);
    const right = Type.integer(2);

    const result = Interpreter.$addition_operator(left, right);
    const expected = Type.float(3.0);

    assert.deepStrictEqual(result, expected);
  });

  it("adds float and float", () => {
    const left = Type.float(1.0);
    const right = Type.float(2.0);

    const result = Interpreter.$addition_operator(left, right);
    const expected = Type.float(3.0);

    assert.deepStrictEqual(result, expected);
  });

  it("returns frozen object", () => {
    const left = Type.integer(1);
    const right = Type.integer(2);
    const result = Interpreter.$addition_operator(left, right);

    assertFrozen(result);
  });
});

describe("$case_expression()", () => {
  it("returns the result of the clauses anonymous function given", () => {
    const clausesAnonFun = (param) => {
      return param
    }

    const result = Interpreter.$case_expression(123, clausesAnonFun)
    assert.equal(result, 123)
  })

  it("returns frozen object", () => {
    const clausesAnonFun = (param) => {
      return param
    }

    const result = Interpreter.$case_expression({}, clausesAnonFun)
    assertFrozen(result)
  })
})

describe("$cons_operator()", () => {
  it("creates a list from a head and a tail", () => {
    const head = Type.integer(1)
    const tail = Type.list([Type.integer(2), Type.integer(3)])

    const result = Interpreter.$cons_operator(head, tail)
    const expected = Type.list([Type.integer(1), Type.integer(2), Type.integer(3)])

    assert.deepStrictEqual(result, expected);
  })
})

describe("$division_operator()", () => {
  it("divides 2 numbers", () => {
    const left = Type.integer(1);
    const right = Type.float(2.0);

    const result = Interpreter.$division_operator(left, right);
    const expected = Type.float(0.5);

    assert.deepStrictEqual(result, expected);
  });
});

describe("$dot_operator()", () => {
  let left, right, value, result;

  beforeEach(() => {
    value = Type.integer(2)

    let elems = {}
    elems[Type.atomKey("a")] = Type.integer(1)
    elems[Type.atomKey("b")] = value

    left =  Type.map(elems)
    right = Type.atom("b")
    
    result = Interpreter.$dot_operator(left, right)
  })

  it("fetches boxed map value by boxed key", () => {
    assert.deepStrictEqual(result, value) 
  })

  it("returns frozen object", () => {
    assertFrozen(result)
  })
})

describe("$equal_to_operator()", () => {
  // non-number == non-number
  it("returns boxed true for a boxed non-number equal to another boxed non-number", () => {
    const value1 = Type.boolean(true);
    const value2 = Type.boolean(true);
    const result = Interpreter.$equal_to_operator(value1, value2);

    assertBoxedTrue(result);
  });

  // non-number != non-number
  it("returns boxed false for a boxed non-number not equal to another boxed non-number", () => {
    const value1 = Type.boolean(true);
    const value2 = Type.string("abc");
    const result = Interpreter.$equal_to_operator(value1, value2);

    assertBoxedFalse(result);
  });

  // integer == integer
  it("returns boxed true for a boxed integer equal to another boxed integer", () => {
    const value1 = Type.integer(1);
    const value2 = Type.integer(1);
    const result = Interpreter.$equal_to_operator(value1, value2);

    assertBoxedTrue(result);
  });

  // integer != integer
  it("returns boxed false for a boxed integer not equal to another boxed integer", () => {
    const value1 = Type.integer(1);
    const value2 = Type.integer(2);
    const result = Interpreter.$equal_to_operator(value1, value2);

    assertBoxedFalse(result);
  });

  // integer == float
  it("returns boxed true for a boxed integer equal to a boxed float", () => {
    const value1 = Type.integer(1);
    const value2 = Type.float(1.0);
    const result = Interpreter.$equal_to_operator(value1, value2);

    assertBoxedTrue(result);
  });

  // integer != float
  it("returns boxed false for a boxed integer not equal to a boxed float", () => {
    const value1 = Type.integer(1);
    const value2 = Type.float(2.0);
    const result = Interpreter.$equal_to_operator(value1, value2);

    assertBoxedFalse(result);
  });

  // integer != non-number
  it("returns boxed false when a boxed integer is compared to a boxed value of non-number type", () => {
    const value1 = Type.integer(1);
    const value2 = Type.string("1");
    const result = Interpreter.$equal_to_operator(value1, value2);

    assertBoxedFalse(result);
  });

  // float == float
  it("returns boxed true for a boxed float equal to another boxed float", () => {
    const value1 = Type.float(1.0);
    const value2 = Type.float(1.0);
    const result = Interpreter.$equal_to_operator(value1, value2);

    assertBoxedTrue(result);
  });

  // float != float
  it("returns boxed false for a boxed float not equal to another boxed float", () => {
    const value1 = Type.float(1.0);
    const value2 = Type.float(2.0);
    const result = Interpreter.$equal_to_operator(value1, value2);

    assertBoxedFalse(result);
  });

  // float == integer
  it("returns boxed true for a boxed float equal to a boxed integer", () => {
    const value1 = Type.float(1.0);
    const value2 = Type.integer(1);
    const result = Interpreter.$equal_to_operator(value1, value2);

    assertBoxedTrue(result);
  });

  // float != integer
  it("returns boxed false for a boxed float not equal to a boxed integer", () => {
    const value1 = Type.float(1.0);
    const value2 = Type.integer(2);
    const result = Interpreter.$equal_to_operator(value1, value2);

    assertBoxedFalse(result);
  });

  // float != non-number
  it("returns boxed false when a boxed float is compared to a boxed value of non-number type", () => {
    const value1 = Type.float(1.0);
    const value2 = Type.string("1.0");
    const result = Interpreter.$equal_to_operator(value1, value2);

    assertBoxedFalse(result);
  });

  it("returns frozen object", () => {
    const val = Type.integer(1);
    const result = Interpreter.$equal_to_operator(val, val);

    assertFrozen(result);
  });
});

describe("$if_expression()", () => {
  it("returns doClause result if condition is truthy", () => {
    const expected = Type.integer(1);

    const condition = () => {
      return Type.boolean(true);
    };

    const doClause = () => {
      return expected;
    };

    const elseClause = () => {
      return Type.integer(2);
    };

    const result = Interpreter.$if_expression(condition, doClause, elseClause);
    assert.equal(result, expected);
  });

  it("returns elseClause result if condition is not truthy", () => {
    const expected = Type.integer(2);
    const condition = () => {
      return Type.boolean(false);
    };
    const doClause = () => {
      return Type.integer(1);
    };
    const elseClause = () => {
      return expected;
    };

    const result = Interpreter.$if_expression(condition, doClause, elseClause);
    assert.equal(result, expected);
  });

  it("returns frozen object", () => {
    const condition = () => {
      return Type.boolean(true);
    };
    const doClause = () => {
      return Type.integer(1);
    };
    const elseClause = () => {
      return Type.integer(2);
    };

    const result = Interpreter.$if_expression(condition, doClause, elseClause);
    assertFrozen(result);
  });
});

describe("isConsOperatorPatternMatched()", () => {
  let head, tail, left;

  beforeEach(() => {
    head = Type.integer(1)
    tail = Type.list([Type.integer(2), Type.integer(3)])
    left = Type.consOperatorPattern(head, tail)
  })

  it("returns false if the right arg is not a boxed list", () => {
    const right = Type.integer(4)
    const result = Interpreter.isConsOperatorPatternMatched(left, right)

    assert.isFalse(result)
  })

  it("returns false if the right arg is an empty boxed list", () => {
    const right = Type.list([])
    const result = Interpreter.isConsOperatorPatternMatched(left, right)

    assert.isFalse(result)
  })

  it("returns false if heads don't match by value", () => {
    const right = Type.list([Type.integer(4), Type.integer(2), Type.integer(3)])
    const result = Interpreter.isConsOperatorPatternMatched(left, right)

    assert.isFalse(result)
  })

  it("returns false if tails don't match by value", () => {
    const right = Type.list([head, Type.integer(4), Type.integer(3)])
    const result = Interpreter.isConsOperatorPatternMatched(left, right)

    assert.isFalse(result)
  })

  it("returns true if heads and tails match by value", () => {
    const right = Type.list([head, Type.integer(2), Type.integer(3)])
    const result = Interpreter.isConsOperatorPatternMatched(left, right)

    assert.isTrue(result)
  })

  it("returns true if heads and tails match by variable pattern", () => {
    left = Type.consOperatorPattern(Type.placeholder(), Type.placeholder())

    const right = Type.list([head, Type.integer(2), Type.integer(3)])
    const result = Interpreter.isConsOperatorPatternMatched(left, right)

    assert.isTrue(result)
  })

  it("returns true if heads match by value and tails match by variable pattern", () => {
    left = Type.consOperatorPattern(head, Type.placeholder())

    const right = Type.list([head, Type.integer(2), Type.integer(3)])
    const result = Interpreter.isConsOperatorPatternMatched(left, right)

    assert.isTrue(result)
  })

  it("returns true if heads match by variable pattern and tails match by value", () => {
    tail = Type.list([Type.integer(2), Type.integer(3)])
    left = Type.consOperatorPattern(Type.placeholder(), tail)

    const right = Type.list([head, Type.integer(2), Type.integer(3)])
    const result = Interpreter.isConsOperatorPatternMatched(left, right)

    assert.isTrue(result)
  })
})

describe("isEnumPatternMatched()", () => {
  it("returns true if list boxed type left-hand side matches the list boxed type right-hand side", () => {
    const left = Type.list([Type.integer(1), Type.integer(2)])
    const right = Type.list([Type.integer(1), Type.integer(2)])

    const result = Interpreter.isEnumPatternMatched(left, right)

    assert.isTrue(result)
  })

  it("returns false if left-hand side list item count is different than right-hand side list item count", () => {
    const left = Type.list([Type.integer(1), Type.integer(2)])
    const right = Type.list([Type.integer(1), Type.integer(2), Type.integer(3)])

    const result = Interpreter.isEnumPatternMatched(left, right)

    assert.isFalse(result)
  })

  it("returns false if left-hand side list doesn't match right-hand side list", () => {
    const left = Type.list([Type.integer(1), Type.integer(2)])
    const right = Type.list([Type.integer(1), Type.integer(3)])

    const result = Interpreter.isEnumPatternMatched(left, right)

    assert.isFalse(result)
  })

  it("returns true if tuple boxed type left-hand side matches the tuple boxed type right-hand side", () => {
    const left = Type.tuple([Type.integer(1), Type.integer(2)])
    const right = Type.tuple([Type.integer(1), Type.integer(2)])

    const result = Interpreter.isEnumPatternMatched(left, right)

    assert.isTrue(result)
  })

  it("returns false if left-hand side tuple item count is different than right-hand side tuple item count", () => {
    const left = Type.tuple([Type.integer(1), Type.integer(2)])
    const right = Type.tuple([Type.integer(1), Type.integer(2), Type.integer(3)])

    const result = Interpreter.isEnumPatternMatched(left, right)

    assert.isFalse(result)
  })

  it("returns false if left-hand side tuple doesn't match right-hand side tuple", () => {
    const left = Type.tuple([Type.integer(1), Type.integer(2)])
    const right = Type.tuple([Type.integer(1), Type.integer(3)])

    const result = Interpreter.isEnumPatternMatched(left, right)

    assert.isFalse(result)
  })
})

describe("isFunctionArgsPatternMatched()", () => {
  it("returns false if number of args is different than number of params", () => {
    const params = [Type.placeholder(), Type.placeholder()]
    const args = [Type.integer(1)]
    const result = Interpreter.isFunctionArgsPatternMatched(params, args)

    assert.isFalse(result)
  })

  it("returns false if at least one arg doesn't match the params pattern", () => {
    const params = [Type.placeholder(), Type.atom("a")]
    const args = [Type.atom("b"), Type.atom("c")]
    const result = Interpreter.isFunctionArgsPatternMatched(params, args)

    assert.isFalse(result)
  })

  it("returns true if the args match the params pattern", () => {
    const params = [Type.placeholder(), Type.atom("a")]
    const args = [Type.atom("b"), Type.atom("a")]
    const result = Interpreter.isFunctionArgsPatternMatched(params, args)

    assert.isTrue(result)
  })
})

describe("isMapPatternMatched()", () => {
  it("returns true if map boxed type left-hand side matches the map boxed type right-hand side", () => {
    let left = Type.map()
    left = Map.put(left, Type.atom("a"), Type.integer(1))

    let right = Type.map()
    right = Map.put(right, Type.atom("a"), Type.integer(1))
    right = Map.put(right, Type.atom("b"), Type.integer(2))

    const result = Interpreter.isPatternMatched(left, right)

    assert.isTrue(result)
  })

  it("returns false if right-hand side boxed map doesn't have a key from left-hand side boxed map", () => {
    let left = Type.map()
    left = Map.put(left, Type.atom("a"), Type.integer(1))
    left = Map.put(left, Type.atom("b"), Type.integer(2))
    
    let right = Type.map()
    right = Map.put(right, Type.atom("a"), Type.integer(1))

    const result = Interpreter.isPatternMatched(left, right)

    assert.isFalse(result)
  })

  it("returns false if value in left-hand side boxed map doesn't match the value in right-hand side boxed map", () => {
    let left = Type.map()
    left = Map.put(left, Type.atom("a"), Type.integer(1))
    left = Map.put(left, Type.atom("b"), Type.integer(2))
    
    let right = Type.map()
    right = Map.put(right, Type.atom("a"), Type.integer(1))
    right = Map.put(right, Type.atom("b"), Type.integer(3))

    const result = Interpreter.isPatternMatched(left, right)

    assert.isFalse(result)
  })
})

describe("isPatternMatched()", () => {
  it("returns true if the boxed type of the left-hand side is placeholder", () => {
    const left = Type.placeholder()
    const right = Type.integer(1)
    const result = Interpreter.isPatternMatched(left, right)

    assert.isTrue(result)
  })

  it("matches by cons operator pattern", () => {
    const left = Type.consOperatorPattern(Type.placeholder(), Type.placeholder())
    const right = Type.list([Type.integer(1), Type.integer(2)])
    const result = Interpreter.isPatternMatched(left, right)

    assert.isTrue(result)
  })

  it("returns false if the boxed type of the left-hand side is different than the boxed type of the right-hand side", () => {
    const left = Type.float(1.0)
    const right = Type.integer(1)
    const result = Interpreter.isPatternMatched(left, right)

    assert.isFalse(result)
  })

  it("returns true if atom boxed type left-hand side is equal to atom boxed type right-hand side", () => {
    const left = Type.atom("a")
    const right = Type.atom("a")
    const result = Interpreter.isPatternMatched(left, right)

    assert.isTrue(result)
  })

  it("returns false if atom boxed type left-hand side is not equal to atom boxed type right-hand side", () => {
    const left = Type.atom("a")
    const right = Type.atom("b")
    const result = Interpreter.isPatternMatched(left, right)

    assert.isFalse(result)
  })
  
  it("returns true if integer boxed type left-hand side is equal to integer boxed type right-hand side", () => {
    const left = Type.integer(1)
    const right = Type.integer(1)
    const result = Interpreter.isPatternMatched(left, right)

    assert.isTrue(result)
  })

  it("returns false if integer boxed type left-hand side is not equal to integer boxed type right-hand side", () => {
    const left = Type.integer(1)
    const right = Type.integer(2)
    const result = Interpreter.isPatternMatched(left, right)

    assert.isFalse(result)
  })

  it("throws an error for not implemented boxed types", () => {
    const left = {type: "not implemented", value: "a"}
    const right = {type: "not implemented", value: "b"}
    const expectedMessage = 'Interpreter.isPatternMatched(): left = {"type":"not implemented","value":"a"}'

    assert.throw(() => { Interpreter.isPatternMatched(left, right) }, HologramNotImplementedError, expectedMessage);
  })
})

describe("$less_than_operator", () => {
  // integer < integer
  it("returns boxed true if left boxed integer is smaller than right boxed integer", () => {
    const left = Type.integer(1)
    const right = Type.integer(2)
    const result = Interpreter.$less_than_operator(left, right)

    assertBoxedTrue(result)
  })

  // integer == integer
  it("returns boxed false if left boxed integer is equal to right boxed integer", () => {
    const left = Type.integer(1)
    const right = Type.integer(1)
    const result = Interpreter.$less_than_operator(left, right)

    assertBoxedFalse(result)
  })

  // integer > integer
  it("returns boxed false if left boxed integer is greater than right boxed integer", () => {
    const left = Type.integer(2)
    const right = Type.integer(1)
    const result = Interpreter.$less_than_operator(left, right)

    assertBoxedFalse(result)
  })

  // float < float
  it("returns boxed true if left boxed float is smaller than right boxed float", () => {
    const left = Type.float(1.0)
    const right = Type.float(2.0)
    const result = Interpreter.$less_than_operator(left, right)

    assertBoxedTrue(result)
  })

  // float == float
  it("returns boxed false if left boxed float is equal to right bloxed float", () => {
    const left = Type.float(1.0)
    const right = Type.float(1.0)
    const result = Interpreter.$less_than_operator(left, right)

    assertBoxedFalse(result)
  })

  // float > float
  it("returns boxed false if left boxed float is greater than right boxed float", () => {
    const left = Type.float(2.0)
    const right = Type.float(1.0)
    const result = Interpreter.$less_than_operator(left, right)

    assertBoxedFalse(result)
  })

  // integer < float
  it("returns boxed true if left boxed integer is smaller than right boxed float", () => {
    const left = Type.integer(1)
    const right = Type.float(2.0)
    const result = Interpreter.$less_than_operator(left, right)

    assertBoxedTrue(result)
  })

  // integer == float
  it("returns boxed false if left boxed integer is equal to right boxed float", () => {
    const left = Type.integer(1)
    const right = Type.float(1.0)
    const result = Interpreter.$less_than_operator(left, right)

    assertBoxedFalse(result)
  })

  // integer > float
  it("returns boxed false if left boxed integer is greater than right boxed float", () => {
    const left = Type.integer(2)
    const right = Type.float(1.0)
    const result = Interpreter.$less_than_operator(left, right)

    assertBoxedFalse(result)
  })

  // float < integer
  it("returns boxed true if left boxed float is smaller than right boxed integer", () => {
    const left = Type.float(1.0)
    const right = Type.integer(2)
    const result = Interpreter.$less_than_operator(left, right)

    assertBoxedTrue(result)
  })

  // float == integer
  it("returns boxed false if left boxed float is equal to right bloxed integer", () => {
    const left = Type.float(1.0)
    const right = Type.integer(1)
    const result = Interpreter.$less_than_operator(left, right)

    assertBoxedFalse(result)
  })

  // float > integer
  it("returns boxed false if left boxed float is greater than right boxed integer", () => {
    const left = Type.float(2.0)
    const right = Type.integer(1)
    const result = Interpreter.$less_than_operator(left, right)

    assertBoxedFalse(result)
  })

  it("throws an error if left arg is not a number", () => {
    const left = Type.atom("a")
    const right = Type.integer(2)
    const expectedMessage = 'Interpreter.$less_than_operator(): left = {"type":"atom","value":"a"}'

    assert.throw(() => { Interpreter.$less_than_operator(left, right) }, HologramNotImplementedError, expectedMessage);
  })

  it("throws an error if right arg is not a number", () => {
    const left = Type.integer(1)
    const right = Type.atom("b")
    const expectedMessage = 'Interpreter.$less_than_operator(): right = {"type":"atom","value":"b"}'

    assert.throw(() => { Interpreter.$less_than_operator(left, right) }, HologramNotImplementedError, expectedMessage);
  })
})

describe("$list_concatenation_operator()", () => {
  it("concatenates 2 lists", () => {
    const left = Type.list([Type.integer(1), Type.integer(2)]);
    const right = Type.list([Type.integer(3), Type.integer(4)]);

    const result = Interpreter.$list_concatenation_operator(left, right);

    const expected = Type.list([
      Type.integer(1),
      Type.integer(2),
      Type.integer(3),
      Type.integer(4),
    ]);

    assert.deepStrictEqual(result, expected);
  });
});

describe("$list_subtraction_operator()", () => {
  it("returns the left list if there are no matching elems in the right list", () => {
    const left = Type.list([Type.integer(1), Type.integer(2)]);
    const right = Type.list([Type.integer(3), Type.integer(4)]);
    const result = Interpreter.$list_subtraction_operator(left, right);

    assert.deepStrictEqual(result, left);
  });

  it("removes the first occurrence of an element on the left list for each element on the right", () => {
    const left = Type.list([
      Type.integer(1),
      Type.integer(2),
      Type.integer(3),
      Type.integer(1),
      Type.integer(2),
      Type.integer(3),
      Type.integer(1),
    ]);

    const right = Type.list([
      Type.integer(1),
      Type.integer(3),
      Type.integer(3),
      Type.integer(4),
    ]);

    const result = Interpreter.$list_subtraction_operator(left, right);

    const expected = Type.list([
      Type.integer(2),
      Type.integer(1),
      Type.integer(2),
      Type.integer(1),
    ]);

    assert.deepStrictEqual(result, expected);
  });

  it("returns frozen object", () => {
    const left = Type.list([Type.integer(1)]);
    const right = Type.list([Type.integer(2)]);
    const result = Interpreter.$list_subtraction_operator(left, right);

    assertFrozen(result);
  });
});

describe("$membership_operator()", () => {
  it("calls Enum.$member()", () => {
    const left = Type.integer(1)
    const right = Type.list([Type.integer(1), Type.integer(2)])
    const result = Interpreter.$membership_operator(left, right)

    assertBoxedTrue(result)
  })
})

describe("$multiplication_operator()", () => {
  it("multiplies integer and integer", () => {
    const left = Type.integer(2);
    const right = Type.integer(3);

    const result = Interpreter.$multiplication_operator(left, right);
    const expected = Type.integer(6);

    assert.deepStrictEqual(result, expected);
  });

  it("multiplies integer and float", () => {
    const left = Type.integer(2);
    const right = Type.float(3.0);

    const result = Interpreter.$multiplication_operator(left, right);
    const expected = Type.float(6.0);

    assert.deepStrictEqual(result, expected);
  });

  it("multiplies float and integer", () => {
    const left = Type.float(2.0);
    const right = Type.integer(3);

    const result = Interpreter.$multiplication_operator(left, right);
    const expected = Type.float(6.0);

    assert.deepStrictEqual(result, expected);
  });

  it("multiplies float and float", () => {
    const left = Type.float(2.0);
    const right = Type.float(3.0);

    const result = Interpreter.$multiplication_operator(left, right);
    const expected = Type.float(6.0);

    assert.deepStrictEqual(result, expected);
  });

  it("returns frozen object", () => {
    const left = Type.integer(1);
    const right = Type.integer(2);
    const result = Interpreter.$multiplication_operator(left, right);

    assertFrozen(result);
  });
});

describe("$not_equal_to_operator()", () => {
  it("returns boxed true if both args are not equal", () => {
    const left = Type.boolean(true);
    const right = Type.boolean(false);
    const result = Interpreter.$not_equal_to_operator(left, right);

    assertBoxedTrue(result);
  });

  it("returns boxed false if both args are equal", () => {
    const left = Type.boolean(true);
    const right = Type.boolean(true);
    const result = Interpreter.$not_equal_to_operator(left, right);

    assertBoxedFalse(result);
  });
})

describe("$relaxed_boolean_and_operator()", () => {
  it("returns the second arg if the first one is truthy", () => {
    const left = Type.integer(1);
    const right = Type.integer(2);
    const result = Interpreter.$relaxed_boolean_and_operator(left, right);

    assert.deepStrictEqual(result, right);
  });

  it("returns the first arg if it is falsy", () => {
    const left = Type.nil();
    const right = Type.integer(2);
    const result = Interpreter.$relaxed_boolean_and_operator(left, right);

    assert.deepStrictEqual(result, left);
  });
});

describe("$relaxed_boolean_not_operator()", () => {
  it("returns boxed true if the arg is boxed false", () => {
    const value = Type.boolean(false)
    const result = Interpreter.$relaxed_boolean_not_operator(value);

    assertBoxedTrue(result)
  })

  it("returns boxed true if the arg is boxed nil", () => {
    const value = Type.nil()
    const result = Interpreter.$relaxed_boolean_not_operator(value);

    assertBoxedTrue(result)
  })

  it("returns boxed false if the arg is boxed true", () => {
    const value = Type.boolean(true)
    const result = Interpreter.$relaxed_boolean_not_operator(value);

    assertBoxedFalse(result)
  })

  it("returns boxed false if the arg is of other data type", () => {
    const value = Type.integer(1)
    const result = Interpreter.$relaxed_boolean_not_operator(value);

    assertBoxedFalse(result)
  })
})

describe("$relaxed_boolean_or()", () => {
  it("returns the first arg if it is truthy", () => {
    const left = Type.integer(1);
    const right = Type.integer(2);
    const result = Interpreter.$relaxed_boolean_or_operator(left, right);

    assert.deepStrictEqual(result, left);
  });

  it("returns the second arg if the first is falsy", () => {
    const left = Type.nil();
    const right = Type.integer(2);
    const result = Interpreter.$relaxed_boolean_or_operator(left, right);

    assert.deepStrictEqual(result, right);
  });
});

describe("$subtraction_operator()", () => {
  it("subtracts integer and integer", () => {
    const left = Type.integer(1);
    const right = Type.integer(2);

    const result = Interpreter.$subtraction_operator(left, right);
    const expected = Type.integer(-1);

    assert.deepStrictEqual(result, expected);
  });

  it("subtracts integer and float", () => {
    const left = Type.integer(1);
    const right = Type.float(2.0);

    const result = Interpreter.$subtraction_operator(left, right);
    const expected = Type.float(-1.0);

    assert.deepStrictEqual(result, expected);
  });

  it("subtracts float and integer", () => {
    const left = Type.float(1.0);
    const right = Type.integer(2);

    const result = Interpreter.$subtraction_operator(left, right);
    const expected = Type.float(-1.0);

    assert.deepStrictEqual(result, expected);
  });

  it("subtracts float and float", () => {
    const left = Type.float(1.0);
    const right = Type.float(2.0);

    const result = Interpreter.$subtraction_operator(left, right);
    const expected = Type.float(-1.0);

    assert.deepStrictEqual(result, expected);
  });

  it("returns frozen object", () => {
    const left = Type.integer(1);
    const right = Type.integer(2);
    const result = Interpreter.$subtraction_operator(left, right);

    assertFrozen(result);
  });
});

describe("$type_operator", () => {
  it("returns the value given in the first arg if it is of boxed string type and if the type arg is binary", () => {
    const value = Type.string("test")
    const result = Interpreter.$type_operator(value, "binary")

    assert.equal(result, value)
  })

  it("throws an error for not implemented cases", () => {
    const value = Type.string("test")
    const expectedMessage = 'Interpreter.$type_operator(): value = {"type":"string","value":"test"}, type = "not implemented"'

    assert.throw(() => { Interpreter.$type_operator(value, "not implemented") }, HologramNotImplementedError, expectedMessage);
  })
})

describe("$unary_negative()", () => {
  it("negates the given value", () => {
    const value = Type.integer(123);

    const result = Interpreter.$unary_negative_operator(value);
    const expected = Type.integer(-123);

    assert.deepStrictEqual(result, expected);
  });
});

<|start_filename|>lib/hologram/compiler/call_graph_builder/function_call.ex<|end_filename|>
alias Hologram.Compiler.{CallGraph, CallGraphBuilder, Reflection}
alias Hologram.Compiler.IR.FunctionCall

defimpl CallGraphBuilder, for: FunctionCall do
  def build(
        %{module: module, function: function, args: args},
        module_defs,
        templates,
        from_vertex
      ) do
    CallGraphBuilder.build(args, module_defs, templates, from_vertex)

    unless Reflection.standard_lib?(module) do
      CallGraphBuilder.build(module_defs[module], module_defs, templates, nil)
      CallGraph.add_edge(from_vertex, {module, function})
    end
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/match_operator_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.MatchOperatorTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Config, Context, Opts}

  alias Hologram.Compiler.IR.{
    AtomType,
    Binding,
    IntegerType,
    MapAccess,
    MapType,
    MatchAccess,
    MatchOperator,
    Variable
  }

  alias Hologram.Compiler.JSEncoder

  test "encode/3" do
    # code:
    # %{a: x, b: y} = %{a: 1, b: 2}

    ir = %MatchOperator{
      bindings: [
        %Binding{
          name: :x,
          access_path: [
            %MatchAccess{},
            %MapAccess{key: %AtomType{value: :a}}
          ]
        },
        %Binding{
          name: :y,
          access_path: [
            %MatchAccess{},
            %MapAccess{key: %AtomType{value: :b}}
          ]
        }
      ],
      left: %MapType{
        data: [
          {%AtomType{value: :a}, %Variable{name: :x}},
          {%AtomType{value: :b}, %Variable{name: :y}}
        ]
      },
      right: %MapType{
        data: [
          {%AtomType{value: :a}, %IntegerType{value: 1}},
          {%AtomType{value: :b}, %IntegerType{value: 2}}
        ]
      }
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    match_access_js = Config.match_access_js()

    expected = """
    #{match_access_js} = { type: 'map', data: { '~atom[a]': { type: 'integer', value: 1 }, '~atom[b]': { type: 'integer', value: 2 } } };
    let x = #{match_access_js}.data['~atom[a]'];
    let y = #{match_access_js}.data['~atom[b]'];\
    """

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/unquote_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.UnquoteTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, UnquoteTransformer}
  alias Hologram.Compiler.IR.{Unquote, Variable}

  test "transform/2" do
    code = "unquote(abc)"
    ast = ast(code)

    result = UnquoteTransformer.transform(ast, %Context{})
    expected = %Unquote{expression: %Variable{name: :abc}}

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/runtime/channel_test.exs<|end_filename|>
defmodule Hologram.Runtime.ChannelTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.Serializer
  alias Hologram.Conn
  alias Hologram.Runtime
  alias Hologram.Runtime.Channel
  alias Hologram.Template.Renderer

  @class_name_1 "Elixir_Hologram_Test_Fixtures_Runtime_Channel_Module1"
  @class_name_3 "Elixir_Hologram_Test_Fixtures_Runtime_Channel_Module3"
  @class_name_4 "Elixir_Hologram_Test_Fixtures_Runtime_Channel_Module4"
  @class_name_5 "Elixir_Hologram_Test_Fixtures_Runtime_Channel_Module5"
  @class_name_6 "Elixir_Hologram_Test_Fixtures_Runtime_Channel_Module6"
  @class_name_7 "Elixir_Hologram_Test_Fixtures_Runtime_Channel_Module7"

  @module_5 Hologram.Test.Fixtures.Runtime.Channel.Module5
  @target_id %{"type" => "atom", "value" => "test_target_id_value"}

  defp build_message(target_module, target_id) do
    %{
      "target_module" => target_module,
      "target_id" => target_id,
      "command" => %{"type" => "atom", "value" => "test_command"},
      "params" => %{"type" => "list", "data" => []}
    }
  end

  test "command returning target_id, action and params" do
    target_module = %{
      "type" => "module",
      "className" => @class_name_6
    }

    message = build_message(target_module, @target_id)
    reply = Channel.handle_in("command", message, :socket_dummy)

    expected_response =
      {:test_action_target_id, :test_action, %{a: 1, b: 2}}
      |> Serializer.serialize()

    expected_reply = {:reply, {:ok, expected_response}, :socket_dummy}

    assert reply == expected_reply
  end

  test "command returning target_id and action" do
    target_module = %{
      "type" => "module",
      "className" => @class_name_7
    }

    message = build_message(target_module, @target_id)
    reply = Channel.handle_in("command", message, :socket_dummy)

    expected_response =
      {:test_action_target_id, :test_action, %{}}
      |> Serializer.serialize()

    expected_reply = {:reply, {:ok, expected_response}, :socket_dummy}

    assert reply == expected_reply
  end

  test "command returning action and params" do
    target_module = %{
      "type" => "module",
      "className" => @class_name_3
    }

    message = build_message(target_module, @target_id)
    reply = Channel.handle_in("command", message, :socket_dummy)

    expected_response =
      {:test_target_id_value, :test_action, %{a: 1, b: 2}}
      |> Serializer.serialize()

    expected_reply = {:reply, {:ok, expected_response}, :socket_dummy}

    assert reply == expected_reply
  end

  test "command returning action only" do
    target_module = %{
      "type" => "module",
      "className" => @class_name_1
    }

    message = build_message(target_module, @target_id)
    reply = Channel.handle_in("command", message, :socket_dummy)

    expected_response =
      {:test_target_id_value, :test_action, %{}}
      |> Serializer.serialize()

    expected_reply = {:reply, {:ok, expected_response}, :socket_dummy}

    assert reply == expected_reply
  end

  test "command which receives params" do
    target_module = %{
      "type" => "module",
      "className" => @class_name_4
    }

    message = %{
      "target_module" => target_module,
      "target_id" => @target_id,
      "command" => %{"type" => "atom", "value" => "test_command"},
      "params" => %{
        "type" => "list",
        "data" => [
          %{
            "type" => "tuple",
            "data" => [
              %{"type" => "atom", "value" => "a"},
              %{"type" => "integer", "value" => 1}
            ]
          },
          %{
            "type" => "tuple",
            "data" => [
              %{"type" => "atom", "value" => "b"},
              %{"type" => "integer", "value" => 2}
            ]
          }
        ]
      }
    }

    reply = Channel.handle_in("command", message, :socket_dummy)

    expected_response =
      {:test_target_id_value, :test_action_1, %{}}
      |> Serializer.serialize()

    expected_reply = {:reply, {:ok, expected_response}, :socket_dummy}

    assert reply == expected_reply
  end

  test "command returning __redirect__ action" do
    [
      app_path: "#{@fixtures_path}/runtime/channel",
      templatables: [Hologram.Test.Fixtures.App.DefaultLayout]
    ]
    |> compile()

    Runtime.run()

    target_module = %{
      "type" => "module",
      "className" => @class_name_5
    }

    message = %{
      "target_module" => target_module,
      "target_id" => @target_id,
      "command" => %{"type" => "atom", "value" => "__redirect__"},
      "params" => %{
        "type" => "list",
        "data" => [
          %{
            "type" => "tuple",
            "data" => [
              %{"type" => "atom", "value" => "page"},
              %{"type" => "module", "className" => @class_name_5}
            ]
          }
        ]
      }
    }

    reply = Channel.handle_in("command", message, :socket_dummy)

    html = Renderer.render(@module_5, %Conn{}, %{})

    expected_response =
      {:test_target_id_value, :__redirect__, %{html: html, url: "/test-route-5"}}
      |> Serializer.serialize()

    expected_reply = {:reply, {:ok, expected_response}, :socket_dummy}

    assert reply == expected_reply
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/tmp_fallback.ex<|end_filename|>
# TODO: remove

alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: Any do
  def aggregate(_), do: nil
end


<|start_filename|>test/unit/hologram/compiler/decoders/map_type_decoder_test.exs<|end_filename|>
defmodule Hologram.Compiler.MapTypeDecoderTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Compiler.MapTypeDecoder

  test "encoded map decoding" do
    value = %{
      "type" => "map",
      "data" => %{
        "~string[test_key]" => %{
          "type" => "string",
          "value" => "test_value"
        }
      }
    }

    result = MapTypeDecoder.decode(value)
    expected = %{"test_key" => "test_value"}

    assert result == expected
  end

  test "encoded atom key decoding" do
    value = %{
      "data" => %{
        "~atom[test_key]" => %{
          "type" => "string",
          "value" => "test_value"
        }
      }
    }

    result = MapTypeDecoder.decode(value)
    expected = %{test_key: "test_value"}

    assert result == expected
  end

  test "encoded string key decoding" do
    value = %{
      "data" => %{
        "~string[test_key]" => %{
          "type" => "string",
          "value" => "test_value"
        }
      }
    }

    result = MapTypeDecoder.decode(value)
    expected = %{"test_key" => "test_value"}

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/placeholder_module_3.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.PlaceholderModule3 do
  def test_fun_3a, do: nil
  def test_fun_3b, do: nil
end


<|start_filename|>lib/hologram/runtime/js.ex<|end_filename|>
defmodule Hologram.Runtime.JS do
  @moduledoc """
  JS interop.

  Backend implementation is just a dummy, the actual implementation is on frontend only.
  """

  def exec(_code), do: nil
end


<|start_filename|>lib/hologram/compiler/transformers/tuple_type_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.TupleTypeTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.TupleType

  def transform({:{}, _, data}, %Context{} = context) do
    build_tuple(data, context)
  end

  def transform(data, %Context{} = context) do
    Tuple.to_list(data)
    |> build_tuple(context)
  end

  defp build_tuple(data, context) do
    data = Enum.map(data, &Transformer.transform(&1, context))
    %TupleType{data: data}
  end
end


<|start_filename|>test/unit/fixtures/placeholder_module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.PlaceholderModule2 do
  def test_fun_2a, do: nil
  def test_fun_2b, do: nil
end


<|start_filename|>lib/hologram/runtime/component.ex<|end_filename|>
defmodule Hologram.Component do
  defmacro __using__(_) do
    quote do
      import Hologram.Component
      import Hologram.Router, only: [static_path: 1]
      import Hologram.Runtime.Commons, only: [sigil_H: 2]
      import Map, only: [put: 3]

      alias Hologram.Runtime.JS
      alias Hologram.UI.Link

      def is_component?, do: true
    end
  end
end


<|start_filename|>assets/js/hologram/events/pointer_down_event.js<|end_filename|>
"use strict";

import Map from "../elixir/map"
import Type from "../type"

export default class PointerDownEvent {
  // DEFER: finish & test
  static buildEventData(event, _tag) {
    return Map.put(Type.map(), Type.atom("pointer_type"), Type.atom(event.pointerType))
  }

  // DEFER: test
  static shouldHandleEvent(_event) {
    return true
  }
}

<|start_filename|>lib/hologram/runtime/commons.ex<|end_filename|>
defmodule Hologram.Runtime.Commons do
  def sigil_H(str, _), do: String.trim(str)
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_32.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module32 do
  use Hologram.Page

  layout Hologram.Test.Fixtures.Compiler.Pruner.Module33

  route "/test-route-32"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/control_flow/anonymous_function_call_page.ex<|end_filename|>
defmodule HologramE2E.ControlFlow.AnonymousFunctionCallPage do
  use Hologram.Page

  route "/e2e/control-flow/anonymous-function-call"

  def init(_params, _conn) do
    %{
      result: 0
    }
  end

  def template do
    ~H"""
    <button id="button_regular_syntax" on:click="test_regular_syntax">Test regular syntax</button>
    <div id="text">Result = {@result}</div>
    """
  end

  def action(:test_regular_syntax, _params, state) do
    test_fun = fn x, y -> x * y end
    result = test_fun.(2, 3)

    Map.put(state, :result, result)
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_25.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module25 do
  def test_25a do
    :ok
  end
end


<|start_filename|>assets/js/hologram/action.js<|end_filename|>
"use strict";

import Command from "./command"
import Operation from "./operation"
import Store from "./store"
import Target from "./target";
import Type from "./type"
import Utils from "./utils"
import VDOM from "./vdom"

export default class Action {
  // Covered implicitely in E2E tests.
  static execute(operation) {
    const targetState = Store.getComponentState(operation.target.id)

    let actionResult = operation.target.class.action(operation.name, operation.params, targetState)
    actionResult = Utils.freeze(actionResult)

    Action.handleResult(actionResult, operation.target)
  }

  static getCommandNameFromActionResult(actionResult) {
    if (Type.isMap(actionResult)) {
      return null

    } else { // tuple
      const actionResultElems = actionResult.data
 
      if (actionResultElems.length === 4) {
        return actionResultElems[2]

      } else if (actionResultElems.length === 3 && Type.isAtom(actionResultElems[2])) {
        return actionResultElems[2]
                
      } else if (actionResultElems.length === 3 && !Type.isAtom(actionResultElems[2])) {
        return actionResultElems[1]

      } else if (actionResultElems.length === 2) {
        return actionResultElems[1]

      } else {
        return null
      }
    }
  }

  static getParamsFromActionResult(actionResult) {
    if (Type.isMap(actionResult)) {
      return Type.list()

    } else { // tuple
      const actionResultElems = actionResult.data
 
      if (actionResultElems.length === 4) {
        return actionResultElems[3]

      } else if (actionResultElems.length === 3 && Type.isList(actionResultElems[2])) {
        return actionResultElems[2]

      } else {
        return Type.list()
      }
    }
  }

  static getStateFromActionResult(actionResult) {
    if (Type.isMap(actionResult)) {
      return actionResult

    } else { // tuple
      return actionResult.data[0]
    }
  }

  static getTargetIdFromActionResult(actionResult) {
    if (Type.isMap(actionResult)) {
      return null

    } else { // tuple
      const actionResultElems = actionResult.data

      if (actionResultElems.length === 4) {
        return actionResultElems[1]

      } else if (actionResultElems.length === 3 && Type.isAtom(actionResultElems[2])) {
        return actionResultElems[1]

      } else {
        return null
      }
    }
  }

  // Covered implicitely in E2E tests.
  static handleResult(actionResult, actionTarget) {
    const newState = Action.getStateFromActionResult(actionResult)
    const commandName = Action.getCommandNameFromActionResult(actionResult)

    Store.setComponentState(actionTarget.id, newState)
    VDOM.render()

    if (commandName) {
      const commandTarget = Action.resolveCommandTarget(actionResult, actionTarget)
      const commandParams = Action.getParamsFromActionResult(actionResult)

      const operation = new Operation(actionTarget.id, commandTarget, commandName, commandParams)
      Command.execute(operation)
    } 
  }

  static resolveCommandTarget(actionResult, actionTarget) {
    const boxedCommandTargetId = Action.getTargetIdFromActionResult(actionResult)
    let commandTargetId;

    if (!boxedCommandTargetId) {
      commandTargetId = actionTarget.id
    } else {
      commandTargetId = boxedCommandTargetId.value
    }

    return new Target(commandTargetId)
  }
}

<|start_filename|>lib/hologram/template/evaluators/list_type.ex<|end_filename|>
alias Hologram.Compiler.IR.ListType
alias Hologram.Template.Evaluator

defimpl Evaluator, for: ListType do
  def evaluate(%{data: data}, bindings) do
    Enum.map(data, &Evaluator.evaluate(&1, bindings))
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/function_definition_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.FunctionDefinitionTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, FunctionDefinitionTransformer}

  alias Hologram.Compiler.IR.{
    AtomType,
    Binding,
    Block,
    FunctionDefinition,
    FunctionHead,
    IntegerType,
    MapAccess,
    ParamAccess,
    Variable
  }

  @code """
  def test(1, 2) do
  end
  """

  @context %Context{module: Abc}

  test "module" do
    ast = ast(@code)

    assert %FunctionDefinition{module: Abc} =
             FunctionDefinitionTransformer.transform(ast, @context)
  end

  test "name" do
    ast = ast(@code)

    assert %FunctionDefinition{name: :test} =
             FunctionDefinitionTransformer.transform(ast, @context)
  end

  test "arity" do
    ast = ast(@code)

    assert %FunctionDefinition{arity: 2} = FunctionDefinitionTransformer.transform(ast, @context)
  end

  test "params" do
    code = """
    def test(a, b) do
    end
    """

    ast = ast(code)

    assert %FunctionDefinition{} = result = FunctionDefinitionTransformer.transform(ast, @context)

    expected = [
      %Variable{name: :a},
      %Variable{name: :b}
    ]

    assert result.params == expected
  end

  test "bindings" do
    code = """
    def test(1, %{a: x, b: y}) do
    end
    """

    ast = ast(code)

    assert %FunctionDefinition{} = result = FunctionDefinitionTransformer.transform(ast, @context)

    expected = [
      %Binding{
        name: :x,
        access_path: [
          %ParamAccess{index: 1},
          %MapAccess{key: %AtomType{value: :a}}
        ]
      },
      %Binding{
        name: :y,
        access_path: [
          %ParamAccess{index: 1},
          %MapAccess{key: %AtomType{value: :b}}
        ]
      }
    ]

    assert result.bindings == expected
  end

  test "body, single expression" do
    code = """
    def test do
      1
    end
    """

    ast = ast(code)

    assert %FunctionDefinition{} = result = FunctionDefinitionTransformer.transform(ast, @context)

    expected = %Block{expressions: [
      %IntegerType{value: 1}
    ]}

    assert result.body == expected
  end

  test "body, multiple expressions" do
    code = """
    def test do
      1
      2
    end
    """

    ast = ast(code)

    assert %FunctionDefinition{} = result = FunctionDefinitionTransformer.transform(ast, @context)

    expected = %Block{expressions: [
      %IntegerType{value: 1},
      %IntegerType{value: 2}
    ]}

    assert result.body == expected
  end

  test "visibility public" do
    ast = ast(@code)

    assert %FunctionDefinition{visibility: :public} =
             FunctionDefinitionTransformer.transform(ast, @context)
  end

  test "visibility private" do
    code = """
    defp test(1, 2) do
    end
    """

    ast = ast(code)

    assert %FunctionDefinition{visibility: :private} =
             FunctionDefinitionTransformer.transform(ast, @context)
  end

  test "handles function definitions generated by macros" do
    ast =
      {:def, [context: Abc.Bcd, import: Kernel],
       [
         {:route, [context: Abc.Bcd], Abc.Bcd},
         [do: {:__block__, [], ["test"]}]
       ]}

    assert %FunctionDefinition{} = FunctionDefinitionTransformer.transform(ast, @context)
  end

  test "function head" do
    code = "def test(a, b)"
    ast = ast(code)

    assert %FunctionHead{} = FunctionDefinitionTransformer.transform(ast, %Context{})
  end
end


<|start_filename|>lib/hologram/template/renderer.ex<|end_filename|>
defprotocol Hologram.Template.Renderer do
  def render(vdom, conn, bindings, slots \\ nil)
end


<|start_filename|>test/unit/hologram/compiler/transformers/anonymous_function_call_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.AnonymousFunctionCallTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.AnonymousFunctionCallTransformer
  alias Hologram.Compiler.Context
  alias Hologram.Compiler.IR.AnonymousFunctionCall
  alias Hologram.Compiler.IR.IntegerType

  test "function without params" do
    code = "test.()"
    ast = ast(code)
    result = AnonymousFunctionCallTransformer.transform(ast, %Context{})

    expected = %AnonymousFunctionCall{
      name: :test,
      args: []
    }

    assert result == expected
  end

  test "function with params" do
    code = "test.(1, 2)"
    ast = ast(code)
    result = AnonymousFunctionCallTransformer.transform(ast, %Context{})

    expected = %AnonymousFunctionCall{
      name: :test,
      args: [
        %IntegerType{value: 1},
        %IntegerType{value: 2}
      ]
    }

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/list_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.ListType

defimpl CallGraphBuilder, for: ListType do
  def build(%{data: data}, module_defs, templates, from_vertex) do
    CallGraphBuilder.build(data, module_defs, templates, from_vertex)
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/page_9.ex<|end_filename|>
defmodule HologramE2E.Page9 do
  use Hologram.Page

  route "/e2e/page-9"

  def init(_params, _conn) do
    %{
      condition_1: 123,
      condition_2: nil
    }
  end

  def template do
    ~H"""
    <h1>Page 9</h1>
    <div id="div-1" if={@condition_1}>Element displayed</div>
    <div id="div-2" if={@condition_2}>Element not displayed</div>
    """
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/unary_negative_operator_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.UnaryNegativeOperatorTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}
  alias Hologram.Compiler.IR.{IntegerType, UnaryNegativeOperator}

  test "encode/3" do
    ir = %UnaryNegativeOperator{
      value: %IntegerType{value: 123}
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "Hologram.Interpreter.$unary_negative_operator({ type: 'integer', value: 123 })"

    assert result == expected
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e.ex<|end_filename|>
defmodule HologramE2E do
end


<|start_filename|>lib/hologram/compiler/transformers/membership_operator_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.MembershipOperatorTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.MembershipOperator

  def transform({:in, _, [left, right]}, %Context{} = context) do
    left = Transformer.transform(left, context)
    right = Transformer.transform(right, context)

    %MembershipOperator{left: left, right: right}
  end
end


<|start_filename|>test/unit/hologram/template/evaluators/if_expression_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.IfExpressionTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Template.Evaluator

  test "evaluate/2" do
    bindings = %{a: 1, b: 2, c: 3}
    code = "if @a, do: @b, else: @c"
    ast = ast(code)
    ir = Transformer.transform(ast, %Context{})
    result = Evaluator.evaluate(ir, bindings)

    assert result == 2
  end
end


<|start_filename|>lib/hologram/compiler/call_graph.ex<|end_filename|>
# DEFER: refactor & test

defmodule Hologram.Compiler.CallGraph do
  use Agent

  def add_edge(from_vertex, to_vertex) do
    Agent.update(__MODULE__, &Graph.add_edge(&1, from_vertex, to_vertex))
  end

  def add_vertex(vertex) do
    Agent.update(__MODULE__, &Graph.add_vertex(&1, vertex))
  end

  def edges(vertex) do
    Agent.get(__MODULE__, &Graph.edges(&1, vertex))
  end

  def get do
    Agent.get(__MODULE__, & &1)
  end

  def has_edge?(from_vertex, to_vertex) do
    callback = fn call_graph ->
      edges = Graph.edges(call_graph, from_vertex, to_vertex)
      Enum.count(edges) == 1
    end

    Agent.get(__MODULE__, callback)
  end

  def has_vertex?(vertex) do
    Agent.get(__MODULE__, &Graph.has_vertex?(&1, vertex))
  end

  def num_edges do
    Agent.get(__MODULE__, &Graph.num_edges/1)
  end

  def num_vertices do
    Agent.get(__MODULE__, &Graph.num_vertices/1)
  end

  def reachable(vertices) do
    Agent.get(__MODULE__, &Graph.reachable(&1, vertices))
  end

  def run do
    Agent.start_link(fn -> Graph.new() end, name: __MODULE__)
  end

  def stop do
    Agent.stop(__MODULE__)
  end
end


<|start_filename|>lib/hologram/compiler/helpers.ex<|end_filename|>
defmodule Hologram.Compiler.Helpers do
  alias Hologram.Compiler.{PatternDeconstructor, Transformer}
  alias Hologram.Compiler.IR.{Binding, FunctionDefinitionVariants, ModuleDefinition, ParamAccess}
  alias Hologram.Typespecs, as: T

  def aggregate_bindings_from_expression(expr) do
    PatternDeconstructor.deconstruct(expr)
    |> Enum.reduce([], fn binding, acc ->
      name = List.last(binding).name
      maybe_add_binding(acc, name, binding)
    end)
    |> Enum.sort()
    |> Enum.map(fn {name, path} ->
      %Binding{name: name, access_path: build_binding_access_path(path)}
    end)
  end

  def aggregate_bindings_from_params(params) do
    Enum.with_index(params)
    |> Enum.reduce([], &aggregate_bindings_from_param/2)
    |> Enum.sort()
    |> Enum.map(fn {name, {index, path}} ->
      %Binding{name: name, access_path: build_param_binding_access_path(path, index)}
    end)
  end

  defp build_binding_access_path(pattern_path) do
    pattern_path
    |> Enum.reverse()
    |> tl()
    |> Enum.reverse()
  end

  defp build_param_binding_access_path(pattern_path, param_index) do
    [%ParamAccess{index: param_index} | build_binding_access_path(pattern_path)]
  end

  defp aggregate_bindings_from_param({param, idx}, acc) do
    PatternDeconstructor.deconstruct(param)
    |> Enum.reduce(acc, fn binding, acc ->
      name = List.last(binding).name
      maybe_add_binding(acc, name, {idx, binding})
    end)
  end

  def aggregate_function_def_variants(function_defs) do
    Enum.reduce(function_defs, %{}, fn fd, acc ->
      if Map.has_key?(acc, fd.name) do
        Map.put(acc, fd.name, acc[fd.name] ++ [fd])
      else
        Map.put(acc, fd.name, [fd])
      end
    end)
    |> Enum.map(fn {name, variants} ->
      {name, %FunctionDefinitionVariants{name: name, variants: variants}}
    end)
    |> Enum.into(%{})
  end

  @doc """
  Returns the corresponding class name which can be used in JavaScript.
  ## Examples
      iex> Helpers.class_name(Abc.Bcd)
      "Elixir_Abc_Bcd"
  """
  @spec class_name(module()) :: String.t()

  def class_name(module) do
    [:"Elixir" | Module.split(module)]
    |> Enum.join("_")
  end

  @spec get_components(T.module_definitions_map()) :: list(%ModuleDefinition{})
  def get_components(module_defs_map) do
    module_defs_map
    |> Enum.filter(fn {_, module_def} -> module_def.component? end)
    |> Enum.map(fn {_, module_def} -> module_def end)
  end

  @spec get_pages(T.module_definitions_map()) :: list(%ModuleDefinition{})
  def get_pages(module_defs_map) do
    module_defs_map
    |> Enum.filter(fn {_, module_def} -> module_def.page? end)
    |> Enum.map(fn {_, module_def} -> module_def end)
  end

  defp maybe_add_binding(acc, name, binding) do
    if Keyword.has_key?(acc, name) do
      acc
    else
      Keyword.put(acc, name, binding)
    end
  end

  @doc """
  Returns the corresponding Elixir module.
  ## Examples
      iex> Helpers.module([:Abc, :Bcd])
      Elixir.Abc.Bcd
  """
  @spec module(T.module_name_segments()) :: module()

  def module(module_segs) do
    [:"Elixir" | module_segs]
    |> Enum.join(".")
    |> String.to_existing_atom()
  end

  @doc """
  Returns the corresponding module name (without the "Elixir" segment at the beginning).
  ## Examples
      iex> Helpers.module_name(Abc.Bcd)
      "Abc.Bcd"
  """
  @spec module_name(module()) :: String.t()

  def module_name(module) do
    Module.split(module)
    |> Enum.join(".")
  end

  @doc """
  Returns the corresponding module segments (without the "Elixir" segment at the beginning).
  ## Examples
      iex> Helpers.module_name_segments(Abc.Bcd)
      [:Abc, :Bcd]
  """
  @spec module_name_segments(module() | String.T) :: T.module_name_segments()

  def module_name_segments(module_name) when is_binary(module_name) do
    Module.split("Elixir.#{module_name}")
    |> Enum.map(&String.to_atom/1)
  end

  def module_name_segments(module) do
    Module.split(module)
    |> Enum.map(&String.to_atom/1)
  end

  def transform_params(params, context) do
    if(params, do: params, else: [])
    |> Enum.map(&Transformer.transform(&1, context))
  end

  @doc """
  Returns true if the first module has a "use" directive for the second module.
  ## Examples
      iex> user_module = %ModuleDefinition{module: Hologram.Compiler.Parser, ...}
      iex> Helpers.uses_module?(user_module, Hologram.Commons.Parser)
      true
  """
  @spec uses_module?(%ModuleDefinition{}, module()) :: boolean()

  def uses_module?(user_module_def, used_module) do
    Enum.any?(user_module_def.uses, &(&1.module == used_module))
  end
end


<|start_filename|>lib/hologram/compiler/transformers/less_than_operator_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.LessThanOperatorTransformer do
  alias Hologram.Compiler.Context
  alias Hologram.Compiler.Transformer
  alias Hologram.Compiler.IR.LessThanOperator

  def transform({:<, _, [left, right]}, %Context{} = context) do
    %LessThanOperator{
      left: Transformer.transform(left, context),
      right: Transformer.transform(right, context)
    }
  end
end


<|start_filename|>lib/hologram/ui/runtime.ex<|end_filename|>
# TODO: test

defmodule Hologram.UI.Runtime do
  use Hologram.Component

  def init(_props) do
    %{}
  end

  def template do
    ~H"""
    <script>
      window.hologramArgs = \{
        class: "{@__context__.__class__}",
        digest: "{@__context__.__digest__}",
        state: {@__context__.__state__}
      \}
    </script>
    <script src="/hologram/manifest.js"></script>
    <script src={static_path("/hologram/runtime.js")}></script>
    <script src="/hologram/page-{@__context__.__digest__}.js"></script>
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_31.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module31 do
  use Hologram.Component

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_46.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module46 do
  use Hologram.Component
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module47

  def init(_props) do
    Module47.test_fun_47a()
    %{}
  end

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/js/elixir/map_test.js<|end_filename|>
"use strict";

import {
  assert,
  assertBoxedFalse,
  assertBoxedTrue,
  assertFrozen,
  cleanup,
} from "../support/commons";
beforeEach(() => cleanup());

import Map from "../../../assets/js/hologram/elixir/map";
import Type from "../../../assets/js/hologram/type";

describe("get()", () => {
  let elems = {};
  elems[Type.atomKey("a")] = Type.integer(1);
  elems[Type.atomKey("b")] = Type.integer(2);
  const map = Type.map(elems);

  it("gets the value for a specific key in map if the given key exists in the given map", () => {
    const result = Map.get(map, Type.atom("b"));
    assert.deepStrictEqual(result, Type.integer(2));
  });

  it("returns boxed nil by default if the given key doesn't exist in the given map", () => {
    const result = Map.get(map, Type.atom("c"));
    assert.deepStrictEqual(result, Type.nil());
  });

  it("it returns the default_value arg if the given key doesn't exist in the given map and the default_value param is specified", () => {
    const result = Map.get(map, Type.atom("c"), Type.integer(9));
    assert.deepStrictEqual(result, Type.integer(9));
  });
});

describe("has_key$question()", () => {
  let elems = {};
  elems[Type.atomKey("a")] = Type.integer(1);
  elems[Type.atomKey("b")] = Type.integer(2);
  const map = Type.map(elems);

  it("returns boxed true if the given key exists in the given map", () => {
    const result = Map.has_key$question(map, Type.atom("b"));
    assertBoxedTrue(result);
  });

  it("returns boxed false if the given key doesn't exist in the given map", () => {
    const result = Map.has_key$question(map, Type.atom("c"));
    assertBoxedFalse(result);
  });
});

describe("keys()", () => {
  it("returns keys of a non-empty map", () => {
    let map = Type.map();
    map = Map.put(map, Type.atom("a"), Type.integer(1));
    map = Map.put(map, Type.atom("b"), Type.integer(2));

    const result = Map.keys(map);
    const expected = Type.list([Type.atom("a"), Type.atom("b")]);

    assert.deepStrictEqual(result, expected);
  });

  it("returns keys of an empty map", () => {
    const result = Map.keys(Type.map());
    assert.deepStrictEqual(result, Type.list());
  });

  it("returns frozen object", () => {
    const result = Map.keys(Type.map());
    assertFrozen(result);
  });
});

describe("put()", () => {
  let map1, map2, result;

  beforeEach(() => {
    let elems1 = {};
    elems1[Type.atomKey("a")] = Type.integer(1);
    elems1[Type.atomKey("b")] = Type.integer(2);
    map1 = Type.map(elems1);

    let elems2 = {};
    elems2[Type.atomKey("a")] = Type.integer(1);
    elems2[Type.atomKey("b")] = Type.integer(2);
    elems2[Type.atomKey("c")] = Type.integer(3);
    map2 = Type.map(elems2);

    result = Map.put(map1, Type.atom("c"), Type.integer(3));
  });

  it("adds the key-value pair to the map when the map doesn't contain the given key yet", () => {
    assert.deepStrictEqual(result, map2);
  });

  it("adds the key-value pair to the map when the map already contains the given key", () => {
    const result = Map.put(map2, Type.atom("c"), Type.integer(3));
    assert.deepStrictEqual(result, map2);
  });

  it("clones the orignal map object", () => {
    assert.notEqual(result, map1);
  });

  it("returns frozen object", () => {
    assertFrozen(result);
  });
});

describe("to_list()", () => {
  it("converts empty boxed map to empty boxed list", () => {
    const map = Type.map();

    const result = Map.to_list(map);
    const expected = Type.list();

    assert.deepStrictEqual(result, expected);
  });

  it("converts non-empty boxed map to boxed list consisting of {key, value} tuples", () => {
    let map = Type.map();
    map = Map.put(map, Type.atom("a"), Type.integer(1));
    map = Map.put(map, Type.string("b"), Type.float(2.0));

    const result = Map.to_list(map);

    const expectedData = [
      Type.tuple([Type.atom("a"), Type.integer(1)]),
      Type.tuple([Type.string("b"), Type.float(2.0)]),
    ];

    const expected = Type.list(expectedData);

    assert.deepStrictEqual(result, expected);
  });
});


<|start_filename|>test/unit/fixtures/compiler/pruner/module_39.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module39 do
  use Hologram.Layout

  def init(_conn) do
    %{}
  end

  def template do
    ~H"""
    """
  end
end


<|start_filename|>lib/hologram/template/embedded_expression_parser.ex<|end_filename|>
defmodule Hologram.Template.EmbeddedExpressionParser do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.Parser, as: CompilerParser
  alias Hologram.Template.{TokenHTMLEncoder, Tokenizer}
  alias Hologram.Template.VDOM.{Expression, TextNode}

  @doc """
  Splits a string which may contain embedded expressions into a list of expression nodes and text nodes.
  """
  def parse(str, %Context{} = context) do
    acc = %{
      nodes: [],
      num_open_braces: 0,
      prev_tokens: [],
      token_buffer: []
    }

    Tokenizer.tokenize(str)
    |> assemble_nodes(:text, acc, context)
    |> Map.get(:nodes)
  end

  # status is one of: :text, :expression
  defp assemble_nodes(tokens, status, acc, context)

  defp assemble_nodes([], :text, acc, _) do
    maybe_add_text_node(acc)
  end

  # DEFER: implement this case (raise an error)
  # defp assemble_nodes([], :expression, acc, _)

  defp assemble_nodes([{:symbol, :"{"} = token | rest], :text, acc, context) do
    acc =
      acc
      |> maybe_add_text_node()
      |> increment_num_open_braces()
      |> add_prev_token(token)

    assemble_nodes(rest, :expression, acc, context)
  end

  defp assemble_nodes([{:symbol, :"{"} = token | rest], :expression, acc, context) do
    acc
    |> increment_num_open_braces()
    |> assemble_node_part(token, rest, :expression, context)
  end

  defp assemble_nodes(
         [{:symbol, :"}"} = token | rest],
         :expression,
         %{num_open_braces: 1} = acc,
         context
       ) do
    acc =
      acc
      |> maybe_add_expression_part(context)
      |> decrement_num_open_braces()
      |> add_prev_token(token)

    assemble_nodes(rest, :text, acc, context)
  end

  defp assemble_nodes([{:symbol, :"}"} = token | rest], :expression, acc, context) do
    acc
    |> decrement_num_open_braces()
    |> assemble_node_part(token, rest, :expression, context)
  end

  defp assemble_nodes([token | rest], :text, acc, context) do
    assemble_node_part(acc, token, rest, :text, context)
  end

  defp assemble_nodes([token | rest], :expression, acc, context) do
    assemble_node_part(acc, token, rest, :expression, context)
  end

  defp add_prev_token(acc, token) do
    %{acc | prev_tokens: acc.prev_tokens ++ [token]}
  end

  defp assemble_node_part(acc, token, rest, type, context) do
    acc = acc |> buffer_token(token) |> add_prev_token(token)
    assemble_nodes(rest, type, acc, context)
  end

  defp buffer_token(acc, token) do
    %{acc | token_buffer: acc.token_buffer ++ [token]}
  end

  defp decrement_num_open_braces(acc) do
    %{acc | num_open_braces: acc.num_open_braces - 1}
  end

  defp flush_token_buffer(acc) do
    tokens = acc.token_buffer
    acc = %{acc | token_buffer: []}
    {tokens, acc}
  end

  defp get_ir(code, context) do
    CompilerParser.parse!("{#{code}}")
    |> Transformer.transform(context)
  end

  defp increment_num_open_braces(acc) do
    %{acc | num_open_braces: acc.num_open_braces + 1}
  end

  defp maybe_add_expression_part(acc, context) do
    {tokens, acc} = flush_token_buffer(acc)

    if Enum.any?(tokens) do
      code = TokenHTMLEncoder.encode(tokens)
      node = %Expression{ir: get_ir(code, context)}
      %{acc | nodes: acc.nodes ++ [node]}
    else
      acc
    end
  end

  defp maybe_add_text_node(acc) do
    {tokens, acc} = flush_token_buffer(acc)

    if Enum.any?(tokens) do
      node = %TextNode{content: TokenHTMLEncoder.encode(tokens)}
      %{acc | nodes: acc.nodes ++ [node]}
    else
      acc
    end
  end
end


<|start_filename|>test/unit/fixtures/runtime/component/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Runtime.Component.Module1 do
  use Hologram.Component
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/block.ex<|end_filename|>
alias Hologram.Compiler.IR.Block
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: Block do
  def aggregate(%{expressions: exprs}) do
    Enum.each(exprs, &ModuleDefAggregator.aggregate/1)
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/page_10.ex<|end_filename|>
defmodule HologramE2E.Page10 do
  use Hologram.Page

  route "/e2e/page-10"

  def init(_params, _conn) do
    %{
      field_1: "",
      field_2: ""
    }
  end

  def template do
    ~H"""
    <h1>Page 10</h1>
    <form on:change="change">
      <input id="input-1" type="text" name="field_1" />
      <input id="input-2" type="text" name="field_2" />
    </form>
    <div id="text-1">Field 1 value = {@field_1}</div>
    <div id="text-2">Field 2 value = {@field_2}</div>
    """
  end

  def action(:change, params, state) do
    put(state, :field_1, params.event.field_1)
    |> put(:field_2, params.event.field_2)
  end
end


<|start_filename|>lib/hologram/compiler/transformers/quote_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.QuoteTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.Quote

  def transform({:quote, _, [[do: body]]}, %Context{} = context) do
    body = Transformer.transform(body, context)
    %Quote{body: body}
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/multiplication_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.MultiplicationOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, MultiplicationOperatorTransformer}
  alias Hologram.Compiler.IR.{IntegerType, MultiplicationOperator, Variable}

  test "transform/3" do
    code = "a * 2"
    ast = ast(code)

    result = MultiplicationOperatorTransformer.transform(ast, %Context{})

    expected = %MultiplicationOperator{
      left: %Variable{name: :a},
      right: %IntegerType{value: 2}
    }

    assert result == expected
  end
end


<|start_filename|>lib/mix/tasks/compile.hologram.ex<|end_filename|>
# DEFER: refactor & test

defmodule Mix.Tasks.Compile.Hologram do
  use Mix.Task.Compiler
  require Logger

  alias Hologram.Compiler
  alias Hologram.Compiler.Reflection
  alias Hologram.Utils

  def run(opts \\ []) do
    Logger.debug("Hologram compiler started")

    if has_source?() do
      source_digest =
        list_source_files(opts)
        |> digest_files()

      if !has_source_digest?() || has_source_changes?(source_digest) || opts[:force] do
        Compiler.compile(opts)
        save_source_digest(source_digest)
      end
    end

    Logger.debug("Hologram compiler finished")

    :ok
  end

  defp digest_files(paths) do
    data =
      Enum.reduce(paths, [], &(&2 ++ Utils.list_files_recursively(&1)))
      |> Enum.reduce(%{}, &Map.put(&2, &1, File.read!(&1)))
      |> :erlang.term_to_binary()

    :crypto.hash(:md5, data)
  end

  defp has_source? do
    Reflection.mix_path()
    |> File.exists?()
  end

  defp has_source_changes?(new_source_digest) do
    old_source_digest =
      Reflection.root_source_digest_path()
      |> File.read!()

    new_source_digest != old_source_digest
  end

  defp has_source_digest? do
    Reflection.root_source_digest_path()
    |> File.exists?()
  end

  defp list_source_files(opts) do
    [
      Reflection.app_path(opts),
      Reflection.lib_path(opts),
      Reflection.mix_path(opts),
      Reflection.mix_lock_path(opts)
    ]
  end

  defp save_source_digest(source_digest) do
    Reflection.root_source_digest_path()
    |> File.write!(source_digest)
  end
end


<|start_filename|>test/unit/fixtures/template/renderers/element_node_renderer/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.ElementNodeRenderer.Module2 do
  use Hologram.Component

  def init(_props) do
    %{
      component_2_state_key: "component_2_state_value"
    }
  end

  def template do
    ~H"""
    (in component 2)
    """
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/element_node.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Template.VDOM.ElementNode

defimpl CallGraphBuilder, for: ElementNode do
  def build(%{attrs: attrs, children: children}, module_defs, templates, from_vertex) do
    CallGraphBuilder.build(attrs, module_defs, templates, from_vertex)
    CallGraphBuilder.build(children, module_defs, templates, from_vertex)
  end
end


<|start_filename|>lib/hologram/template/evaluators/module_type.ex<|end_filename|>
alias Hologram.Compiler.IR.ModuleType
alias Hologram.Template.Evaluator

defimpl Evaluator, for: ModuleType do
  def evaluate(%{module: module}, _) do
    module
  end
end


<|start_filename|>lib/hologram/template/evaluators/nil_type.ex<|end_filename|>
alias Hologram.Compiler.IR.NilType
alias Hologram.Template.Evaluator

defimpl Evaluator, for: NilType do
  def evaluate(_, _) do
    nil
  end
end


<|start_filename|>test/unit/fixtures/runtime/channel/module_6.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Runtime.Channel.Module6 do
  use Hologram.Page

  route "/test-route-6"

  def init(_params, _conn) do
    %{}
  end

  def template do
    ~H"""
    """
  end

  def command(:test_command, _params) do
    {:test_action_target_id, :test_action, a: 1, b: 2}
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/list_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.ListType
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: ListType do
  def aggregate(%{data: data}) do
    ModuleDefAggregator.aggregate(data)
  end
end


<|start_filename|>test/js/elixir/hologram/runtime/js_test.js<|end_filename|>
"use strict";

import {
  assert,
  cleanup,
} from "../../../support/commons";
beforeEach(() => cleanup());

import JS from "../../../../../assets/js/hologram/elixir/hologram/runtime/js"
import Type from "../../../../../assets/js/hologram/type"

describe("exec()", () => {
  it("executes JS code evaluated from the given boxed arg", () => {
    const TestClass = class {
      static testField = null
    }

    globalThis.TestClass = TestClass

    const code = Type.binary([
      Type.string("TestClass.testField"),
      Type.string(" = "),
      Type.string("'testValue'")
    ])

    JS.exec(code)

    assert.equal(TestClass.testField, "testValue")
  })
})

<|start_filename|>test/unit/hologram/compiler/js_encoders/string_type_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.StringTypeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}
  alias Hologram.Compiler.IR.StringType

  test "encode/3" do
    ir = %StringType{value: "abc'bcd\ncde"}

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "{ type: 'string', value: 'abc\\'bcd\\ncde' }"

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/element_node.ex<|end_filename|>
alias Hologram.Compiler.ModuleDefAggregator
alias Hologram.Template.VDOM.ElementNode

defimpl ModuleDefAggregator, for: ElementNode do
  def aggregate(%{attrs: attrs, children: children}) do
    ModuleDefAggregator.aggregate(attrs)
    ModuleDefAggregator.aggregate(children)
  end
end


<|start_filename|>test/unit/fixtures/compiler/reflection/module_5.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Reflection.Module5 do
end


<|start_filename|>test/unit/hologram/runtime/template_store_test.exs<|end_filename|>
defmodule Hologram.Runtime.TemplateStoreTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.Reflection
  alias Hologram.Runtime.TemplateStore

  test "populate_table/0" do
    dump_path = Reflection.release_template_store_path()
    store_content = %{key_1: :value_1, key_2: :value_2}
    Path.dirname(dump_path) |> File.mkdir_p!()
    File.write!(dump_path, Utils.serialize(store_content))

    TemplateStore.run()

    assert TemplateStore.get_all() == store_content
  end
end


<|start_filename|>test/js/support/commons.js<|end_filename|>
"use strict";

import Runtime from "../../../assets/js/hologram/runtime";
import Store from "../../../assets/js/hologram/store";
import Type from "../../../assets/js/hologram/type";
import Utils from "../../../assets/js/hologram/utils";

export const assert = require("chai").assert;
export const sinon = require("sinon");

const { JSDOM } = require("jsdom");
const util = require("util");

export function assertBoxedFalse(boxedValue) {
  assert.isTrue(Type.isFalse(boxedValue));
}

export function assertBoxedNil(boxedValue) {
  assert.isTrue(Type.isNil(boxedValue));
}

export function assertBoxedTrue(boxedValue) {
  assert.isTrue(Type.isTrue(boxedValue));
}

export function assertFrozen(obj) {
  assert.isTrue(Object.isFrozen(obj));
}

export function assertNotFrozen(obj) {
  assert.isFalse(Object.isFrozen(obj));
}

export function cleanup() {
  Runtime.componentClassRegistry = {};
  Store.componentStateRegistry = {};
}

export function debug(obj) {
  console.log(util.inspect(obj, { showHidden: false, depth: null }));
}

export function fixtureOperationParamsKeyword() {
  const paramsTuples = [
    Type.tuple([Type.atom("a"), Type.integer(1)]),
    Type.tuple([Type.atom("b"), Type.integer(2)]),
  ];

  return Type.list(paramsTuples);
}

export function fixtureOperationParamsMap() {
  const operationParamsKeyword = fixtureOperationParamsKeyword();
  return Type.keywordToMap(operationParamsKeyword);
}

export function fixtureOperationSpecExpressionNode(specElems) {
  const callback = (_$bindings) => {
    return Type.tuple(specElems);
  };
  const expressionNodeSpec = { type: "expression", callback: callback };

  return Utils.freeze(expressionNodeSpec);
}

export function mockWindow() {
  return new JSDOM().window;
}

global.Event = mockWindow().Event;

global.ModuleStub1 = class ModuleStub1 {
  static test(arg1, arg2) {
    return Type.integer(arg1.value + arg2.value);
  }
};


<|start_filename|>test/unit/hologram/compiler/module_def_store_test.exs<|end_filename|>
defmodule Hologram.Compiler.ModuleDefStoreTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{ModuleDefStore, ModuleDefStoreTest}
  alias Hologram.Compiler.IR.ModuleDefinition

  setup do
    ModuleDefStore.run()
    :ok
  end

  describe "get_if_not_exists/1 (and handle_call/3 :get_if_not_exists implicitely)" do
    test "module is not in store yet" do
      assert %ModuleDefinition{module: ModuleDefStoreTest} =
               ModuleDefStore.get_if_not_exists(ModuleDefStoreTest)
    end

    test "module is in store already" do
      ModuleDefStore.put(:test_module, %ModuleDefinition{module: :test_module})
      refute ModuleDefStore.get_if_not_exists(:test_module)
    end
  end
end


<|start_filename|>lib/hologram/template/evaluators/function_call.ex<|end_filename|>
alias Hologram.Compiler.IR.FunctionCall
alias Hologram.Template.Evaluator

defimpl Evaluator, for: FunctionCall do
  def evaluate(%{module: module, function: function, args: args}, bindings) do
    args = Enum.map(args, &Evaluator.evaluate(&1, bindings))
    apply(module, function, args)
  end
end


<|start_filename|>lib/hologram/template/evaluators/map_type.ex<|end_filename|>
alias Hologram.Compiler.IR.MapType
alias Hologram.Template.Evaluator

defimpl Evaluator, for: MapType do
  def evaluate(%{data: data}, bindings) do
    Enum.map(data, fn {key, value} ->
      {Evaluator.evaluate(key, bindings), Evaluator.evaluate(value, bindings)}
    end)
    |> Enum.into(%{})
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/dot_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.DotOperator

defimpl CallGraphBuilder, for: DotOperator do
  def build(%{left: left, right: right}, module_defs, templates, from_vertex) do
    CallGraphBuilder.build(left, module_defs, templates, from_vertex)
    CallGraphBuilder.build(right, module_defs, templates, from_vertex)
  end
end


<|start_filename|>lib/hologram/compiler/map_key_encoders/atom_type.ex<|end_filename|>
alias Hologram.Compiler.IR.AtomType
alias Hologram.Compiler.MapKeyEncoder

defimpl MapKeyEncoder, for: AtomType do
  use Hologram.Commons.Encoder

  def encode(%{value: value}, _, _) do
    encode_primitive_key(:atom, value)
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/case_expression_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.CaseExpressionTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{CaseExpressionTransformer, Context}

  alias Hologram.Compiler.IR.{
    AtomType,
    Binding,
    Block,
    CaseConditionAccess,
    CaseExpression,
    IntegerType,
    MapAccess,
    MapType,
    Variable
  }

  test "single expression clause body" do
    code = """
    case x do
      1 -> :ok
    end
    """

    result =
      code
      |> ast()
      |> CaseExpressionTransformer.transform(%Context{})

    expected = %CaseExpression{
      clauses: [
        %{
          bindings: [],
          body: %Block{expressions: [
            %AtomType{value: :ok}
          ]},
          pattern: %IntegerType{value: 1}
        }
      ],
      condition: %Variable{name: :x}
    }

    assert result == expected
  end

  test "multiple expression clause body" do
    code = """
    case x do
      1 ->
        :expr_1
        :expr_2
    end
    """

    result =
      code
      |> ast()
      |> CaseExpressionTransformer.transform(%Context{})

    expected = %CaseExpression{
      clauses: [
        %{
          bindings: [],
          body: %Block{expressions: [
            %AtomType{value: :expr_1},
            %AtomType{value: :expr_2}
          ]},
          pattern: %IntegerType{value: 1}
        }
      ],
      condition: %Variable{name: :x}
    }

    assert result == expected
  end

  test "clause without bindings" do
    code = """
    case x do
      1 -> :ok
    end
    """

    result =
      code
      |> ast()
      |> CaseExpressionTransformer.transform(%Context{})

    expected = %CaseExpression{
      clauses: [
        %{
          bindings: [],
          body: %Block{expressions: [
            %AtomType{value: :ok}
          ]},
          pattern: %IntegerType{value: 1}
        }
      ],
      condition: %Variable{name: :x}
    }

    assert result == expected
  end

  test "clause with bindings" do
    code = """
    case x do
      %{a: a} -> :ok
    end
    """

    result =
      code
      |> ast()
      |> CaseExpressionTransformer.transform(%Context{})

    expected = %CaseExpression{
      clauses: [
        %{
          bindings: [
            %Binding{
              name: :a,
              access_path: [
                %CaseConditionAccess{},
                %MapAccess{key: %AtomType{value: :a}}
              ]
            }
          ],
          body: %Block{expressions: [
            %AtomType{value: :ok}
          ]},
          pattern: %MapType{
            data: [
              {%AtomType{value: :a}, %Variable{name: :a}}
            ]
          }
        }
      ],
      condition: %Variable{name: :x}
    }

    assert result == expected
  end
end


<|start_filename|>lib/hologram/commons/parser.ex<|end_filename|>
defmodule Hologram.Commons.Parser do
  @callback parse(String.t()) :: {:ok, term} | {:error, term}

  defmacro __using__(_) do
    quote do
      @behaviour Hologram.Commons.Parser

      def parse!(str) do
        case parse(str) do
          {:ok, ast} ->
            ast

          _ ->
            raise """
            Invalid code:
            -----
            #{str}
            -----
            """
        end
      end

      def parse_file(filepath) do
        filepath
        |> File.read!()
        |> parse()
      end

      def parse_file!(filepath) do
        filepath
        |> File.read!()
        |> parse!()
      end
    end
  end
end


<|start_filename|>test/unit/hologram/compiler/decoders/module_type_decoder_test.exs<|end_filename|>
defmodule Hologram.Compiler.ModuleTypeDecoderTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Compiler.ModuleTypeDecoder

  test "encoded module decoding" do
    value = %{
      "type" => "module",
      "className" => "Elixir_Hologram_Compiler_ModuleTypeDecoderTest"
    }

    result = ModuleTypeDecoder.decode(value)
    expected = Hologram.Compiler.ModuleTypeDecoderTest

    assert result == expected
  end
end


<|start_filename|>lib/hologram/template/token_html_encoder.ex<|end_filename|>
# Covered in Hologram.Template.Parser integration tests

defmodule Hologram.Template.TokenHTMLEncoder do
  def encode(arg)

  def encode(tokens) when is_list(tokens) do
    Enum.map(tokens, &encode/1)
    |> Enum.join("")
  end

  def encode({:symbol, symbol}), do: to_string(symbol)

  def encode({:string, str}), do: str

  def encode({:whitespace, char}), do: char

  def encode(nil), do: ""
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_40.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module40 do
  use Hologram.Page

  layout Hologram.Test.Fixtures.Compiler.Pruner.Module41

  route "/test-route-40"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/struct_type.ex<|end_filename|>
alias Hologram.Compiler.{Context, Helpers, JSEncoder, Opts}
alias Hologram.Compiler.IR.StructType

defimpl JSEncoder, for: StructType do
  use Hologram.Commons.Encoder

  def encode(%{module: module, data: data}, %Context{} = context, %Opts{} = opts) do
    class_name = Helpers.class_name(module)
    data = encode_map_data(data, context, opts)

    "{ type: 'struct', className: '#{class_name}', data: #{data} }"
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/string_type.ex<|end_filename|>
alias Hologram.Compiler.IR.StringType
alias Hologram.Compiler.JSEncoder

defimpl JSEncoder, for: StringType do
  use Hologram.Commons.Encoder

  def encode(%{value: value}, _, _) do
    value =
      String.replace(value, "'", "\\'")
      |> String.replace("\n", "\\n")

    encode_primitive_type(:string, "'#{value}'")
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/pattern_matching/cons_operator_page.ex<|end_filename|>
defmodule HologramE2E.PatternMatching.ConsOperatorPage do
  use Hologram.Page

  route "/e2e/pattern-matching/cons-operator"

  def init(_params, _conn) do
    %{
      case_condition_value: [3 | [4, 5]],
      function_call_value: [2 | [3, 4]],
      match_expression_value: [1 | [2, 3]],
      result: 0
    }
  end

  def template do
    ~H"""
    <button id="button_match_expression" on:click="test_match_expression">Test match expression</button>
    <button id="button_function_call" on:click="test_function_call">Test function call</button>
    <button id="button_case_condition" on:click="test_case_condition">Test case condition</button>
    <div id="text">Result = {@result}</div>
    """
  end

  def action(:test_match_expression, _params, state) do
    [h | t] = state.match_expression_value
    Map.put(state, :result, h + Enum.count(t))
  end

  def action(:test_function_call, _params, state) do
    result = dummy_function(state.function_call_value)
    Map.put(state, :result, result)
  end

  def action(:test_case_condition, _params, state) do
    result =
      case state.case_condition_value do
        123 ->
          :not_matched

        [h | t] ->
          h + Enum.count(t)

        _ ->
          :default_match
      end

    Map.put(state, :result, result)
  end

  defp dummy_function([h | t]) do
    h + Enum.count(t)
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/integer_type.ex<|end_filename|>
alias Hologram.Compiler.IR.IntegerType
alias Hologram.Compiler.JSEncoder

defimpl JSEncoder, for: IntegerType do
  use Hologram.Commons.Encoder

  def encode(%{value: value}, _, _) do
    encode_primitive_type(:integer, "#{value}")
  end
end


<|start_filename|>test/unit/fixtures/compiler/resolver/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Resolver.Module1 do
  def test(_a, _b) do
    :ok
  end
end


<|start_filename|>lib/hologram/template/syntax_error.ex<|end_filename|>
defmodule Hologram.Template.SyntaxError do
  defexception [:message]
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/component.ex<|end_filename|>
alias Hologram.Compiler.ModuleDefAggregator
alias Hologram.Template.VDOM.Component

defimpl ModuleDefAggregator, for: Component do
  def aggregate(%{module: module, props: props, children: children}) do
    ModuleDefAggregator.aggregate(children)
    ModuleDefAggregator.aggregate(module)
    ModuleDefAggregator.aggregate(props)
  end
end


<|start_filename|>lib/hologram/compiler/map_key_encoder.ex<|end_filename|>
defprotocol Hologram.Compiler.MapKeyEncoder do
  def encode(ir, context, opts)
end


<|start_filename|>test/unit/fixtures/compiler/transformers/module_definition_transformer/module_3.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefinitionTransformer.Module3 do
end


<|start_filename|>test/unit/hologram/runtime/page_test.exs<|end_filename|>
defmodule Hologram.Runtime.PageTest do
  use Hologram.Test.UnitCase, async: true
  require Hologram.Page

  @default_layout Application.compile_env!(:hologram, :default_layout)

  test "is_page?/0" do
    module = Hologram.Test.Fixtures.Runtime.Page.Module1
    assert module.is_page?()
  end

  describe "layout/0" do
    test "default layout" do
      module = Hologram.Test.Fixtures.Runtime.Page.Module1
      result = module.layout()

      assert result == @default_layout
    end

    test "non-default layout" do
      module = Hologram.Test.Fixtures.Runtime.Page.Module2
      result = module.layout()

      assert result == :test_layout
    end
  end

  test "layout/1" do
    ast =
      Macro.expand_once(
        quote do
          Hologram.Page.layout(Hologram.PageTest)
        end,
        __ENV__
      )

    assert {
             :def,
             [context: Hologram.Page, import: Kernel],
             [
               {:custom_layout, [counter: _, context: Hologram.Page], Hologram.Page},
               [do: {:__aliases__, [counter: _, alias: false], [:Hologram, :PageTest]}]
             ]
           } = ast
  end

  test "route/1" do
    ast =
      Macro.expand_once(
        quote do
          Hologram.Page.route("/test-path")
        end,
        __ENV__
      )

    assert {:def, [context: Hologram.Page, import: Kernel],
            [{:route, [counter: _, context: Hologram.Page], Hologram.Page}, [do: "/test-path"]]} =
             ast
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/elixir_tuple.ex<|end_filename|>
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: Tuple do
  def aggregate(tuple) do
    tuple
    |> Tuple.to_list()
    |> ModuleDefAggregator.aggregate()
  end
end


<|start_filename|>assets/js/hologram/elixir/kernel.js<|end_filename|>
"use strict";

import { HologramNotImplementedError } from "../errors";
import Map from "../elixir/map"
import Runtime from "../runtime"
import Type from "../type"

export default class Kernel {
  static apply() {
    if (arguments.length === 3) {
      const module = Runtime.getClassByClassName(arguments[0].className)
      const functionName = arguments[1].value
      const args = arguments[2].data

      return module[functionName](...args)

    } else {
      const message = `Kernel.apply(): arguments = ${JSON.stringify(arguments)}`
      throw new HologramNotImplementedError(message)
    }
  }

  // TODO: raise ArgumentError when index is negative or it is out of range
  static elem(tuple, index) {
    return tuple.data[index]
  }

  // TODO: raise ArgumentError if the list is empty.
  static hd(list) {
    return list.data[0]
  }

  static is_function(term) {
    return Type.isAnonymousFunction(term) ? Type.boolean(true) : Type.boolean(false)
  }

  // DEFER: implement other types (works for maps only)
  static put_in(data, keys, value) {
    const key = keys.data[0]

    if (keys.data.length > 1) {
      const subtree = Map.get(data, key)
      const subtreeKeys = Type.list(keys.data.slice(1))
      const newSubtree = Kernel.put_in(subtree, subtreeKeys, value)
      return Map.put(data, key, newSubtree)

    } else {
      return Map.put(data, key, value)
    }
  }

  // TODO: raise ArgumentError if the list is empty.
  static tl(list) {
    return Type.list(list.data.slice(1))
  }

  static to_string(boxedValue) {
    switch (boxedValue.type) {
      case "atom":
      case "boolean":
      case "float":
      case "integer":
        return Type.string(`${boxedValue.value}`)

      case "binary":
        const str = boxedValue.data
          .map(elem => Kernel.to_string(elem).value)
          .join("")

        return Type.string(str)

      case "nil":
        return Type.string("")

      case "string":
        return boxedValue

      default:
        const message = `Kernel.to_string(): boxedValue = ${JSON.stringify(boxedValue)}`
        throw new HologramNotImplementedError(message)
    }
  }
}

<|start_filename|>lib/hologram/compiler/call_graph_builder/block.ex<|end_filename|>
alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.Block

defimpl CallGraphBuilder, for: Block do
  def build(%{expressions: expressions}, module_defs, templates, from_vertex) do
    Enum.each(expressions, &CallGraphBuilder.build(&1, module_defs, templates, from_vertex))
  end
end


<|start_filename|>lib/hologram/compiler/config.ex<|end_filename|>
defmodule Hologram.Compiler.Config do
  def case_condition_js, do: "$condition"
  def match_access_js, do: "window.$hologramMatchAccess"
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_48.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module48 do
  use Hologram.Page

  route "/test-route-48"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>lib/hologram/compiler/transformers/case_expression_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.CaseExpressionTransformer do
  alias Hologram.Compiler.{Context, Helpers, Transformer}
  alias Hologram.Compiler.IR.{CaseConditionAccess, CaseExpression}

  def transform({:case, _, [condition, [do: clauses]]}, %Context{} = context) do
    %CaseExpression{
      condition: Transformer.transform(condition, context),
      clauses: Enum.map(clauses, &build_clause(&1, context))
    }
  end

  defp build_clause({:->, _, [[pattern], body]}, context) do
    pattern = Transformer.transform(pattern, context)
    body = Transformer.transform(body, context)

    bindings =
      Helpers.aggregate_bindings_from_expression(pattern)
      |> Enum.map(&prepend_variable_access/1)

    %{
      pattern: pattern,
      bindings: bindings,
      body: body
    }
  end

  defp prepend_variable_access(binding) do
    %{binding | access_path: [%CaseConditionAccess{} | binding.access_path]}
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/unary_positive_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.UnaryPositiveOperator

defimpl CallGraphBuilder, for: UnaryPositiveOperator do
  def build(%{value: value}, module_defs, templates, from_vertex) do
    CallGraphBuilder.build(value, module_defs, templates, from_vertex)
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/module_attribute_operator_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.ModuleAttributeOperatorTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}
  alias Hologram.Compiler.IR.ModuleAttributeOperator

  @ir %ModuleAttributeOperator{name: :xyz}

  describe "encode/3" do
    test "when doesn't have template opt" do
      context = %Context{module: Hologram.Compiler.JSEncoder.ModuleAttributeOperatorTest}

      result = JSEncoder.encode(@ir, context, %Opts{})
      expected = "Elixir_Hologram_Compiler_JSEncoder_ModuleAttributeOperatorTest.$xyz"

      assert result == expected
    end

    test "when has template opt" do
      opts = %Opts{template: true}

      result = JSEncoder.encode(@ir, %Context{}, opts)
      expected = "$bindings.data['~atom[xyz]']"

      assert result == expected
    end
  end
end


<|start_filename|>test/unit/hologram/template/encoders/text_node_test.exs<|end_filename|>
defmodule Hologram.Template.Encoder.TextNodeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Template.Encoder
  alias Hologram.Template.VDOM.TextNode

  test "encode/1" do
    text_node = %TextNode{content: "a'b\nc'd\ne\\{f\}ga'b\nc'd\ne\\{f\}g"}
    expected_encoded_content = "a\\'b\\nc\\'d\\ne{f}ga\\'b\\nc\\'d\\ne{f}g"

    result = Encoder.encode(text_node)
    expected = "{ type: 'text', content: '#{expected_encoded_content}' }"

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/transformers/import_directive_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.ImportDirectiveTransformer do
  alias Hologram.Compiler.Helpers
  alias Hologram.Compiler.IR.ImportDirective

  def transform({:import, _, [{:__aliases__, _, module_segs}, opts]}) do
    only = if opts[:only], do: opts[:only], else: []
    build_import(module_segs, only)
  end

  def transform({:import, _, [{:__aliases__, _, module_segs}]}) do
    build_import(module_segs, [])
  end

  defp build_import(module_segs, only) do
    module = Helpers.module(module_segs)
    %ImportDirective{module: module, only: only}
  end
end


<|start_filename|>lib/hologram/template/parser.ex<|end_filename|>
defmodule Hologram.Template.Parser do
  alias Hologram.Template.{DOMTreeBuilder, TagAssembler, Tokenizer}

  def parse!(markup) do
    context = %{
      attrs: [],
      attr_key: nil,
      double_quote_opened?: 0,
      num_open_braces: 0,
      prev_tokens: [],
      tag_name: nil,
      token_buffer: []
    }

    markup
    |> remove_doctype()
    |> remove_comments()
    |> String.trim()
    |> Tokenizer.tokenize()
    |> TagAssembler.assemble(:text_tag, context, [])
    |> DOMTreeBuilder.build()
  end

  defp remove_comments(markup) do
    Regex.replace(~r/<!\-\-.*\-\->/sU, markup, "")
  end

  defp remove_doctype(markup) do
    regex = ~r/^\s*<!DOCTYPE[^>]*>\s*/i
    String.replace(markup, regex, "")
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/unary_positive_operator.ex<|end_filename|>
alias Hologram.Compiler.{Context, JSEncoder, Opts}
alias Hologram.Compiler.IR.UnaryPositiveOperator

defimpl JSEncoder, for: UnaryPositiveOperator do
  def encode(%{value: value}, %Context{} = context, %Opts{} = opts) do
    JSEncoder.encode(value, context, opts)
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/binary_type_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.BinaryTypeTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, BinaryTypeTransformer}
  alias Hologram.Compiler.IR.{BinaryType, IntegerType}

  test "empty binary" do
    code = "<<>>"
    ast = ast(code)

    result = BinaryTypeTransformer.transform(ast, %Context{})
    expected = %BinaryType{parts: []}

    assert result == expected
  end

  test "non-empty binary" do
    code = "<<1, 2>>"
    ast = ast(code)

    result = BinaryTypeTransformer.transform(ast, %Context{})

    expected = %BinaryType{
      parts: [
        %IntegerType{value: 1},
        %IntegerType{value: 2}
      ]
    }

    assert result == expected
  end
end


<|start_filename|>test/js/runtime_test.js<|end_filename|>
"use strict";

import { assert, assertNotFrozen, cleanup } from "./support/commons";
beforeEach(() => cleanup())

import Runtime from "../../assets/js/hologram/runtime";
import Target from "../../assets/js/hologram/target";
import Type from "../../assets/js/hologram/type";

const layoutTarget = Target.TYPE.layout
const pageTarget = Target.TYPE.page

describe("determineLayoutClass()", () => {
  it("returns layout class given page class", () => {
    const TestLayoutClass = class {}
    globalThis.TestLayoutClass = TestLayoutClass


    const TestPageClass = class {
      static layout() {
        return Type.module("TestLayoutClass")
      }
    }

    const result = Runtime.determineLayoutClass(TestPageClass)

    assert.equal(result, TestLayoutClass)
  })
})

describe("getClassByClassName()", () => {
  const TestClass_Abc_Xyz = class {}
  globalThis.TestClass_Abc_Xyz = TestClass_Abc_Xyz
  const result = Runtime.getClassByClassName("TestClass_Abc_Xyz")

  it("returns class object given a class name", () => {
    assert.equal(result, TestClass_Abc_Xyz)
  })

  it("doesn't freeze returned class", () => {
    assertNotFrozen(result)
  })
})

describe("getComponentClass()", () => {
  it("returns component class by the given component ID", () => {
    const TestClass1 = class{}
    const TestClass2 = class{}
    const TestClass3 = class{}

    Runtime.componentClassRegistry = {
      component_1: TestClass1,
      component_2: TestClass2,
      component_3: TestClass3
    }

    const result = Runtime.getComponentClass("component_2")
    
    assert.equal(result, TestClass2)
  })

  it("returns null if the given component ID is not registered", () => {
    const result = Runtime.getComponentClass("not_registered")
    assert.isNull(result)
  })
})

describe("getLayoutClass()", () => {
  it("returns the class of the current layout", () => {
    const TestLayoutClass = class {}
    Runtime.componentClassRegistry[layoutTarget] = TestLayoutClass
    const result = Runtime.getLayoutClass()

    assert.equal(result, TestLayoutClass)
  })
})

describe("getLayoutTemplate()", () => {
  it("returns the template of the current page's layout", () => {
    const TestLayoutClass = class {
      static template() {
        return "test_template"
      }
    }
    Runtime.registerLayoutClass(TestLayoutClass)

    const result = Runtime.getLayoutTemplate()

    assert.equal(result, "test_template")
  })
})

describe("getPageClass()", () => {
  it("returns the class of the current page", () => {
    const TestPageClass = class {}
    Runtime.componentClassRegistry[pageTarget] = TestPageClass
    const result = Runtime.getPageClass()

    assert.equal(result, TestPageClass)
  })
})

describe("getPageTemplate()", () => {
  it("returns the template of the current page", () => {
    const TestPageClass = class {
      static template() {
        return "test_template"
      }
    }
    Runtime.registerPageClass(TestPageClass)

    const result = Runtime.getPageTemplate()

    assert.equal(result, "test_template")
  })
})

describe("registerComponentClass()", () => {
  it("registers the class of the given component", () => {
    const TestComponentClass = class {}
    Runtime.registerComponentClass("testComponentId", TestComponentClass)

    assert.equal(Runtime.componentClassRegistry["testComponentId"], TestComponentClass)
  })
})

describe("registerLayoutClass()", () => {
  it("registers the given class as layout class", () => {
    const TestLayoutClass = class {}
    Runtime.registerLayoutClass(TestLayoutClass)

    assert.equal(Runtime.componentClassRegistry[layoutTarget], TestLayoutClass)
  })
})

describe("registerPageClass()", () => {
  it("registers the given class as page class", () => {
    const TestPageClass = class {}
    Runtime.registerPageClass(TestPageClass)

    assert.equal(Runtime.componentClassRegistry[pageTarget], TestPageClass)
  })
})

describe("resolveComponentClass()", () => {
  const TestComponentClass = class TestComponentClass {}
  const node = Type.componentNode(TestComponentClass, {}, [])

  it("returns the already registered class of a stateful component", () => {
    Runtime.registerComponentClass("test_id", TestComponentClass)
    const result = Runtime.resolveComponentClass(node, "test_id")

    assert.equal(result, TestComponentClass)
  })

  it("returns the not registered yet class of a stateful component and registers it", () => {
    assert.isNull(Runtime.getComponentClass("test_id"))

    const result = Runtime.resolveComponentClass(node, "test_id")
    assert.equal(result.name, "TestComponentClass")

    const registeredClassName = Runtime.componentClassRegistry["test_id"].name
    assert.equal(registeredClassName, "TestComponentClass")
  })

  it("returns the class of a stateless component", () => {
    const result = Runtime.resolveComponentClass(node, null)
    assert.equal(result.name, "TestComponentClass")
  })
})

<|start_filename|>test/e2e/lib/hologram_e2e_web/components/component_4.ex<|end_filename|>
defmodule HologramE2E.Component4 do
  use Hologram.Component

  def init(_props) do
    %{
      text: "",
      value: :c4
    }
  end

  def template do
    ~H"""
    <div id="text-component-4">{@text}</div><br />

    <button id="component-4-button-1" on:click.command={:page, :command_10}>Page 4 Command 10</button>
    """
  end

  def action(:component_4_action_1_b, _params, state) do
    put(state, :text, "text updated by component_4_action_1_b, state.value = #{state.value}")
  end

  def action(:component_4_action_2_b, params, state) do
    put(
      state,
      :text,
      "text updated by component_4_action_2_b, params.a = #{params.a}, params.b = #{params.b}, state.value = #{state.value}"
    )
  end

  def action(:component_4_action_3_b, _params, state) do
    put(state, :text, "text updated by component_4_action_3_b, state.value = #{state.value}")
  end

  def action(:component_4_action_4_b, params, state) do
    put(
      state,
      :text,
      "text updated by component_4_action_4_b, params.a = #{params.a}, params.b = #{params.b}, state.value = #{state.value}"
    )
  end

  def action(:component_4_action_5_b, _params, state) do
    put(state, :text, "text updated by component_4_action_5_b, state.value = #{state.value}")
  end

  def command(:component_4_command_1, _params) do
    :component_4_action_1_b
  end

  def command(:component_4_command_2, params) do
    {:component_4_action_2_b, a: params.a * 10, b: params.b * 10}
  end

  def command(:component_4_command_3, _params) do
    :component_4_action_5_b
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/map_type_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.MapTypeTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, MapTypeTransformer}
  alias Hologram.Compiler.IR.{AtomType, IntegerType, MapType}

  @context %Context{module: Abc}

  test "transform/2" do
    code = "%{a: 1, b: 2}"
    ast = ast(code)

    result = MapTypeTransformer.transform(ast, @context)

    expected = %MapType{
      data: [
        {%AtomType{value: :a}, %IntegerType{value: 1}},
        {%AtomType{value: :b}, %IntegerType{value: 2}}
      ]
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/expander/module_5.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Expander.Module5 do
  defmacro __using__(_) do
    quote do
      import Hologram.Test.Fixtures.Compiler.Expander.Module1
      import Hologram.Test.Fixtures.Compiler.Expander.Module3
    end
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/import_directive_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.ImportDirectiveTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.ImportDirectiveTransformer
  alias Hologram.Compiler.IR.ImportDirective

  @expected_module Hologram.Test.Fixtures.Compiler.Transformer.Module1

  test "without 'only' clause" do
    code = "import Hologram.Test.Fixtures.Compiler.Transformer.Module1"
    ast = ast(code)

    result = ImportDirectiveTransformer.transform(ast)
    expected = %ImportDirective{module: @expected_module, only: []}

    assert result == expected
  end

  test "with 'only' clause" do
    code = "import Hologram.Test.Fixtures.Compiler.Transformer.Module1, only: [abc: 2]"
    ast = ast(code)

    result = ImportDirectiveTransformer.transform(ast)
    expected = %ImportDirective{module: @expected_module, only: [abc: 2]}

    assert result == expected
  end

  test "ignores other opts" do
    code = "import Hologram.Test.Fixtures.Compiler.Transformer.Module1, warn: false"
    ast = ast(code)

    result = ImportDirectiveTransformer.transform(ast)
    expected = %ImportDirective{module: @expected_module, only: []}

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/pattern_deconstructor/cons_operator.ex<|end_filename|>
alias Hologram.Compiler.IR.ConsOperator
alias Hologram.Compiler.IR.ListIndexAccess
alias Hologram.Compiler.IR.ListTailAccess
alias Hologram.Compiler.PatternDeconstructor

defimpl PatternDeconstructor, for: ConsOperator do
  def deconstruct(%{head: head, tail: tail}, path) do
    head_paths = PatternDeconstructor.deconstruct(head, path ++ [%ListIndexAccess{index: 0}])
    tail_paths = PatternDeconstructor.deconstruct(tail, path ++ [%ListTailAccess{}])

    [] ++ head_paths ++ tail_paths
  end
end


<|start_filename|>lib/hologram/compiler/transformers/unquote_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.UnquoteTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.Unquote

  def transform({:unquote, _, [expr]}, %Context{} = context) do
    expr = Transformer.transform(expr, context)
    %Unquote{expression: expr}
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/less_than_operator.ex<|end_filename|>
alias Hologram.Compiler.Context
alias Hologram.Compiler.IR.LessThanOperator
alias Hologram.Compiler.JSEncoder
alias Hologram.Compiler.Opts

defimpl JSEncoder, for: LessThanOperator do
  def encode(%{left: left, right: right}, %Context{} = context, %Opts{} = opts) do
    left = JSEncoder.encode(left, context, opts)
    right = JSEncoder.encode(right, context, opts)

    "Hologram.Interpreter.$less_than_operator(#{left}, #{right})"
  end
end


<|start_filename|>test/unit/hologram/compiler/call_graph_builder/module_definition_test.exs<|end_filename|>
defmodule Hologram.Compiler.CallGraphBuilder.ModuleDefinitionTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{CallGraph, CallGraphBuilder, Reflection}
  alias Hologram.Compiler.IR.{Block, FunctionDefinition, ModuleDefinition, ModuleType}
  alias Hologram.Test.Fixtures.{PlaceholderModule1, PlaceholderModule2}
  alias Hologram.Test.Fixtures.Compiler.CallGraphBuilder.ModuleDefinition.{Module4, Module5}

  @from_vertex nil
  @templates %{}

  setup do
    CallGraph.run()
    :ok
  end

  test "module that isn't in the call graph yet" do
    module_def = Reflection.module_definition(PlaceholderModule1)
    module_defs = %{PlaceholderModule1 => module_def}

    CallGraphBuilder.build(module_def, module_defs, @templates, @from_vertex)

    assert CallGraph.has_vertex?(PlaceholderModule1)
  end

  test "module that is already in the call graph" do
    ir = %ModuleDefinition{
      module: PlaceholderModule1,
      functions: []
    }

    CallGraph.add_vertex(PlaceholderModule1)
    module_defs = %{}

    CallGraphBuilder.build(ir, module_defs, @templates, @from_vertex)

    assert CallGraph.has_vertex?(PlaceholderModule1)
  end

  test "functions traversing" do
    module_def_1 = %ModuleDefinition{
      module: PlaceholderModule1,
      functions: [
        %FunctionDefinition{
          module: PlaceholderModule1,
          name: :test_fun,
          body: %Block{expressions: [
            %ModuleType{module: PlaceholderModule2}
          ]}
        }
      ]
    }

    module_def_2 = %ModuleDefinition{
      module: PlaceholderModule2
    }

    module_defs = %{
      PlaceholderModule1 => module_def_1,
      PlaceholderModule2 => module_def_2
    }

    CallGraphBuilder.build(module_def_1, module_defs, @templates, @from_vertex)

    assert CallGraph.has_edge?({PlaceholderModule1, :test_fun}, PlaceholderModule2)
  end

  test "template traversing" do
    opts = [
      app_path: "#{@fixtures_path}/compiler/call_graph_builder/module_definition",
      templatables: [Hologram.Test.Fixtures.App.DefaultLayout]
    ]

    %{templates: templates} = compile(opts)

    module_defs = %{
      Module4 => Reflection.module_definition(Module4),
      Module5 => Reflection.module_definition(Module5)
    }

    CallGraph.run()
    CallGraphBuilder.build(module_defs[Module4], module_defs, templates, @from_vertex)

    assert CallGraph.has_vertex?(Module5)
    assert CallGraph.has_edge?(Module4, {Module4, :template})
    assert CallGraph.has_edge?({Module4, :template}, Module5)
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/match_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.MatchOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, MatchOperatorTransformer}

  alias Hologram.Compiler.IR.{
    AtomType,
    Binding,
    IntegerType,
    MapAccess,
    MapType,
    MatchAccess,
    MatchOperator,
    Variable
  }

  test "transform/2" do
    code = "%{a: x, b: y} = %{a: 1, b: 2}"
    ast = ast(code)

    result = MatchOperatorTransformer.transform(ast, %Context{})

    expected = %MatchOperator{
      bindings: [
        %Binding{
          name: :x,
          access_path: [%MatchAccess{}, %MapAccess{key: %AtomType{value: :a}}]
        },
        %Binding{name: :y, access_path: [%MatchAccess{}, %MapAccess{key: %AtomType{value: :b}}]}
      ],
      left: %MapType{
        data: [
          {%AtomType{value: :a}, %Variable{name: :x}},
          {%AtomType{value: :b}, %Variable{name: :y}}
        ]
      },
      right: %MapType{
        data: [
          {%AtomType{value: :a}, %IntegerType{value: 1}},
          {%AtomType{value: :b}, %IntegerType{value: 2}}
        ]
      }
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/transformers/struct_type_transformer/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.StructTypeTransformer.Module2 do
  defstruct b: nil
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/function_call/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.FunctionCall.Module2 do
  def test_fun_2a, do: nil
end


<|start_filename|>test/unit/fixtures/compiler/expander/module_3.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Expander.Module3 do
end


<|start_filename|>test/unit/hologram/compiler/pattern_deconstructors/cons_operator_test.exs<|end_filename|>
defmodule Hologram.Compiler.PatternDeconstructor.ConsOperatorTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.IR.ConsOperator
  alias Hologram.Compiler.IR.IntegerType
  alias Hologram.Compiler.IR.ListIndexAccess
  alias Hologram.Compiler.IR.ListTailAccess
  alias Hologram.Compiler.IR.ListType
  alias Hologram.Compiler.IR.Variable
  alias Hologram.Compiler.PatternDeconstructor

  test "non-nested cons operator without vars" do
    # [1 | [2, 3]]

    ir = %ConsOperator{
      head: %IntegerType{value: 1},
      tail: %ListType{
        data: [
          %IntegerType{value: 2},
          %IntegerType{value: 3}
        ]
      }
    }

    assert PatternDeconstructor.deconstruct(ir) == []
  end

  test "non-nested cons operator with head var" do
    # [x | [1, 2]]

    ir = %ConsOperator{
      head: %Variable{name: :x},
      tail: %ListType{
        data: [
          %IntegerType{value: 1},
          %IntegerType{value: 2}
        ]
      }
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %ListIndexAccess{index: 0},
        %Variable{name: :x}
      ]
    ]

    assert result == expected
  end

  test "non-nested cons operator with tail var" do
    # [1 | x]

    ir = %ConsOperator{
      head: %IntegerType{value: 1},
      tail: %Variable{name: :x}
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %ListTailAccess{},
        %Variable{name: :x}
      ]
    ]

    assert result == expected
  end

  test "non-nested cons operator with head and tail vars" do
    # [x | y]

    ir = %ConsOperator{
      head: %Variable{name: :x},
      tail: %Variable{name: :y}
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %ListIndexAccess{index: 0},
        %Variable{name: :x}
      ],
      [
        %ListTailAccess{},
        %Variable{name: :y}
      ]
    ]

    assert result == expected
  end

  test "nested cons operator without vars" do
    # [1 | [2 | [3, 4]]]

    ir = %ConsOperator{
      head: %IntegerType{value: 1},
      tail: %ConsOperator{
        head: %IntegerType{value: 2},
        tail: %ListType{
          data: [
            %IntegerType{value: 3},
            %IntegerType{value: 4}
          ]
        }
      }
    }

    assert PatternDeconstructor.deconstruct(ir) == []
  end

  test "nested cons operator with vars in heads" do
    # [x | [y | [3, 4]]]

    ir = %ConsOperator{
      head: %Variable{name: :x},
      tail: %ConsOperator{
        head: %Variable{name: :y},
        tail: %ListType{
          data: [
            %IntegerType{value: 3},
            %IntegerType{value: 4}
          ]
        }
      }
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %ListIndexAccess{index: 0},
        %Variable{name: :x}
      ],
      [
        %ListTailAccess{},
        %ListIndexAccess{index: 0},
        %Variable{name: :y}
      ]
    ]

    assert result == expected
  end

  test "nested cons operator with vars in most-nested tail" do
    # [1 | [2 | [x, y]]]

    ir = %ConsOperator{
      head: %IntegerType{value: 1},
      tail: %ConsOperator{
        head: %IntegerType{value: 2},
        tail: %ListType{
          data: [
            %Variable{name: :x},
            %Variable{name: :y}
          ]
        }
      }
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %ListTailAccess{},
        %ListTailAccess{},
        %ListIndexAccess{index: 0},
        %Variable{name: :x}
      ],
      [
        %ListTailAccess{},
        %ListTailAccess{},
        %ListIndexAccess{index: 1},
        %Variable{name: :y}
      ]
    ]

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/compiler/static_digest_store_test.exs<|end_filename|>
defmodule Hologram.Runtime.StaticDigestStoreTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.Reflection
  alias Hologram.Runtime.StaticDigestStore

  setup do
    static_path = Reflection.release_static_path()

    dir_2_path = static_path <> "/test_dir_1/test_dir_2"
    file_1_path = dir_2_path <> "/test_file_1-11111111111111111111111111111111.css"
    file_2_path = dir_2_path <> "/test_file_2-22222222222222222222222222222222.css"
    file_3_path = dir_2_path <> "/page-33333333333333333333333333333333.js"

    dir_3_path = static_path <> "/test_dir_3"
    file_4_path = dir_3_path <> "/test_file_4-44444444444444444444444444444444.css"
    file_5_path = dir_3_path <> "/test_file_5-55555555555555555555555555555555.css"
    file_6_path = dir_3_path <> "/page-66666666666666666666666666666666.js"

    dir_4_path = static_path <> "/hologram"
    file_7_path = dir_4_path <> "/page-77777777777777777777777777777777.js"
    file_8_path = dir_4_path <> "/page-88888888888888888888888888888888.js"
    file_9_path = dir_4_path <> "/test_file_9-99999999999999999999999999999999.css"

    File.mkdir_p!(dir_2_path)
    File.mkdir_p!(dir_3_path)

    [
      file_1_path,
      file_2_path,
      file_3_path,
      file_4_path,
      file_5_path,
      file_6_path,
      file_7_path,
      file_8_path,
      file_9_path
    ]
    |> Enum.each(&File.write!(&1, ""))

    expected_store_content_without_manifest = %{
      "/test_dir_1/test_dir_2/test_file_1.css":
        "/test_dir_1/test_dir_2/test_file_1-11111111111111111111111111111111.css",
      "/test_dir_1/test_dir_2/test_file_2.css":
        "/test_dir_1/test_dir_2/test_file_2-22222222222222222222222222222222.css",
      "/test_dir_1/test_dir_2/page.js":
        "/test_dir_1/test_dir_2/page-33333333333333333333333333333333.js",
      "/test_dir_3/test_file_4.css":
        "/test_dir_3/test_file_4-44444444444444444444444444444444.css",
      "/test_dir_3/test_file_5.css":
        "/test_dir_3/test_file_5-55555555555555555555555555555555.css",
      "/test_dir_3/page.js": "/test_dir_3/page-66666666666666666666666666666666.js",
      "/hologram/test_file_9.css": "/hologram/test_file_9-99999999999999999999999999999999.css"
    }

    expected_store_content =
      %{
        __manifest__:
          "window.__hologramStaticDigestStore__ = #{Jason.encode!(expected_store_content_without_manifest)};"
      }
      |> Map.merge(expected_store_content_without_manifest)

    [
      expected_store_content: expected_store_content
    ]
  end

  test "populate_table/0", %{expected_store_content: expected_store_content} do
    StaticDigestStore.run()
    assert StaticDigestStore.get_all() == expected_store_content
  end

  test "get_manifest/0", %{expected_store_content: expected_store_content} do
    StaticDigestStore.run()
    assert StaticDigestStore.get_manifest() == expected_store_content.__manifest__
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/binary_type_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.BinaryTypeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}
  alias Hologram.Compiler.IR.{BinaryType, StringType}

  test "encode/3" do
    ir = %BinaryType{
      parts: [
        %StringType{value: "abc"},
        %StringType{value: "xyz"}
      ]
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected =
      "{ type: 'binary', data: [ { type: 'string', value: 'abc' }, { type: 'string', value: 'xyz' } ] }"

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/block_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.BlockTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{BlockTransformer, Context}
  alias Hologram.Compiler.IR.{Block, IntegerType}

  test "transform/2" do
    ast = {:__block__, [], [1, 2]}
    result = BlockTransformer.transform(ast, %Context{})

    expected = %Block{
      expressions: [
        %IntegerType{value: 1},
        %IntegerType{value: 2}
      ]
    }

    assert result == expected
  end
end


<|start_filename|>lib/hologram/template/evaluators/boolean_type.ex<|end_filename|>
alias Hologram.Compiler.IR.BooleanType
alias Hologram.Template.Evaluator

defimpl Evaluator, for: BooleanType do
  def evaluate(%{value: value}, _) do
    value
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/elixir_map.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder

defimpl CallGraphBuilder, for: Map do
  def build(map, module_defs, templates, from_vertex) do
    map
    |> Map.to_list()
    |> CallGraphBuilder.build(module_defs, templates, from_vertex)
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/if_expression.ex<|end_filename|>
alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.IfExpression

defimpl CallGraphBuilder, for: IfExpression do
  def build(
        %{condition: condition, do: do_clause, else: else_clause},
        module_defs,
        templates,
        from_vertex
      ) do
    CallGraphBuilder.build(condition, module_defs, templates, from_vertex)
    CallGraphBuilder.build(do_clause, module_defs, templates, from_vertex)
    CallGraphBuilder.build(else_clause, module_defs, templates, from_vertex)
  end
end


<|start_filename|>lib/hologram/compiler/transformers/list_concatenation_operator_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.ListConcatenationOperatorTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.ListConcatenationOperator

  def transform({:++, _, [left, right]}, %Context{} = context) do
    %ListConcatenationOperator{
      left: Transformer.transform(left, context),
      right: Transformer.transform(right, context)
    }
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/addition_operator_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.AdditionOperatorTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}
  alias Hologram.Compiler.IR.{AdditionOperator, AtomType, Variable}

  test "encode/3" do
    ir = %AdditionOperator{
      left: %Variable{name: :x},
      right: %AtomType{value: :a}
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "Hologram.Interpreter.$addition_operator(x, { type: 'atom', value: 'a' })"

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/transformers/binary_type_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.BinaryTypeTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.BinaryType

  def transform({:<<>>, _, parts}, %Context{} = context) do
    parts = Enum.map(parts, &Transformer.transform(&1, context))
    %BinaryType{parts: parts}
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder.ex<|end_filename|>
defprotocol Hologram.Compiler.CallGraphBuilder do
  def build(ir, module_defs, templates, from_vertex)
end


<|start_filename|>test/unit/hologram/compiler/transformers/list_type_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.ListTypeTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, ListTypeTransformer}
  alias Hologram.Compiler.IR.{IntegerType, ListType}

  @context %Context{module: Abc}

  test "transform/2" do
    code = "[1, 2]"
    ast = ast(code)

    result = ListTypeTransformer.transform(ast, @context)

    expected = %ListType{
      data: [
        %IntegerType{value: 1},
        %IntegerType{value: 2}
      ]
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_6.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module6 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module7

  route "/test-route-6"

  def template do
    ~H"""
    """
  end

  def action(:test_6a, _params, _state) do
    Module7.test_fun_7a()
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/module_attribute_operator.ex<|end_filename|>
alias Hologram.Compiler.{Context, Helpers, JSEncoder, MapKeyEncoder, Opts}
alias Hologram.Compiler.IR.{AtomType, ModuleAttributeOperator}

defimpl JSEncoder, for: ModuleAttributeOperator do
  def encode(%{name: name}, %Context{} = context, %Opts{template: true} = opts) do
    key = MapKeyEncoder.encode(%AtomType{value: name}, context, opts)
    "$bindings.data['#{key}']"
  end

  def encode(%{name: name}, %Context{} = context, _) do
    class_name = Helpers.class_name(context.module)
    "#{class_name}.$#{name}"
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/binary_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.BinaryType
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: BinaryType do
  def aggregate(_), do: nil
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/elixir_atom.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.{CallGraphBuilder, Reflection}
alias Hologram.Compiler.IR.ModuleType

defimpl CallGraphBuilder, for: Atom do
  def build(ir, module_defs, templates, from_vertex) do
    if Reflection.is_module?(ir) do
      %ModuleType{module: ir}
      |> CallGraphBuilder.build(module_defs, templates, from_vertex)
    end
  end
end


<|start_filename|>lib/hologram/compiler/context.ex<|end_filename|>
defmodule Hologram.Compiler.Context do
  defstruct module: nil,
            uses: [],
            imports: [],
            requires: [],
            aliases: [],
            attributes: [],
            block_bindings: []
end


<|start_filename|>lib/hologram/compiler/parser.ex<|end_filename|>
defmodule Hologram.Compiler.Parser do
  use Hologram.Commons.Parser

  @impl Hologram.Commons.Parser
  def parse(code) do
    Code.string_to_quoted(code)
  end
end


<|start_filename|>test/unit/hologram/template/evaluators/module_type_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.ModuleTypeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.ModuleType
  alias Hologram.Template.Evaluator

  test "evaluate/2" do
    ir = %ModuleType{module: Hologram.Template.Evaluator.ModuleTypeTest}

    result = Evaluator.evaluate(ir, %{})
    expected = Hologram.Template.Evaluator.ModuleTypeTest

    assert result == expected
  end
end


<|start_filename|>lib/hologram/template/evaluator.ex<|end_filename|>
defprotocol Hologram.Template.Evaluator do
  def evaluate(ir, bindings)
end


<|start_filename|>test/unit/hologram/compiler/transformers/strict_boolean_and_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.StrictBooleanAndOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, StrictBooleanAndOperatorTransformer}
  alias Hologram.Compiler.IR.{BooleanType, StrictBooleanAndOperator}

  test "transform/3" do
    code = "true and false"
    ast = ast(code)

    result = StrictBooleanAndOperatorTransformer.transform(ast, %Context{})

    expected = %StrictBooleanAndOperator{
      left: %BooleanType{value: true},
      right: %BooleanType{value: false}
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/compiler/serializer_test.exs<|end_filename|>
defmodule Hologram.Compiler.SerializerTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Compiler.Serializer

  test "serialize/1" do
    state = %{a: 1, b: 2}

    result = Serializer.serialize(state)

    expected =
      "{ type: 'map', data: { '~atom[a]': { type: 'integer', value: 1 }, '~atom[b]': { type: 'integer', value: 2 } } }"

    assert result == expected
  end
end


<|start_filename|>test/e2e/test/types_test.exs<|end_filename|>
defmodule HologramE2E.TypesTest do
  use HologramE2E.TestCase, async: false

  describe "anonymous function" do
    feature "regular syntax", %{session: session} do
      session
      |> visit("/e2e/types/anonymous-function")
      |> click(css("#button_regular_syntax"))
      |> assert_has(css("#text", text: "Result = true"))
    end

    # TODO: implement
    # feature "shorthand syntax"
  end

  describe "float" do
    feature "encoding", %{session: session} do
      session
      |> visit("/e2e/types/float")
      |> click(css("#button_test_encoding"))
      |> assert_has(css("#text_encoding_result", text: "Result encoding = 1.23"))
    end

    feature "decoding", %{session: session} do
      session
      |> visit("/e2e/types/float")
      |> click(css("#button_test_decoding"))
      |> assert_has(css("#text_decoding_result", text: "Result decoding = 12.34"))
    end
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/page_7.ex<|end_filename|>
defmodule HologramE2E.Page7 do
  use Hologram.Page

  route "/e2e/page-7"

  def init(_params, _conn) do
    %{}
  end

  def template do
    ~H"""
    <h1>Page 7</h1>
    page 7 template
    """
  end
end


<|start_filename|>test/unit/hologram/template/renderers/page_test.exs<|end_filename|>
defmodule Hologram.Template.Renderer.PageTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Conn
  alias Hologram.Runtime
  alias Hologram.Runtime.PageDigestStore
  alias Hologram.Template.Renderer

  setup do
    [
      app_path: "#{@fixtures_path}/template/renderers/page_renderer",
      templatables: [Hologram.Test.Fixtures.App.DefaultLayout]
    ]
    |> compile()

    Runtime.run()

    :ok
  end

  test "render/4" do
    module = Hologram.Test.Fixtures.Template.PageRenderer.Module1
    bindings = %{}

    conn = %Conn{
      params: %{
        c: 567
      },
      session: %{
        d: 345,
        e: 678
      }
    }

    result = Renderer.render(module, conn, bindings)

    digest = PageDigestStore.get!(module)
    assert digest =~ md5_hex_regex()

    expected = """
    <!DOCTYPE html>
    <html>
      <head>
        <script>
      window.hologramArgs = {
        class: "Elixir_Hologram_Test_Fixtures_Template_PageRenderer_Module1",
        digest: "#{digest}",
        state: { type: 'map', data: { '~atom[component_3_id]': { type: 'map', data: { '~atom[component_3_state_key]': { type: 'string', value: 'component_3_state_value' } } }, '~atom[layout]': { type: 'map', data: { '~atom[b]': { type: 'integer', value: 987 }, '~atom[e]': { type: 'integer', value: 678 } } }, '~atom[page]': { type: 'map', data: { '~atom[a]': { type: 'integer', value: 123 }, '~atom[c]': { type: 'integer', value: 567 }, '~atom[d]': { type: 'integer', value: 345 } } } } }
      }
    </script>
    <script src="/hologram/manifest.js"></script>
    <script src="/hologram/runtime.js"></script>
    <script src="/hologram/page-#{digest}.js"></script>
      </head>
      <body>
        layout template assign 987, layout template conn session 678
        page template assign 123, page template param 567, page template conn session 345
    (in component 3)
      </body>
    </html>\
    """

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/pattern_deconstructor/list_type.ex<|end_filename|>
alias Hologram.Compiler.IR.ListIndexAccess
alias Hologram.Compiler.IR.ListType
alias Hologram.Compiler.PatternDeconstructor

defimpl PatternDeconstructor, for: ListType do
  def deconstruct(%{data: data}, path) do
    data
    |> Enum.with_index()
    |> Enum.reduce([], fn {value, index}, acc ->
      acc ++ PatternDeconstructor.deconstruct(value, path ++ [%ListIndexAccess{index: index}])
    end)
  end
end


<|start_filename|>test/unit/hologram/compiler/module_def_aggregators/anonymous_function_call_test.exs<|end_filename|>
defmodule Hologram.Compiler.ModuleDefAggregator.AnonymousFunctionCallTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{ModuleDefAggregator, ModuleDefStore}
  alias Hologram.Compiler.IR.{AnonymousFunctionCall, ModuleDefinition, ModuleType}
  alias Hologram.Test.Fixtures.PlaceholderModule1
  alias Hologram.Test.Fixtures.PlaceholderModule2

  setup do
    ModuleDefStore.run()
    :ok
  end

  test "aggregates args" do
    ir = %AnonymousFunctionCall{
      name: :test,
      args: [
        %ModuleType{module: PlaceholderModule1},
        %ModuleType{module: PlaceholderModule2}
      ]
    }

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(PlaceholderModule1)
    assert %ModuleDefinition{} = ModuleDefStore.get!(PlaceholderModule2)
  end
end


<|start_filename|>test/unit/hologram/template/evaluators/function_call_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.FunctionCallTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.{FunctionCall, IntegerType, ListType}
  alias Hologram.Template.Evaluator

  test "evaluate/2" do
    args = [
      %ListType{
        data: [
          %IntegerType{value: 1},
          %IntegerType{value: 2}
        ]
      }
    ]

    ir = %FunctionCall{module: Kernel, function: :hd, args: args}

    result = Evaluator.evaluate(ir, %{})
    expected = 1

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/template/renderers/page_renderer/module_3.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.PageRenderer.Module3 do
  use Hologram.Component

  def init(_props) do
    %{
      component_3_state_key: "component_3_state_value"
    }
  end

  def template do
    ~H"""
    (in component 3)
    """
  end
end


<|start_filename|>test/unit/hologram/compiler/decoders/tuple_type_decoder_test.exs<|end_filename|>
defmodule Hologram.Compiler.TupleTypeDecoderTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Compiler.TupleTypeDecoder

  test "encoded tuple decoding" do
    value = %{
      "type" => "tuple",
      "data" => [
        %{"type" => "integer", "value" => 1},
        %{"type" => "atom", "value" => "test"}
      ]
    }

    result = TupleTypeDecoder.decode(value)
    expected = {1, :test}

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/binding.ex<|end_filename|>
alias Hologram.Compiler.{Config, Context, JSEncoder, MapKeyEncoder, Opts}

alias Hologram.Compiler.IR.{
  Binding,
  CaseConditionAccess,
  ListIndexAccess,
  ListTailAccess,
  MapAccess,
  MatchAccess,
  ParamAccess,
  TupleAccess
}

defimpl JSEncoder, for: Binding do
  def encode(%{access_path: access_path, name: name}, %Context{} = context, %Opts{} = opts) do
    encoded_parts = Enum.reduce(access_path, "", &encode_part(&1, &2, context, opts))
    statement = if name in context.block_bindings, do: "", else: "let "
    "#{statement}#{name} = #{encoded_parts};"
  end

  defp encode_part(%CaseConditionAccess{}, acc, _context, _opts) do
    acc <> Config.case_condition_js()
  end

  defp encode_part(%ListIndexAccess{index: index}, acc, _context, _opts) do
    acc <> ".data[#{index}]"
  end

  defp encode_part(%ListTailAccess{}, acc, _context, _opts) do
    "Elixir_Kernel.tl(#{acc})"
  end

  defp encode_part(%MapAccess{key: key}, acc, context, opts) do
    encoded_key = MapKeyEncoder.encode(key, context, opts)
    acc <> ".data['#{encoded_key}']"
  end

  defp encode_part(%MatchAccess{}, acc, _context, _opts) do
    acc <> Config.match_access_js()
  end

  defp encode_part(%ParamAccess{index: index}, acc, _context, _opts) do
    acc <> "arguments[#{index}]"
  end

  defp encode_part(%TupleAccess{index: index}, acc, _context, _opts) do
    acc <> ".data[#{index}]"
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/less_than_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.LessThanOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.Context
  alias Hologram.Compiler.IR.IntegerType
  alias Hologram.Compiler.IR.LessThanOperator
  alias Hologram.Compiler.LessThanOperatorTransformer

  test "transform/3" do
    code = "1 < 2"
    ast = ast(code)

    result = LessThanOperatorTransformer.transform(ast, %Context{})

    expected = %LessThanOperator{
      left: %IntegerType{value: 1},
      right: %IntegerType{value: 2}
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_51.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module51 do
  use Hologram.Page

  layout Hologram.Test.Fixtures.PlaceholderModule1

  route "/test-route-51"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/unit/hologram/template/transformer_test.exs<|end_filename|>
defmodule Hologram.Template.TransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.Context
  alias Hologram.Compiler.IR.{IntegerType, TupleType}
  alias Hologram.Template.{Parser, Transformer}
  alias Hologram.Template.VDOM.{Component, ElementNode, Expression, TextNode}

  @context %Context{}

  test "list of nodes" do
    html = "<div></div><span></span>"

    result =
      Parser.parse!(html)
      |> Transformer.transform(@context)

    expected = [
      %ElementNode{
        attrs: %{},
        children: [],
        tag: "div"
      },
      %ElementNode{
        attrs: %{},
        children: [],
        tag: "span"
      }
    ]

    assert result == expected
  end

  test "component node" do
    html = "<Hologram.Test.Fixtures.PlaceholderComponent />"

    result =
      Parser.parse!(html)
      |> Transformer.transform(@context)

    assert [%Component{}] = result
  end

  test "element node" do
    html = "<div><span></span></div>"

    result =
      Parser.parse!(html)
      |> Transformer.transform(@context)

    assert [%ElementNode{children: [%ElementNode{}]}] = result
  end

  test "text node" do
    html = "test_text"

    result =
      Parser.parse!(html)
      |> Transformer.transform(@context)

    expected = [%TextNode{content: "test_text"}]

    assert result == expected
  end

  test "embedded expression" do
    html = "a{1}b"

    result =
      Parser.parse!(html)
      |> Transformer.transform(@context)

    expected = [
      %TextNode{content: "a"},
      %Expression{
        ir: %TupleType{
          data: [%IntegerType{value: 1}]
        }
      },
      %TextNode{content: "b"}
    ]

    assert result == expected
  end
end


<|start_filename|>test/js/operation_test.js<|end_filename|>
"use strict";

import { assert, assertFrozen, cleanup } from "./support/commons";
beforeEach(() => cleanup())

import Operation from "../../assets/js/hologram/operation"
import Target from "../../assets/js/hologram/target"
import Type from "../../assets/js/hologram/type"
import Runtime from "../../assets/js/hologram/runtime";

const bindings = Type.map()
const sourceId = "test_source_id"

const textOperationSpec = {
  modifiers: [],
  value: [Type.textNode("test_action")]
}

const TestComponentClass = class {}

describe("build()", () => {
  it("returns a frozen Operation object", () => {
    Runtime.registerComponentClass(sourceId, TestComponentClass)

    const result = Operation.build(textOperationSpec, sourceId, bindings, Type.map())

    assert.isTrue(result instanceof Operation)
    assertFrozen(result)
  })
})

describe("buildMethod()", () => {
  it("returns command enum value if the modifiers in operation spec include 'command'", () => {
    const operationSpec = {
      modifiers: ["command"]
    }

    const result = Operation.buildMethod(operationSpec)
    const expected = Operation.METHOD.command

    assert.equal(result, expected)
  })

  it("returns action enum value if the modifiers in operation spec don't include 'command'", () => {
    const operationSpec = {
      modifiers: ["command8888"]
    }

    const result = Operation.buildMethod(operationSpec)
    const expected = Operation.METHOD.action

    assert.equal(result, expected)
  })
})

describe("buildName()", () => {
  const expected = Type.atom("test_action")

  it("returns the first spec elem if the operation spec has only one elem", () => {
    const specElems = [Type.atom("test_action")]
    const result = Operation.buildName(specElems)

    assert.deepStrictEqual(result, expected)
  })

  it("returns the first spec elem if the second operation spec elem is not of boxed atom type", () => {
    const specElems = [Type.atom("test_action"), Type.integer(1)]
    const result = Operation.buildName(specElems)

    assert.deepStrictEqual(result, expected)
  })

  it("returns the second spec elem if the operation spec has at least two elems and the second one is of boxed atom type", () => {
    const specElems = [Type.atom("layout"), Type.atom("test_action")]
    const result = Operation.buildName(specElems)

    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    const specElems = [Type.atom("test_action")]
    const result = Operation.buildName(specElems)

    assertFrozen(result)
  })
})

describe("buildParams()", () => {
  const params = Type.list([
    Type.tuple([Type.atom("a"), Type.integer(1)]),
    Type.tuple([Type.atom("b"), Type.integer(2)]),
  ])

  let eventDataElems = {}
  eventDataElems[Type.atomKey("x")] = Type.integer(1)
  const eventData = Type.map(eventDataElems)

  let expectedData = {}
  expectedData[Type.atomKey("event")] = eventData
  expectedData[Type.atomKey("a")] = Type.integer(1)
  expectedData[Type.atomKey("b")] = Type.integer(2)
  const expected = Type.map(expectedData)

  it("returns the third spec elem if the operation spec has three elems", () => {
    const specElems = [Type.atom("test_target"), Type.atom("test_action"), params]
    const result = Operation.buildParams(specElems, eventData)

    assert.deepStrictEqual(result, expected)
  })

  it("returns the second spec elem if the operation spec has two elems and the second one is of boxed list type", () => {
    const specElems = [Type.atom("test_action"), params]
    const result = Operation.buildParams(specElems, eventData)

    assert.deepStrictEqual(result, expected)
  })

  it("returns a boxed keyword list with 'event' key only if the operation spec doesn't contain params", () => {
    const specElems = [Type.atom("test_target"), Type.atom("test_action")]

    const result = Operation.buildParams(specElems, eventData)
    
    let expectedData = {}
    expectedData[Type.atomKey("event")] = eventData
    const expected = Type.map(expectedData)

    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    const specElems = [Type.atom("test_action")]
    const result = Operation.buildParams(specElems, eventData)

    assertFrozen(result)
  })
})

describe("getSpecElems()", () => {
  it("returns operation spec elems when the operation spec is of text type", () => {
    const result = Operation.getSpecElems(textOperationSpec, bindings)
    const expected = [Type.atom("test_action")]

    assert.deepStrictEqual(result, expected)
  })

  it("returns operation spec elems when the operation spec is of expression type", () => {
    const callback = (_$bindings) => { return Type.tuple([Type.atom("layout"), Type.atom("test_action")]) }

    const operationSpec = {
      value: [Type.expressionNode(callback)]
    }

    const result = Operation.getSpecElems(operationSpec, bindings)
    const expected = [Type.atom("layout"), Type.atom("test_action")]

    assert.deepStrictEqual(result, expected)
  })

  it("returns frozen object", () => {
    const operationSpec = {
      value: [Type.textNode("test_action")]
    }

    const result = Operation.getSpecElems(operationSpec, bindings)

    assertFrozen(result)
  })
})

describe("getSpecType()", () => {
  it("returns expression enum value if the operation spec value is an expression node", () => {
    const operationSpec = {
      value: [Type.expressionNode(null)]
    }

    const result = Operation.getSpecType(operationSpec)
    const expected = Operation.SPEC_TYPE.expression

    assert.equal(result, expected)
  })

  it("returns text enum value if the operation spec value is a text node", () => {
    const result = Operation.getSpecType(textOperationSpec)
    const expected = Operation.SPEC_TYPE.text

    assert.equal(result, expected)
  })
})

describe("getTargetSpecValue()", () => {
  it("returns null if the operation spec has only one elem", () => {
    const specElems = [Type.atom("layout")]
    const result = Operation.getTargetSpecValue(specElems)

    assert.isNull(result)
  })

  it("returns null if the second operation spec elem is not a boxed atom", () => {
    const specElems = [Type.atom("layout"), Type.integer(1)]
    const result = Operation.getTargetSpecValue(specElems)

    assert.isNull(result)
  })

  it("returns unboxed target value if the operation spec contains a boxed atom target value", () => {
    const specElems = [Type.atom("test_target"), Type.atom("test_action")]
    const result = Operation.getTargetSpecValue(specElems)

    assert.equal(result, "test_target")
  })
})

describe("resolveTarget()", () => {
  it("returns a Target object with id equal to the sourceId arg if the operation spec doesn't contain target value", () => {
    Runtime.registerComponentClass(sourceId, TestComponentClass)

    const specElems = [Type.textNode("test_action")]
    const result = Operation.resolveTarget(specElems, sourceId)

    assert.isTrue(result instanceof Target)
    assert.equal(result.id, sourceId)
  })

  it("returns a Target object with id equal to the layout enum value if the operation spec target value is equal to 'layout' boxed atom", () => {
    const TestLayoutClass = class {}
    Runtime.registerLayoutClass(TestLayoutClass)

    const specElems = [Type.atom("layout"), Type.atom("test_action")]

    const result = Operation.resolveTarget(specElems, sourceId)
    const expected = Target.TYPE.layout

    assert.isTrue(result instanceof Target)
    assert.equal(result.id, expected)
  })

  it("returns a Target object with id equal to the page enum value if the operation spec target value is equal to 'page' boxed atom", () => {
    const TestPageClass = class {}
    Runtime.registerPageClass(TestPageClass)

    const specElems = [Type.atom("page"), Type.atom("test_action")]

    const result = Operation.resolveTarget(specElems, sourceId)
    const expected = Target.TYPE.page

    assert.isTrue(result instanceof Target)
    assert.equal(result.id, expected)
  })

  it("returns a Target object with id equal to the unboxed target value if the operation spec contains a boxed target value", () => {
    const targetId = "test_target_id"
    Runtime.registerComponentClass(targetId, TestComponentClass)

    const specElems = [Type.atom(targetId), Type.atom("test_action")]

    const result = Operation.resolveTarget(specElems, sourceId)

    assert.isTrue(result instanceof Target)
    assert.equal(result.id, targetId)
  })
})

<|start_filename|>test/unit/fixtures/compiler/pruner/module_7.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module7 do
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module14

  def test_fun_7a do
    Module14.test_fun_14a()
  end
end


<|start_filename|>lib/hologram/template/helpers.ex<|end_filename|>
defmodule Hologram.Template.Helpers do
  # DEFER: test
  def tag_type(tag_name) do
    first_char = String.at(tag_name, 0)
    downcased_first_char = String.downcase(first_char)

    if first_char == downcased_first_char do
      :element
    else
      :component
    end
  end
end


<|start_filename|>lib/hologram/runtime/channel.ex<|end_filename|>
defmodule Hologram.Runtime.Channel do
  use Phoenix.Channel

  alias Hologram.Compiler.{Decoder, Serializer}
  alias Hologram.Conn
  alias Hologram.Template.Renderer

  def join("hologram", _, socket) do
    {:ok, socket}
  end

  def handle_in(
        "command",
        %{"target_module" => target_module, "target_id" => target_id} = payload,
        socket
      ) do
    target_module = Decoder.decode(target_module)
    target_id = Decoder.decode(target_id)

    response =
      execute_command(target_module, payload)
      |> build_response(target_id)
      |> Serializer.serialize()

    {:reply, {:ok, response}, socket}
  end

  defp build_response({target_id, action, params}, _) do
    {target_id, action, Enum.into(params, %{})}
  end

  defp build_response({target_id, action}, _) when is_atom(action) do
    {target_id, action, %{}}
  end

  defp build_response({action, params}, target_id) do
    {target_id, action, Enum.into(params, %{})}
  end

  defp build_response(action, target_id) do
    {target_id, action, %{}}
  end

  defp execute_command(target_module, %{"command" => command, "params" => params}) do
    command = Decoder.decode(command)

    params =
      Decoder.decode(params)
      |> Enum.into(%{})

    if command == :__redirect__ do
      html = Renderer.render(params.page, %Conn{}, %{})
      # DEFER: inject params
      url = params.page.route()
      {:__redirect__, html: html, url: url}
    else
      apply(target_module, :command, [command, params])
    end
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/unary_positive_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.UnaryPositiveOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, UnaryPositiveOperatorTransformer}
  alias Hologram.Compiler.IR.{IntegerType, UnaryPositiveOperator}

  test "transform/3" do
    code = "+2"
    ast = ast(code)

    result = UnaryPositiveOperatorTransformer.transform(ast, %Context{})

    expected = %UnaryPositiveOperator{
      value: %IntegerType{value: 2}
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/placeholder_module_5.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.PlaceholderModule5 do
  def test_fun_5a, do: nil
  def test_fun_5b, do: nil
end


<|start_filename|>test/unit/hologram/compiler/transformers/relaxed_boolean_or_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.RelaxedBooleanOrOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, RelaxedBooleanOrOperatorTransformer}
  alias Hologram.Compiler.IR.{IntegerType, RelaxedBooleanOrOperator}

  test "transform/3" do
    code = "1 || 2"
    ast = ast(code)

    result = RelaxedBooleanOrOperatorTransformer.transform(ast, %Context{})

    expected = %RelaxedBooleanOrOperator{
      left: %IntegerType{value: 1},
      right: %IntegerType{value: 2}
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/compiler/call_graph_builder/if_expression_test.exs<|end_filename|>
defmodule Hologram.Compiler.CallGraphBuilder.IfExpressionTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{CallGraph, CallGraphBuilder}
  alias Hologram.Compiler.IR.{ModuleType, IfExpression, NilType}
  alias Hologram.Test.Fixtures.{PlaceholderModule1, PlaceholderModule2}

  @module_defs %{}
  @templates %{}

  setup do
    CallGraph.run()
    :ok
  end

  test "condition is traversed" do
    ir = %IfExpression{
      condition: %ModuleType{module: PlaceholderModule2},
      do: %NilType{},
      else: %NilType{}
    }

    from_vertex = PlaceholderModule1
    CallGraphBuilder.build(ir, @module_defs, @templates, from_vertex)

    assert CallGraph.has_edge?(PlaceholderModule1, PlaceholderModule2)
  end

  test "do clause is traversed" do
    ir = %IfExpression{
      condition: %NilType{},
      do: %ModuleType{module: PlaceholderModule2},
      else: %NilType{}
    }

    from_vertex = PlaceholderModule1
    CallGraphBuilder.build(ir, @module_defs, @templates, from_vertex)

    assert CallGraph.has_edge?(PlaceholderModule1, PlaceholderModule2)
  end

  test "else clause is traversed" do
    ir = %IfExpression{
      condition: %NilType{},
      do: %NilType{},
      else: %ModuleType{module: PlaceholderModule2}
    }

    from_vertex = PlaceholderModule1
    CallGraphBuilder.build(ir, @module_defs, @templates, from_vertex)

    assert CallGraph.has_edge?(PlaceholderModule1, PlaceholderModule2)
  end
end


<|start_filename|>lib/hologram/compiler/pattern_deconstructor/map_type.ex<|end_filename|>
alias Hologram.Compiler.IR.MapAccess
alias Hologram.Compiler.IR.MapType
alias Hologram.Compiler.PatternDeconstructor

defimpl PatternDeconstructor, for: MapType do
  def deconstruct(%{data: data}, path) do
    Enum.reduce(data, [], fn {key, value}, acc ->
      acc ++ PatternDeconstructor.deconstruct(value, path ++ [%MapAccess{key: key}])
    end)
  end
end


<|start_filename|>test/unit/fixtures/template/renderers/component_renderer/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.ComponentRenderer.Module2 do
  use Hologram.Component

  def init(_props) do
    %{
      test_state: 1
    }
  end

  def template do
    ~H"""
    {@test_state}.{@test_prop}
    """
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/case_expression_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.CaseExpressionTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Compiler.{Config, Context, JSEncoder, Opts}

  @case_condition_js Config.case_condition_js()

  defp encode(code) do
    code
    |> ir()
    |> JSEncoder.encode(%Context{}, %Opts{})
  end

  test "single clause / single expression clause body / no bindings" do
    code = """
    case x do
      1 -> :ok
    end
    """

    result = encode(code)

    expected = """
    Hologram.Interpreter.$case_expression(x, (#{@case_condition_js}) => {
    if (Hologram.Interpreter.isPatternMatched({ type: 'integer', value: 1 }, #{@case_condition_js})) {
    return { type: 'atom', value: 'ok' };
    }
    else {
    throw 'No case clause matching'
    }
    })\
    """

    assert result == expected
  end

  test "multiple clauses" do
    code = """
    case x do
      1 -> :ok_1
      2 -> :ok_2
    end
    """

    result = encode(code)

    expected = """
    Hologram.Interpreter.$case_expression(x, (#{@case_condition_js}) => {
    if (Hologram.Interpreter.isPatternMatched({ type: 'integer', value: 1 }, #{@case_condition_js})) {
    return { type: 'atom', value: 'ok_1' };
    }
    else if (Hologram.Interpreter.isPatternMatched({ type: 'integer', value: 2 }, #{@case_condition_js})) {
    return { type: 'atom', value: 'ok_2' };
    }
    else {
    throw 'No case clause matching'
    }
    })\
    """

    assert result == expected
  end

  test "multiple expression clause body" do
    code = """
    case x do
      1 ->
        :expr_1
        :expr_2
    end
    """

    result = encode(code)

    expected = """
    Hologram.Interpreter.$case_expression(x, (#{@case_condition_js}) => {
    if (Hologram.Interpreter.isPatternMatched({ type: 'integer', value: 1 }, #{@case_condition_js})) {
    { type: 'atom', value: 'expr_1' };
    return { type: 'atom', value: 'expr_2' };
    }
    else {
    throw 'No case clause matching'
    }
    })\
    """

    assert result == expected
  end

  test "single binding" do
    code = """
    case x do
      %{a: a} -> :ok
    end
    """

    result = encode(code)

    expected = """
    Hologram.Interpreter.$case_expression(x, (#{@case_condition_js}) => {
    if (Hologram.Interpreter.isPatternMatched({ type: 'map', data: { '~atom[a]': { type: 'placeholder' } } }, #{@case_condition_js})) {
    let a = #{@case_condition_js}.data['~atom[a]'];
    return { type: 'atom', value: 'ok' };
    }
    else {
    throw 'No case clause matching'
    }
    })\
    """

    assert result == expected
  end

  test "multiple bindings" do
    code = """
    case x do
      %{a: a, b: b} -> :ok
    end
    """

    result = encode(code)

    expected = """
    Hologram.Interpreter.$case_expression(x, (#{@case_condition_js}) => {
    if (Hologram.Interpreter.isPatternMatched({ type: 'map', data: { '~atom[a]': { type: 'placeholder' }, '~atom[b]': { type: 'placeholder' } } }, #{@case_condition_js})) {
    let a = #{@case_condition_js}.data['~atom[a]'];
    let b = #{@case_condition_js}.data['~atom[b]'];
    return { type: 'atom', value: 'ok' };
    }
    else {
    throw 'No case clause matching'
    }
    })\
    """

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_55.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module55 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module56

  route "/test-route-55"

  def template do
    ~H"""
    """
  end

  def action do
    Module56
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/text_node.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Template.VDOM.TextNode

defimpl CallGraphBuilder, for: TextNode do
  def build(_, _, _, _), do: nil
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/struct_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.StructType
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: StructType do
  def aggregate(%{data: data}) do
    ModuleDefAggregator.aggregate(data)
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web.ex<|end_filename|>
defmodule HologramE2EWeb do
  def router do
    quote do
      use Phoenix.Router

      import Plug.Conn
      import Phoenix.Controller
    end
  end

  def view do
    quote do
      use Phoenix.View,
        root: "lib/hologram_e2e_web/templates",
        namespace: HologramE2EWeb

      import Phoenix.Controller, only: [view_module: 1, view_template: 1]

      unquote(view_helpers())
    end
  end

  defp view_helpers do
    quote do
      import Phoenix.View
    end
  end

  defmacro __using__(which) when is_atom(which) do
    apply(__MODULE__, which, [])
  end
end


<|start_filename|>lib/hologram/utils.ex<|end_filename|>
defmodule Hologram.Utils do
  def append(str, suffix), do: str <> suffix

  def atomize_keys(data) when is_struct(data), do: data

  def atomize_keys(data) when is_map(data) do
    data
    |> Enum.map(fn {key, value} ->
      {to_string(key) |> String.to_atom(), atomize_keys(value)}
    end)
    |> Enum.into(%{})
  end

  def atomize_keys([head | tail]) do
    [atomize_keys(head) | atomize_keys(tail)]
  end

  def atomize_keys(data), do: data

  # DEFER: test
  def await_tasks(tasks) do
    Enum.map(tasks, &Task.await(&1, :infinity))
  end

  def deserialize(data) do
    :erlang.binary_to_term(data)
  end

  # DEFER: test
  def list_files_recursively(path) do
    cond do
      File.regular?(path) ->
        [path]

      File.dir?(path) ->
        File.ls!(path)
        |> Enum.map(&Path.join(path, &1))
        |> Enum.map(&list_files_recursively/1)
        |> Enum.concat()

      true ->
        []
    end
  end

  # DEFER: test
  def map_async(enumerable, callback) do
    Enum.map(enumerable, fn elem ->
      Task.async(fn -> callback.(elem) end)
    end)
  end

  def prepend(str, prefix), do: prefix <> str

  def serialize(data) do
    :erlang.term_to_binary(data, compressed: 9)
  end
end


<|start_filename|>test/unit/hologram/compiler/normalizer_test.exs<|end_filename|>
defmodule Hologram.Compiler.NormalizerTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Compiler.{Normalizer, Parser}

  describe "anonymous function" do
    test "arrow syntax" do
      code = "fn x -> x end"

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected =
        {:fn, [line: 1],
         [
           {:->, [line: 1], [[{:x, [line: 1], nil}], {:__block__, [], [{:x, [line: 1], nil}]}]}
         ]}

      assert result == expected
    end

    # TODO: shorthand syntax
  end

  describe "case expression" do
    test "clause with single expression" do
      code = """
      case x do
        1 ->
          :expr
      end
      """

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected =
        {:case, [line: 1],
         [
           {:x, [line: 1], nil},
           [
             do: [
               {:->, [line: 2], [[1], {:__block__, [], [:expr]}]}
             ]
           ]
         ]}

      assert result == expected
    end

    test "clause with multiple expressions" do
      code = """
      case x do
        1 ->
          :expr_a
          :expr_b
      end
      """

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected =
        {:case, [line: 1],
         [
           {:x, [line: 1], nil},
           [do: [{:->, [line: 2], [[1], {:__block__, [], [:expr_a, :expr_b]}]}]]
         ]}

      assert result == expected
    end
  end

  describe "do param" do
    test "single expression in do block" do
      code = """
      test do
        :ok
      end
      """

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected = {:test, [line: 1], [[do: {:__block__, [], [:ok]}]]}

      assert result == expected
    end

    test "single expression in do keyword" do
      code = "test do: :ok"

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected = {:test, [line: 1], [[do: {:__block__, [], [:ok]}]]}

      assert result == expected
    end

    test "multiple expressions" do
      code = """
      test do
        1
        2
      end
      """

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected = {:test, [line: 1], [[do: {:__block__, [], [1, 2]}]]}

      assert result == expected
    end
  end

  describe "function definition" do
    test "single expression in do block" do
      code = """
      def test do
        :ok
      end
      """

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected = {:def, [line: 1], [{:test, [line: 1], nil}, [do: {:__block__, [], [:ok]}]]}

      assert result == expected
    end

    test "single expression in do keyword" do
      code = "def test, do: :ok"
      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected = {:def, [line: 1], [{:test, [line: 1], nil}, [do: {:__block__, [], [:ok]}]]}

      assert result == expected
    end

    test "multiple expressions" do
      code = """
      def test do
        1
        2
      end
      """

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected = {:def, [line: 1], [{:test, [line: 1], nil}, [do: {:__block__, [], [1, 2]}]]}

      assert result == expected
    end
  end

  describe "if expression" do
    test "standard notation if block with single expression without else block" do
      code = """
      if true do
        1
      end
      """

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected =
        {:if, [line: 1], [true, [do: {:__block__, [], [1]}, else: {:__block__, [], [nil]}]]}

      assert result == expected
    end

    test "standard notation if block with single expression and else block with single expression" do
      code = """
      if true do
        1
      else
        2
      end
      """

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected =
        {:if, [line: 1], [true, [do: {:__block__, [], [1]}, else: {:__block__, [], [2]}]]}

      assert result == expected
    end

    test "standard notation if block with single expression and else block with multiple expressions" do
      code = """
      if true do
        1
      else
        2
        3
      end
      """

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected =
        {:if, [line: 1], [true, [do: {:__block__, [], [1]}, else: {:__block__, [], [2, 3]}]]}

      assert result == expected
    end

    test "standard notation if block with multiple expressions without else block" do
      code = """
      if true do
        1
        2
      end
      """

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected =
        {:if, [line: 1], [true, [do: {:__block__, [], [1, 2]}, else: {:__block__, [], [nil]}]]}

      assert result == expected
    end

    test "standard notation if block with multiple expressions and else block with single expression" do
      code = """
      if true do
        1
        2
      else
        3
      end
      """

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected =
        {:if, [line: 1], [true, [do: {:__block__, [], [1, 2]}, else: {:__block__, [], [3]}]]}

      assert result == expected
    end

    test "standard notation if block with multiple expressions and else block with multiple expressions" do
      code = """
      if true do
        1
        2
      else
        3
        4
      end
      """

      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected =
        {:if, [line: 1], [true, [do: {:__block__, [], [1, 2]}, else: {:__block__, [], [3, 4]}]]}

      assert result == expected
    end

    test "shorthand notation if block without else block" do
      code = "if true, do: 1"
      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected =
        {:if, [line: 1], [true, [do: {:__block__, [], [1]}, else: {:__block__, [], [nil]}]]}

      assert result == expected
    end

    test "shorhand notation if block with else block" do
      code = "if true, do: 1, else: 2"
      ast = Parser.parse!(code)
      result = Normalizer.normalize(ast)

      expected =
        {:if, [line: 1], [true, [do: {:__block__, [], [1]}, else: {:__block__, [], [2]}]]}

      assert result == expected
    end
  end

  test "other types of expressions" do
    code = "1 + 2"
    ast = Parser.parse!(code)
    result = Normalizer.normalize(ast)

    assert result == ast
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/cons_operator.ex<|end_filename|>
alias Hologram.Compiler.{Context, JSEncoder, Opts}
alias Hologram.Compiler.IR.ConsOperator

defimpl JSEncoder, for: ConsOperator do
  def encode(ir, %Context{} = context, %Opts{placeholder: true} = opts) do
    {head, tail} = encode_parts(ir, context, opts)
    "{ type: 'cons_operator_pattern', head: #{head}, tail: #{tail} }"
  end

  def encode(ir, %Context{} = context, %Opts{} = opts) do
    {head, tail} = encode_parts(ir, context, opts)
    "Hologram.Interpreter.$cons_operator(#{head}, #{tail})"
  end

  defp encode_parts(%{head: head, tail: tail}, context, opts) do
    {
      JSEncoder.encode(head, context, opts),
      JSEncoder.encode(tail, context, opts)
    }
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_29.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module29 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module30, warn: false

  route "/test-route-29"

  def template do
    ~H"""
    <Module30 />
    """
  end
end


<|start_filename|>lib/hologram/template/dom_tree_builder.ex<|end_filename|>
# Covered in Hologram.Template.Parser integration tests

defmodule Hologram.Template.DOMTreeBuilder do
  alias Hologram.Template.{Helpers, SyntaxError}

  def build(tags, acc \\ [])

  def build([], acc), do: acc

  def build([{:self_closing_tag, {tag_name, attrs}} | rest], acc) do
    type = Helpers.tag_type(tag_name)
    build(rest, acc ++ [{type, tag_name, attrs, []}])
  end

  def build([{:start_tag, {tag_name, attrs}} | _] = tags, acc) do
    subtree_tags = get_subtree_tags(tags)
    remaining_tags = Enum.drop(tags, Enum.count(subtree_tags))

    children_tags =
      subtree_tags
      |> Enum.drop(1)
      |> Enum.drop(-1)

    type = Helpers.tag_type(tag_name)
    children = build(children_tags)
    subtree = {type, tag_name, attrs, children}

    build(remaining_tags, acc ++ [subtree])
  end

  def build([{:text_tag, str} | rest], acc) do
    build(rest, acc ++ [{:text, str}])
  end

  defp get_subtree_tags([{:start_tag, {tag_name, _}} = start_tag | rest]) do
    {tag_buffer, num_open_tags} =
      Enum.reduce_while(rest, {[start_tag], 1}, fn tag, {tag_buffer, num_open_tags} ->
        tag_buffer = tag_buffer ++ [tag]

        num_open_tags =
          case tag do
            {:start_tag, {^tag_name, _}} ->
              num_open_tags + 1

            {:end_tag, ^tag_name} ->
              num_open_tags - 1

            _ ->
              num_open_tags
          end

        res = if num_open_tags == 0, do: :halt, else: :cont
        {res, {tag_buffer, num_open_tags}}
      end)

    if num_open_tags > 0 do
      raise SyntaxError, message: "#{tag_name} tag is unclosed"
    end

    tag_buffer
  end
end


<|start_filename|>test/unit/hologram/compiler/module_def_aggregators/element_node_test.exs<|end_filename|>
defmodule Hologram.Compiler.ModuleDefAggregator.ElementNodeTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{ModuleDefAggregator, ModuleDefStore}
  alias Hologram.Compiler.IR.{ModuleDefinition, ModuleType}
  alias Hologram.Template.VDOM.ElementNode

  alias Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ElementNode.{
    Module1,
    Module2,
    Module3
  }

  setup do
    ModuleDefStore.run()
    :ok
  end

  test "aggregation from attrs" do
    ir = %ElementNode{
      attrs: %{
        attr_1: %{
          value: [
            %ModuleType{module: Module1},
            %ModuleType{module: Module2}
          ],
          modifiers: []
        },
        attr_2: %{
          value: [
            %ModuleType{module: Module3}
          ],
          modifiers: []
        }
      },
      children: []
    }

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(Module1)
    assert %ModuleDefinition{} = ModuleDefStore.get!(Module2)
    assert %ModuleDefinition{} = ModuleDefStore.get!(Module3)
  end

  test "aggregation from children" do
    ir = %ElementNode{
      attrs: %{},
      children: [
        %ModuleType{module: Module1},
        %ModuleType{module: Module2}
      ]
    }

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(Module1)
    assert %ModuleDefinition{} = ModuleDefStore.get!(Module2)
  end
end


<|start_filename|>test/unit/hologram/compiler/call_graph_builder/function_definition_test.exs<|end_filename|>
defmodule Hologram.Compiler.CallGraphBuilder.FunctionDefinitionTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{CallGraph, CallGraphBuilder}
  alias Hologram.Compiler.IR.{Block, FunctionDefinition, ModuleType}
  alias Hologram.Test.Fixtures.{PlaceholderModule1, PlaceholderModule2, PlaceholderModule3}

  @module_defs %{}
  @templates %{}

  setup do
    CallGraph.run()
    :ok
  end

  test "function definition with call graph edges" do
    ir = %FunctionDefinition{
      module: PlaceholderModule1,
      name: :test_fun,
      body: %Block{expressions: [
        %ModuleType{module: PlaceholderModule2},
        %ModuleType{module: PlaceholderModule3}
      ]}
    }

    from_vertex = PlaceholderModule1
    CallGraphBuilder.build(ir, @module_defs, @templates, from_vertex)

    refute CallGraph.has_edge?(PlaceholderModule1, {PlaceholderModule1, :test_fun})
    assert CallGraph.has_edge?({PlaceholderModule1, :test_fun}, PlaceholderModule2)
    assert CallGraph.has_edge?({PlaceholderModule1, :test_fun}, PlaceholderModule3)
  end

  test "function definition without call graph edges" do
    ir = %FunctionDefinition{
      module: PlaceholderModule1,
      name: :test_fun,
      body: %Block{expressions: []}
    }

    from_vertex = PlaceholderModule1
    CallGraphBuilder.build(ir, @module_defs, @templates, from_vertex)

    refute CallGraph.has_edge?(PlaceholderModule1, {PlaceholderModule1, :test_fun})
    assert CallGraph.edges([{PlaceholderModule1, :test_fun}]) == []
  end
end


<|start_filename|>test/unit/fixtures/compiler/builder/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Builder.Module2 do
  def test_2, do: nil
end


<|start_filename|>test/unit/hologram/compiler/transformers/subtraction_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.SubtractionOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, SubtractionOperatorTransformer}
  alias Hologram.Compiler.IR.{IntegerType, SubtractionOperator, Variable}

  test "transform/3" do
    code = "a - 2"
    ast = ast(code)

    result = SubtractionOperatorTransformer.transform(ast, %Context{})

    expected = %SubtractionOperator{
      left: %Variable{name: :a},
      right: %IntegerType{value: 2}
    }

    assert result == expected
  end
end


<|start_filename|>test/js/vdom_test.js<|end_filename|>
"use strict";

import { assert, assertFrozen, cleanup } from "./support/commons";
beforeEach(() => cleanup())

import Interpreter from "../../assets/js/hologram/interpreter";
import Target from "../../assets/js/hologram/target";
import Type from "../../assets/js/hologram/type";
import Store from "../../assets/js/hologram/store";
import VDOM from "../../assets/js/hologram/vdom";

const elems = {}
elems[Type.atomKey("a")] = Type.integer(1)

const bindings = Type.map(elems)
const key = Type.atom("a")
const callback = ($bindings) => { return Type.tuple([Interpreter.$dot_operator($bindings, key)])}

describe("aggregateComponentBindings()", () => {
  it("aggregates component bindings", () => {
    const outerBindingsElems = {}
    outerBindingsElems[Type.atomKey("context")] = Type.string("text_context_value")
    outerBindingsElems[Type.atomKey("a")] = Type.integer(1)
    outerBindingsElems[Type.atomKey("x")] = Type.string("x_value")
    const outerBindings = Type.map(outerBindingsElems)

    const stateElems = {}
    stateElems[Type.atomKey("k")] = Type.string("value_2")
    stateElems[Type.atomKey("y")] = Type.string("y_value")
    const state = Type.map(stateElems)
    Store.setComponentState("test_id", state)

    const props = {
      id: [Type.textNode("test_id")],
      k: [Type.textNode("value_1")],
      b: [Type.expressionNode(callback)]
    }

    const componentNode = Type.componentNode("test_class_name", props, [])

    const result = VDOM.aggregateComponentBindings("test_id", componentNode, outerBindings)

    const expectedElems = {}
    expectedElems[Type.atomKey("context")] = Type.string("text_context_value")
    expectedElems[Type.atomKey("id")] = Type.string("test_id")
    expectedElems[Type.atomKey("k")] = Type.string("value_2")
    expectedElems[Type.atomKey("b")] = Type.integer(1)
    expectedElems[Type.atomKey("y")] = Type.string("y_value")
    const expected = Type.map(expectedElems)

    assert.deepStrictEqual(result, expected)
    assertFrozen(result)
  })
})

describe("aggregateComponentContextBindings()", () => {
  it("aggregates component context bindings", () => {
    const outerBindingsElems = {}
    outerBindingsElems[Type.atomKey("xyz")] = Type.integer(9)
    outerBindingsElems[Type.atomKey("context")] = Type.string("text_context_value")
    const outerBindings = Type.map(outerBindingsElems)

    const result = VDOM.aggregateComponentContextBindings(outerBindings)

    const expectedElems = {}
    expectedElems[Type.atomKey("context")] = Type.string("text_context_value")
    const expected = Type.map(expectedElems)

    assert.deepStrictEqual(result, expected)
    assertFrozen(result)
  })
})

describe("aggregateComponentPropsBindings()", () => {
  it("aggregates component props bindings", () => {
    const props = {
      id: [Type.textNode("test_id")],
      value: [Type.expressionNode(callback)]
    }

    const componentNode = Type.componentNode("test_class_name", props, [])

    const result = VDOM.aggregateComponentPropsBindings(componentNode, bindings)

    const elems = {}
    elems[Type.atomKey("id")] = Type.string("test_id")
    elems[Type.atomKey("value")] = Type.integer(1)
    const expected = Type.map(elems)

    assert.deepStrictEqual(result, expected)
    assertFrozen(result)
  })
})

describe("aggregateLayoutBindings()", () => {
  it("aggregates layout bindings", () => {
    let pageStateElems = {}
    pageStateElems[Type.atomKey("a")] = Type.integer(1)
    pageStateElems[Type.atomKey("b")] = Type.integer(2)
    const pageState = Type.map(pageStateElems)

    let layoutStateElems = {}
    layoutStateElems[Type.atomKey("b")] = Type.integer(3)
    layoutStateElems[Type.atomKey("c")] = Type.integer(4)
    const layoutState = Type.map(layoutStateElems)

    Store.setComponentState(Target.TYPE.page, pageState)
    Store.setComponentState(Target.TYPE.layout, layoutState)

    const result = VDOM.aggregateLayoutBindings()

    const expectedElems = {}
    expectedElems[Type.atomKey("a")] = Type.integer(1)
    expectedElems[Type.atomKey("b")] = Type.integer(3)
    expectedElems[Type.atomKey("c")] = Type.integer(4)
    const expected = Type.map(expectedElems)

    assert.deepStrictEqual(result, expected)
    assertFrozen(result)
  })
})

describe("buildComponentVNodes()", () => {
  it("builds stateless component's vnodes", () => {
    const TestStatelessComponent = class {
      static template() {
        return [
          Type.textNode("text_node_1"),
          Type.elementNode("slot", {}, []),
          Type.textNode("text_node_2")
        ]
      }
    }
    globalThis.TestStatelessComponent = TestStatelessComponent

    const children = [Type.textNode("text_node_3")]
    const node = Type.componentNode("TestStatelessComponent", {}, children)

    const result = VDOM.buildComponentVNodes(node, "test_sourceId", Type.map()) 
    const expected = ["text_node_1", "text_node_3", "text_node_2"]

    assert.deepStrictEqual(result, expected)
  })

  it("builds stateful component's vnodes", () => {
    const TestStatefulComponent = class {
      static template() {
        return [
          Type.textNode("text_node_1"),
          Type.elementNode("slot", {}, []),
          Type.textNode("text_node_2")
        ]
      }
    }
    globalThis.TestStatefulComponent = TestStatefulComponent

    const props = {id: [Type.textNode("test_component_id")]}
    const children = [Type.expressionNode(callback)]
    const node = Type.componentNode("TestStatefulComponent", props, children)

    const elems = {}
    elems[Type.atomKey("a")] = Type.integer(99)
    Store.setComponentState("test_component_id", Type.map(elems))

    const result = VDOM.buildComponentVNodes(node, "test_sourceId", Type.map()) 
    const expected = ["text_node_1", "99", "text_node_2"]

    assert.deepStrictEqual(result, expected)
  })
})

describe("buildElementVNode()", () => {
  const sourceId = Target.TYPE.page

  it("builds tag element vnode", () => {
    const attrs = {
      abc: {
        value: [Type.textNode("valueAbc")],
        modifiers: []
      },
      "on:click": "test_on_click_spec"
    }

    const children = [Type.textNode("childTextNode")]
    const node = Type.elementNode("div", attrs, children)

    const result = VDOM.buildElementVNode(node, sourceId, bindings, {})
    const clickHandler = result[0].data.on.click

    const expected = [{
      sel: "div",
      data: {
        attrs: {
          abc: "valueAbc"
        },
        on: {
          click: clickHandler
        } 
      },
      children: [{
        children: undefined,
        data: undefined,
        elm: undefined,
        key: undefined,
        sel: undefined,
        text: "childTextNode"
      }],
      text: undefined,
      elm: undefined,
      key: undefined
    }]

    assert.isFunction(clickHandler)
    assert.deepStrictEqual(result, expected)
  })

  it("builds slot vnodes", () => {
    const node = Type.elementNode("slot", {}, [])

    const slots = {
      default: [Type.textNode("test_text_node")]
    }

    const result = VDOM.buildElementVNode(node, sourceId, bindings, slots)
    const expected = ["test_text_node"]

    assert.deepStrictEqual(result, expected)
  })

  it("handles the 'if' directive", () => {
    const attrs = {
      if: {
        modifiers: [],
        value: [Type.expressionNode(() => {
          return Type.tuple([Type.nil()])
        })]
      }
    }

    const node = Type.elementNode("div", attrs)
    const bindings = Type.map()
    const result = VDOM.buildElementVNode(node, "test-source-id", bindings, {})

    assert.deepStrictEqual(result, [])
  })

  // Covered implicitely in E2E tests.
  // it("changes the sourceId to page if the current sourceId is layout and the current tag is a slot")
  // it("loads page bindings if the current sourceId is layout and the current tag is a slot")
})

describe("buildTextVNodeFromTextNode()", () => {
  it("builds text vnode from text node", () => {
    const textNode = Type.textNode("test")
    const result = VDOM.buildTextVNodeFromTextNode(textNode)

    assert.deepStrictEqual(result, ["test"])
  })
})

describe("buildTextVNodeFromExpression()", () => {
  it("evaluates expression node and interpolates the result to a text vnode", () => {
    const node = Type.expressionNode(callback)

    const result = VDOM.buildTextVNodeFromExpression(node, bindings)
    const expected = ["1"]

    assert.deepStrictEqual(result, expected)
  })
})

describe("buildVNodeAttrs()", () => {
  it("builds literal attribute", () => {
    const node = {
      attrs: {
        abc: {
          value: [Type.textNode("valueAbc")],
          modifiers: [] 
        },
      }
    }

    const result = VDOM.buildVNodeAttrs(node, bindings)
    const expected = {abc: "valueAbc"}

    assert.deepStrictEqual(result, expected)
  })

  it("builds expression attribute", () => {
    const node = {
      attrs: {
        abc: {
          value: [Type.expressionNode(callback)],
          modifiers: []
        }
      }
    }

    const result = VDOM.buildVNodeAttrs(node, bindings)
    const expected = {abc: "1"}

    assert.deepStrictEqual(result, expected)
  })

  it("builds boolean attribute", () => {
    const node = {
      attrs: {
        abc: {
          value: null,
          modifiers: []
        }
      }
    }

    const result = VDOM.buildVNodeAttrs(node, bindings)
    const expected = {abc: true}

    assert.deepStrictEqual(result, expected)
  })

  it("builds expression attribute which evaluates to nil", () => {
    const callback = ($) => { return Type.tuple([Type.nil()]) }

    const node = {
      attrs: {
        abc: {
          value: [Type.expressionNode(callback)],
          modifiers: []
        }
      }
    }

    const result = VDOM.buildVNodeAttrs(node, bindings)
    const expected = {abc: true}

    assert.deepStrictEqual(result, expected)
  })

  it("doesn't build expression attribute which evaluates to false", () => {
    const callback = ($) => { return Type.tuple([Type.boolean(false)]) }

    const node = {
      attrs: {
        abc: {
          value: [Type.expressionNode(callback)],
          modifiers: []
        }
      }
    }

    const result = VDOM.buildVNodeAttrs(node, bindings)
    const expected = {}

    assert.deepStrictEqual(result, expected)
  })

  it("builds multiple attributes", () => {
    const node = {
      attrs: {
        abc: {
          value: [Type.textNode("valueAbc")],
          modifiers: [] 
        },
        xyz: {
          value: [Type.textNode("valueXyz")],
          modifiers: [] 
        },
      }
    }

    const result = VDOM.buildVNodeAttrs(node, bindings)
    const expected = {abc: "valueAbc", xyz: "valueXyz"}

    assert.deepStrictEqual(result, expected)
  })

  it("doesn't include on:(event) attributes", () => {
    const node = {
      attrs: {
        "on:click": {
          value: [Type.textNode("valueOnClick")],
          modifiers: [] 
        }
      }
    }

    const result = VDOM.buildVNodeAttrs(node, bindings)

    assert.deepStrictEqual(result, {})
  })
})

describe("buildVNodeEventHandlers()", () => {
  it("builds vnode change event handler", () => {
    const attrs = {"on:change": "test_on_change_spec"}
    const elementNode = Type.elementNode("form", attrs, [])
    const result = VDOM.buildVNodeEventHandlers(elementNode)

    assert.isFunction(result.change)
  })

  it("builds vnode click event handler", () => {
    const attrs = {"on:click": "test_on_click_spec"}
    const elementNode = Type.elementNode("div", attrs, [])
    const result = VDOM.buildVNodeEventHandlers(elementNode)

    assert.isFunction(result.click)
  })

  it("builds vnode submit event handler", () => {
    const attrs = {"on:submit": "test_on_submit_spec"}
    const elementNode = Type.elementNode("form", attrs, [])
    const result = VDOM.buildVNodeEventHandlers(elementNode)

    assert.isFunction(result.submit)
  })
})

describe("buildVNodeList()", () => {
  it("converts expression nodes into text nodes", () => {
    const nodes = [Type.expressionNode(callback)]

    const result = VDOM.buildVNodeList(nodes, Target.TYPE.page, bindings, {})
    const expected = ["1"]

    assert.deepStrictEqual(result, expected)
  })

  it("merges consecutive text nodes", () => {
    const nodes = [Type.textNode("abc"), Type.expressionNode(callback), Type.textNode("xyz")]

    const result = VDOM.buildVNodeList(nodes, Target.TYPE.page, bindings, {})
    const expected = ["abc1xyz"]

    assert.deepStrictEqual(result, expected)
  })
})

describe("evaluateAttrParts()", () => {
  it("evaluates attribute parts", () => {
    const nodes = [
      Type.textNode("abc"),
      Type.expressionNode(callback)
    ]

    const result = VDOM.evaluateAttrParts(nodes, bindings)
    const expected = [Type.string("abc"), Type.integer(1)]

    assert.deepStrictEqual(result, expected)
  })
})

describe("evaluateNode()", () => {
  it("evaluates expression node to a boxed value", () => {
    const expressionNode = Type.expressionNode(callback)

    const result = VDOM.evaluateNode(expressionNode, bindings)
    const expected = Type.integer(1)

    assert.deepStrictEqual(result, expected)
    assertFrozen(result)
  })

  it("evaluates text node to a boxed value", () => {
    const bindings = Type.map()
    const textNode = Type.textNode("test_content")

    const result = VDOM.evaluateNode(textNode, bindings)
    const expected = Type.string("test_content")

    assert.deepStrictEqual(result, expected)
    assertFrozen(result)
  })

  // DEFER: test
  // it("injects bindings key to bindings", () => {})
})

describe("evaluateProp()", () => {
  it("evaluates the prop value to a boxed string when the value is composed of one value node only", () => {
    const nodes = [Type.textNode("test_text_node")]

    const result = VDOM.evaluateProp(nodes, bindings)
    const expected = Type.string("test_text_node")

    assert.deepStrictEqual(result, expected)
  })

  it("evaluates the prop value to a boxed string when the value is composed of multiple value nodes", () => {
    const nodes = [
      Type.textNode("test_text_node"),
      Type.expressionNode(callback)
    ]

    const result = VDOM.evaluateProp(nodes, bindings)
    const expected = Type.string("test_text_node1")

    assert.deepStrictEqual(result, expected)
  })
})

describe("evaluateAttrToString()", () => {
  it("interpolates and joins parts of attribute value", () => {
    const nodes = [
      Type.string("abc"),
      Type.integer(1),
      Type.string("xyz")
    ]

    const result = VDOM.evaluateAttrToString(nodes, bindings)

    assert.deepStrictEqual(result, "abc1xyz")
  })
})

describe("getComponentId()", () => {
  it("returns component ID prop as JS string if the component is stateful", () => {
    const props = {
      id: [
        Type.textNode("test"),
        Type.expressionNode(callback)
      ]
    }

    const node = Type.componentNode("test_class_name", props, [])
    const result = VDOM.getComponentId(node, bindings)

    assert.equal(result, "test1")
  })

  it("returns null if the component is stateless", () => {
    const node = Type.componentNode("test_class_name", {}, [])
    const result = VDOM.getComponentId(node, bindings)

    assert.isNull(result)
  })
})

describe("interpolate()", () => {
  it("converts a boxed list to JS string", () => {
    const value = Type.list([Type.integer(1), Type.integer(2)])
    const result = VDOM.interpolate(value)

    assert.equal(result, "[1, 2]")
  })

  it("converts a non-string boxed value to JS string", () => {
    const value = Type.atom("abc")
    const result = VDOM.interpolate(value)

    assert.equal(result, "abc")
  })
})

describe("isStatefulComponent()", () => {
  const TestComponent = class {}

  it("returns true if the given component has 'id' prop", () => {
    const props = {
      id: [Type.textNode("test_id")]
    }
    
    const node = Type.componentNode(TestComponent, props, [])
    const result = VDOM.isStatefulComponent(node)

    assert.isTrue(result)
  })

  it("returns false if the given component doesn't have 'id' prop", () => {
    const props = {
      test_prop: [Type.textNode("test_value")]
    }

    const node = Type.componentNode(TestComponent, props, [])
    const result = VDOM.isStatefulComponent(node)

    assert.isFalse(result)
  })
})

describe("reset()", () => {
  it("sets the virtualDocument field to null", () => {
    VDOM.virtualDocument = "test_virtual_document"
    VDOM.reset()
    
    assert.isNull(VDOM.virtualDocument)
  })
})

describe("shouldRenderElementVNode()", () => {
  it("returns true when element vnode doesn't have 'if' attribute", () => {
    const node = Type.elementNode("div")
    const bindings = Type.map()
    const result = VDOM.shouldRenderElementVNode(node, bindings)

    assert.isTrue(result)
  })

  it("returns true when element vnode has 'if' attribute which evaluates to a truthy value", () => {
    const attrs = {
      if: {
        modifiers: [],
        value: [Type.expressionNode(() => {
          return Type.tuple([Type.integer(123)])
        })]
      }
    }

    const node = Type.elementNode("div", attrs)
    const bindings = Type.map()
    const result = VDOM.shouldRenderElementVNode(node, bindings)

    assert.isTrue(result)
  })

  it("returns false when element vnode has 'if' attribute which evaluates to a falsy value", () => {
    const attrs = {
      if: {
        modifiers: [],
        value: [Type.expressionNode(() => {
          return Type.tuple([Type.nil()])
        })]
      }
    }

    const node = Type.elementNode("div", attrs)
    const bindings = Type.map()
    const result = VDOM.shouldRenderElementVNode(node, bindings)

    assert.isFalse(result)
  })
})

<|start_filename|>test/unit/hologram/compiler/transformers/pipe_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.PipeOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, PipeOperatorTransformer}
  alias Hologram.Compiler.IR.{FunctionCall, IntegerType}

  test "non-nested pipeline" do
    code = "100 |> div(2)"
    ast = ast(code)

    result = PipeOperatorTransformer.transform(ast, %Context{})

    expected = %FunctionCall{
      function: :div,
      module: Kernel,
      args: [
        %IntegerType{value: 100},
        %IntegerType{value: 2}
      ]
    }

    assert result == expected
  end

  test "nested pipeline" do
    code = "100 |> div(2) |> div(3)"
    ast = ast(code)

    result = PipeOperatorTransformer.transform(ast, %Context{})

    expected = %FunctionCall{
      function: :div,
      module: Kernel,
      args: [
        %FunctionCall{
          function: :div,
          module: Kernel,
          args: [
            %IntegerType{value: 100},
            %IntegerType{value: 2}
          ]
        },
        %IntegerType{value: 3}
      ]
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/for_expression_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.ForExpressionTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, ForExpressionTransformer, Transformer}

  test "single generator, single binding" do
    code = "for n <- [1, 2], do: n * n"

    expected_code = """
    Enum.reduce([1, 2], [], fn holo__, acc ->
      n = holo__
      acc ++ [n * n]
    end)
    """

    result_ast =
      code
      |> ast()
      |> ForExpressionTransformer.transform(%Context{})

    expected_ast =
      expected_code
      |> ast()
      |> Transformer.transform(%Context{})

    assert result_ast == expected_ast
  end

  test "multiple generators" do
    code = "for n <- [1, 2], m <- [3, 4], do: n * m"

    expected_code = """
    Enum.reduce([1, 2], [], fn holo__, acc ->
      n = holo__
      acc ++ Enum.reduce([3, 4], [], fn holo__, acc ->
        m = holo__
        acc ++ [n * m]
      end)
    end)
    """

    result_ast =
      code
      |> ast()
      |> ForExpressionTransformer.transform(%Context{})

    expected_ast =
      expected_code
      |> ast()
      |> Transformer.transform(%Context{})

    assert result_ast == expected_ast
  end

  test "single generator, multiple bindings" do
    code = "for {a, b} <- [{1, 2}, {3, 4}], do: a * b"

    expected_code = """
    Enum.reduce([{1, 2}, {3, 4}], [], fn holo__, acc ->
      {a, b} = holo__
      acc ++ [a * b]
    end)
    """

    result_ast =
      code
      |> ast()
      |> ForExpressionTransformer.transform(%Context{})

    expected_ast =
      expected_code
      |> ast()
      |> Transformer.transform(%Context{})

    assert result_ast == expected_ast
  end
end


<|start_filename|>lib/hologram/compiler/transformers/relaxed_boolean_not_operator_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.RelaxedBooleanNotOperatorTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.RelaxedBooleanNotOperator

  def transform({:__block__, _, [{:!, _, [value]}]}, %Context{} = context) do
    build_ir(value, context)
  end

  def transform({:!, _, [value]}, %Context{} = context) do
    build_ir(value, context)
  end

  defp build_ir(value, context) do
    %RelaxedBooleanNotOperator{
      value: Transformer.transform(value, context)
    }
  end
end


<|start_filename|>test/unit/hologram/template/transformers/element_node_transformer_test.exs<|end_filename|>
defmodule Hologram.Template.ElementNodeTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.Context
  alias Hologram.Compiler.IR.{AdditionOperator, IntegerType, ModuleAttributeOperator, TupleType}
  alias Hologram.Template.VDOM.{ElementNode, Expression, TextNode}
  alias Hologram.Template.ElementNodeTransformer

  @children :children_placeholder
  @context %Context{}
  @tag_name "div"

  test "attr without modifiers" do
    attrs = [{:literal, "test_key", "test_value"}]
    result = ElementNodeTransformer.transform(@tag_name, @children, attrs, @context)

    expected = %ElementNode{
      tag: @tag_name,
      children: @children,
      attrs: %{
        test_key: %{
          value: [%TextNode{content: "test_value"}],
          modifiers: []
        }
      }
    }

    assert result == expected
  end

  test "attr with modifier" do
    attrs = [{:literal, "test_key.modifier_1.modifier_2", "test_value"}]
    result = ElementNodeTransformer.transform(@tag_name, @children, attrs, @context)

    expected = %ElementNode{
      tag: @tag_name,
      children: @children,
      attrs: %{
        test_key: %{
          value: [%TextNode{content: "test_value"}],
          modifiers: [:modifier_1, :modifier_2]
        }
      }
    }

    assert result == expected
  end

  test "boolean attr value" do
    attrs = [{:boolean, "test_key", nil}]
    result = ElementNodeTransformer.transform(@tag_name, @children, attrs, @context)

    expected = %ElementNode{
      tag: @tag_name,
      children: @children,
      attrs: %{
        test_key: %{
          value: nil,
          modifiers: []
        }
      }
    }

    assert result == expected
  end

  test "literal attr value with embedded expression" do
    attrs = [{:literal, "test_key", "abc{@k}xyz"}]
    result = ElementNodeTransformer.transform(@tag_name, @children, attrs, @context)

    expected = %ElementNode{
      tag: @tag_name,
      children: @children,
      attrs: %{
        test_key: %{
          value: [
            %TextNode{content: "abc"},
            %Expression{
              ir: %TupleType{
                data: [
                  %ModuleAttributeOperator{name: :k}
                ]
              }
            },
            %TextNode{content: "xyz"}
          ],
          modifiers: []
        }
      }
    }

    assert result == expected
  end

  test "expression attr value" do
    attrs = [{:expression, "test_key", "1 + 2"}]
    result = ElementNodeTransformer.transform(@tag_name, @children, attrs, @context)

    expected = %ElementNode{
      tag: @tag_name,
      children: @children,
      attrs: %{
        test_key: %{
          value: [
            %Expression{
              ir: %TupleType{
                data: [
                  %AdditionOperator{
                    left: %IntegerType{value: 1},
                    right: %IntegerType{value: 2}
                  }
                ]
              }
            }
          ],
          modifiers: []
        }
      }
    }

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/expression.ex<|end_filename|>
alias Hologram.Compiler.ModuleDefAggregator
alias Hologram.Template.VDOM.Expression

defimpl ModuleDefAggregator, for: Expression do
  def aggregate(%{ir: ir}) do
    ModuleDefAggregator.aggregate(ir)
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/if_expression_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.IfExpressionTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, IfExpressionTransformer}
  alias Hologram.Compiler.IR.{Block, BooleanType, IfExpression, IntegerType}

  test "transform/2" do
    code = """
    if true do
      1
      2
    else
      3
      4
    end
    """

    ast = ast(code)
    result = IfExpressionTransformer.transform(ast, %Context{})

    expected = %IfExpression{
      condition: %BooleanType{value: true},
      do: %Block{
        expressions: [
          %IntegerType{value: 1},
          %IntegerType{value: 2}
        ]
      },
      else: %Block{
        expressions: [
          %IntegerType{value: 3},
          %IntegerType{value: 4}
        ]
      },
      ast: ast
    }

    assert result == expected
  end
end


<|start_filename|>test/e2e/test/support/helpers.ex<|end_filename|>
defmodule HologramE2E.Test.Helpers do
  alias Hologram.Compiler

  def compile(opts \\ []) do
    Keyword.put(opts, :force, true)
    |> Compiler.compile()
  end
end


<|start_filename|>lib/hologram/template/renderers/element_node.ex<|end_filename|>
alias Hologram.Template.{Evaluator, Renderer}
alias Hologram.Template.VDOM.{ElementNode, Expression}

defimpl Renderer, for: ElementNode do
  alias Hologram.Compiler.IR.TupleType

  @pruned_attrs [
    :if,
    :"on:blur",
    :"on:change",
    :"on:click",
    :"on:pointer_down",
    :"on:pointer_up",
    :"on:submit",
    :"on:transition_end"
  ]

  # see: https://html.spec.whatwg.org/multipage/syntax.html#void-elements
  @void_elems ~w(area base br col embed hr img input link meta param source track wbr)

  def render(%{tag: "slot"}, conn, _, {slot_bindings, default: default_slot}) do
    Renderer.render(default_slot, conn, slot_bindings, nil)
  end

  def render(%{attrs: attrs} = node, conn, bindings, slots) do
    if Map.has_key?(attrs, :if) && !Evaluator.evaluate(hd(attrs.if.value), bindings) do
      {"", %{}}
    else
      render_element(node, conn, bindings, slots)
    end
  end

  defp render_attr(%{value: nil}, key, _bindings) do
    " #{key}"
  end

  defp render_attr(%{value: [%Expression{ir: %TupleType{data: [expr]}}]}, key, bindings) do
    value = Evaluator.evaluate(expr, bindings)

    case value do
      nil ->
        " #{key}"

      false ->
        ""

      value ->
        " #{key}=\"#{value}\""
    end
  end

  defp render_attr(%{value: value}, key, bindings) do
    value = Evaluator.evaluate(value, bindings)
    " #{key}=\"#{value}\""
  end

  defp render_attrs(attrs, bindings) do
    Enum.reject(attrs, fn {key, _} -> key in @pruned_attrs end)
    |> Enum.map(fn {key, spec} -> render_attr(spec, key, bindings) end)
    |> Enum.join("")
  end

  defp render_children(children, conn, bindings, slots) do
    Enum.reduce(children, {"", %{}}, fn child, {html, initial_state} ->
      {child_html, child_initial_state} = Renderer.render(child, conn, bindings, slots)
      {html <> child_html, Map.merge(initial_state, child_initial_state)}
    end)
  end

  defp render_element(%{attrs: attrs, children: children, tag: tag}, conn, bindings, slots) do
    attrs_html = render_attrs(attrs, bindings)
    {children_html, children_initial_state} = render_children(children, conn, bindings, slots)

    html =
      if Enum.member?(@void_elems, tag) do
        "<#{tag}#{attrs_html} />"
      else
        "<#{tag}#{attrs_html}>#{children_html}</#{tag}>"
      end

    {html, children_initial_state}
  end
end


<|start_filename|>test/e2e/test/support/channel_case.ex<|end_filename|>
defmodule HologramE2E.Test.ChannelCase do
  use ExUnit.CaseTemplate

  using do
    quote do
      import Phoenix.ChannelTest
      import HologramE2E.Test.ChannelCase
      import HologramE2E.Test.Helpers

      @endpoint HologramE2EWeb.Endpoint
      @fixtures_path "#{File.cwd!()}/test/fixtures"
    end
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_56.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module56 do
  use Hologram.Page

  route "/test-route-56"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e/application.ex<|end_filename|>
defmodule HologramE2E.Application do
  use Application
  alias HologramE2EWeb.Endpoint

  @impl true
  def start(_type, _args) do
    children = [
      {Phoenix.PubSub, name: HologramE2E.PubSub},
      Endpoint
    ]

    opts = [strategy: :one_for_one, name: HologramE2E.Supervisor]
    Supervisor.start_link(children, opts)
  end

  @impl true
  def config_change(changed, _new, removed) do
    Endpoint.config_change(changed, removed)
    :ok
  end
end


<|start_filename|>lib/hologram/runtime/page.ex<|end_filename|>
defmodule Hologram.Page do
  @default_layout Application.compile_env!(:hologram, :default_layout)

  defmacro __using__(_) do
    quote do
      require Hologram.Page

      import Hologram.Page
      import Hologram.Router, only: [static_path: 1]
      import Hologram.Runtime.Commons, only: [sigil_H: 2]
      import Map, only: [put: 3]

      alias Hologram.Runtime.JS
      alias Hologram.UI.Link

      def is_page?, do: true

      def layout do
        if Keyword.has_key?(__MODULE__.__info__(:functions), :custom_layout) do
          apply(__MODULE__, :custom_layout, [])
        else
          unquote(@default_layout)
        end
      end
    end
  end

  defmacro layout(module) do
    quote do
      def custom_layout do
        unquote(module)
      end
    end
  end

  defmacro route(path) do
    quote do
      def route do
        unquote(path)
      end
    end
  end
end


<|start_filename|>assets/js/hologram/events/change_event.js<|end_filename|>
"use strict";

import Map from "../elixir/map"
import Type from "../type"

export default class ChangeEvent {
  // DEFER: test
  static buildEventData(event, tag) {
    if (tag === "form") {
      return ChangeEvent.buildFormEventData(event)

    } else {
      // DEFER: implement
      return Type.map()
    }
  }

  // DEFER: test
  static buildFormEventData(event) {
    const formData = new FormData(event.target.form)
    let params = Type.map()

    for (let el of formData.entries()) {
      params = Map.put(params, Type.atom(el[0]), Type.string(el[1]))
    }

    return params
  }

  // DEFER: test
  static shouldHandleEvent(_event) {
    return true
  }
}

<|start_filename|>assets/js/hologram/scripts_reloader.js<|end_filename|>
"use strict";

// based on: https://ghinda.net/article/script-tags/

import Utils from "./utils"

// DEFER: test (already refactored)
export default class ScriptsReloader {
  // https://html.spec.whatwg.org/multipage/scripting.html
  static EXECUTABLE_TYPES = [
    'application/javascript',
    'application/ecmascript',
    'application/x-ecmascript',
    'application/x-javascript',
    'text/ecmascript',
    'text/javascript',
    'text/javascript1.0',
    'text/javascript1.1',
    'text/javascript1.2',
    'text/javascript1.3',
    'text/javascript1.4',
    'text/javascript1.5',
    'text/jscript',
    'text/livescript',
    'text/x-ecmascript',
    'text/x-javascript'
  ]

  static dispatchDOMContentLoadedEvent(document) {
    const DOMContentLoadedEvent = document.createEvent('Event')
    DOMContentLoadedEvent.initEvent('DOMContentLoaded', true, true)
    document.dispatchEvent(DOMContentLoadedEvent)
  }

  static insertScript(document, script, callback) {
    const reloadedScript = document.createElement('script')
    reloadedScript.type = 'text/javascript'

    if (script.src) {
      reloadedScript.onload = callback
      reloadedScript.onerror = callback
      reloadedScript.src = script.src
    } else {
      reloadedScript.textContent = script.innerText
    }

    if (ScriptsReloader.isInlineScript(reloadedScript)) {
      Utils.exec(reloadedScript.textContent)
      callback()
    } else {
      if (script.parentNode) {
        script.parentNode.insertBefore(reloadedScript, script)
        script.parentNode.removeChild(script)
      }
    }
  }

  static isExecutable(script) {
    const type = script.getAttribute('type')
    return !type || ScriptsReloader.EXECUTABLE_TYPES.includes(type)
  }

  static isReloadable(script) {
    return script.getAttribute('hologram-policy') !== "no-reload"
  }

  static isInlineScript(script) {
    return !script.src
  }

  static reload(document) {
    let taskQueue = []

    Array.from(document.querySelectorAll('script'))
      .filter((script) => {
        return ScriptsReloader.isExecutable(script) && ScriptsReloader.isReloadable(script)
      })
      .forEach(script => {
        taskQueue.push(callback => {
          ScriptsReloader.insertScript(document, script, callback)
        })
      })

    const callback = () => { ScriptsReloader.dispatchDOMContentLoadedEvent(document) }
    ScriptsReloader.runSequentially(taskQueue, callback, 0)
  }

  static runSequentially(taskQueue, callback, index) {
    taskQueue[index](() => {
      ++index

      if (index == taskQueue.length) {
        callback()

      } else {
        ScriptsReloader.runSequentially(taskQueue, callback, index)
      }
    })
  }
}

<|start_filename|>test/unit/fixtures/compiler/pruner/module_59.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module59 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module60

  route "/test-route-59"

  def template do
    ~H"""
    """
  end

  def action do
    Module60
  end
end


<|start_filename|>test/unit/hologram/compiler/parser_test.exs<|end_filename|>
defmodule Hologram.Compiler.ParserTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Compiler.Parser

  describe "parse/1" do
    test "valid code" do
      assert {:ok, _} = Parser.parse("1 + 2")
    end

    test "invalid code" do
      assert {:error, _} = Parser.parse(".1")
    end
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/access_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.AccessOperator

defimpl CallGraphBuilder, for: AccessOperator do
  def build(%{data: data, key: key}, module_defs, templates, from_vertex) do
    CallGraphBuilder.build(data, module_defs, templates, from_vertex)
    CallGraphBuilder.build(key, module_defs, templates, from_vertex)
  end
end


<|start_filename|>lib/hologram/compiler/serializer.ex<|end_filename|>
defmodule Hologram.Compiler.Serializer do
  alias Hologram.Compiler.{Context, JSEncoder, Normalizer, Opts, Transformer}

  def serialize(state) do
    # TODO: pass actual %Context{} struct received from compiler
    context = %Context{}

    Macro.escape(state)
    |> Normalizer.normalize()
    |> Transformer.transform(context)
    |> JSEncoder.encode(context, %Opts{})
  end
end


<|start_filename|>test/js/elixir/events/blur_event_test.js<|end_filename|>
"use strict";

import {
  assert,
  assertFrozen,
  cleanup,
} from "../../support/commons";
beforeEach(() => cleanup());

import BlurEvent from "../../../../assets/js/hologram/events/blur_event"
import Type from "../../../../assets/js/hologram/type";

describe("buildEventData()", () => {
  it("returns frozen empty boxed map", () => {
    const result = BlurEvent.buildEventData(new Event("blur"), "input")

    assert.deepStrictEqual(result, Type.map())
    assertFrozen(result)
  })
})

<|start_filename|>test/unit/fixtures/compiler/builder/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Builder.Module1 do
  use Hologram.Page

  alias Hologram.Test.Fixtures.Compiler.Builder.Module2, warn: false
  alias Hologram.Test.Fixtures.Compiler.Builder.Module3

  route "/test-route-1"

  def action(:test, _a, _b) do
    Module3.test_3()
  end

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/e2e/test/control_flow_test.exs<|end_filename|>
defmodule HologramE2E.ControlFlowTest do
  use HologramE2E.TestCase, async: false

  describe "anonymous function call" do
    feature "regular syntax", %{session: session} do
      session
      |> visit("/e2e/control-flow/anonymous-function-call")
      |> click(css("#button_regular_syntax"))
      |> assert_has(css("#text", text: "Result = 6"))
    end

    # TODO: implement
    # feature "shorthand syntax"
  end

  feature "case expression", %{session: session} do
    session
    |> visit("/e2e/control-flow/case-expression")
    |> click(css("#button"))
    |> assert_has(css("#text", text: "Result = 22"))
  end

  describe "for expression" do
    test "single generator", %{session: session} do
      session
      |> visit("/e2e/control-flow/for-expression")
      |> click(css("#button_single_generator"))
      |> assert_has(css("#text", text: "Result = [1, 4, 9]"))
    end

    test "multiple generators", %{session: session} do
      session
      |> visit("/e2e/control-flow/for-expression")
      |> click(css("#button_multiple_generators"))
      |> assert_has(css("#text", text: "Result = [3, 4, 6, 8]"))
    end

    test "nested", %{session: session} do
      session
      |> visit("/e2e/control-flow/for-expression")
      |> click(css("#button_nested"))
      |> assert_has(css("#text", text: "Result = [[1, 9, 16], [2, 9, 16]]"))
    end

    test "pattern matching", %{session: session} do
      session
      |> visit("/e2e/control-flow/for-expression")
      |> click(css("#button_pattern_matching"))
      |> assert_has(css("#text", text: "Result = [2, 12]"))
    end
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/control_flow/for_expression_page.ex<|end_filename|>
defmodule HologramE2E.ControlFlow.ForExpressionPage do
  use Hologram.Page

  route "/e2e/control-flow/for-expression"

  def init(_params, _conn) do
    %{
      result: 0
    }
  end

  def template do
    ~H"""
    <button id="button_single_generator" on:click="test_single_generator">
      Test for expression with single generator
    </button>
    <button id="button_multiple_generators" on:click="test_multiple_generators">
      Test for expression with multiple generators
    </button>
    <button id="button_nested" on:click="test_nested">
      Test nested for expressions
    </button>
    <button id="button_pattern_matching" on:click="test_pattern_matching">
      Test pattern matching
    </button>
    <div id="text">Result = {@result}</div>
    """
  end

  def action(:test_single_generator, _params, state) do
    result = for n <- [1, 2, 3], do: n * n
    Map.put(state, :result, result)
  end

  def action(:test_multiple_generators, _params, state) do
    result = for n <- [1, 2], m <- [3, 4], do: n * m
    Map.put(state, :result, result)
  end

  def action(:test_nested, _params, state) do
    result = for n <- [1, 2], do: [n] ++ for n <- [3, 4], do: n * n
    Map.put(state, :result, result)
  end

  def action(:test_pattern_matching, _params, state) do
    result = for {n, m} <- [{1, 2}, {3, 4}], do: n * m
    Map.put(state, :result, result)
  end
end


<|start_filename|>lib/hologram/compiler/transformers/access_operator_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.AccessOperatorTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.AccessOperator

  def transform({{:., _, [Access, :get]}, _, [data, key]}, %Context{} = context) do
    data = Transformer.transform(data, context)
    key = Transformer.transform(key, context)
    %AccessOperator{data: data, key: key}
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/nil_type.ex<|end_filename|>
alias Hologram.Compiler.IR.NilType
alias Hologram.Compiler.JSEncoder

defimpl JSEncoder, for: NilType do
  def encode(_, _, _) do
    "{ type: 'nil' }"
  end
end


<|start_filename|>test/unit/hologram/template/encoders/element_node_test.exs<|end_filename|>
defmodule Hologram.Template.Encoder.ElementNodeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.{StringType, TupleType}
  alias Hologram.Template.VDOM.{ElementNode, Expression, TextNode}
  alias Hologram.Template.Encoder

  test "not attrs, no children" do
    result =
      %ElementNode{tag: "div", attrs: %{}, children: []}
      |> Encoder.encode()

    expected = "{ type: 'element', tag: 'div', attrs: {}, children: [] }"

    assert result == expected
  end

  test "has attrs without modifiers" do
    attrs = %{
      attr_1: %{value: [%TextNode{content: "value_1"}], modifiers: []},
      attr_2: %{value: [%TextNode{content: "value_2"}], modifiers: []}
    }

    result =
      %ElementNode{tag: "div", attrs: attrs, children: []}
      |> Encoder.encode()

    attr_1 = "{ value: [ { type: 'text', content: 'value_1' } ], modifiers: [] }"
    attr_2 = "{ value: [ { type: 'text', content: 'value_2' } ], modifiers: [] }"

    expected =
      "{ type: 'element', tag: 'div', attrs: { 'attr_1': #{attr_1}, 'attr_2': #{attr_2} }, children: [] }"

    assert result == expected
  end

  test "has attrs with modifiers" do
    attrs = %{
      attr_1: %{value: [%TextNode{content: "value_1"}], modifiers: [:abc]},
      attr_2: %{value: [%TextNode{content: "value_2"}], modifiers: [:bcd, :cde]}
    }

    result =
      %ElementNode{tag: "div", attrs: attrs, children: []}
      |> Encoder.encode()

    attr_1 = "{ value: [ { type: 'text', content: 'value_1' } ], modifiers: [ 'abc' ] }"
    attr_2 = "{ value: [ { type: 'text', content: 'value_2' } ], modifiers: [ 'bcd', 'cde' ] }"

    expected =
      "{ type: 'element', tag: 'div', attrs: { 'attr_1': #{attr_1}, 'attr_2': #{attr_2} }, children: [] }"

    assert result == expected
  end

  test "has children" do
    children = [
      %ElementNode{tag: "span", attrs: %{}, children: []},
      %ElementNode{tag: "h1", attrs: %{}, children: []}
    ]

    result =
      %ElementNode{tag: "div", attrs: %{}, children: children}
      |> Encoder.encode()

    child_1 = "{ type: 'element', tag: 'span', attrs: {}, children: [] }"
    child_2 = "{ type: 'element', tag: 'h1', attrs: {}, children: [] }"

    expected = "{ type: 'element', tag: 'div', attrs: {}, children: [ #{child_1}, #{child_2} ] }"

    assert result == expected
  end

  test "doesn't remove any attrs" do
    attrs = %{
      "on:click": %{
        value: [%TextNode{content: "test"}],
        modifiers: []
      }
    }

    result =
      %ElementNode{tag: "div", attrs: attrs, children: []}
      |> Encoder.encode()

    on_click = "{ value: [ { type: 'text', content: 'test' } ], modifiers: [] }"
    expected = "{ type: 'element', tag: 'div', attrs: { 'on:click': #{on_click} }, children: [] }"

    assert result == expected
  end

  test "expression attr" do
    nodes = [
      %Expression{
        ir: %TupleType{
          data: [%StringType{value: "abc"}]
        }
      }
    ]

    attrs = %{"on:click": %{value: nodes, modifiers: []}}

    result =
      %ElementNode{tag: "div", attrs: attrs, children: []}
      |> Encoder.encode()

    callback =
      "($bindings) => { return { type: 'tuple', data: [ { type: 'string', value: 'abc' } ] } }"

    expected =
      "{ type: 'element', tag: 'div', attrs: { 'on:click': { value: [ { type: 'expression', callback: #{callback} } ], modifiers: [] } }, children: [] }"

    assert result == expected
  end

  test "boolean attr" do
    attrs = %{
      attr_1: %{value: nil, modifiers: []}
    }

    result =
      %ElementNode{tag: "div", attrs: attrs, children: []}
      |> Encoder.encode()

    attr_1 = "{ value: null, modifiers: [] }"

    expected = "{ type: 'element', tag: 'div', attrs: { 'attr_1': #{attr_1} }, children: [] }"

    assert result == expected
  end

  test "multiple nodes in attr" do
    attrs = %{
      test_key: %{
        value: [
          %TextNode{content: "abc"},
          %TextNode{content: "xyz"}
        ],
        modifiers: []
      }
    }

    result =
      %ElementNode{tag: "div", attrs: attrs, children: []}
      |> Encoder.encode()

    value = "[ { type: 'text', content: 'abc' }, { type: 'text', content: 'xyz' } ]"

    expected =
      "{ type: 'element', tag: 'div', attrs: { 'test_key': { value: #{value}, modifiers: [] } }, children: [] }"

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/relaxed_boolean_not_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.RelaxedBooleanNotOperator

defimpl CallGraphBuilder, for: RelaxedBooleanNotOperator do
  def build(%{value: value}, module_defs, templates, from_vertex) do
    CallGraphBuilder.build(value, module_defs, templates, from_vertex)
  end
end


<|start_filename|>test/unit/fixtures/runtime/channel/module_4.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Runtime.Channel.Module4 do
  use Hologram.Page

  route "/test-route-4"

  def init(_params, _conn) do
    %{}
  end

  def template do
    ~H"""
    """
  end

  def command(:test_command, params) do
    :"test_action_#{params.a}"
  end
end


<|start_filename|>test/unit/hologram/template/renderers/text_node_test.exs<|end_filename|>
defmodule Hologram.Template.Renderer.TextNodeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Conn
  alias Hologram.Template.Renderer
  alias Hologram.Template.VDOM.TextNode

  test "render/4" do
    content = "a\\{b\\}ca\\{b\\}c"
    text_node = %TextNode{content: content}

    result = Renderer.render(text_node, %Conn{}, %{})
    expected = {"a{b}ca{b}c", %{}}

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/expander/module_6.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Expander.Module6 do
  defmacro test_macro_1 do
    quote do
      abc
    end
  end

  defmacro test_macro_2 do
    quote do
      abc
      bcd
    end
  end

  defmacro test_macro_3(a, b) do
    result = a + b

    quote do
      unquote(result)
    end
  end

  defmacro test_macro_4(x, y) do
    quote do
      z + unquote(x) + unquote(y)
    end
  end

  defmacro test_macro_5 do
    quote do
      def test_function, do: 123
    end
  end

  defmacro test_macro_6 do
    quote do
      def test_function do
        1
        2
      end
    end
  end

  defmacro test_macro_7 do
    quote do
      777
    end
  end

  defmacro __using__(_) do
    quote do
      import Hologram.Test.Fixtures.Compiler.Expander.Module5
    end
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/page_8.ex<|end_filename|>
defmodule HologramE2E.Page8 do
  use Hologram.Page

  route "/e2e/page-8"

  def init(_params, _conn) do
    %{
      field_1: "",
      field_2: ""
    }
  end

  def template do
    ~H"""
    <h1>Page 8</h1>
    <form on:submit="submit">
      <input id="input-1" type="text" name="field_1" />
      <input id="input-2" type="text" name="field_2" />
      <button id="submit-button">Submit</button>
    </form>
    <div id="text-1">Field 1 value = {@field_1}</div>
    <div id="text-2">Field 2 value = {@field_2}</div>
    """
  end

  def action(:submit, params, state) do
    put(state, :field_1, params.event.field_1)
    |> put(:field_2, params.event.field_2)
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_8.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module8 do
  use Hologram.Page

  layout Hologram.Test.Fixtures.Compiler.Pruner.Module9

  route "/test-route-8"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>lib/hologram/compiler/pattern_deconstructor.ex<|end_filename|>
defprotocol Hologram.Compiler.PatternDeconstructor do
  @fallback_to_any true
  
  def deconstruct(pattern, path \\ [])
end


<|start_filename|>lib/hologram/compiler/resolver.ex<|end_filename|>
defmodule Hologram.Compiler.Resolver do
  alias Hologram.Compiler.{Context, Helpers, Reflection}
  alias Hologram.Typespecs, as: T

  @doc """
  Resolves to a fully qualified module.

  ## Examples
      iex> aliases = [%AliasDirective{module: Abc.Bcd, as: [:Bcd]}]
      iex> context = %Context{aliases: aliases}
      iex> resolve([:Bcd], context)
      Abc.Bcd
  """
  @spec resolve(T.module_name_segments(), %Context{}) :: module()

  def resolve(module_segs, %Context{} = context) do
    resolve(module_segs, nil, nil, context)
  end

  @doc """
  Resolves to a fully qualified module.

  ## Examples
      iex> imports = [%ImportDirective{module: Enum}]
      iex> context = %Context{imports: imports}
      iex> resolve([], :put, 3, context)
      Enum
  """
  @spec resolve(
          T.module_name_segments(),
          T.function_name(),
          integer(),
          %Context{}
        ) :: module()

  def resolve([], function, arity, %Context{} = context) do
    imported_module = resolve_to_imported_module(function, arity, context.imports)

    cond do
      imported_module -> imported_module
      resolve_to_kernel_module(function, arity) -> Kernel
      true -> context.module
    end
  end

  def resolve(module_segs, _function, _arity, %Context{} = context) do
    aliased_module = resolve_to_aliased_module(module_segs, context.aliases)
    if aliased_module, do: aliased_module, else: Helpers.module(module_segs)
  end

  defp resolve_to_aliased_module(module_segs, aliases) do
    resolved = Enum.find(aliases, &(&1.as == module_segs))
    if resolved, do: resolved.module, else: nil
  end

  defp resolve_to_kernel_module(function, arity) do
    if Reflection.has_function?(Kernel, function, arity) ||
         Reflection.has_macro?(Kernel, function, arity) do
      Kernel
    else
      nil
    end
  end

  # TODO: take into account "only" and "except" import opts
  defp resolve_to_imported_module(function, arity, imports) do
    resolved = Enum.find(imports, &Reflection.has_function?(&1.module, function, arity))

    if resolved, do: resolved.module, else: nil
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_50.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module50 do
  use Hologram.Page

  route "/test-route-50"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>lib/hologram/compiler/pruner.ex<|end_filename|>
defmodule Hologram.Compiler.Pruner do
  def prune(page_module, module_defs, call_graph) do
    find_reachable_code(page_module, module_defs, call_graph)
    |> remove_redundant_funs(module_defs)
    |> remove_redundant_modules()
  end

  defp find_reachable_code(page_module, module_defs, call_graph) do
    layout_module = page_module.layout()
    component_modules = find_component_modules(module_defs)
    page_modules = find_page_modules(module_defs)

    {[], []}
    |> include_code_reachable_from_page_actions(call_graph, page_module)
    |> include_code_reachable_from_page_template(call_graph, page_module)
    |> include_code_reachable_from_page_layout_fun(call_graph, page_module)
    |> include_code_reachable_from_page_custom_layout_fun(call_graph, page_module)
    |> include_code_reachable_from_page_info_fun(call_graph, page_module)
    |> include_code_reachable_from_layout_init_fun(call_graph, layout_module)
    |> include_code_reachable_from_layout_actions(call_graph, layout_module)
    |> include_code_reachable_from_layout_template(call_graph, layout_module)
    |> include_code_reachable_from_component_init_fun(call_graph, component_modules)
    |> include_code_reachable_from_component_actions(call_graph, component_modules)
    |> include_code_reachable_from_component_templates(call_graph, component_modules)
    |> include_page_routes(page_modules)
    |> elem(1)
    |> Enum.uniq()
  end

  defp find_component_modules(module_defs) do
    Enum.filter(module_defs, fn {_, module_def} -> module_def.component? end)
    |> Enum.map(fn {module, _} -> module end)
    |> MapSet.new()
  end

  defp find_page_modules(module_defs) do
    Enum.filter(module_defs, fn {_, module_def} -> module_def.page? end)
    |> Enum.map(fn {module, _} -> module end)
    |> MapSet.new()
  end

  defp include_code_reachable_from_component_actions(
         {modules, _} = acc,
         call_graph,
         component_modules
       ) do
    modules
    |> Enum.filter(&MapSet.member?(component_modules, &1))
    |> Enum.reduce(acc, fn module, acc ->
      Graph.reachable(call_graph, [{module, :action}])
      |> include_reachable_code(acc)
    end)
  end

  defp include_code_reachable_from_component_init_fun(
         {modules, _} = acc,
         call_graph,
         component_modules
       ) do
    modules
    |> Enum.filter(&MapSet.member?(component_modules, &1))
    |> Enum.reduce(acc, fn module, acc ->
      Graph.reachable(call_graph, [{module, :init}])
      |> include_reachable_code(acc)
    end)
  end

  defp include_code_reachable_from_component_templates(
         {modules, _} = acc,
         call_graph,
         component_modules
       ) do
    modules
    |> Enum.filter(&MapSet.member?(component_modules, &1))
    |> Enum.reduce(acc, fn module, acc ->
      Graph.reachable(call_graph, [{module, :template}])
      |> include_reachable_code(acc)
    end)
  end

  defp include_code_reachable_from_layout_actions(acc, call_graph, layout_module) do
    Graph.reachable(call_graph, [{layout_module, :action}])
    |> include_reachable_code(acc)
  end

  defp include_code_reachable_from_layout_init_fun(acc, call_graph, layout_module) do
    Graph.reachable(call_graph, [{layout_module, :init}])
    |> include_reachable_code(acc)
  end

  defp include_code_reachable_from_layout_template(acc, call_graph, layout_module) do
    Graph.reachable(call_graph, [{layout_module, :template}])
    |> include_reachable_code(acc)
  end

  defp include_code_reachable_from_page_actions(acc, call_graph, page_module) do
    Graph.reachable(call_graph, [{page_module, :action}])
    |> include_reachable_code(acc)
  end

  defp include_code_reachable_from_page_custom_layout_fun(acc, call_graph, page_module) do
    Graph.reachable(call_graph, [{page_module, :custom_layout}])
    |> include_reachable_code(acc)
  end

  defp include_code_reachable_from_page_info_fun(acc, call_graph, page_module) do
    Graph.reachable(call_graph, [{page_module, :__info__}])
    |> include_reachable_code(acc)
  end

  defp include_code_reachable_from_page_layout_fun(acc, call_graph, page_module) do
    Graph.reachable(call_graph, [{page_module, :layout}])
    |> include_reachable_code(acc)
  end

  defp include_code_reachable_from_page_template(acc, call_graph, page_module) do
    Graph.reachable(call_graph, [{page_module, :template}])
    |> include_reachable_code(acc)
  end

  defp include_page_routes({modules, _} = acc, page_modules) do
    modules
    |> Enum.filter(&MapSet.member?(page_modules, &1))
    |> Enum.reduce(acc, fn module, {modules, functions} ->
      {modules, functions ++ [{module, :route}]}
    end)
  end

  defp include_reachable_code(reachable_code, acc) do
    Enum.reduce(reachable_code, acc, fn vertex, {modules, functions} ->
      case vertex do
        nil ->
          {modules, functions}

        {module, function} ->
          {modules, functions ++ [{module, function}]}

        module ->
          {modules ++ [module], functions}
      end
    end)
  end

  defp remove_module_redundant_funs(
         %{functions: funs, module: module} = module_def,
         reachable_code_hash_table
       ) do
    funs = Enum.filter(funs, &reachable_code_hash_table[{module, &1.name}])
    %{module_def | functions: funs}
  end

  defp remove_redundant_funs(reachable_code, module_defs) do
    reachable_code_hash_table =
      reachable_code
      |> Enum.map(&{&1, true})
      |> Enum.into(%{})

    Enum.map(module_defs, fn {module, module_def} ->
      {module, remove_module_redundant_funs(module_def, reachable_code_hash_table)}
    end)
    |> Enum.into(%{})
  end

  defp remove_redundant_modules(module_defs) do
    Enum.filter(module_defs, fn {_, module_def} ->
      Enum.any?(module_def.functions)
    end)
    |> Enum.into(%{})
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/relaxed_boolean_not_operator.ex<|end_filename|>
alias Hologram.Compiler.{Context, JSEncoder, Opts}
alias Hologram.Compiler.IR.RelaxedBooleanNotOperator

defimpl JSEncoder, for: RelaxedBooleanNotOperator do
  def encode(%{value: value}, %Context{} = context, %Opts{} = opts) do
    value = JSEncoder.encode(value, context, opts)
    "Hologram.Interpreter.$relaxed_boolean_not_operator(#{value})"
  end
end


<|start_filename|>test/unit/hologram/template/evaluators/variable_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.VariableTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.Variable
  alias Hologram.Template.Evaluator

  @bindings %{a: 1, b: 2}

  test "evaluates the variable if binding exists" do
    ir = %Variable{name: :b}
    result = Evaluator.evaluate(ir, @bindings)

    assert result == 2
  end

  test "raises error if binding doesn't exist" do
    ir = %Variable{name: :c}

    assert_raise KeyError, "key :c not found in: %{a: 1, b: 2}", fn ->
      Evaluator.evaluate(ir, @bindings)
    end
  end
end


<|start_filename|>.formatter.exs<|end_filename|>
locals_without_parens = [
  # Hologram.Page
  layout: 1,
  route: 1
]

[
  export: [locals_without_parens: locals_without_parens],
  import_deps: [:phoenix],
  inputs: [
    "*.{ex,exs}",
    "{config,lib}/**/*.{ex,exs}",
    "test/{js,unit}/**/*.{ex,exs}",
    "test/e2e/{config,lib,test}/**/*.{ex,exs}"
  ],
  locals_without_parens: locals_without_parens,
  subdirectories: []
]


<|start_filename|>lib/hologram/compiler/call_graph_builder/module_attribute_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.ModuleAttributeOperator

defimpl CallGraphBuilder, for: ModuleAttributeOperator do
  def build(_, _, _, _), do: nil
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/components/component_1.ex<|end_filename|>
defmodule HologramE2E.Component1 do
  use Hologram.Component

  def template do
    ~H"""
    in component 1 template header
    <slot />
    in component 1 template footer
    """
  end
end


<|start_filename|>test/unit/fixtures/template/renderers/component_renderer/module_8.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.ComponentRenderer.Module8 do
  use Hologram.Component
  alias Hologram.Test.Fixtures.Template.ComponentRenderer.Module9, warn: false

  def init(_props) do
    %{
      component_8_state_key: "component_8_state_value"
    }
  end

  def template do
    ~H"""
    abc<Module9 id="component_9_id" />xyz
    """
  end
end


<|start_filename|>lib/hologram/template/vdom.ex<|end_filename|>
defmodule Hologram.Template.VDOM do
  defmodule Component do
    defstruct module: nil, module_def: nil, props: %{}, children: []
  end

  defmodule ElementNode do
    defstruct tag: nil, attrs: %{}, children: []
  end

  defmodule Expression do
    defstruct ir: nil
  end

  defmodule TextNode do
    defstruct content: nil
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/alias_directive_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.AliasDirectiveTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.AliasDirectiveTransformer
  alias Hologram.Compiler.IR.AliasDirective

  @module_1 Hologram.Test.Fixtures.Compiler.AliasDirectiveTransformer.Module1
  @module_2 Hologram.Test.Fixtures.Compiler.AliasDirectiveTransformer.Module2

  test "default 'as' option" do
    code = "alias Hologram.Test.Fixtures.Compiler.AliasDirectiveTransformer.Module1"
    ast = ast(code)

    result = AliasDirectiveTransformer.transform(ast)
    expected = %AliasDirective{module: @module_1, as: [:Module1]}

    assert result == expected
  end

  test "one-part 'as' option" do
    code = "alias Hologram.Test.Fixtures.Compiler.AliasDirectiveTransformer.Module1, as: Xyz"
    ast = ast(code)

    result = AliasDirectiveTransformer.transform(ast)
    expected = %AliasDirective{module: @module_1, as: [:Xyz]}

    assert result == expected
  end

  test "multiple-part 'as' option" do
    code = "alias Hologram.Test.Fixtures.Compiler.AliasDirectiveTransformer.Module1, as: Xyz.Kmn"
    ast = ast(code)

    result = AliasDirectiveTransformer.transform(ast)
    expected = %AliasDirective{module: @module_1, as: [:Xyz, :Kmn]}

    assert result == expected
  end

  test "'warn' option" do
    code = "alias Hologram.Test.Fixtures.Compiler.AliasDirectiveTransformer.Module1, warn: false"
    ast = ast(code)

    result = AliasDirectiveTransformer.transform(ast)
    expected = %AliasDirective{module: @module_1, as: [:Module1]}

    assert result == expected
  end

  test "'as' option + 'warn' option" do
    code =
      "alias Hologram.Test.Fixtures.Compiler.AliasDirectiveTransformer.Module1, as: Xyz, warn: false"

    ast = ast(code)

    result = AliasDirectiveTransformer.transform(ast)
    expected = %AliasDirective{module: @module_1, as: [:Xyz]}

    assert result == expected
  end

  test "multi-alias without options" do
    code = "alias Hologram.Test.Fixtures.Compiler.AliasDirectiveTransformer.{Module1, Module2}"
    ast = ast(code)

    result = AliasDirectiveTransformer.transform(ast)

    expected = [
      %AliasDirective{module: @module_1, as: [:Module1]},
      %AliasDirective{module: @module_2, as: [:Module2]}
    ]

    assert result == expected
  end

  test "multi-alias with options" do
    code =
      "alias Hologram.Test.Fixtures.Compiler.AliasDirectiveTransformer.{Module1, Module2}, warn: false"

    ast = ast(code)

    result = AliasDirectiveTransformer.transform(ast)

    expected = [
      %AliasDirective{module: @module_1, as: [:Module1]},
      %AliasDirective{module: @module_2, as: [:Module2]}
    ]

    assert result == expected
  end
end


<|start_filename|>lib/hologram/runtime/router.ex<|end_filename|>
# DEFER: test

defmodule Hologram.Router do
  alias Hologram.Runtime.StaticDigestStore
  alias Hologram.Template.Renderer
  alias Phoenix.Controller

  def init(opts) do
    opts
  end

  def call(%Plug.Conn{request_path: "/hologram/manifest.js"} = phx_conn, _opts) do
    body = StaticDigestStore.get_manifest()

    phx_conn
    |> Plug.Conn.put_resp_header("cache-control", "no-store")
    |> Plug.Conn.put_resp_header("content-type", "text/javascript")
    |> Plug.Conn.send_resp(200, body)
    |> Plug.Conn.halt()
  end

  def call(%Plug.Conn{request_path: request_path} = phx_conn, _opts) do
    arg =
      get_path_segments(request_path)
      |> List.to_tuple()

    # apply/3 is used to prevent compile warnings about undefined module
    match_result = apply(Hologram.Runtime.RouterMatcher, :match, [arg])

    if match_result do
      {page, params} = match_result
      holo_conn = %Hologram.Conn{params: params}
      bindings = %{}
      output = Renderer.render(page, holo_conn, bindings)
      Controller.html(phx_conn, output)
    else
      phx_conn
    end
  end

  def get_path_segments(path) do
    path
    |> String.split("/")
    |> List.delete_at(0)
  end

  # DEFER: test
  def static_path(file_path) do
    case StaticDigestStore.get(file_path) do
      {:ok, file_path} ->
        file_path

      :error ->
        file_path
    end
  end
end


<|start_filename|>test/js/utils_test.js<|end_filename|>
"use strict";

import { assert, assertFrozen, assertNotFrozen, cleanup } from "./support/commons";
beforeEach(() => cleanup())

import Utils from "../../assets/js/hologram/utils";

describe("clone()", () => {
  let obj, result;

  beforeEach(() => {
    obj = {a: 1, b: {c: 3, d: 4}}
    result = Utils.clone(obj)
  })

  it("clones object recursively (deep clone)", () => {
    assert.deepStrictEqual(result, obj) 
    assert.notEqual(result, obj)
  })

  it("returns unfrozen object", () => {
    assertNotFrozen(result)
  })
})

describe("exec()", () => {
  it("executes given JS code", () => {
    const TestClass = class {
      static testField = null
    }

    globalThis.TestClass = TestClass

    Utils.exec("TestClass.testField = 'testValue'")

    assert.equal(TestClass.testField, "testValue")
  })
})

describe("eval()", () => {
  let result;

  beforeEach(() => {
    result = Utils.eval("{value: 2 + 2}")
  })

  it("evaluates code", () => {
    assert.deepStrictEqual(result, {value: 4})
  })

  it("returns frozen object by default", () => {
    assertFrozen(result)
  })

  it("returns not frozen object if second arg is false", () => {
    result = Utils.eval("{value: 2 + 2}", false)
    assertNotFrozen(result)
  })
})

describe("freeze()", () => {
  it("freezes object and all of its properties recursively (deep freeze)", () => {
    let obj = {
      a: {
        b: {
          c: {
            d: 1
          }
        }
      }
    }

    Utils.freeze(obj)

    assertFrozen(obj.a)
    assertFrozen(obj.a.b)
    assertFrozen(obj.a.b.c)
  })
})

describe("isEqual()", () => {
  it("returns true if left arg is deep equal to right arg", () => {
    const left = {
      a: 1,
      b: {
        c: 3
      }
    }

    const right = {
      a: 1,
      b: {
        c: 3
      }
    }

    const result = Utils.isEqual(left, right)
    assert.isTrue(result)
  })

  it("returns false if left arg is not deep equal to right arg", () => {
    const left = {
      a: 1,
      b: {
        c: 3
      }
    }

    const right = {
      a: 1,
      b: {
        c: 4
      }
    }

    const result = Utils.isEqual(left, right)
    assert.isFalse(result)
  })
})

<|start_filename|>lib/hologram/template/encoders/element_node.ex<|end_filename|>
alias Hologram.Template.VDOM.ElementNode
alias Hologram.Template.Encoder

defimpl Encoder, for: ElementNode do
  use Hologram.Commons.Encoder

  def encode(%{tag: tag, attrs: attrs, children: children}) do
    encoded_attrs = encode_attrs(attrs)
    encoded_children = Encoder.encode(children)

    "{ type: 'element', tag: '#{tag}', attrs: #{encoded_attrs}, children: #{encoded_children} }"
  end

  defp encode_attr(name, %{value: value, modifiers: modifiers}) do
    value = if value, do: Encoder.encode(value), else: "null"
    modifiers = encode_modifiers(modifiers)
    "'#{name}': { value: #{value}, modifiers: #{modifiers} }"
  end

  defp encode_attrs(attrs) do
    attrs
    |> Enum.map(fn {name, spec} -> encode_attr(name, spec) end)
    |> Enum.join(", ")
    |> wrap_with_object()
  end

  defp encode_modifiers(modifiers) do
    Enum.map(modifiers, &"'#{&1}'")
    |> Enum.join(", ")
    |> wrap_with_array()
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/variable.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.Variable
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: Variable do
  def aggregate(_), do: nil
end


<|start_filename|>assets/js/hologram/elixir/list.js<|end_filename|>
"use strict";

import Utils from "../utils";
import Type from "../type";

export default class List {
  static insert_at(list, index, value) {
    if (index === -1) {
      index = list.data.length
    } else if (index < 0) {
      index = index + 1
    }

    const elems = Utils.clone(list.data)
    elems.splice(index, 0, value)
    
    return Type.list(elems)
  }
}

<|start_filename|>test/unit/fixtures/compiler/transformers/alias_directive_transformer/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.AliasDirectiveTransformer.Module2 do
end


<|start_filename|>lib/hologram/compiler/opts.ex<|end_filename|>
defmodule Hologram.Compiler.Opts do
  defstruct placeholder: false, template: false
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_53.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module53 do
  use Hologram.Page

  layout Hologram.Test.Fixtures.PlaceholderModule1

  route "/test-route-53"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/float_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.FloatType

defimpl CallGraphBuilder, for: FloatType do
  def build(_, _, _, _), do: nil
end


<|start_filename|>test/unit/support/helpers.ex<|end_filename|>
defmodule Hologram.Test.Helpers do
  alias Hologram.Compiler

  def compile(opts \\ []) do
    Keyword.put(opts, :force, true)
    |> Compiler.compile()
  end

  def md5_hex_regex do
    ~r/^[0-9a-f]{32}$/
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_4.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module4 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module5, warn: false

  route "/test-route-4"

  def template do
    ~H"""
      <Module5 />
    """
  end
end


<|start_filename|>lib/mix/tasks/holo.run.ex<|end_filename|>
defmodule Mix.Tasks.Holo.Run do
  def run(_) do
    Mix.Task.run("holo.assets.build", [])

    if Mix.env() == :prod do
      Mix.Task.run("holo.assets.clean")
      Mix.Task.run("phx.digest")
    end

    Mix.Task.run("phx.server")
  end
end


<|start_filename|>assets/js/hologram/errors.js<|end_filename|>
"use strict";

export class HologramNotImplementedError extends Error {
  constructor(message) {
    super(message);
    this.name = "HologramNotImplementedError";
  }
}

export class HologramRuntimeError extends Error {
  constructor(message) {
    super(message);
    this.name = "HologramRuntimeError";
  }
}

<|start_filename|>lib/hologram/compiler/transformers/module_attribute_definition_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.ModuleAttributeDefinitionTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.{ModuleAttributeDefinition, NotSupportedExpression}

  def transform({:@, _, [{:callback, _, _}]} = ast, %Context{}) do
    %NotSupportedExpression{ast: ast, type: :behaviour_callback_spec}
  end

  def transform({:@, _, [{name, _, [ast]}]}, %Context{} = context) do
    {value, _} = Code.eval_quoted(ast)
    value_ast = Macro.escape(value)

    %ModuleAttributeDefinition{
      name: name,
      value: Transformer.transform(value_ast, context)
    }
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_62.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module62 do
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module63

  def test_fun_62a do
    Module63.test_fun_63a()
  end
end


<|start_filename|>test/unit/fixtures/compiler/call_graph_builder/module_definition/module_4.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.CallGraphBuilder.ModuleDefinition.Module4 do
  use Hologram.Component
  alias Hologram.Test.Fixtures.Compiler.CallGraphBuilder.ModuleDefinition.Module5, warn: false

  def template do
    ~H"""
      <Module5 />
    """
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/case_expression.ex<|end_filename|>
alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.CaseExpression

defimpl CallGraphBuilder, for: CaseExpression do
  def build(%{condition: condition, clauses: clauses}, module_defs, templates, from_vertex) do
    CallGraphBuilder.build(condition, module_defs, templates, from_vertex)
    Enum.map(clauses, &CallGraphBuilder.build(&1.body, module_defs, templates, from_vertex))
  end
end


<|start_filename|>lib/hologram/template/evaluators/node_list.ex<|end_filename|>
alias Hologram.Template.Evaluator

defimpl Evaluator, for: List do
  def evaluate(nodes, bindings) do
    Enum.reduce(nodes, "", fn node, acc ->
      result = Evaluator.evaluate(node, bindings)
      acc <> to_string(result)
    end)
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/tuple_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.TupleType

defimpl CallGraphBuilder, for: TupleType do
  def build(%{data: data}, module_defs, templates, from_vertex) do
    CallGraphBuilder.build(data, module_defs, templates, from_vertex)
  end
end


<|start_filename|>test/unit/hologram/utils_test.exs<|end_filename|>
defmodule Hologram.UtilsTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Test.Fixtures.Utils.Module1
  alias Hologram.Utils

  test "append/2" do
    assert Utils.append("string", "suffix") == "stringsuffix"
  end

  describe "atomize_keys/1" do
    test "map" do
      map = %{"key_1" => 1, "key_2" => %{"key_3" => 3}, key_4: 4}
      expected = %{key_1: 1, key_2: %{key_3: 3}, key_4: 4}

      assert Utils.atomize_keys(map) == expected
    end

    test "list" do
      list = [%{"map_1" => 1}, %{"map_2" => 2}]
      expected = [%{map_1: 1}, %{map_2: 2}]

      assert Utils.atomize_keys(list) == expected
    end

    test "struct" do
      struct = %Module1{key_1: "value_1", key_2: "value_2"}
      assert Utils.atomize_keys(struct) == struct
    end

    test "nil" do
      assert Utils.atomize_keys(nil) == nil
    end

    test "integer" do
      assert Utils.atomize_keys(9) == 9
    end
  end

  test "deserialize/1" do
    data = %{
      key_2: 2,
      key_1: {1, 3},
      key_3: %{
        b: 22,
        a: 11
      }
    }

    serialized = :erlang.term_to_binary(data, compressed: 9)
    result = Utils.deserialize(serialized)

    assert result == data
  end

  test "prepend/2" do
    assert Utils.prepend("string", "prefix") == "prefixstring"
  end

  test "serialize/1" do
    data_1 = %{
      key_2: 2,
      key_1: {1, 3},
      key_3: %{
        b: 22,
        a: 11
      }
    }

    data_2 = %{
      key_3: %{
        a: 11,
        b: 22
      },
      key_1: {1, 3},
      key_2: 2
    }

    result_1 = Utils.serialize(data_1)
    result_2 = Utils.serialize(data_2)

    assert result_1 == result_2
    assert byte_size(result_1) == 53

    assert :erlang.binary_to_term(result_1) == data_1
    assert :erlang.binary_to_term(result_2) == data_2
  end
end


<|start_filename|>lib/hologram/compiler/transformers/macro_definition_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.MacroDefinitionTransformer do
  alias Hologram.Compiler.{Context, Helpers, Transformer}
  alias Hologram.Compiler.IR.MacroDefinition

  def transform(ast, %Context{} = context) do
    {:defmacro, _, [{name, _, params}, [do: body]]} = ast

    params = Helpers.transform_params(params, context)
    arity = Enum.count(params)
    bindings = Helpers.aggregate_bindings_from_params(params)
    body = Transformer.transform(body, context)

    %MacroDefinition{
      module: context.module,
      name: name,
      arity: arity,
      params: params,
      bindings: bindings,
      body: body
    }
  end
end


<|start_filename|>test/unit/hologram/compiler/pruner_test.exs<|end_filename|>
defmodule Hologram.Compiler.PrunerTest do
  use Hologram.Test.UnitCase, async: false
  alias Hologram.Compiler.Pruner

  defp function_kept?(pruned_module, tested_module, function, arity, context) do
    result = Pruner.prune(pruned_module, context.module_defs, context.call_graph)

    if result[tested_module] do
      result[tested_module].functions
      |> Enum.any?(&(&1.arity == arity && &1.name == function))
    else
      false
    end
  end

  defp module_kept?(pruned_module, tested_module, context) do
    Pruner.prune(pruned_module, context.module_defs, context.call_graph)
    |> Map.has_key?(tested_module)
  end

  setup_all do
    opts = [
      app_path: @fixtures_path <> "/compiler/pruner",
      templatables: [Hologram.Test.Fixtures.App.DefaultLayout]
    ]

    compile(opts)
  end

  describe "kept page functions" do
    test "page actions", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module1
      tested_module = pruned_module

      assert function_kept?(pruned_module, tested_module, :action, 3, context)
      assert function_kept?(pruned_module, tested_module, :action, 4, context)
    end

    test "functions reachable from page actions", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module6
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module14

      assert function_kept?(pruned_module, tested_module, :test_fun_14a, 0, context)
    end

    test "page template", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module15
      tested_module = pruned_module

      assert function_kept?(pruned_module, tested_module, :template, 0, context)
    end

    test "functions reachable from page template", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module20
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module22

      assert function_kept?(pruned_module, tested_module, :test_22a, 0, context)
    end

    test "components reachable from page template", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module29
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module31

      assert function_kept?(pruned_module, tested_module, :template, 0, context)
    end

    test "entry page layout function", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module48
      tested_module = pruned_module

      assert function_kept?(pruned_module, tested_module, :layout, 0, context)
    end

    test "entry page custom_layout function", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module51
      tested_module = pruned_module

      assert function_kept?(pruned_module, tested_module, :custom_layout, 0, context)
    end

    test "entry page route", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module54
      tested_module = pruned_module

      assert function_kept?(pruned_module, tested_module, :route, 0, context)
    end

    test "non-entry page route", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module55
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module56

      assert function_kept?(pruned_module, tested_module, :route, 0, context)
    end

    test "entry page __info__ function", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module64
      tested_module = pruned_module

      assert function_kept?(pruned_module, tested_module, :__info__, 1, context)
    end
  end

  describe "kept layout functions" do
    test "layout actions", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module2
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module3

      assert function_kept?(pruned_module, tested_module, :action, 3, context)
      assert function_kept?(pruned_module, tested_module, :action, 4, context)
    end

    test "functions reachable from layout actions", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module8
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module10

      assert function_kept?(pruned_module, tested_module, :test_fun_10a, 0, context)
    end

    test "layout template", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module16
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module17

      assert function_kept?(pruned_module, tested_module, :template, 0, context)
    end

    test "functions reachable from layout template", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module23
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module25

      assert function_kept?(pruned_module, tested_module, :test_25a, 0, context)
    end

    test "components reachable from layout template", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module32
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module34

      assert function_kept?(pruned_module, tested_module, :template, 0, context)
    end

    test "layout init/1", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module38
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module39

      assert function_kept?(pruned_module, tested_module, :init, 1, context)
    end

    test "functions reachable from layout init/1", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module40
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module42

      assert function_kept?(pruned_module, tested_module, :test_fun_42a, 0, context)
    end
  end

  describe "kept component functions" do
    test "component actions", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module4
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module5

      assert function_kept?(pruned_module, tested_module, :action, 3, context)
      assert function_kept?(pruned_module, tested_module, :action, 4, context)
    end

    test "functions reachable from component actions", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module11
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module13

      assert function_kept?(pruned_module, tested_module, :test_fun_13a, 0, context)
    end

    test "component template", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module18
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module19

      assert function_kept?(pruned_module, tested_module, :template, 0, context)
    end

    test "functions reachable from component template", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module26
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module28

      assert function_kept?(pruned_module, tested_module, :test_28a, 0, context)
    end

    test "components reachable from component template", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module35
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module37

      assert function_kept?(pruned_module, tested_module, :template, 0, context)
    end

    test "component init/1", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module43
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module44

      assert function_kept?(pruned_module, tested_module, :init, 1, context)
    end

    test "functions reachable from component init/1", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module45
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module47

      assert function_kept?(pruned_module, tested_module, :test_fun_47a, 0, context)
    end
  end

  describe "pruned code" do
    test "non-entry page layout function", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module49
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module50

      refute function_kept?(pruned_module, tested_module, :layout, 0, context)
    end

    test "non-entry page custom_layout function", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module52
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module53

      refute function_kept?(pruned_module, tested_module, :custom_layout, 0, context)
    end

    test "non-entry page __info__ function", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module65
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module66

      refute function_kept?(pruned_module, tested_module, :__info__, 1, context)
    end

    test "unreachable pages", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module1
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module2

      refute module_kept?(pruned_module, tested_module, context)
    end

    test "unreachable functions", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module57
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module58

      refute function_kept?(pruned_module, tested_module, :test_fun_58a, 0, context)
    end

    test "modules with unreachable-only functions", context do
      pruned_module = Hologram.Test.Fixtures.Compiler.Pruner.Module59
      tested_module = Hologram.Test.Fixtures.Compiler.Pruner.Module60

      refute module_kept?(pruned_module, tested_module, context)
    end
  end

  test "circular dependencies are handled correctly", context do
    module_61 = Hologram.Test.Fixtures.Compiler.Pruner.Module61
    module_62 = Hologram.Test.Fixtures.Compiler.Pruner.Module62
    module_63 = Hologram.Test.Fixtures.Compiler.Pruner.Module63

    assert function_kept?(module_61, module_62, :test_fun_62a, 0, context)
    assert function_kept?(module_61, module_63, :test_fun_63a, 0, context)
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/text_node.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.ModuleDefAggregator
alias Hologram.Template.VDOM.TextNode

defimpl ModuleDefAggregator, for: TextNode do
  def aggregate(_), do: nil
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/module_type/module_4.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ModuleType.Module4 do
  use Hologram.Page

  layout Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ModuleType.Module5

  route "/test-route-4"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/unit/fixtures/template/renderer/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.Renderer.Module1 do
  use Hologram.Component

  def template do
    ~H"""
    <div>test template</div>
    """
  end
end


<|start_filename|>lib/hologram/template/encoders/node_list.ex<|end_filename|>
alias Hologram.Template.Encoder

defimpl Encoder, for: List do
  def encode(nodes) do
    js =
      Enum.map(nodes, &Encoder.encode/1)
      |> Enum.join(", ")

    if js != "", do: "[ #{js} ]", else: "[]"
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/division_operator.ex<|end_filename|>
alias Hologram.Compiler.{Context, JSEncoder, Opts}
alias Hologram.Compiler.IR.DivisionOperator

defimpl JSEncoder, for: DivisionOperator do
  def encode(%{left: left, right: right}, %Context{} = context, %Opts{} = opts) do
    left = JSEncoder.encode(left, context, opts)
    right = JSEncoder.encode(right, context, opts)

    "Hologram.Interpreter.$division_operator(#{left}, #{right})"
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_33.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module33 do
  use Hologram.Layout
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module34, warn: false

  def template do
    ~H"""
    <Module34 />
    """
  end
end


<|start_filename|>lib/hologram/runtime/static_digest_store.ex<|end_filename|>
defmodule Hologram.Runtime.StaticDigestStore do
  use Hologram.Commons.MemoryStore

  alias Hologram.Compiler.Reflection
  alias Hologram.Utils

  @manifest_key :__manifest__

  @impl true
  def populate_table() do
    digests = find_digests()

    Enum.each(digests, fn {file_path, digest} ->
      put(file_path, digest)
    end)

    put_manifest(digests)
  end

  @impl true
  def table_name, do: :hologram_static_digest_store

  defp find_digests do
    static_path = Reflection.release_static_path()
    regex = ~r/^#{Regex.escape(static_path)}(.+)\-([0-9a-f]{32})(.+)$/

    static_path
    |> Utils.list_files_recursively()
    |> Stream.map(&Regex.run(regex, &1))
    |> Stream.filter(& &1)
    |> Stream.map(&List.to_tuple/1)
    |> Stream.reject(fn {_, prefix, _, _} -> prefix == "/hologram/page" end)
    |> Stream.map(fn {_, prefix, digest, suffix} ->
      {String.to_atom(prefix <> suffix), prefix <> "-" <> digest <> suffix}
    end)
    |> Enum.to_list()
  end

  def get_manifest do
    get!(@manifest_key)
  end

  defp put_manifest(digests) do
    json =
      digests
      |> Enum.into(%{})
      |> Jason.encode!()

    content = "window.__hologramStaticDigestStore__ = #{json};"
    put(@manifest_key, content)
  end
end


<|start_filename|>lib/hologram/template/evaluators/string_type.ex<|end_filename|>
alias Hologram.Compiler.IR.StringType
alias Hologram.Template.Evaluator

defimpl Evaluator, for: StringType do
  def evaluate(%{value: value}, _) do
    value
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/unary_negative_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.UnaryNegativeOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, UnaryNegativeOperatorTransformer}
  alias Hologram.Compiler.IR.{IntegerType, UnaryNegativeOperator}

  test "transform/3" do
    code = "-2"
    ast = ast(code)

    result = UnaryNegativeOperatorTransformer.transform(ast, %Context{})

    expected = %UnaryNegativeOperator{
      value: %IntegerType{value: 2}
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/app/layouts/default_layout.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.App.DefaultLayout do
  use Hologram.Layout

  def init(_conn) do
    %{}
  end

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/element_node/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ElementNode.Module2 do
end


<|start_filename|>test/unit/fixtures/template/renderers/component_renderer/module_7.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.ComponentRenderer.Module7 do
  use Hologram.Component

  def init(_props) do
    %{
      child_component_state_key: "child_component_state_value"
    }
  end

  def template do
    ~H"""
    child
    """
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/struct_type_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.StructTypeTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, StructTypeTransformer}
  alias Hologram.Compiler.IR.{AliasDirective, AtomType, IntegerType, StructType}

  test "not aliased" do
    code = "%Hologram.Test.Fixtures.Compiler.StructTypeTransformer.Module1{a: 1}"
    ast = ast(code)

    result = StructTypeTransformer.transform(ast, %Context{})

    expected = %StructType{
      data: [
        {%AtomType{value: :a}, %IntegerType{value: 1}}
      ],
      module: Hologram.Test.Fixtures.Compiler.StructTypeTransformer.Module1
    }

    assert result == expected
  end

  test "aliased" do
    code = "%Abc{b: 2}"
    ast = ast(code)

    context = %Context{
      aliases: [
        %AliasDirective{
          module: Hologram.Test.Fixtures.Compiler.StructTypeTransformer.Module2,
          as: [:Abc]
        }
      ]
    }

    result = StructTypeTransformer.transform(ast, context)

    expected = %StructType{
      data: [
        {%AtomType{value: :b}, %IntegerType{value: 2}}
      ],
      module: Hologram.Test.Fixtures.Compiler.StructTypeTransformer.Module2
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_57.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module57 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module58

  route "/test-route-57"

  def template do
    ~H"""
    """
  end

  def action do
    Module58.test_fun_58b()
  end
end


<|start_filename|>test/unit/hologram/template/encoders/component_test.exs<|end_filename|>
defmodule Hologram.Template.Encoder.ComponentTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.{IntegerType, TupleType}
  alias Hologram.Template.VDOM.{Component, Expression, TextNode}
  alias Hologram.Template.Encoder

  describe "encode/1" do
    test "component doesn't have any props" do
      module = Hologram.Test.Fixtures.Template.ComponentEncoder.Module1

      children = [
        %TextNode{content: "test_content"},
        %Expression{ir: %TupleType{data: [%IntegerType{value: 1}]}}
      ]

      result =
        %Component{module: module, children: children, props: %{}}
        |> Encoder.encode()

      encoded_module = "Elixir_Hologram_Test_Fixtures_Template_ComponentEncoder_Module1"

      encoded_children =
        "[ { type: 'text', content: 'test_content' }, { type: 'expression', callback: ($bindings) => { return { type: 'tuple', data: [ { type: 'integer', value: 1 } ] } } } ]"

      expected =
        "{ type: 'component', className: '#{encoded_module}', children: #{encoded_children}, props: {} }"

      assert result == expected
    end

    test "component has single prop" do
      module = Hologram.Test.Fixtures.Template.ComponentEncoder.Module1

      props = %{
        prop_1: [%TextNode{content: "value_1"}]
      }

      result =
        %Component{module: module, children: [], props: props}
        |> Encoder.encode()

      encoded_module = "Elixir_Hologram_Test_Fixtures_Template_ComponentEncoder_Module1"
      encoded_props = "{ 'prop_1': [ { type: 'text', content: 'value_1' } ] }"

      expected =
        "{ type: 'component', className: '#{encoded_module}', children: [], props: #{encoded_props} }"

      assert result == expected
    end

    test "component has multiple props" do
      module = Hologram.Test.Fixtures.Template.ComponentEncoder.Module1

      props = %{
        prop_1: [%TextNode{content: "value_1"}],
        prop_2: [%TextNode{content: "value_2"}]
      }

      result =
        %Component{module: module, children: [], props: props}
        |> Encoder.encode()

      encoded_module = "Elixir_Hologram_Test_Fixtures_Template_ComponentEncoder_Module1"
      encoded_prop_1 = "'prop_1': [ { type: 'text', content: 'value_1' } ]"
      encoded_prop_2 = "'prop_2': [ { type: 'text', content: 'value_2' } ]"
      encoded_props = "{ #{encoded_prop_1}, #{encoded_prop_2} }"

      expected =
        "{ type: 'component', className: '#{encoded_module}', children: [], props: #{encoded_props} }"

      assert result == expected
    end
  end
end


<|start_filename|>test/unit/hologram/compiler/module_def_aggregators/function_definition_test.exs<|end_filename|>
defmodule Hologram.Compiler.ModuleDefAggregator.FunctionDefinitionTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{ModuleDefAggregator, ModuleDefStore}
  alias Hologram.Compiler.IR.{Block, FunctionDefinition, ModuleDefinition, ModuleType}
  alias Hologram.Test.Fixtures.{PlaceholderModule1, PlaceholderModule2}

  setup do
    ModuleDefStore.run()
    :ok
  end

  test "aggregate/1" do
    ir = %FunctionDefinition{
      body: %Block{expressions: [
        %ModuleType{module: PlaceholderModule1},
        %ModuleType{module: PlaceholderModule2}
      ]}
    }

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(PlaceholderModule1)
    assert %ModuleDefinition{} = ModuleDefStore.get!(PlaceholderModule2)
  end
end


<|start_filename|>test/unit/hologram/template/evaluators/integer_type_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.IntegerTypeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.IntegerType
  alias Hologram.Template.Evaluator

  test "evaluate/2" do
    ir = %IntegerType{value: 123}

    result = Evaluator.evaluate(ir, %{})
    expected = 123

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/reflection/module_6.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Reflection.Module6 do
  defmacro test_macro(_a, _b), do: nil
end


<|start_filename|>assets/js/hologram/elixir/hologram/router.js<|end_filename|>
"use strict";

import Runtime from "../../runtime";
import Type from "../../type";

export default class Router {
  // DEFER: test
  static static_path(filePath) {
    const pathWithDigest = Runtime.staticDigestStore[filePath.value]
    return pathWithDigest ? Type.string(pathWithDigest) : filePath
  }
}

<|start_filename|>test/unit/fixtures/compiler/pruner/module_14.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module14 do
  def test_fun_14a do
    :ok
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/access_operator.ex<|end_filename|>
alias Hologram.Compiler.{Context, Helpers, JSEncoder, Opts}
alias Hologram.Compiler.IR.AccessOperator

defimpl JSEncoder, for: AccessOperator do
  def encode(%{data: data, key: key}, %Context{} = context, %Opts{} = opts) do
    data = JSEncoder.encode(data, context, opts)
    key = JSEncoder.encode(key, context, opts)
    class_name = Helpers.class_name(Access)

    "#{class_name}.get(#{data}, #{key})"
  end
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/component/module_3.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.Component.Module3 do
end


<|start_filename|>test/js/elixir/io_test.js<|end_filename|>
"use strict";

import { assert, cleanup, sinon } from "../support/commons";
beforeEach(() => cleanup());

import IO from "../../../assets/js/hologram/elixir/io";
import Type from "../../../assets/js/hologram/type";

describe("inspect()", () => {
  it("prints debug info for the given value", () => {
    const stub = sinon.stub(console, "debug");

    const val = 123;
    IO.inspect(val);

    sinon.assert.calledWith(stub, val);
  });

  it("returns the value passed as argument", () => {
    const val = Type.integer(1);
    const result = IO.inspect(val);

    assert.equal(result, val);
  });
});


<|start_filename|>assets/js/hologram/elixir/access.js<|end_filename|>
"use strict";

import { HologramNotImplementedError } from "../errors";
import Keyword from "../elixir/keyword"
import Map from "../elixir/map"
import Type from "../type";

export default class Access {
  // DEFER: test
  static get(container, key, default_value = Type.nil()) {
    switch (container.type) {
      case "list":
        return Keyword.get(container, key, default_value)

      case "map":
        return Map.get(container, key, default_value)

      default:
        const message = `Access.get(): cointainer = ${JSON.stringify(cointainer)}, key = ${JSON.stringify(key)}, default_value = ${JSON.stringify(default_value)}`
        throw new HologramNotImplementedError(message)
    }
  }
}

<|start_filename|>test/unit/hologram/compiler/module_def_aggregators/function_call_test.exs<|end_filename|>
defmodule Hologram.Compiler.ModuleDefAggregator.FunctionCallTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{ModuleDefAggregator, ModuleDefStore}
  alias Hologram.Compiler.IR.{FunctionCall, ModuleDefinition, ModuleType}
  alias Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.FunctionCall.{Module1, Module2}

  setup do
    ModuleDefStore.run()
    :ok
  end

  test "aggregates called module" do
    ir = %FunctionCall{
      module: Module2,
      function: :test_fun_2a
    }

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(Module2)
  end

  test "aggregates args" do
    ir = %FunctionCall{
      module: Kernel,
      args: [
        %ModuleType{module: Module1},
        %ModuleType{module: Module2}
      ]
    }

    ModuleDefAggregator.aggregate(ir)

    assert %ModuleDefinition{} = ModuleDefStore.get!(Module1)
    assert %ModuleDefinition{} = ModuleDefStore.get!(Module2)
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/module_attribute_definition_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.ModuleAttributeDefinitionTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, ModuleAttributeDefinitionTransformer}

  alias Hologram.Compiler.IR.{
    IntegerType,
    ModuleAttributeDefinition,
    NotSupportedExpression,
    TupleType
  }

  def test_fun do
    {1, 2, 3, 4}
  end

  test "hardcoded value" do
    code = "@abc 1"
    ast = ast(code)

    result = ModuleAttributeDefinitionTransformer.transform(ast, %Context{})
    expected = %ModuleAttributeDefinition{name: :abc, value: %IntegerType{value: 1}}

    assert result == expected
  end

  test "expression" do
    code = "@abc 1 + 2"
    ast = ast(code)

    result = ModuleAttributeDefinitionTransformer.transform(ast, %Context{})
    expected = %ModuleAttributeDefinition{name: :abc, value: %IntegerType{value: 3}}

    assert result == expected
  end

  test "function call" do
    code = "@abc Hologram.Compiler.ModuleAttributeDefinitionTransformerTest.test_fun()"
    ast = ast(code)

    result = ModuleAttributeDefinitionTransformer.transform(ast, %Context{})

    expected = %ModuleAttributeDefinition{
      name: :abc,
      value: %TupleType{
        data: [
          %IntegerType{value: 1},
          %IntegerType{value: 2},
          %IntegerType{value: 3},
          %IntegerType{value: 4}
        ]
      }
    }

    assert result == expected
  end

  test "behaviour callback spec" do
    code = "@callback some_fun :: any()"
    ast = ast(code)

    result = ModuleAttributeDefinitionTransformer.transform(ast, %Context{})
    assert %NotSupportedExpression{type: :behaviour_callback_spec} = result
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/anonymous_function_call.ex<|end_filename|>
alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.AnonymousFunctionCall

defimpl CallGraphBuilder, for: AnonymousFunctionCall do
  def build(%{args: args}, module_defs, templates, from_vertex) do
    CallGraphBuilder.build(args, module_defs, templates, from_vertex)
  end
end


<|start_filename|>test/unit/fixtures/template/builder/module_3.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.Builder.Module3 do
  use Hologram.Layout

  def template do
    ~H"""
    """
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/not_supported_expression.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.NotSupportedExpression

defimpl CallGraphBuilder, for: NotSupportedExpression do
  def build(_, _, _, _), do: nil
end


<|start_filename|>test/unit/hologram/runtime/commons_test.exs<|end_filename|>
defmodule Hologram.Runtime.CommonsTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Runtime.Commons

  test "sigil_H/2" do
    assert Commons.sigil_H(" \ntest \n", []) == "test"
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/components/component_2.ex<|end_filename|>
defmodule HologramE2E.Component2 do
  use Hologram.Component

  def template do
    ~H"""
    in component 2 template header
    <slot />
    in component 2 template footer
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/reflection/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Reflection.Module1 do
end


<|start_filename|>lib/hologram/template/tokenizer.ex<|end_filename|>
# DEFER: test

defmodule Hologram.Template.Tokenizer do
  def tokenize(""), do: []

  def tokenize(" " <> rest) do
    [{:whitespace, " "} | tokenize(rest)]
  end

  def tokenize("\n" <> rest) do
    [{:whitespace, "\n"} | tokenize(rest)]
  end

  def tokenize("\r" <> rest) do
    [{:whitespace, "\r"} | tokenize(rest)]
  end

  def tokenize("\t" <> rest) do
    [{:whitespace, "\t"} | tokenize(rest)]
  end

  def tokenize("</" <> rest) do
    [{:symbol, :"</"} | tokenize(rest)]
  end

  def tokenize("/>" <> rest) do
    [{:symbol, :"/>"} | tokenize(rest)]
  end

  def tokenize("<" <> rest) do
    [{:symbol, :<} | tokenize(rest)]
  end

  def tokenize(">" <> rest) do
    [{:symbol, :>} | tokenize(rest)]
  end

  def tokenize("/" <> rest) do
    [{:symbol, :/} | tokenize(rest)]
  end

  def tokenize("=" <> rest) do
    [{:symbol, :=} | tokenize(rest)]
  end

  def tokenize("\"" <> rest) do
    [{:symbol, :"\""} | tokenize(rest)]
  end

  def tokenize("{" <> rest) do
    [{:symbol, :"{"} | tokenize(rest)]
  end

  def tokenize("}" <> rest) do
    [{:symbol, :"}"} | tokenize(rest)]
  end

  def tokenize("\\{" <> rest) do
    [{:symbol, :"\\{"} | tokenize(rest)]
  end

  def tokenize("\\}" <> rest) do
    [{:symbol, :"\\}"} | tokenize(rest)]
  end

  def tokenize(rest) do
    excluded_chars = "\s<>/=\"{}" |> Regex.escape()
    regex = ~r/\A([^#{excluded_chars}]+)(.*)\z/msu
    [_, string, rest] = Regex.run(regex, rest)
    [{:string, string} | tokenize(rest)]
  end
end


<|start_filename|>test/unit/hologram/compiler/transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.TransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, Transformer}

  alias Hologram.Compiler.IR.{
    AccessOperator,
    AdditionOperator,
    AliasDirective,
    AnonymousFunctionCall,
    AnonymousFunctionType,
    AtomType,
    BinaryType,
    Block,
    BooleanType,
    CaseExpression,
    ConsOperator,
    DivisionOperator,
    DotOperator,
    EqualToOperator,
    FloatType,
    FunctionDefinition,
    FunctionCall,
    IfExpression,
    ImportDirective,
    IntegerType,
    LessThanOperator,
    ListConcatenationOperator,
    ListSubtractionOperator,
    ListType,
    MacroDefinition,
    MapType,
    MatchOperator,
    MembershipOperator,
    ModuleDefinition,
    ModuleAttributeDefinition,
    ModuleAttributeOperator,
    ModulePseudoVariable,
    ModuleType,
    MultiplicationOperator,
    NilType,
    NotEqualToOperator,
    ProtocolDefinition,
    Quote,
    RelaxedBooleanAndOperator,
    RelaxedBooleanNotOperator,
    RelaxedBooleanOrOperator,
    RequireDirective,
    StrictBooleanAndOperator,
    StringType,
    StructType,
    SubtractionOperator,
    TupleType,
    TypeOperator,
    Typespec,
    UnaryNegativeOperator,
    UnaryPositiveOperator,
    Unquote,
    UseDirective,
    Variable
  }

  describe "operators" do
    test "access" do
      code = "a[:b]"
      ast = ast(code)

      assert %AccessOperator{} = Transformer.transform(ast, %Context{})
    end

    test "addition" do
      code = "1 + 2"
      ast = ast(code)

      assert %AdditionOperator{} = Transformer.transform(ast, %Context{})
    end

    test "cons" do
      code = "[h | t]"
      ast = ast(code)

      assert %ConsOperator{} = Transformer.transform(ast, %Context{})
    end

    test "division" do
      code = "1 / 2"
      ast = ast(code)

      assert %DivisionOperator{} = Transformer.transform(ast, %Context{})
    end

    test "dot" do
      code = "a.b"
      ast = ast(code)

      assert %DotOperator{} = Transformer.transform(ast, %Context{})
    end

    test "equal to" do
      code = "1 == 2"
      ast = ast(code)

      assert %EqualToOperator{} = Transformer.transform(ast, %Context{})
    end

    test "less than" do
      code = "1 < 2"
      ast = ast(code)

      assert %LessThanOperator{} = Transformer.transform(ast, %Context{})
    end

    test "list concatenation" do
      code = "[1, 2] ++ [3, 4]"
      ast = ast(code)

      assert %ListConcatenationOperator{} = Transformer.transform(ast, %Context{})
    end

    test "list subtraction" do
      code = "[1, 2] -- [3, 2]"
      ast = ast(code)

      assert %ListSubtractionOperator{} = Transformer.transform(ast, %Context{})
    end

    test "match" do
      code = "a = 1"
      ast = ast(code)

      assert %MatchOperator{} = Transformer.transform(ast, %Context{})
    end

    test "membership" do
      code = "1 in [1, 2]"
      ast = ast(code)

      assert %MembershipOperator{} = Transformer.transform(ast, %Context{})
    end

    test "module attribute" do
      code = "@a"
      ast = ast(code)

      result = Transformer.transform(ast, %Context{})
      assert result == %ModuleAttributeOperator{name: :a}
    end

    test "multiplication" do
      code = "1 * 2"
      ast = ast(code)

      assert %MultiplicationOperator{} = Transformer.transform(ast, %Context{})
    end

    test "not equal to" do
      code = "1 != 2"
      ast = ast(code)

      assert %NotEqualToOperator{} = Transformer.transform(ast, %Context{})
    end

    test "pipe" do
      code = "100 |> div(2)"
      ast = ast(code)

      assert %FunctionCall{} = Transformer.transform(ast, %Context{})
    end

    test "relaxed boolean and" do
      code = "true && false"
      ast = ast(code)

      assert %RelaxedBooleanAndOperator{} = Transformer.transform(ast, %Context{})
    end

    test "relaxed boolean not, block AST" do
      code = "!false"
      ast = ast(code)

      assert %RelaxedBooleanNotOperator{} = Transformer.transform(ast, %Context{})
    end

    test "relaxed boolean not, non-block AST" do
      code = "true && !false"
      ast = ast(code)

      assert %RelaxedBooleanAndOperator{right: %RelaxedBooleanNotOperator{}} =
               Transformer.transform(ast, %Context{})
    end

    test "relaxed boolean or" do
      code = "true || false"
      ast = ast(code)

      assert %RelaxedBooleanOrOperator{} = Transformer.transform(ast, %Context{})
    end

    test "strict boolean and" do
      code = "true and false"
      ast = ast(code)

      assert %StrictBooleanAndOperator{} = Transformer.transform(ast, %Context{})
    end

    test "subtraction" do
      code = "1 - 2"
      ast = ast(code)

      assert %SubtractionOperator{} = Transformer.transform(ast, %Context{})
    end

    test "type" do
      code = "str::binary"
      ast = ast(code)

      assert %TypeOperator{} = Transformer.transform(ast, %Context{})
    end

    test "unary negative" do
      code = "-2"
      ast = ast(code)

      assert %UnaryNegativeOperator{} = Transformer.transform(ast, %Context{})
    end

    test "unary positive" do
      code = "+2"
      ast = ast(code)

      assert %UnaryPositiveOperator{} = Transformer.transform(ast, %Context{})
    end
  end

  describe "types" do
    test "anonymous function" do
      code = "fn -> 1 end"
      ast = ast(code)

      assert %AnonymousFunctionType{} = Transformer.transform(ast, %Context{})
    end

    test "atom" do
      code = ":test"
      ast = ast(code)

      result = Transformer.transform(ast, %Context{})
      assert result == %AtomType{value: :test}
    end

    test "binary" do
      code = "<<1, 2>>"
      ast = ast(code)

      assert %BinaryType{} = Transformer.transform(ast, %Context{})
    end

    test "boolean" do
      code = "true"
      ast = ast(code)

      result = Transformer.transform(ast, %Context{})
      assert result == %BooleanType{value: true}
    end

    test "float" do
      code = "1.0"
      ast = ast(code)

      result = Transformer.transform(ast, %Context{})
      assert result == %FloatType{value: 1.0}
    end

    test "integer" do
      code = "1"
      ast = ast(code)

      result = Transformer.transform(ast, %Context{})
      assert result == %IntegerType{value: 1}
    end

    test "list" do
      code = "[1, 2]"
      ast = ast(code)

      assert %ListType{} = Transformer.transform(ast, %Context{})
    end

    test "map" do
      code = "%{a: 1, b: 2}"
      ast = ast(code)

      assert %MapType{} = Transformer.transform(ast, %Context{})
    end

    test "module from module segments" do
      code = "Hologram.Compiler.TransformerTest"
      ast = ast(code)

      assert %ModuleType{} = Transformer.transform(ast, %Context{})
    end

    test "module from atom" do
      module = Hologram.Compiler.TransformerTest
      assert %ModuleType{} = Transformer.transform(module, %Context{})
    end

    test "nil" do
      code = "nil"
      ast = ast(code)

      assert %NilType{} = Transformer.transform(ast, %Context{})
    end

    test "string" do
      code = "\"test\""
      ast = ast(code)

      result = Transformer.transform(ast, %Context{})
      assert result == %StringType{value: "test"}
    end

    test "struct" do
      code = "%Hologram.Test.Fixtures.Compiler.Transformer.Module2{a: 1}"
      ast = ast(code)

      assert %StructType{} = Transformer.transform(ast, %Context{})
    end

    test "tuple, 2 elements" do
      code = "{1, 2}"
      ast = ast(code)

      assert %TupleType{} = Transformer.transform(ast, %Context{})
    end

    test "tuple, non-2 elements" do
      code = "{1, 2, 3}"
      ast = ast(code)

      assert %TupleType{} = Transformer.transform(ast, %Context{})
    end

    test "nested" do
      code = "[1, {2, 3, 4}]"
      ast = ast(code)
      result = Transformer.transform(ast, %Context{})

      expected = %ListType{
        data: [
          %IntegerType{value: 1},
          %TupleType{
            data: [
              %IntegerType{value: 2},
              %IntegerType{value: 3},
              %IntegerType{value: 4}
            ]
          }
        ]
      }

      assert result == expected
    end
  end

  describe "definitions" do
    test "public function" do
      code = "def test, do: :ok"
      ast = ast(code)

      assert %FunctionDefinition{} = Transformer.transform(ast, %Context{})
    end

    test "private function" do
      code = "defp test, do: :ok"
      ast = ast(code)

      assert %FunctionDefinition{} = Transformer.transform(ast, %Context{})
    end

    test "macro" do
      code = """
      defmacro test_macro(a, b) do
        quote do
          1
        end
      end
      """

      ast = ast(code)

      assert %MacroDefinition{} = Transformer.transform(ast, %Context{})
    end

    test "module" do
      code = "defmodule Hologram.Test.Fixtures.Compiler.Transformer.Module1 do end"
      ast = ast(code)

      assert %ModuleDefinition{} = Transformer.transform(ast, %Context{})
    end

    test "module attribute" do
      code = "@a 1"
      ast = ast(code)

      assert %ModuleAttributeDefinition{} = Transformer.transform(ast, %Context{})
    end

    test "protocol" do
      code = """
      defprotocol Hologram.Test.Fixtures.PlaceholderModule1 do
        def test_fun(a, b)
      end
      """

      ast = ast(code)

      assert %ProtocolDefinition{} = Transformer.transform(ast, %Context{})
    end
  end

  describe "directives" do
    test "alias" do
      code = "alias Hologram.Test.Fixtures.Compiler.Transformer.Module1"
      ast = ast(code)

      assert %AliasDirective{} = Transformer.transform(ast, %Context{})
    end

    test "import" do
      code = "import Hologram.Test.Fixtures.Compiler.Transformer.Module1"
      ast = ast(code)

      assert %ImportDirective{} = Transformer.transform(ast, %Context{})
    end

    test "require" do
      code = "require Hologram.Test.Fixtures.Compiler.Transformer.Module1"
      ast = ast(code)

      assert %RequireDirective{} = Transformer.transform(ast, %Context{})
    end

    test "use" do
      code = "use Hologram.Compiler.TransformerTest"
      ast = ast(code)

      assert %UseDirective{} = Transformer.transform(ast, %Context{})
    end
  end

  describe "control flow" do
    test "anonymous function call" do
      code = "test.(123)"
      ast = ast(code)

      assert %AnonymousFunctionCall{} = Transformer.transform(ast, %Context{})
    end

    test "case expression" do
      code = """
      case x do
        %{a: a} -> :ok
        2 -> :error
      end
      """

      ast = ast(code)

      assert %CaseExpression{} = Transformer.transform(ast, %Context{})
    end

    test "for expression" do
      code = "for n <- [1, 2], do: n * n"
      ast = ast(code)

      assert %FunctionCall{module: Enum, function: :reduce} = Transformer.transform(ast, %Context{})
    end

    test "function called without a module" do
      code = "test(123)"
      ast = ast(code)

      assert %FunctionCall{} = Transformer.transform(ast, %Context{})
    end

    test "function called on module" do
      code = "Hologram.Compiler.TransformerTest.test(123)"
      ast = ast(code)

      assert %FunctionCall{} = Transformer.transform(ast, %Context{})
    end

    test "if expression" do
      code = "if true, do: 1, else: 2"
      ast = ast(code)

      assert %IfExpression{} = Transformer.transform(ast, %Context{})
    end
  end

  describe "other" do
    test "block" do
      ast = {:__block__, [], [1, 2]}
      assert %Block{} = Transformer.transform(ast, %Context{})
    end

    test "quote" do
      code = "quote do 1 end"
      ast = ast(code)

      assert %Quote{} = Transformer.transform(ast, %Context{})
    end

    test "typespec" do
      code = "@spec test_fun(atom()) :: list(integer())"
      ast = ast(code)

      assert %Typespec{} = Transformer.transform(ast, %Context{})
    end

    test "unquote" do
      code = "unquote(abc)"
      ast = ast(code)

      assert %Unquote{} = Transformer.transform(ast, %Context{})
    end

    test "__MODULE__ macro" do
      code = "__MODULE__"
      ast = ast(code)

      result = Transformer.transform(ast, %Context{})
      assert result == %ModulePseudoVariable{}
    end

    test "variable, last AST tuple elem is nil" do
      code = "a"
      ast = ast(code)

      result = Transformer.transform(ast, %Context{})
      assert result == %Variable{name: :a}
    end

    test "variable, last AST tuple elem is module" do
      ast = {:a, [line: 1], Hologram.Compiler.TransformerTest}

      result = Transformer.transform(ast, %Context{})
      assert result == %Variable{name: :a}
    end
  end
end


<|start_filename|>test/unit/hologram/template/embedded_expression_parser_test.exs<|end_filename|>
defmodule Hologram.Template.EmbeddedExpressionParserTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.Context
  alias Hologram.Compiler.IR.{AliasDirective, FunctionCall, ModuleAttributeOperator, TupleType}
  alias Hologram.Template.VDOM.{Expression, TextNode}
  alias Hologram.Template.EmbeddedExpressionParser

  @context %Context{}

  test "text" do
    str = "test"
    result = EmbeddedExpressionParser.parse(str, @context)

    assert result == [%TextNode{content: str}]
  end

  test "expression" do
    str = "{@abc}"
    result = EmbeddedExpressionParser.parse(str, @context)

    expected = [
      %Expression{
        ir: %TupleType{
          data: [%ModuleAttributeOperator{name: :abc}]
        }
      }
    ]

    assert result == expected
  end

  test "text, expression" do
    str = "bcd{@abc}"
    result = EmbeddedExpressionParser.parse(str, @context)

    expected = [
      %TextNode{content: "bcd"},
      %Expression{
        ir: %TupleType{
          data: [%ModuleAttributeOperator{name: :abc}]
        }
      }
    ]

    assert result == expected
  end

  test "expression, text" do
    str = "{@abc}bcd"
    result = EmbeddedExpressionParser.parse(str, @context)

    expected = [
      %Expression{
        ir: %TupleType{
          data: [%ModuleAttributeOperator{name: :abc}]
        }
      },
      %TextNode{content: "bcd"}
    ]

    assert result == expected
  end

  test "expression, expression" do
    str = "{@abc}{@bcd}"
    result = EmbeddedExpressionParser.parse(str, @context)

    expected = [
      %Expression{
        ir: %TupleType{
          data: [%ModuleAttributeOperator{name: :abc}]
        }
      },
      %Expression{
        ir: %TupleType{
          data: [%ModuleAttributeOperator{name: :bcd}]
        }
      }
    ]

    assert result == expected
  end

  test "text, expression, text" do
    str = "cde{@abc}bcd"
    result = EmbeddedExpressionParser.parse(str, @context)

    expected = [
      %TextNode{content: "cde"},
      %Expression{
        ir: %TupleType{
          data: [%ModuleAttributeOperator{name: :abc}]
        }
      },
      %TextNode{content: "bcd"}
    ]

    assert result == expected
  end

  test "expression, text, expression" do
    str = "{@abc}bcd{@cde}"
    result = EmbeddedExpressionParser.parse(str, @context)

    expected = [
      %Expression{
        ir: %TupleType{
          data: [%ModuleAttributeOperator{name: :abc}]
        }
      },
      %TextNode{content: "bcd"},
      %Expression{
        ir: %TupleType{
          data: [%ModuleAttributeOperator{name: :cde}]
        }
      }
    ]

    assert result == expected
  end

  test "text, expression, text, expression" do
    str = "cde{@abc}bcd{@def}"
    result = EmbeddedExpressionParser.parse(str, @context)

    expected = [
      %TextNode{content: "cde"},
      %Expression{
        ir: %TupleType{
          data: [%ModuleAttributeOperator{name: :abc}]
        }
      },
      %TextNode{content: "bcd"},
      %Expression{
        ir: %TupleType{
          data: [%ModuleAttributeOperator{name: :def}]
        }
      }
    ]

    assert result == expected
  end

  test "expression, text, expression, text" do
    str = "{@abc}bcd{@cde}def"
    result = EmbeddedExpressionParser.parse(str, @context)

    expected = [
      %Expression{
        ir: %TupleType{
          data: [%ModuleAttributeOperator{name: :abc}]
        }
      },
      %TextNode{content: "bcd"},
      %Expression{
        ir: %TupleType{
          data: [%ModuleAttributeOperator{name: :cde}]
        }
      },
      %TextNode{content: "def"}
    ]

    assert result == expected
  end

  test "context passing" do
    context = %Context{
      aliases: [
        %AliasDirective{
          module: Abc.Bcd.Module1,
          as: [:Module1]
        }
      ]
    }

    str = "{Module1.test_fun()}"
    result = EmbeddedExpressionParser.parse(str, context)

    expected = [
      %Expression{
        ir: %TupleType{
          data: [
            %FunctionCall{
              module: Abc.Bcd.Module1,
              function: :test_fun,
              args: []
            }
          ]
        }
      }
    ]

    assert result == expected
  end

  test "handles JS code" do
    str = """
    function isPositiveNumber(param) \\{
      if (param > 0) \\{
        return true;
      \\}
      if (param <= 0) \\{
        return false;
      \\}
    \\}
    """

    result = EmbeddedExpressionParser.parse(str, @context)
    expected = [%TextNode{content: str}]

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/require_directive_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.RequireDirectiveTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.RequireDirective
  alias Hologram.Compiler.RequireDirectiveTransformer

  test "transform/1" do
    code = "require Hologram.Test.Fixtures.Compiler.Transformer.Module1"
    ast = ast(code)

    result = RequireDirectiveTransformer.transform(ast)
    expected = %RequireDirective{module: Hologram.Test.Fixtures.Compiler.Transformer.Module1}

    assert result == expected
  end
end


<|start_filename|>assets/js/hologram/operation.js<|end_filename|>
"use strict";

import Keyword from "./elixir/keyword"
import Target from "./target";
import Type from "./type";
import Utils from "./utils";

export default class Operation {
  static get METHOD() {
    return {
      action: 0,
      command: 1
    }
  }

  static get SPEC_TYPE() {
    return {
      expression: 1,
      text: 0
    }
  }

  constructor(sourceId, target, name, params, method = null) {
    this.sourceId = sourceId
    this.target = target
    this.name = name
    this.params = params
    this.method = method
  }

  static build(operationSpec, sourceId, bindings, eventData) {
    const specElems = Operation.getSpecElems(operationSpec, bindings)

    const target = Operation.resolveTarget(specElems, sourceId)
    const name = Operation.buildName(specElems)
    const params = Operation.buildParams(specElems, eventData)
    const method = Operation.buildMethod(operationSpec)
    
    const operation = new Operation(sourceId, target, name, params, method)
    return Utils.freeze(operation)
  }

  static buildMethod(operationSpec) {
    if (operationSpec.modifiers.includes("command")) {
      return Operation.METHOD.command
    } else {
      return Operation.METHOD.action
    }
  }

  static buildName(specElems) {
    if (specElems.length === 1 || !Type.isAtom(specElems[1])) {
      return specElems[0]
    } else {
      return specElems[1]
    }
  }

  static buildParams(specElems, eventData) {
    let params;

    if (specElems.length === 3) {
      params = specElems[2]

    } else if (specElems.length === 2 && Type.isList(specElems[1])) {
      params = specElems[1]

    } else {
      params = Type.list()
    }

    params = Keyword.put(params, Type.atom("event"), eventData)
    return Type.keywordToMap(params)
  }

  static getSpecElems(operationSpec, bindings) {
    let elems;

    if (Operation.getSpecType(operationSpec) === Operation.SPEC_TYPE.text) {
      const value = operationSpec.value[0].content
      elems = [Type.atom(value)]
    } else { // expression
      elems = operationSpec.value[0].callback(bindings).data
    }

    return Utils.freeze(elems)
  }

  static getSpecType(operationSpec) {
    const node = operationSpec.value[0];

    if (node.type === "expression") {
      return Operation.SPEC_TYPE.expression
    } else {
      return Operation.SPEC_TYPE.text
    }
  }

  static getTargetSpecValue(specElems) {
    if (specElems.length === 1 || !Type.isAtom(specElems[1])) {
      return null
    }

    return specElems[0].value
  }

  static resolveTarget(specElems, sourceId) {
    const targetSpecValue = Operation.getTargetSpecValue(specElems)

    switch (targetSpecValue) {
      case null:
        return new Target(sourceId)

      case "layout":
        return new Target(Target.TYPE.layout)

      case "page":
        return new Target(Target.TYPE.page)

      default:
        return new Target(targetSpecValue);
    }
  }
}

<|start_filename|>test/unit/hologram/compiler/transformers/use_directive_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.UseDirectiveTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.UseDirective
  alias Hologram.Compiler.UseDirectiveTransformer

  @module Hologram.Compiler.UseDirectiveTransformerTest

  test "use directive without opts" do
    code = "use Hologram.Compiler.UseDirectiveTransformerTest"
    ast = ast(code)

    result = UseDirectiveTransformer.transform(ast)
    expected = %UseDirective{module: @module, opts: []}

    assert result == expected
  end

  test "use directive with opts" do
    code = "use Hologram.Compiler.UseDirectiveTransformerTest, a: 1, b: 2"
    ast = ast(code)

    result = UseDirectiveTransformer.transform(ast)

    opts = [a: 1, b: 2]
    expected = %UseDirective{module: @module, opts: opts}

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module2 do
  use Hologram.Page

  layout Hologram.Test.Fixtures.Compiler.Pruner.Module3

  route "/test-route-2"

  def template do
    ~H"""
    """
  end

  def action(:test_2a, _params, _state) do
    :ok
  end

  def action(:test_2b, _a, _b, _c) do
    :ok
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_52.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module52 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module53

  route "/test-route-52"

  def template do
    ~H"""
    """
  end

  def action do
    Module53
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/atom_type.ex<|end_filename|>
alias Hologram.Compiler.IR.AtomType
alias Hologram.Compiler.JSEncoder

defimpl JSEncoder, for: AtomType do
  use Hologram.Commons.Encoder

  def encode(%{value: value}, _, _) do
    encode_primitive_type(:atom, "'#{value}'")
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/type_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.TypeOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, TypeOperatorTransformer}
  alias Hologram.Compiler.IR.{TypeOperator, Variable}

  test "transform/2" do
    code = "str::binary"
    ast = ast(code)

    result = TypeOperatorTransformer.transform(ast, %Context{})

    expected = %TypeOperator{
      left: %Variable{name: :str},
      right: :binary
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_17.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module17 do
  use Hologram.Layout

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/macro_definition_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.MacroDefinitionTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, MacroDefinitionTransformer}

  alias Hologram.Compiler.IR.{
    AtomType,
    Binding,
    Block,
    IntegerType,
    MacroDefinition,
    MapAccess,
    ParamAccess,
    Variable
  }

  @context %Context{module: Abc}

  describe "transform/2" do
    test "module" do
      code = "defmacro test(1, 2) do end"
      ast = ast(code)

      assert %MacroDefinition{module: Abc} = MacroDefinitionTransformer.transform(ast, @context)
    end

    test "name" do
      code = "defmacro test(1, 2) do end"
      ast = ast(code)

      assert %MacroDefinition{name: :test} = MacroDefinitionTransformer.transform(ast, @context)
    end

    test "arity" do
      code = "defmacro test(1, 2) do end"
      ast = ast(code)

      assert %MacroDefinition{arity: 2} = MacroDefinitionTransformer.transform(ast, @context)
    end

    test "params" do
      code = "defmacro test(a, b) do end"
      ast = ast(code)

      assert %MacroDefinition{} = result = MacroDefinitionTransformer.transform(ast, @context)

      expected = [
        %Variable{name: :a},
        %Variable{name: :b}
      ]

      assert result.params == expected
    end

    test "bindings" do
      code = "defmacro test(1, %{a: x, b: y}) do end"
      ast = ast(code)

      assert %MacroDefinition{} = result = MacroDefinitionTransformer.transform(ast, @context)

      expected = [
        %Binding{
          name: :x,
          access_path: [
            %ParamAccess{index: 1},
            %MapAccess{key: %AtomType{value: :a}}
          ]
        },
        %Binding{
          name: :y,
          access_path: [
            %ParamAccess{index: 1},
            %MapAccess{key: %AtomType{value: :b}}
          ]
        }
      ]

      assert result.bindings == expected
    end

    test "body, single expression" do
      code = """
      defmacro test do
        1
      end
      """

      ast = ast(code)

      assert %MacroDefinition{} = result = MacroDefinitionTransformer.transform(ast, @context)

      expected = %Block{expressions: [
        %IntegerType{value: 1}
      ]}

      assert result.body == expected
    end

    test "body, multiple expressions" do
      code = """
      defmacro test do
        1
        2
      end
      """

      ast = ast(code)

      assert %MacroDefinition{} = result = MacroDefinitionTransformer.transform(ast, @context)

      expected = %Block{expressions: [
        %IntegerType{value: 1},
        %IntegerType{value: 2}
      ]}

      assert result.body == expected
    end
  end
end


<|start_filename|>test/unit/hologram/compiler/pattern_deconstructors/map_type_test.exs<|end_filename|>
defmodule Hologram.Compiler.PatternDeconstructor.MapTypeTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.IR.AtomType
  alias Hologram.Compiler.IR.IntegerType
  alias Hologram.Compiler.IR.MapAccess
  alias Hologram.Compiler.IR.MapType
  alias Hologram.Compiler.IR.Variable
  alias Hologram.Compiler.PatternDeconstructor

  test "non-nested map without vars" do
    ir = %MapType{
      data: [
        {%AtomType{value: :a}, %IntegerType{value: 1}},
        {%AtomType{value: :b}, %IntegerType{value: 2}}
      ]
    }

    assert PatternDeconstructor.deconstruct(ir) == []
  end

  test "non-nested map with single var" do
    ir = %MapType{
      data: [
        {%AtomType{value: :a}, %IntegerType{value: 1}},
        {%AtomType{value: :b}, %Variable{name: :x}}
      ]
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %MapAccess{
          key: %AtomType{value: :b}
        },
        %Variable{name: :x}
      ]
    ]

    assert result == expected
  end

  test "non-nested map with multiple vars" do
    ir = %MapType{
      data: [
        {%AtomType{value: :a}, %Variable{name: :x}},
        {%AtomType{value: :b}, %IntegerType{value: 2}},
        {%AtomType{value: :c}, %Variable{name: :y}}
      ]
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %MapAccess{
          key: %AtomType{value: :a}
        },
        %Variable{name: :x}
      ],
      [
        %MapAccess{
          key: %AtomType{value: :c}
        },
        %Variable{name: :y}
      ]
    ]

    assert result == expected
  end

  test "nested map without vars" do
    ir = %MapType{
      data: [
        {%AtomType{value: :a}, %IntegerType{value: 1}},
        {%AtomType{value: :b},
          %MapType{
            data: [
              {%AtomType{value: :c}, %IntegerType{value: 3}},
              {%AtomType{value: :d}, %IntegerType{value: 4}}
            ]
          }}
      ]
    }

    assert PatternDeconstructor.deconstruct(ir) == []
  end

  test "nested map with single var" do
    ir = %MapType{
      data: [
        {%AtomType{value: :a}, %IntegerType{value: 1}},
        {%AtomType{value: :b},
          %MapType{
            data: [
              {%AtomType{value: :c}, %Variable{name: :x}},
              {%AtomType{value: :d}, %IntegerType{value: 4}}
            ]
          }}
      ]
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %MapAccess{
          key: %AtomType{value: :b}
        },
        %MapAccess{
          key: %AtomType{value: :c}
        },
        %Variable{name: :x}
      ]
    ]

    assert result == expected
  end

  test "nested map with multiple vars" do
    ir = %MapType{
      data: [
        {%AtomType{value: :a}, %IntegerType{value: 1}},
        {%AtomType{value: :b}, %Variable{name: :x}},
        {%AtomType{value: :c},
          %MapType{
            data: [
              {%AtomType{value: :d}, %Variable{name: :y}},
              {%AtomType{value: :e}, %IntegerType{value: 4}}
            ]
          }}
      ]
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %MapAccess{
          key: %AtomType{value: :b}
        },
        %Variable{name: :x}
      ],
      [
        %MapAccess{
          key: %AtomType{value: :c}
        },
        %MapAccess{
          key: %AtomType{value: :d}
        },
        %Variable{name: :y}
      ]
    ]

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/quote_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.QuoteTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, QuoteTransformer}
  alias Hologram.Compiler.IR.{Block, IntegerType, Quote}

  test "transform/2" do
    code = """
    quote do
      1
      2
    end
    """

    ast = ast(code)

    result = QuoteTransformer.transform(ast, %Context{})

    expected = %Quote{
      body: %Block{expressions: [
        %IntegerType{value: 1},
        %IntegerType{value: 2}
      ]}
    }

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/elixir_list.ex<|end_filename|>
alias Hologram.Compiler.CallGraphBuilder

defimpl CallGraphBuilder, for: List do
  def build(list, module_defs, templates, from_vertex) do
    Enum.each(list, &CallGraphBuilder.build(&1, module_defs, templates, from_vertex))
  end
end


<|start_filename|>lib/hologram/compiler/map_key_encoders/string_type.ex<|end_filename|>
alias Hologram.Compiler.IR.StringType
alias Hologram.Compiler.MapKeyEncoder

defimpl MapKeyEncoder, for: StringType do
  use Hologram.Commons.Encoder

  def encode(%{value: value}, _, _) do
    encode_primitive_key(:string, value)
  end
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/function_call/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.FunctionCall.Module1 do
  alias Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.FunctionCall.Module2

  def test_fun_1a do
    Module2.test_fun_2a()
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/binding_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.BindingTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Config, Context, JSEncoder, Opts}

  alias Hologram.Compiler.IR.{
    AtomType,
    Binding,
    CaseConditionAccess,
    ListIndexAccess,
    ListTailAccess,
    MapAccess,
    MatchAccess,
    ParamAccess,
    TupleAccess
  }

  @case_condition_js Config.case_condition_js()
  @match_access_js Config.match_access_js()

  test "case condition access" do
    ir = %Binding{name: :abc, access_path: [%CaseConditionAccess{}]}

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "let abc = #{@case_condition_js};"

    assert result == expected
  end

  test "list index access" do
    ir = %Binding{name: :abc, access_path: [%ParamAccess{index: 0}, %ListIndexAccess{index: 2}]}

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "let abc = arguments[0].data[2];"

    assert result == expected
  end

  test "list tail access" do
    ir = %Binding{name: :abc, access_path: [%ParamAccess{index: 0}, %ListTailAccess{}]}

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "let abc = Elixir_Kernel.tl(arguments[0]);"

    assert result == expected
  end

  test "map access" do
    ir = %Binding{name: :abc, access_path: [%ParamAccess{index: 0}, %MapAccess{key: %AtomType{value: :x}}]}

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "let abc = arguments[0].data['~atom[x]'];"

    assert result == expected
  end

  test "match access" do
    ir = %Binding{name: :abc, access_path: [%MatchAccess{}]}

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "let abc = #{Config.match_access_js()};"

    assert result == expected
  end

  test "param access" do
    ir = %Binding{name: :abc, access_path: [%ParamAccess{index: 2}]}

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "let abc = arguments[2];"

    assert result == expected
  end

  test "tuple access" do
    ir = %Binding{name: :abc, access_path: [%ParamAccess{index: 0}, %TupleAccess{index: 2}]}

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "let abc = arguments[0].data[2];"

    assert result == expected
  end

  test "multiple parts" do
    ir = %Binding{
      name: :abc,
      access_path: [
        %ParamAccess{index: 2},
        %MapAccess{key: %AtomType{value: :x}}
      ]
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "let abc = arguments[2].data['~atom[x]'];"

    assert result == expected
  end

  test "binding isn't in the block scope yet" do
    ir = %Binding{name: :abc, access_path: [%MatchAccess{}]}

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "let abc = #{@match_access_js};"

    assert result == expected
  end

  test "binding is already in the block scope" do
    ir = %Binding{name: :abc, access_path: [%MatchAccess{}]}

    context = %Context{block_bindings: [:abc]}
    result = JSEncoder.encode(ir, context, %Opts{})

    expected = "abc = #{@match_access_js};"

    assert result == expected
  end
end


<|start_filename|>test/e2e/test/components_test.exs<|end_filename|>
defmodule HologramE2E.ComponentsTest do
  use HologramE2E.TestCase, async: false

  # TODO: refactor
  # feature "component with nested slots", %{session: session} do
  #   session
  #   |> visit("/e2e/page-3")
  #   |> click(css("#update-button"))

  #   page_source = page_source(session)

  #   assert page_source =~ "in page template: abc"
  #   assert page_source =~ "in component 1 template header"
  #   assert page_source =~ "in component 1 slot: bcd"
  #   assert page_source =~ "in component 2 template header"
  #   assert page_source =~ "in component 2 slot: xyz"
  #   assert page_source =~ "in component 2 template footer"
  #   assert page_source =~ "in component 1 template footer"
  # end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/if_expression_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.IfExpressionTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}
  alias Hologram.Compiler.IR.{Block, BooleanType, IfExpression, IntegerType}

  test "encode/3" do
    ir = %IfExpression{
      condition: %BooleanType{value: true},
      do: %Block{expressions: [%IntegerType{value: 1}]},
      else: %Block{expressions: [%IntegerType{value: 2}]}
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    Hologram.Interpreter.$if_expression(() => {
    return { type: 'boolean', value: true };
    }, () => {
    return { type: 'integer', value: 1 };
    }, () => {
    return { type: 'integer', value: 2 };
    })\
    """

    assert result == expected
  end
end


<|start_filename|>lib/hologram/template/evaluators/module_attribute_operator.ex<|end_filename|>
alias Hologram.Compiler.IR.ModuleAttributeOperator
alias Hologram.Template.Evaluator

defimpl Evaluator, for: ModuleAttributeOperator do
  def evaluate(%{name: name}, bindings) do
    # DEFER: raise custom Hologram error here, see: https://github.com/segmetric/hologram/issues/27
    Map.fetch!(bindings, name)
  end
end


<|start_filename|>test/unit/fixtures/template/renderers/node_list_renderer/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.NodeListRenderer.Module1 do
  use Hologram.Component

  def init(_props) do
    %{
      component_1_state_key: "component_1_state_value"
    }
  end

  def template do
    ~H"""
    (in component 1)
    """
  end
end


<|start_filename|>lib/hologram/compiler/decoders/module_type_decoder.ex<|end_filename|>
defmodule Hologram.Compiler.ModuleTypeDecoder do
  def decode(%{"className" => class_name}) do
    String.replace(class_name, "_", ".")
    |> String.to_existing_atom()
  end
end


<|start_filename|>lib/hologram/runtime/layout.ex<|end_filename|>
defmodule Hologram.Layout do
  defmacro __using__(_) do
    quote do
      import Hologram.Layout
      import Hologram.Router, only: [static_path: 1]
      import Hologram.Runtime.Commons, only: [sigil_H: 2]
      import Map, only: [put: 3]

      alias Hologram.Runtime.JS
      alias Hologram.UI.Link

      def is_layout?, do: true
    end
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/function_definition.ex<|end_filename|>
alias Hologram.Compiler.{CallGraph, CallGraphBuilder}
alias Hologram.Compiler.IR.FunctionDefinition

defimpl CallGraphBuilder, for: FunctionDefinition do
  def build(%{module: module, name: name, body: body}, module_defs, templates, _) do
    CallGraph.add_vertex({module, name})
    CallGraphBuilder.build(body, module_defs, templates, {module, name})
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/anonymous_function_type_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.AnonymousFunctionTypeTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{AnonymousFunctionTypeTransformer, Context}

  alias Hologram.Compiler.IR.{
    AnonymousFunctionType,
    AtomType,
    Binding,
    Block,
    IntegerType,
    MapAccess,
    NotSupportedExpression,
    ParamAccess,
    Variable
  }

  test "arity" do
    code = "fn 1, 2 -> 9 end"
    ast = ast(code)

    assert %AnonymousFunctionType{arity: 2} =
             AnonymousFunctionTypeTransformer.transform(ast, %Context{})
  end

  test "params" do
    code = "fn a, b -> 9 end"
    ast = ast(code)

    assert %AnonymousFunctionType{} =
             result = AnonymousFunctionTypeTransformer.transform(ast, %Context{})

    expected = [
      %Variable{name: :a},
      %Variable{name: :b}
    ]

    assert result.params == expected
  end

  test "bindings" do
    code = "fn 1, %{a: x, b: y} -> 9 end"
    ast = ast(code)

    assert %AnonymousFunctionType{} =
             result = AnonymousFunctionTypeTransformer.transform(ast, %Context{})

    expected = [
      %Binding{
        name: :x,
        access_path: [
          %ParamAccess{index: 1},
          %MapAccess{key: %AtomType{value: :a}}
        ]
      },
      %Binding{
        name: :y,
        access_path: [
          %ParamAccess{index: 1},
          %MapAccess{key: %AtomType{value: :b}}
        ]
      }
    ]

    assert result.bindings == expected
  end

  test "body, single expression" do
    code = "fn -> 1 end"
    ast = ast(code)

    assert %AnonymousFunctionType{} =
             result = AnonymousFunctionTypeTransformer.transform(ast, %Context{})

    assert result.body == %Block{expressions: [%IntegerType{value: 1}]}
  end

  test "body, multiple expressions" do
    code = """
    fn ->
      1
      2
    end
    """

    ast = ast(code)

    assert %AnonymousFunctionType{} =
             result = AnonymousFunctionTypeTransformer.transform(ast, %Context{})

    expected = %Block{
      expressions: [
        %IntegerType{value: 1},
        %IntegerType{value: 2}
      ]
    }

    assert result.body == expected
  end

  # TODO: implement anonymous functions with multiple clauses
  test "multiple clauses" do
    code = """
    fn
      1 -> :a
      2 -> :b
    end
    """

    ast = ast(code)
    result = AnonymousFunctionTypeTransformer.transform(ast, %Context{})
    expected = %NotSupportedExpression{ast: ast, type: :multi_clause_anonymous_function_type}

    assert result ==  expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/module_type/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.ModuleType.Module2 do
end


<|start_filename|>lib/hologram/compiler/transformers/module_type_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.ModuleTypeTransformer do
  alias Hologram.Compiler.{Context, Resolver}
  alias Hologram.Compiler.IR.ModuleType

  def transform({:__aliases__, _, module_segs}, %Context{} = context) do
    module = Resolver.resolve(module_segs, context)
    %ModuleType{module: module}
  end

  def transform(module, _), do: %ModuleType{module: module}
end


<|start_filename|>config/config.exs<|end_filename|>
import Config

config :hologram,
  otp_app: :hologram,
  app_path: "#{File.cwd!()}/test/unit/fixtures/app",
  default_layout: Hologram.Test.Fixtures.App.DefaultLayout,
  env: config_env(),
  ignored_namespaces: [
    Hologram.Commons,
    Hologram.Compiler,
    Hologram.Template
  ]

config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id]

config :phoenix, :json_library, Jason

import_config "#{config_env()}.exs"

Application.ensure_all_started(:logger)
logger_config = Application.fetch_env!(:logger, :console)
Logger.configure_backend(:console, logger_config)


<|start_filename|>lib/hologram/template/renderers/page.ex<|end_filename|>
alias Hologram.Compiler.{Helpers, Serializer}
alias Hologram.Runtime.{PageDigestStore, TemplateStore}
alias Hologram.Template.Renderer

defimpl Renderer, for: Atom do
  @state_placeholder "###STATE###"

  def render(page_module, conn, _bindings, _slots) do
    context = init_context(page_module)

    layout_module = page_module.layout()
    layout_template = TemplateStore.get!(layout_module)
    layout_state = layout_module.init(conn)
    layout_bindings = layout_state |> put_context(context)

    page_template = TemplateStore.get!(page_module)
    page_state = page_module.init(conn.params, conn)
    page_bindings = page_state |> put_context(context)
    slots = {page_bindings, default: page_template}

    {html, initial_state} = Renderer.render(layout_template, conn, layout_bindings, slots)

    initial_state =
      initial_state
      |> Map.put(:layout, layout_state)
      |> Map.put(:page, page_state)

    pattern = "state: #{@state_placeholder}"
    serialized_state = Serializer.serialize(initial_state)
    replacement = "state: #{serialized_state}"

    """
    <!DOCTYPE html>
    #{html}\
    """
    |> String.replace(pattern, replacement)
  end

  defp init_context(page_module) do
    %{
      __class__: Helpers.class_name(page_module),
      __digest__: PageDigestStore.get!(page_module),
      __state__: @state_placeholder
    }
  end

  defp put_context(bindings, context) do
    Map.put(bindings, :__context__, context)
  end
end


<|start_filename|>test/unit/hologram/compiler/formatter_test.exs<|end_filename|>
defmodule Hologram.Compiler.FormatterTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Compiler.Formatter

  test "append/2" do
    result = Formatter.append("part1", "part2")
    assert result == "part1part2"
  end

  test "append_line_break/1" do
    result = Formatter.append_line_break("abc")
    assert result == "abc\n"
  end

  describe "maybe_append_new_expression/2" do
    test "empty string appended" do
      result = Formatter.maybe_append_new_expression("abc", "")
      assert result == "abc"
    end

    test "non-empty string appended to a string ending with a space" do
      result = Formatter.maybe_append_new_expression("abc ", "xyz")
      assert result == "abc xyz"
    end

    test "non-empty string appended to a string not ending with a space" do
      result = Formatter.maybe_append_new_expression("abc", "xyz")
      assert result == "abc xyz"
    end
  end

  describe "maybe_append_new_line/2" do
    test "empty string appended" do
      result = Formatter.maybe_append_new_line("abc", "")
      assert result == "abc"
    end

    test "non-empty string appended to a string ending with a newline" do
      result = Formatter.maybe_append_new_line("abc\n", "xyz")
      assert result == "abc\nxyz"
    end

    test "non-empty string appended to a string not ending with a newline" do
      result = Formatter.maybe_append_new_line("abc", "xyz")
      assert result == "abc\nxyz"
    end
  end

  describe "maybe_append_new_section/2" do
    test "empty string appended" do
      result = Formatter.maybe_append_new_section("abc", "")
      assert result == "abc"
    end

    test "non-empty string appended to a string ending with a double newline" do
      result = Formatter.maybe_append_new_section("abc\n\n", "xyz")
      assert result == "abc\n\nxyz"
    end

    test "non-empty string appended to a string not ending with a double newline" do
      result = Formatter.maybe_append_new_section("abc", "xyz")
      assert result == "abc\n\nxyz"
    end
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/string_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.StringType

defimpl CallGraphBuilder, for: StringType do
  def build(_, _, _, _), do: nil
end


<|start_filename|>test/unit/hologram/template/evaluators/nil_type_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.NilTypeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.NilType
  alias Hologram.Template.Evaluator

  test "evaluate/2" do
    ir = %NilType{}

    result = Evaluator.evaluate(ir, %{})
    expected = nil

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/evaluator.ex<|end_filename|>
defmodule Hologram.Compiler.Evaluator do
  def evaluate(ast, bindings) do
    bindings =
      bindings
      |> Map.put(:bindings, bindings)
      |> Enum.map(fn {key, value} -> {:"hologram_#{key}__", value} end)

    replace_module_attributes(ast)
    |> Code.eval_quoted(bindings)
    |> elem(0)
  end

  defp replace_module_attributes({:@, metadata, [{name, _, _}]}) do
    {:"hologram_#{name}__", metadata, nil}
  end

  defp replace_module_attributes(ast) when is_list(ast) do
    Enum.map(ast, &replace_module_attributes/1)
  end

  defp replace_module_attributes(ast) when is_tuple(ast) do
    Tuple.to_list(ast)
    |> Enum.map(&replace_module_attributes/1)
    |> List.to_tuple()
  end

  defp replace_module_attributes(ast), do: ast
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/page_2.ex<|end_filename|>
defmodule HologramE2E.Page2 do
  use Hologram.Page

  route "/e2e/page-2"

  def init(_params, _conn) do
    %{
      text: ""
    }
  end

  def template do
    ~H"""
    <h1>Page 2</h1>

    <button id="page-2-update-text-button" on:click="update_text">Update text</button>
    <button id="page-2-back-button">Back</button>
    <br />

    <div id="page-2-text">{@text}</div><br />

    <script>
      document.getElementById("page-2-back-button")
        .addEventListener("click", () => \{ history.back() \})
    </script>
    """
  end

  def action(:update_text, _params, state) do
    put(state, :text, "text updated by page 2 update button")
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/boolean_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.BooleanType
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: BooleanType do
  def aggregate(_), do: nil
end


<|start_filename|>lib/hologram/runtime/socket.ex<|end_filename|>
defmodule Hologram.Runtime.Socket do
  use Phoenix.Socket

  channel("hologram", Hologram.Runtime.Channel)

  @impl true
  def connect(_, socket, _) do
    {:ok, socket}
  end

  @impl true
  def id(_), do: nil
end


<|start_filename|>test/unit/hologram/template/evaluators/list_type_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.ListTypeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.{IntegerType, ListType}
  alias Hologram.Template.Evaluator

  test "evaluate/2" do
    ir = %ListType{
      data: [
        %IntegerType{value: 1},
        %IntegerType{value: 2}
      ]
    }

    result = Evaluator.evaluate(ir, %{})
    expected = [1, 2]

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/module_definition_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.ModuleDefinitionTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}

  alias Hologram.Compiler.IR.{
    AtomType,
    Binding,
    Block,
    FunctionDefinition,
    IntegerType,
    ModuleAttributeDefinition,
    ModuleDefinition,
    NotSupportedExpression,
    ParamAccess,
    Variable
  }

  @module Abc.Bcd

  # this case is not possible, because such module would be pruned:
  # test "empty module"

  describe "attributes" do
    test "single attribute" do
      ir = %ModuleDefinition{
        module: @module,
        attributes: [
          %ModuleAttributeDefinition{
            name: :abc,
            value: %IntegerType{value: 123}
          }
        ]
      }

      result = JSEncoder.encode(ir, %Context{}, %Opts{})

      expected = """
      window.Elixir_Abc_Bcd = class Elixir_Abc_Bcd {

      static $abc = { type: 'integer', value: 123 };
      }
      """

      assert result == expected
    end

    test "multiple attributes" do
      ir = %ModuleDefinition{
        module: @module,
        attributes: [
          %ModuleAttributeDefinition{
            name: :abc,
            value: %IntegerType{value: 123}
          },
          %ModuleAttributeDefinition{
            name: :bcd,
            value: %AtomType{value: :bcd_value}
          }
        ]
      }

      result = JSEncoder.encode(ir, %Context{}, %Opts{})

      expected = """
      window.Elixir_Abc_Bcd = class Elixir_Abc_Bcd {

      static $abc = { type: 'integer', value: 123 };
      static $bcd = { type: 'atom', value: 'bcd_value' };
      }
      """

      assert result == expected
    end

    test "behaviour callback spec" do
      ir = %ModuleDefinition{
        module: @module,
        attributes: [%NotSupportedExpression{type: :behaviour_callback_spec}]
      }

      result = JSEncoder.encode(ir, %Context{}, %Opts{})
      expected = "window.Elixir_Abc_Bcd = class Elixir_Abc_Bcd {\n}\n"

      assert result == expected
    end
  end

  describe "functions" do
    test "not preceded by attributes section" do
      # code:
      #
      # defmodule Abc.Bcd do
      #   def test(a) do
      #     1
      #   end
      # end

      ir = %ModuleDefinition{
        module: @module,
        attributes: [],
        functions: [
          %FunctionDefinition{
            bindings: [
              %Binding{
                name: :a,
                access_path: [%ParamAccess{index: 0}]
              }
            ],
            body: %Block{expressions: [
              %IntegerType{value: 1}
            ]},
            name: :test,
            params: [
              %Variable{name: :a}
            ]
          }
        ]
      }

      result = JSEncoder.encode(ir, %Context{}, %Opts{})

      expected = """
      window.Elixir_Abc_Bcd = class Elixir_Abc_Bcd {

      static test() {
      if (Hologram.Interpreter.isFunctionArgsPatternMatched([ { type: 'placeholder' } ], arguments)) {
      let a = arguments[0];
      return { type: 'integer', value: 1 };
      }
      else {
      console.debug(arguments)
      throw 'No match for the function call'
      }
      }
      }
      """

      assert result == expected
    end

    test "preceded by attributes section" do
      # code:
      #
      # defmodule Abc.Bcd do
      #   @abc 123
      #
      #   def test(a) do
      #     1
      #   end
      # end

      ir = %ModuleDefinition{
        module: @module,
        attributes: [
          %ModuleAttributeDefinition{
            name: :abc,
            value: %IntegerType{value: 123}
          }
        ],
        functions: [
          %FunctionDefinition{
            bindings: [
              %Binding{
                name: :a,
                access_path: [%ParamAccess{index: 0}]
              }
            ],
            body: %Block{expressions: [
              %IntegerType{value: 1}
            ]},
            name: :test,
            params: [
              %Variable{name: :a}
            ]
          }
        ]
      }

      result = JSEncoder.encode(ir, %Context{}, %Opts{})

      expected = """
      window.Elixir_Abc_Bcd = class Elixir_Abc_Bcd {

      static $abc = { type: 'integer', value: 123 };

      static test() {
      if (Hologram.Interpreter.isFunctionArgsPatternMatched([ { type: 'placeholder' } ], arguments)) {
      let a = arguments[0];
      return { type: 'integer', value: 1 };
      }
      else {
      console.debug(arguments)
      throw 'No match for the function call'
      }
      }
      }
      """

      assert result == expected
    end
  end
end


<|start_filename|>test/unit/hologram/compiler/map_key_encoders/string_type_test.exs<|end_filename|>
defmodule Hologram.Compiler.MapKeyEncoder.StringTypeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, MapKeyEncoder, Opts}
  alias Hologram.Compiler.IR.StringType

  test "encode/3" do
    ir = %StringType{value: "test"}

    result = MapKeyEncoder.encode(ir, %Context{}, %Opts{})
    expected = "~string[test]"

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/reflection/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Reflection.Module2 do
  defmacro test_macro(a) do
    quote do
      unquote(a)
    end
  end

  defmacro test_macro(a, b) do
    quote do
      unquote(a) + unquote(b)
    end
  end

  defmacro test_macro(a, b, c) do
    quote do
      unquote(a) + unquote(b) + unquote(c)
    end
  end
end


<|start_filename|>mix.exs<|end_filename|>
defmodule Hologram.MixProject do
  use Mix.Project

  def project do
    [
      aliases: aliases(),
      app: :hologram,
      compilers: Mix.compilers(),
      deps: deps(),
      description:
        "Full stack isomorphic Elixir web framework that can be used on top of Phoenix.",
      elixir: "~> 1.0",
      elixirc_paths: elixirc_paths(Mix.env()),
      package: package(),
      preferred_cli_env: preferred_cli_env(),
      start_permanent: Mix.env() == :prod,
      test_paths: ["test/unit"],
      version: "0.1.0"
    ]
  end

  defp aliases do
    [
      "format.all": [
        "format",
        "cmd npx prettier --write 'assets/**/*.js' 'test/js/**/*.js' '**/*.json'"
      ],
      "test.all": [&test_js/1, "test", "test.e2e"],
      "test.e2e": ["cmd cd test/e2e && mix test"],
      "test.js": [&test_js/1]
    ]
  end

  def application do
    if is_dep?() do
      [
        mod: {Hologram.Runtime.Application, []},
        extra_applications: [:logger]
      ]
    else
      []
    end
  end

  defp deps do
    [
      {:file_system, "~> 0.2"},
      {:jason, "~> 1.0"},
      {:libgraph, "~> 0.13"},
      {:phoenix, "~> 1.6"}
    ]
  end

  defp elixirc_paths(:test), do: ["lib", "test/unit/fixtures", "test/unit/support"]
  defp elixirc_paths(_), do: ["lib"]

  def is_dep? do
    __MODULE__.module_info()[:compile][:source]
    |> to_string()
    |> String.ends_with?("/deps/hologram/mix.exs")
  end

  def package do
    [
      files: ["lib", "mix.exs", "README.md"],
      licenses: ["MIT"],
      links: %{"GitHub" => "https://github.com/bartblast/hologram"},
      maintainers: ["<NAME>last"]
    ]
  end

  defp preferred_cli_env do
    [
      "test.all": :test,
      "test.e2e": :test,
      "test.js": :test
    ]
  end

  defp test_js(args) do
    cmd =
      if Enum.empty?(args) do
        ["test"]
      else
        ["run", "test-file", "../#{hd(args)}"]
      end

    opts = [cd: "assets", into: IO.stream(:stdio, :line)]
    System.cmd("npm", ["install"], opts)
    {_, status} = System.cmd("npm", cmd, opts)

    if status > 0 do
      Mix.raise("JavaScript tests failed!")
    end
  end
end


<|start_filename|>assets/js/hologram/events/submit_event.js<|end_filename|>
"use strict";

import Map from "../elixir/map"
import Type from "../type"

export default class SubmitEvent {
  // TODO: test
  static buildEventData(event, _tag) {
    const formData = new FormData(event.target)
    let params = Type.map()

    for (let el of formData.entries()) {
      params = Map.put(params, Type.atom(el[0]), Type.string(el[1]))
    }

    return params
  }

  // DEFER: test
  static shouldHandleEvent(_event) {
    return true
  } 
}

<|start_filename|>lib/hologram/compiler/module_def_aggregators/relaxed_boolean_not_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.RelaxedBooleanNotOperator
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: RelaxedBooleanNotOperator do
  def aggregate(%{value: value}) do
    ModuleDefAggregator.aggregate(value)
  end
end


<|start_filename|>lib/hologram/compiler/transformers/module_definiton_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.ModuleDefinitionTransformer do
  alias Hologram.Compiler.{Context, Expander, Helpers, Reflection, Transformer}
  alias Hologram.Compiler.IR.{FunctionHead, ModuleDefinition}

  def transform(ast) do
    exprs = fetch_module_body(ast)
    uses = aggregate_expressions(:use, exprs, %Context{})

    ast = Expander.expand_use_directives(ast)
    exprs = fetch_module_body(ast)
    requires = aggregate_expressions(:require, exprs, %Context{})

    Expander.expand_macros(ast, requires)
    |> Expander.expand_module_pseudo_variable()
    |> build_module(uses, requires)
  end

  defp aggregate_expressions(type, exprs, context) do
    Enum.reduce(exprs, [], fn expr, acc ->
      case expr do
        {^type, _, _} ->
          ir = Transformer.transform(expr, context)
          # multi-alias is returned as a list of alias structs
          # DEFER: always return a list of alias structs
          ir = if is_list(ir), do: ir, else: [ir]
          acc ++ ir

        _ ->
          acc
      end
    end)
  end

  defp build_module(ast, uses, requires) do
    {:defmodule, _, [{:__aliases__, _, module_segs}, [do: {:__block__, _, exprs}]]} = ast

    module = Helpers.module(module_segs)
    imports = aggregate_expressions(:import, exprs, %Context{})
    aliases = aggregate_expressions(:alias, exprs, %Context{})
    attributes = aggregate_expressions(:@, exprs, %Context{})
    macros = aggregate_expressions(:defmacro, exprs, %Context{module: module})
    module_type_fields = determine_module_type_fields(uses)

    context = %Context{
      module: module,
      uses: uses,
      imports: imports,
      requires: requires,
      aliases: aliases,
      attributes: attributes
    }

    defs = aggregate_expressions(:def, exprs, context)
    defps = aggregate_expressions(:defp, exprs, context)

    functions =
      (defs ++ defps)
      |> Enum.reject(&(&1.__struct__ == FunctionHead))
      |> inject_module_info_callback(context)

    fields =
      Map.from_struct(context)
      |> Map.put(:functions, functions)
      |> Map.put(:macros, macros)
      |> Map.merge(module_type_fields)

    struct(ModuleDefinition, fields)
  end

  defp fetch_module_body(ast) do
    {:defmodule, _, [_, [do: {:__block__, _, exprs}]]} = ast
    exprs
  end

  defp inject_module_info_callback(functions, context) do
    name_arity_pairs =
      Enum.map(functions, &"#{&1.name}: #{&1.arity}")
      |> Enum.uniq()

    ir =
      "def __info__(:functions), do: [#{Enum.join(name_arity_pairs, ", ")}]"
      |> Reflection.ir(context)

    [ir | functions]
  end

  defp determine_module_type_fields(uses) do
    fields = %{
      component?: false,
      layout?: false,
      page?: false,
      templatable?: false
    }

    cond do
      Enum.any?(uses, &(&1.module == Hologram.Component)) ->
        Map.merge(fields, %{component?: true, templatable?: true})

      Enum.any?(uses, &(&1.module == Hologram.Layout)) ->
        Map.merge(fields, %{layout?: true, templatable?: true})

      Enum.any?(uses, &(&1.module == Hologram.Page)) ->
        Map.merge(fields, %{page?: true, templatable?: true})

      true ->
        fields
    end
  end
end


<|start_filename|>test/unit/fixtures/compiler/call_graph_builder/module_definition/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.CallGraphBuilder.ModuleDefinition.Module1 do
  use Hologram.Page

  layout Hologram.Test.Fixtures.Compiler.CallGraphBuilder.ModuleDefinition.Module2

  route "/test-route-1"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/nil_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.NilType
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: NilType do
  def aggregate(_), do: nil
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/function_definition_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.FunctionDefinitionTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}
  alias Hologram.Compiler.IR.FunctionDefinitionVariants

  test "no vars / single expression / single variant" do
    code = "def test, do: 1"

    ir = %FunctionDefinitionVariants{
      name: :test,
      variants: [ir(code)]
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    static test() {
    if (Hologram.Interpreter.isFunctionArgsPatternMatched([], arguments)) {
    return { type: 'integer', value: 1 };
    }
    else {
    console.debug(arguments)
    throw 'No match for the function call'
    }
    }
    """

    assert result == expected
  end

  test "single var" do
    code = "def test(x), do: 1"

    ir = %FunctionDefinitionVariants{
      name: :test,
      variants: [ir(code)]
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    static test() {
    if (Hologram.Interpreter.isFunctionArgsPatternMatched([ { type: 'placeholder' } ], arguments)) {
    let x = arguments[0];
    return { type: 'integer', value: 1 };
    }
    else {
    console.debug(arguments)
    throw 'No match for the function call'
    }
    }
    """

    assert result == expected
  end

  test "multiple vars" do
    code = "def test(x, y), do: 1"

    ir = %FunctionDefinitionVariants{
      name: :test,
      variants: [ir(code)]
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    static test() {
    if (Hologram.Interpreter.isFunctionArgsPatternMatched([ { type: 'placeholder' }, { type: 'placeholder' } ], arguments)) {
    let x = arguments[0];
    let y = arguments[1];
    return { type: 'integer', value: 1 };
    }
    else {
    console.debug(arguments)
    throw 'No match for the function call'
    }
    }
    """

    assert result == expected
  end

  test "multiple expressions" do
    code = """
    def test do
      1
      2
    end
    """

    ir = %FunctionDefinitionVariants{
      name: :test,
      variants: [ir(code)]
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    static test() {
    if (Hologram.Interpreter.isFunctionArgsPatternMatched([], arguments)) {
    { type: 'integer', value: 1 };
    return { type: 'integer', value: 2 };
    }
    else {
    console.debug(arguments)
    throw 'No match for the function call'
    }
    }
    """

    assert result == expected
  end

  test "multiple variants" do
    code_1 = "def test(1), do: 1"
    code_2 = "def test(2), do: 2"

    ir = %FunctionDefinitionVariants{
      name: :test,
      variants: [ir(code_1), ir(code_2)]
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    static test() {
    if (Hologram.Interpreter.isFunctionArgsPatternMatched([ { type: 'integer', value: 1 } ], arguments)) {
    return { type: 'integer', value: 1 };
    }
    else if (Hologram.Interpreter.isFunctionArgsPatternMatched([ { type: 'integer', value: 2 } ], arguments)) {
    return { type: 'integer', value: 2 };
    }
    else {
    console.debug(arguments)
    throw 'No match for the function call'
    }
    }
    """

    assert result == expected
  end

  test "name" do
    code = "def test?, do: 1"

    ir = %FunctionDefinitionVariants{
      name: :test?,
      variants: [ir(code)]
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = """
    static test$question() {
    if (Hologram.Interpreter.isFunctionArgsPatternMatched([], arguments)) {
    return { type: 'integer', value: 1 };
    }
    else {
    console.debug(arguments)
    throw 'No match for the function call'
    }
    }
    """

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/runtime/layout_test.exs<|end_filename|>
defmodule Hologram.Runtime.LayoutTest do
  use Hologram.Test.UnitCase, async: true

  test "is_layout?/0" do
    module = Hologram.Test.Fixtures.Runtime.Layout.Module1
    assert module.is_layout?()
  end
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/component/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.Component.Module1 do
  alias Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.Component.Module2

  def test_fun_1a do
    Module2.test_fun_2a()
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/page_4.ex<|end_filename|>
defmodule HologramE2E.Page4 do
  use Hologram.Page
  alias HologramE2E.Component4, warn: false

  route "/e2e/page-4"

  def init(_params, _conn) do
    %{
      text: "",
      value: :p4,
      x: 0
    }
  end

  def template do
    ~H"""
    <h1>Page 4</h1><br />

    <button id="page-4-button-1" on:click.command="command_1">Command 1</button>
    <button id="page-4-button-2" on:click.command={:command_2}>Command 2</button>
    <button id="page-4-button-3" on:click.command={:command_3, a: 5, b: 6}>Command 3</button>
    <button id="page-4-button-4" on:click.command={:component_4_id, :component_4_command_1}>Component 4 Command 1</button>
    <button id="page-4-button-5" on:click.command={:component_4_id, :component_4_command_2, a: 5, b: 6}>Component 4 Command 2</button>
    <button id="page-4-button-6" on:click.command="command_4">Command 4</button>
    <button id="page-4-button-7" on:click.command={:command_5}>Command 5</button>
    <button id="page-4-button-8" on:click.command={:command_6, a: 5, b: 6}>Command 6</button>
    <button id="page-4-button-9" on:click.command="command_7">Command 7</button>
    <button id="page-4-button-10" on:click.command={:command_8, a: 5, b: 6}>Command 8</button>
    <button id="page-4-button-11" on:click.command="command_9">Command 9</button>
    <button id="page-4-button-12" on:click.command={:component_4_id, :component_4_command_3}>Component 4 Command 3</button>
    <button id="page-4-button-13" on:click.command={:layout, :default_layout_command_1}>Layout 4 Command 1</button>
    <button id="page-4-button-14" on:click.command="command_11">Command 11</button>
    <button id="page-4-button-15" on:click="action_12">Action 12</button>
    <br />

    <div id="text-page-4">{@text}</div><br />

    <Component4 id="component_4_id" /><br />
    """
  end

  def action(:action_1_b, _params, state) do
    put(state, :text, "text updated by action_1_b, state.value = #{state.value}")
  end

  def action(:action_2_b, _params, state) do
    put(state, :text, "text updated by action_2_b, state.value = #{state.value}")
  end

  def action(:action_3_b, params, state) do
    put(
      state,
      :text,
      "text updated by action_3_b, params.a = #{params.a}, params.b = #{params.b}, state.value = #{state.value}"
    )
  end

  def action(:action_4_b, _params, state) do
    put(state, :text, "text updated by action_4_b, state.value = #{state.value}")
  end

  def action(:action_5_b, _params, state) do
    put(state, :text, "text updated by action_5_b, state.value = #{state.value}")
  end

  def action(:action_6_b, params, state) do
    put(
      state,
      :text,
      "text updated by action_6_b, params.a = #{params.a}, params.b = #{params.b}, state.value = #{state.value}"
    )
  end

  def action(:action_9_b, _params, state) do
    put(state, :text, "text updated by action_9_b, state.value = #{state.value}")
  end

  def action(:action_10_b, _params, state) do
    put(state, :text, "text updated by action_10_b, state.value = #{state.value}")
  end

  def action(:action_11_b, _params, state) do
    put(state, :text, "text updated by action_11_b, state.value = #{state.value}")
  end

  def action(:action_12, _params, state) do
    {state, :command_12}
  end

  def action(:action_12_b, _params, state) do
    put(state, :text, "text updated by action_12_b, state.value = #{state.value}")
  end

  def command(:command_1, _params) do
    :action_1_b
  end

  def command(:command_2, _params) do
    :action_2_b
  end

  def command(:command_3, params) do
    {:action_3_b, a: params.a * 10, b: params.b * 10}
  end

  def command(:command_4, _params) do
    :action_4_b
  end

  def command(:command_5, _params) do
    :action_5_b
  end

  def command(:command_6, params) do
    {:action_6_b, a: params.a * 10, b: params.b * 10}
  end

  def command(:command_7, _params) do
    {:component_4_id, :component_4_action_3_b}
  end

  def command(:command_8, params) do
    {:component_4_id, :component_4_action_4_b, a: params.a * 10, b: params.b * 10}
  end

  def command(:command_9, _params) do
    :action_9_b
  end

  def command(:command_10, _params) do
    :action_10_b
  end

  def command(:command_11, _params) do
    :action_11_b
  end

  def command(:command_12, _params) do
    :action_12_b
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/relaxed_boolean_not_operator_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.RelaxedBooleanNotOperatorTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}
  alias Hologram.Compiler.IR.{BooleanType, RelaxedBooleanNotOperator}

  test "encode/3" do
    ir = %RelaxedBooleanNotOperator{
      value: %BooleanType{value: false}
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected = "Hologram.Interpreter.$relaxed_boolean_not_operator({ type: 'boolean', value: false })"

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/boolean_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.BooleanType

defimpl CallGraphBuilder, for: BooleanType do
  def build(_, _, _, _), do: nil
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_35.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module35 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module36, warn: false

  route "/test-route-35"

  def template do
    ~H"""
    <Module36 />
    """
  end
end


<|start_filename|>lib/hologram/compiler/pattern_deconstructor/variable.ex<|end_filename|>
alias Hologram.Compiler.IR.Variable
alias Hologram.Compiler.PatternDeconstructor

defimpl PatternDeconstructor, for: Variable do
  def deconstruct(var, path) do
    [path ++ [var]]
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/case_expression.ex<|end_filename|>
alias Hologram.Compiler.{Config, Context, Formatter, JSEncoder, Opts}
alias Hologram.Compiler.IR.CaseExpression

defimpl JSEncoder, for: CaseExpression do
  import Hologram.Commons.Encoder, only: [encode_vars: 3]

  @case_condition_js Config.case_condition_js()

  def encode(%{condition: condition, clauses: clauses}, %Context{} = context, %Opts{} = opts) do
    fallback_clause = """
    else {
    throw 'No case clause matching'
    }\
    """

    condition_value = JSEncoder.encode(condition, context, %Opts{})

    anon_fun_body =
      encode_clauses(clauses, context, opts)
      |> Formatter.maybe_append_new_line(fallback_clause)

    """
    Hologram.Interpreter.$case_expression(#{condition_value}, (#{@case_condition_js}) => {
    #{anon_fun_body}
    })\
    """
  end

  defp encode_clauses(clauses, context, opts) do
    Enum.reduce(clauses, "", fn clause, acc ->
      statement = if acc == "", do: "if", else: "else if"
      clause_pattern = JSEncoder.encode(clause.pattern, context, %Opts{placeholder: true})
      vars = encode_vars(clause.bindings, context, opts)
      body = JSEncoder.encode(clause.body, context, opts)

      acc
      |> Formatter.maybe_append_new_line(
        "#{statement} (Hologram.Interpreter.isPatternMatched(#{clause_pattern}, #{@case_condition_js})) {"
      )
      |> Formatter.maybe_append_new_line(vars)
      |> Formatter.maybe_append_new_line(body)
      |> Formatter.maybe_append_new_line("}")
    end)
    |> String.trim_leading()
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_30.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module30 do
  use Hologram.Component
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module31, warn: false

  def template do
    ~H"""
    <Module31 />
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_18.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module18 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module19, warn: false

  route "/test-route-18"

  def template do
    ~H"""
    <Module19 />
    """
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/tuple_type_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.TupleTypeTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, TupleTypeTransformer}
  alias Hologram.Compiler.IR.{IntegerType, TupleType}

  @context %Context{module: Abc}

  describe "transform/2" do
    test "2-element tuple" do
      code = "{1, 2}"
      ast = ast(code)

      result = TupleTypeTransformer.transform(ast, @context)

      expected = %TupleType{
        data: [
          %IntegerType{value: 1},
          %IntegerType{value: 2}
        ]
      }

      assert result == expected
    end

    test "non-2-element tuple" do
      code = "{1, 2, 3}"
      ast = ast(code)

      result = TupleTypeTransformer.transform(ast, @context)

      expected = %TupleType{
        data: [
          %IntegerType{value: 1},
          %IntegerType{value: 2},
          %IntegerType{value: 3}
        ]
      }

      assert result == expected
    end
  end
end


<|start_filename|>test/unit/fixtures/placeholder_component.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.PlaceholderComponent do
  use Hologram.Component

  def template do
    ~H"""
    component template
    """
  end
end


<|start_filename|>lib/hologram/runtime/page_digest_store.ex<|end_filename|>
defmodule Hologram.Runtime.PageDigestStore do
  use Hologram.Commons.MemoryStore
  alias Hologram.Compiler.Reflection

  @impl true
  def populate_table do
    Reflection.release_page_digest_store_path()
    |> populate_table_from_file()
  end

  @impl true
  def table_name, do: :hologram_page_digest_store
end


<|start_filename|>lib/hologram/template/transformer.ex<|end_filename|>
defmodule Hologram.Template.Transformer do
  alias Hologram.Compiler.Context
  alias Hologram.Template.{ComponentTransformer, ElementNodeTransformer, EmbeddedExpressionParser}

  def transform(nodes, %Context{} = context) when is_list(nodes) do
    Enum.reduce(nodes, [], &(&2 ++ transform(&1, context)))
  end

  def transform({:text, str}, context) do
    EmbeddedExpressionParser.parse(str, context)
  end

  def transform({type, tag_name, attrs, children}, context) do
    children = Enum.reduce(children, [], &(&2 ++ transform(&1, context)))

    case type do
      :component ->
        [ComponentTransformer.transform(tag_name, attrs, children, context)]

      :element ->
        [ElementNodeTransformer.transform(tag_name, children, attrs, context)]
    end
  end
end


<|start_filename|>test/unit/fixtures/template/renderers/component_renderer/module_9.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.ComponentRenderer.Module9 do
  use Hologram.Component

  def init(_props) do
    %{
      component_9_state_key: "component_9_state_value"
    }
  end

  def template do
    ~H"""
    (in component 9: {@__context__.test_context_key})
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_12.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module12 do
  use Hologram.Component
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module13

  def template do
    ~H"""
    """
  end

  def action(:test_12a, _params, _state) do
    Module13.test_fun_13a()
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/page_12.ex<|end_filename|>
defmodule HologramE2E.Page12 do
  use Hologram.Page

  route "/e2e/page-12"

  def init(_params, _conn) do
    %{
      count: 0
    }
  end

  def template do
    ~H"""
    <button id="target" on:pointer_down="increment_count">Target</button>
    <div id="text">Event count: {@count}</div>
    """
  end

  def action(:increment_count, _params, state) do
    Map.put(state, :count, state.count + 1)
  end
end


<|start_filename|>lib/hologram/template/transformers/component_transformer.ex<|end_filename|>
defmodule Hologram.Template.ComponentTransformer do
  alias Hologram.Compiler.{Context, Helpers, Reflection, Resolver}
  alias Hologram.Template.VDOM.Component
  alias Hologram.Template.EmbeddedExpressionParser

  def transform(module_name, props, children, %Context{} = context) do
    module =
      Helpers.module_name_segments(module_name)
      |> Resolver.resolve(context)

    module_def = Reflection.module_definition(module)
    props = transform_props(props, context)

    %Component{module: module, module_def: module_def, props: props, children: children}
  end

  defp transform_props(props, context) do
    Enum.map(props, fn {type, key, value} ->
      value =
        if(type == :expression, do: "{#{value}}", else: value)
        |> EmbeddedExpressionParser.parse(context)

      {String.to_atom(key), value}
    end)
    |> Enum.into(%{})
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/if_expression.ex<|end_filename|>
alias Hologram.Compiler.{Context, JSEncoder, Opts}
alias Hologram.Compiler.IR.{Block, IfExpression}

defimpl JSEncoder, for: IfExpression do
  import Hologram.Commons.Encoder, only: [encode_as_arrow_function: 3]

  def encode(
        %{condition: condition_expr, do: do_block, else: else_block},
        %Context{} = context,
        %Opts{} = opts
      ) do
    condition_block = %Block{expressions: [condition_expr]}
    condition_anon_fun = encode_as_arrow_function(condition_block, context, opts)

    do_anon_fun = encode_as_arrow_function(do_block, context, opts)
    else_anon_fun = encode_as_arrow_function(else_block, context, opts)

    "Hologram.Interpreter.$if_expression(#{condition_anon_fun}, #{do_anon_fun}, #{else_anon_fun})"
  end
end


<|start_filename|>assets/js/hologram/vdom.js<|end_filename|>
"use strict";

import Kernel from "./elixir/kernel";
import Map from "./elixir/map";

import Runtime from "./runtime";
import Store from "./store";
import Target from "./target";
import Type from "./type";
import Utils from "./utils";

import BlurEvent from "./events/blur_event";
import ChangeEvent from "./events/change_event";
import ClickEvent from "./events/click_event";
import PointerDownEvent from "./events/pointer_down_event";
import PointerUpEvent from "./events/pointer_up_event";
import SubmitEvent from "./events/submit_event";
import TransitionEndEvent from "./events/transition_end_event";

import { attributesModule, eventListenersModule, h, init, toVNode } from "snabbdom";
const patch = init([attributesModule, eventListenersModule]);

export default class VDOM {
  static PRUNED_ATTRS = [
    "if", 
    "on:blur", 
    "on:change", 
    "on:click", 
    "on:pointer_down", 
    "on:pointer_up", 
    "on:submit", 
    "on:transition_end"
  ]

  static virtualDocument = null

  static aggregateComponentBindings(componentId, node, outerBindings) {
    const contextBindings = VDOM.aggregateComponentContextBindings(outerBindings)
    const propsBindings = VDOM.aggregateComponentPropsBindings(node, outerBindings)
    const stateBindings = Store.resolveComponentState(componentId, propsBindings)

    const elems = Object.assign({}, contextBindings.data, propsBindings.data, stateBindings.data)
    return Type.map(elems)
  }

  static aggregateComponentContextBindings(outerBindings) {
    const key = Type.atomKey("__context__")

    let elems = {}
    elems[key] = outerBindings.data[key]

    return Type.map(elems)
  }

  static aggregateComponentPropsBindings(node, outerBindings) {
    const elems = Object.keys(node.props).reduce((acc, key) => {
      acc[Type.atomKey(key)] = VDOM.evaluateProp(node.props[key], outerBindings)
      return acc
    }, {})

    return Type.map(elems)
  }

  static aggregateLayoutBindings() {
    const propsBindings = Store.getPageState()
    const stateBindings = Store.getLayoutState()

    const elems = Object.assign({}, propsBindings.data, stateBindings.data)
    return Type.map(elems)
  }

  static buildElementVNode(node, sourceId, bindings, slots) {
    if (node.tag === "slot") {
      if (sourceId === Target.TYPE.layout) {
        sourceId = Target.TYPE.page
        bindings = Store.getPageState()
      }
      
      return VDOM.buildVNodeList(slots.default, sourceId, bindings, slots)
    }

    if (VDOM.shouldRenderElementVNode(node, bindings)) {
      const attrs = VDOM.buildVNodeAttrs(node, bindings)
      const eventHandlers = VDOM.buildVNodeEventHandlers(node, sourceId, bindings)
      const children = VDOM.buildVNodeList(node.children, sourceId, bindings, slots)
      return [h(node.tag, {attrs: attrs, on: eventHandlers}, children)]

    } else {
      return []
    }
  }

  static buildTextVNodeFromExpression(node, bindings) {
    const evaluatedNode = VDOM.evaluateNode(node, bindings)
    return [VDOM.interpolate(evaluatedNode)]
  }

  static buildTextVNodeFromTextNode(node) {
    return [node.content]
  }

  // Covered implicitely in E2E tests.
  static build(node, sourceId, bindings, slots) {
    if (Array.isArray(node)) {
      return VDOM.buildVNodeList(node, sourceId, bindings, slots)
    }

    switch (node.type) {
      case "component":
        return VDOM.buildComponentVNodes(node, sourceId, bindings)

      case "element":
        return VDOM.buildElementVNode(node, sourceId, bindings, slots)

      case "expression":
        return VDOM.buildTextVNodeFromExpression(node, bindings)

      case "text":
        return VDOM.buildTextVNodeFromTextNode(node)
    }
  }

  static buildComponentVNodes(node, sourceId, outerBindings) {
    const componentId = VDOM.getComponentId(node, outerBindings)
    const componentClass = Runtime.resolveComponentClass(node, componentId)

    if (componentId) {
      sourceId = componentId
    }

    const bindings = VDOM.aggregateComponentBindings(componentId, node, outerBindings)

    const childrenCopy = Utils.clone(node.children)
    const slots = { default: Utils.freeze(childrenCopy) }

    return VDOM.build(componentClass.template(), sourceId, bindings, slots)
  }

  static buildVNodeAttrs(node, bindings) {
    return Object.keys(node.attrs).reduce((acc, key) => {
      if (!VDOM.PRUNED_ATTRS.includes(key)) {
        const valueNodes = node.attrs[key].value

        if (valueNodes) {
          const value = VDOM.evaluateAttrParts(valueNodes, bindings)

          if (Type.isNil(value[0])) {
            acc[key] = true
          } else if (!Type.isFalse(value[0])) {
            acc[key] = VDOM.evaluateAttrToString(value)
          }

        } else {
          acc[key] = true
        }
      }
      return acc
    }, {})
  }

  static buildVNodeEventHandlers(node, sourceId, bindings) {
    const eventHandlers = {}

    if (node.attrs["on:blur"]) {
      eventHandlers.blur = (event) => { Runtime.handleEvent(event, BlurEvent, sourceId, bindings, node.attrs["on:blur"], node.tag) }
    }

    if (node.attrs["on:change"]) {
      eventHandlers.change = (event) => { Runtime.handleEvent(event, ChangeEvent, sourceId, bindings, node.attrs["on:change"], node.tag) }
    }

    if (node.attrs["on:click"]) {
      eventHandlers.click = (event) => { Runtime.handleEvent(event, ClickEvent, sourceId, bindings, node.attrs["on:click"], node.tag) }
    }

    if (node.attrs["on:pointer_down"]) {
      eventHandlers.pointerdown = (event) => { Runtime.handleEvent(event, PointerDownEvent, sourceId, bindings, node.attrs["on:pointer_down"], node.tag) }
    }

    if (node.attrs["on:pointer_up"]) {
      eventHandlers.pointerup = (event) => { Runtime.handleEvent(event, PointerUpEvent, sourceId, bindings, node.attrs["on:pointer_up"], node.tag) }
    }

    if (node.attrs["on:submit"]) {
      eventHandlers.submit = (event) => { Runtime.handleEvent(event, SubmitEvent, sourceId, bindings, node.attrs["on:submit"], node.tag) }
    }

    if (node.attrs["on:transition_end"]) {
      eventHandlers.transitionend = (event) => { Runtime.handleEvent(event, TransitionEndEvent, sourceId, bindings, node.attrs["on:transition_end"], node.tag) }
    }

    return eventHandlers
  }

  static buildVNodeList(nodes, sourceId, bindings, slots) {
    nodes = VDOM._convertExpressionNodesToTextNodes(nodes, bindings)
    nodes = VDOM._mergeConsecutiveTextNodes(nodes)

    return nodes.reduce((acc, node) => {
      acc.push(...VDOM.build(node, sourceId, bindings, slots))
      return acc
    }, [])
  }

  // DEFER: test
  static _convertExpressionNodesToTextNodes(nodes, bindings) {
    return nodes.map((node) => {
      if (node.type === "expression") {
        const evaluatedNode = VDOM.evaluateNode(node, bindings)
        const content = VDOM.interpolate(evaluatedNode)
        return Type.textNode(content)
      } else {
        return node
      }
    })
  }

  // DEFER: test
  static _mergeConsecutiveTextNodes(nodes) {
    const mergedNodes = []

    for (const node of nodes) {
      if (node.type === "text" && mergedNodes.length > 0) {
        const prevNode = mergedNodes[mergedNodes.length - 1]

        if (prevNode.type === "text") {
          const mergedNode = Type.textNode(prevNode.content + node.content)
          mergedNodes[mergedNodes.length - 1] = mergedNode
          continue;
        }
      }

      mergedNodes.push(node)
    }

    return mergedNodes
  }

  static evaluateAttrParts(nodes, bindings) {
    return nodes.map(node => VDOM.evaluateNode(node, bindings))
  }

  // DEFER: use bindings
  static evaluateAttrToString(nodes, _bindings) {
    return nodes.map(node => VDOM.interpolate(node)).join("")
  }

  static evaluateNode(node, bindings) {
    bindings = Map.put(bindings, Type.atom("bindings"), bindings)

    switch (node.type) {
      case "expression":
        return Utils.freeze(node.callback(bindings).data[0])

      case "text":
        return Utils.freeze({type: "string", value: node.content})
    }
  }

  static evaluateProp(nodes, bindings) {
    if (nodes.length == 1) {
      return VDOM.evaluateNode(nodes[0], bindings)

    } else {
      const concatenatedStr = nodes.reduce((acc, node) => {
        const nodeStr = VDOM.interpolate(VDOM.evaluateNode(node, bindings))
        return acc + nodeStr
      }, "")

      return Type.string(concatenatedStr)
    }
  }

  static getComponentId(node, bindings) {
    if (VDOM.isStatefulComponent(node)) {
      const boxedId = VDOM.evaluateProp(node.props.id, bindings)
      return VDOM.interpolate(boxedId)

    } else {
      return null
    }
  }

  // DEFER: test
  static getDocumentHTML(document) {
    const doctype = new XMLSerializer().serializeToString(document.doctype)
    const outerHTML = document.documentElement.outerHTML
    return doctype + outerHTML;
  }

  static interpolate(boxedValue) {
    if (Type.isList(boxedValue)) {
      return `[${boxedValue.data.map(item => VDOM.interpolate(item)).join(", ")}]`
    } 
    
    return Kernel.to_string(boxedValue).value
  }

  static isStatefulComponent(node) {
    return node.props.hasOwnProperty("id")
  }

  // Covered implicitely in E2E tests.
  static render() {
    if (!VDOM.virtualDocument) {
      VDOM.virtualDocument = toVNode(Runtime.document.documentElement)
    }

    const layoutTemplate = Runtime.getLayoutTemplate()
    const slots = {default: Runtime.getPageTemplate()}
    const sourceId = Target.TYPE.layout
    const bindings = VDOM.aggregateLayoutBindings()

    const newVirtualDocument = VDOM.build(layoutTemplate, sourceId, bindings, slots)[0]
    patch(VDOM.virtualDocument, newVirtualDocument)
    VDOM.virtualDocument = newVirtualDocument
  }

  static reset() {
    VDOM.virtualDocument = null
  }

  static shouldRenderElementVNode(node, bindings) {
    if (node.attrs.if) {
      const ifAttrValue = VDOM.evaluateNode(node.attrs.if.value[0], bindings)
      return Type.isTruthy(ifAttrValue)

    } else {
      return true
    }
  }
}

<|start_filename|>lib/hologram/template/evaluators/expression.ex<|end_filename|>
alias Hologram.Template.VDOM.Expression
alias Hologram.Template.Evaluator

defimpl Evaluator, for: Expression do
  def evaluate(%Expression{ir: ir}, bindings) do
    Evaluator.evaluate(ir, bindings)
    |> elem(0)
  end
end


<|start_filename|>test/unit/fixtures/template/encoders/component_encoder/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.ComponentEncoder.Module1 do
  use Hologram.Component

  def template do
    ~H"""
    <div>test</div>
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_36.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module36 do
  use Hologram.Component
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module37, warn: false

  def template do
    ~H"""
    <Module37 />
    """
  end
end


<|start_filename|>assets/js/hologram/elixir/io.js<|end_filename|>
"use strict";

import Type from "../type";

export default class IO {
  // DEFER: implement inspect/2 and inspect/3
  static inspect(val) {
    console.debug(val)
    return val
  }

  // DEFER: test and consider the device param
  static puts(str) {
    console.log(str)
    return Type.atom("ok")
  }
}

<|start_filename|>lib/hologram/hologram.ex<|end_filename|>
defmodule Hologram do
  @moduledoc ~S'''

  Hologram is a full stack isomorphic Elixir web framework that can be used on top of Phoenix.

  ## Inspired by

  Hologram was inspired by Elm, Phoenix LiveView, Surface, Svelte, Vue.js, Mint, Ruby on Rails.

  ## How it works

  The Hologram concept is that your web app is composed from the basic Hologram blocks of Layouts, Pages and Components.

  Hologram builds a call graph from the content of your Pages (which must follow some basic conventions) and determines what code is to used on the client and what code is to be used on the server. Hologram then transpiles the code to be used on the client to JavaScript.

  Because the state is kept on the client, the programming model is simpler and thanks to stateless or stateful components the app is easily scalable.

  Code that is to be run on the client is encapsulated in “actions”, and code that is to be run on the server is encapsulated in “commands”.
  Actions can trigger commands, commands can trigger actions. Both actions and commands can be triggered directly by DOM events.

  The Client communicates with the Server using websockets. There is no boilerplate code required, Hologram automatically works out what is required.

  ## I want to see some code!

  To see how Hologram app is structured, and see some actual code, take a look at the Hologram’s test app: [hologram/test/e2e](https://github.com/bartblast/hologram/tree/master/test/e2e)

  ## Basic example
      defmodule MyPage do
      use Hologram.Page

      route "/my-page-path"

      def init(_params, _conn) do
        %{
          count: 0
        }
      end

      def template do
        ~H"""
        <div>Count is {@count}</div>
        <button on:click={:increment, by: 3}>Increment by</button>
        <Link to={MyOtherPage}>Go to other page</Link>
        """
      end

      def action(:increment, params, state) do
        put(state, :count, state.count + params.by)
      end

      def command(:save_to_db, _params) do
        # Repo.update (…)
        :counter_saved
      end
    end

  ## Is it used in production?

  Yes, it's used in Segmetric: https://www.segmetric.com/. Take a look at the “join beta” and “contact pages" which showcase form handling, or check the mobile menu.
  This all works on transpiled Elixir code! (But please, submit the forms only if you actually want to join the Segmetric beta or contact Segmetric - it is a live page, thanks!)

  ## Selling Points
  * State on the client - and all of the problems that get solved by this approach (below)…

  * No latency issues as most of the code is run immediately on the client. This makes it possible to create rich UI or even games.
  At the moment with LiveView you need something like fly.io to make it bearable, but you still have latency and can’t guarantee
  the response time (there is always some variance). And you still need some JS or Alpine to make it work. Until someone manages
  to create quantum internet (e.g. by taking advantage of entanglement), there are no workarounds for this problem.
  Not sure if this is even technically possible, though :wink:

  * Better offline support (internet connection loss, poor signal, etc.). Since most of the code is run on the client and you only hit the server to run some command from time to time,
  Hologram can work offline most of the time. This would also make it possible to create PWA’s or mobile apps through WebView, assuming you use something like LocalStorage.

  * Less server RAM used - state is kept in the browser instead of the socket.

  * Less CPU used - most of the code is run by the browser not by the server.

  * Less bandwidth used - only commands need to communicate with the server, no need to send diffs to rerender components.

  * No state sync problems - state is kept only in one place (browser) and the websocket communication used is stateless.

  * No JS or Alpine.js needed except for communication with some third party scripts or widgets,
  but this can also be solved by creating some standardized libs for popular packages that would handle the interop.

  * Very friendly to new Elixir converts or beginner devs. I want it to be very, very intuitive, so that you can focus on working on new features in your project instead
  of solving technical problems and writing boilerplate code.

  ## Roadmap

  This is work in progress (although usable and used in production). To check what works and what is planned - take a look at the roadmap in the readme at: [Github bartblast/hologram](https://github.com/bartblast/hologram#readme)

  To meet the objective of being a very friendly developer experience, Hologram will provide out of the box such things as UI component library (CSS framework agnostic),
  authentication, authorization, easy debugging (with time travel), caching, localization and some other features that you typically use in a web app.

  I believe that using Hologram’s approach, i.e. Elixir-JS transpilation, code on client and action/command architecture it will be possible to create something as productive as Rails,
  without its shortcomings related to scalability, efficiency, etc.

  ## History / background

  I tried to write this kind of framework first in Ruby, and actually managed to create a working prototype, but the performance was not satisfactory.
  Then I tried Crystal, but it was very hard to work with its AST. Then I moved to Kotlin, but I realised that it’s better to use a dynamically typed language …
  Then I found Elixir in 2018 and fell in love with it. I started work on Hologram in the summer of 2020.

  '''
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/nil_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.NilType

defimpl CallGraphBuilder, for: NilType do
  def build(_, _, _, _), do: nil
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_43.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module43 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module44, warn: false

  route "/test-route-43"

  def template do
    ~H"""
    <Module44 />
    """
  end
end


<|start_filename|>test/e2e/mix.exs<|end_filename|>
defmodule HologramE2E.MixProject do
  use Mix.Project

  def project do
    [
      aliases: [],
      app: :hologram_e2e,
      compilers: Mix.compilers() ++ [:hologram],
      deps: deps(),
      elixir: "~> 1.12",
      elixirc_paths: elixirc_paths(Mix.env()),
      start_permanent: Mix.env() == :prod,
      version: "0.1.0"
    ]
  end

  def application do
    [
      mod: {HologramE2E.Application, []},
      extra_applications: [:logger, :runtime_tools]
    ]
  end

  defp deps do
    [
      {:esbuild, "~> 0.4", runtime: Mix.env() == :dev},
      {:hologram, git: "https://github.com/bartblast/hologram.git"},
      {:jason, "~> 1.2"},
      {:phoenix, "~> 1.6"},
      {:plug_cowboy, "~> 2.5"},
      {:wallaby, "~> 0.29", only: :test, runtime: false}
    ]
  end

  defp elixirc_paths(:test), do: ["lib", "test/fixtures", "test/support"]
  defp elixirc_paths(_), do: ["lib"]
end


<|start_filename|>lib/hologram/compiler/transformers/if_expression_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.IfExpressionTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.IfExpression

  def transform(
        {:if, _, [condition, [do: do_block, else: else_block]]} = full_ast,
        %Context{} = context
      ) do
    %IfExpression{
      condition: Transformer.transform(condition, context),
      do: Transformer.transform(do_block, context),
      else: Transformer.transform(else_block, context),
      ast: full_ast
    }
  end
end


<|start_filename|>lib/hologram/compiler/transformers/require_directive_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.RequireDirectiveTransformer do
  alias Hologram.Compiler.Helpers
  alias Hologram.Compiler.IR.RequireDirective

  def transform({:require, _, [{_, _, module_segs}]}) do
    module = Helpers.module(module_segs)
    %RequireDirective{module: module}
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_24.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module24 do
  use Hologram.Layout
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module25, warn: false

  def template do
    ~H"""
    {Module25.test_25a()}
    """
  end
end


<|start_filename|>lib/hologram/template/evaluators/if_expression.ex<|end_filename|>
alias Hologram.Compiler.IR.IfExpression
alias Hologram.Template.Evaluator

defimpl Evaluator, for: IfExpression do
  alias Hologram.Compiler.Evaluator, as: ASTEvaluator

  def evaluate(%{ast: ast}, bindings) do
    ASTEvaluator.evaluate(ast, bindings)
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_23.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module23 do
  use Hologram.Page

  layout Hologram.Test.Fixtures.Compiler.Pruner.Module24

  route "/test-route-23"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_38.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module38 do
  use Hologram.Page

  layout Hologram.Test.Fixtures.Compiler.Pruner.Module39

  route "/test-route-38"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_54.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module54 do
  use Hologram.Page

  route "/test-route-54"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/match_operator.ex<|end_filename|>
alias Hologram.Compiler.{Config, Context, JSEncoder, Opts}
alias Hologram.Compiler.IR.MatchOperator

defimpl JSEncoder, for: MatchOperator do
  import Hologram.Commons.Encoder, only: [encode_vars: 3]

  def encode(%{bindings: bindings, right: right}, %Context{} = context, %Opts{} = opts) do
    right = JSEncoder.encode(right, context, opts)

    """
    #{Config.match_access_js()} = #{right};
    #{encode_vars(bindings, context, opts)}\
    """
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/endpoint.ex<|end_filename|>
defmodule HologramE2EWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :hologram_e2e

  @session_options [
    key: "_hologram_e2e_key",
    signing_salt: "<KEY>",
    store: :cookie
  ]

  socket("/hologram", Hologram.Runtime.Socket)

  plug(Plug.Static,
    at: "/",
    from: :hologram_e2e,
    gzip: false,
    only: ~w(assets fonts hologram images favicon.ico robots.txt)
  )

  plug(Plug.RequestId)

  plug(Plug.Parsers,
    json_decoder: Phoenix.json_library(),
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"]
  )

  plug(Plug.MethodOverride)
  plug(Plug.Head)
  plug(Plug.Session, @session_options)
  plug(Hologram.Router)
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/match_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.MatchOperator

defimpl CallGraphBuilder, for: MatchOperator do
  def build(%{right: right}, module_defs, templates, from_vertex) do
    CallGraphBuilder.build(right, module_defs, templates, from_vertex)
  end
end


<|start_filename|>test/unit/hologram/compiler/call_graph_builder/module_type_test.exs<|end_filename|>
defmodule Hologram.Compiler.CallGraphBuilder.ModuleTypeTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{CallGraph, CallGraphBuilder}
  alias Hologram.Compiler.IR.ModuleType
  alias Hologram.Test.Fixtures.{PlaceholderModule1, PlaceholderModule2}

  @module_defs %{}
  @templates %{}

  setup do
    CallGraph.run()
    :ok
  end

  test "non-standard lib module" do
    ir = %ModuleType{module: PlaceholderModule2}
    from_vertex = PlaceholderModule1
    CallGraphBuilder.build(ir, @module_defs, @templates, from_vertex)

    assert CallGraph.has_edge?(PlaceholderModule1, PlaceholderModule2)
  end

  test "standard lib module" do
    ir = %ModuleType{module: Kernel}
    from_vertex = PlaceholderModule1
    CallGraphBuilder.build(ir, @module_defs, @templates, from_vertex)

    refute CallGraph.has_edge?(PlaceholderModule1, Kernel)
  end
end


<|start_filename|>test/unit/fixtures/template/encoder/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.Encoder.Module1 do
  use Hologram.Component

  def template, do: ""
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_5.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module5 do
  use Hologram.Component

  def template do
    ~H"""
    """
  end

  def action(:test_5a, _params, _state) do
    :ok
  end

  def action(:test_5b, _a, _b, _c) do
    :ok
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/anonymous_function_type.ex<|end_filename|>
alias Hologram.Compiler.{Context, Formatter, JSEncoder, Opts}
alias Hologram.Compiler.IR.AnonymousFunctionType

defimpl JSEncoder, for: AnonymousFunctionType do
  use Hologram.Commons.Encoder

  def encode(%{bindings: bindings, body: body}, %Context{} = context, %Opts{} = opts) do
    vars = encode_vars(bindings, context, opts)

    vars_bindings = Enum.map(bindings, & &1.name)
    context = %{context | block_bindings: context.block_bindings ++ vars_bindings}
    body = JSEncoder.encode(body, context, opts)

    "{ type: 'anonymous_function', callback: function() {"
    |> Formatter.maybe_append_new_line(vars)
    |> Formatter.maybe_append_new_line(body)
    |> Formatter.maybe_append_new_line("}")
    |> Formatter.maybe_append_new_line("}")
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/integer_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.IntegerType
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: IntegerType do
  def aggregate(_), do: nil
end


<|start_filename|>assets/js/hologram/events/transition_end_event.js<|end_filename|>
"use strict";

import Type from "../type"

export default class TransitionEndEvent {
  // DEFER: test
  static buildEventData(_event, _tag) {
    return Type.map()
  }

  // DEFER: test
  static shouldHandleEvent(_event) {
    return true
  }
}

<|start_filename|>lib/hologram/template/encoder.ex<|end_filename|>
defprotocol Hologram.Template.Encoder do
  alias Hologram.Typespecs, as: T

  @doc """
  Given VDOM template, generates virtual DOM template JS representation,
  which can be used by the frontend runtime to re-render the VDOM.

  ## Examples
      iex> encode(%ElementNode{tag: "div", children: [%TextNode{content: "test}]})
      "{ type: 'element', tag: 'div', attrs: {}, children: [{ type: 'text', content: 'test' }] }"
  """
  @spec encode(T.vdom_node() | list(T.vdom_node())) :: String.t()

  def encode(vdom)
end


<|start_filename|>lib/hologram/compiler/transformers/for_expression_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.ForExpressionTransformer do
  alias Hologram.Compiler.{Context, Reflection, Transformer}

  def transform({:for, _, parts}, %Context{} = context) do
    generators = find_generators(parts)
    mapper = find_mapper(parts)

    build_code(generators, mapper)
    |> Reflection.ast()
    |> Transformer.transform(context)
  end

  defp build_code([generator | rest_of_generators], mapper) do
    {:<-, _, [pattern, values]} = generator

    """
    Enum.reduce(#{Macro.to_string(values)}, [], fn holo__, acc ->
    #{Macro.to_string(pattern)} = holo__
    acc ++ #{build_code(rest_of_generators, mapper)}
    end)
    """
  end

  defp build_code([], mapper) do
    [do: {:__block__, _, [mapper_ast]}] = mapper
    "[#{Macro.to_string(mapper_ast)}]"
  end

  defp find_generators(parts) do
    Enum.filter(parts, fn part ->
      match?({:<-, _, _}, part)
    end)
  end

  defp find_mapper(parts) do
    List.last(parts)
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/page_11.ex<|end_filename|>
defmodule HologramE2E.Page11 do
  use Hologram.Page

  route "/e2e/page-11"

  def init(_params, _conn) do
    %{
      text: "Field has not been blurred"
    }
  end

  def template do
    ~H"""
    <h1>Page 11</h1>
    <form>
      <input id="input" type="text" name="field" on:blur="update" />
    </form>
    <div id="text">{@text}</div>
    """
  end

  def action(:update, _params, state) do
    put(state, :text, "Field has been blurred")
  end
end


<|start_filename|>lib/hologram/compiler/transformers/use_directive_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.UseDirectiveTransformer do
  alias Hologram.Compiler.Helpers
  alias Hologram.Compiler.IR.UseDirective

  def transform({:use, _, [{_, _, module_segs}]}) do
    module = Helpers.module(module_segs)
    %UseDirective{module: module, opts: []}
  end

  def transform({:use, _, [{_, _, module_segs}, opts]}) do
    module = Helpers.module(module_segs)
    %UseDirective{module: module, opts: opts}
  end
end


<|start_filename|>lib/hologram/compiler/transformers/map_type_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.MapTypeTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.MapType

  def transform({:%{}, _, data}, %Context{} = context) do
    data =
      Enum.map(data, fn {key, value} ->
        {
          Transformer.transform(key, context),
          Transformer.transform(value, context)
        }
      end)

    %MapType{data: data}
  end
end


<|start_filename|>test/unit/fixtures/template/renderers/page_renderer/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.PageRenderer.Module1 do
  use Hologram.Page

  alias Hologram.Test.Fixtures.Template.PageRenderer.Module2
  alias Hologram.Test.Fixtures.Template.PageRenderer.Module3, warn: false

  layout Module2

  route "/test-route-1"

  def init(params, conn) do
    %{
      a: 123,
      c: params.c,
      d: conn.session.d
    }
  end

  def template do
    ~H"""
    page template assign {@a}, page template param {@c}, page template conn session {@d}
    <Module3 id="component_3_id" />
    """
  end
end


<|start_filename|>test/unit/hologram/template/renderers/expression_test.exs<|end_filename|>
defmodule Hologram.Template.Renderer.ExpressionTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.ModuleAttributeOperator
  alias Hologram.Compiler.IR.TupleType
  alias Hologram.Conn
  alias Hologram.Template.Renderer
  alias Hologram.Template.VDOM.Expression

  test "render/4" do
    expression = %Expression{
      ir: %TupleType{
        data: [%ModuleAttributeOperator{name: :a}]
      }
    }

    bindings = %{a: 123}

    result = Renderer.render(expression, %Conn{}, bindings)
    expected = {"123", %{}}

    assert result == expected
  end
end


<|start_filename|>test/unit/hologram/commons/parser_test.exs<|end_filename|>
defmodule Hologram.Commons.ParserTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Compiler.Parser

  describe "parse!/1" do
    test "valid code" do
      assert Parser.parse!("1 + 2") == {:+, [line: 1], [1, 2]}
    end

    test "invalid code" do
      assert_raise RuntimeError, ~r/Invalid code/, fn ->
        Parser.parse!(".1")
      end
    end
  end

  describe "parse_file/1" do
    test "valid code" do
      assert {:ok, _} = Parser.parse_file("#{@fixtures_path}/commons/file_1.ex")
    end

    test "invalid code" do
      assert {:error, _} = Parser.parse_file("#{@fixtures_path}/commons/file_2.txt")
    end
  end

  describe "parse_file!/1" do
    test "valid code" do
      assert Parser.parse_file!("#{@fixtures_path}/commons/file_1.ex")
    end

    test "invalid code" do
      assert_raise RuntimeError, ~r/Invalid code/, fn ->
        Parser.parse_file!("#{@fixtures_path}/commons/file_2.txt")
      end
    end
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/components/component_3.ex<|end_filename|>
defmodule HologramE2E.Component3 do
  use Hologram.Component

  def init(_props) do
    %{
      text: "",
      value: :c3
    }
  end

  def template do
    ~H"""
    <div id="text-component-3">{@text}</div>
    """
  end

  def action(:component_3_action_1, _params, state) do
    put(state, :text, "text updated by component_3_action_1, state.value = #{state.value}")
  end

  def action(:component_3_action_2, params, state) do
    put(
      state,
      :text,
      "text updated by component_3_action_2, params.a = #{params.a}, params.b = #{params.b}, state.value = #{state.value}"
    )
  end

  def action(:component_3_action_3, _params, state) do
    put(state, :text, "text updated by component_3_action_3, state.value = #{state.value}")
  end

  def command(:component_3_command_1, _params) do
    {:page, :action_7_b}
  end

  def command(:component_3_command_2, params) do
    {:page, :action_8_b, a: params.a * 10, b: params.b * 10}
  end
end


<|start_filename|>lib/hologram/template/transformers/element_node_transformer.ex<|end_filename|>
defmodule Hologram.Template.ElementNodeTransformer do
  alias Hologram.Compiler.Context
  alias Hologram.Template.VDOM.ElementNode
  alias Hologram.Template.EmbeddedExpressionParser

  def transform(tag, children, attrs, %Context{} = context) do
    attrs = transform_attrs(attrs, context)
    %ElementNode{tag: tag, children: children, attrs: attrs}
  end

  defp transform_attrs(attrs, context) do
    Enum.map(attrs, fn {type, key, value} ->
      [name | modifiers] = String.split(key, ".")

      name = String.to_atom(name)
      modifiers = Enum.map(modifiers, &String.to_atom/1)

      value =
        case type do
          :boolean ->
            nil

          :expression ->
            "{#{value}}" |> EmbeddedExpressionParser.parse(context)

          :literal ->
            EmbeddedExpressionParser.parse(value, context)
        end

      {name, %{value: value, modifiers: modifiers}}
    end)
    |> Enum.into(%{})
  end
end


<|start_filename|>test/unit/fixtures/runtime/channel/module_3.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Runtime.Channel.Module3 do
  use Hologram.Page

  route "/test-route-3"

  def init(_params, _conn) do
    %{}
  end

  def template do
    ~H"""
    """
  end

  def command(:test_command, _params) do
    {:test_action, a: 1, b: 2}
  end
end


<|start_filename|>test/unit/fixtures/runtime/channel/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Runtime.Channel.Module1 do
  use Hologram.Page

  route "/test-route-1"

  def init(_params, _conn) do
    %{}
  end

  def template do
    ~H"""
    """
  end

  def command(:test_command, _params) do
    :test_action
  end
end


<|start_filename|>test/e2e/test/events_test.exs<|end_filename|>
defmodule HologramE2E.EventsTest do
  use HologramE2E.TestCase, async: false

  feature "blur event", %{session: session} do
    session
    |> visit("/e2e/page-11")
    |> click(css("#input"))
    |> click(css("#text"))
    |> assert_has(css("#text", text: "Field has been blurred"))
  end

  feature "change event", %{session: session} do
    session
    |> visit("/e2e/page-10")
    |> fill_in(css("#input-1"), with: "abc")
    |> fill_in(css("#input-2"), with: "xyz")
    # triggers change event, since #input-2 loses focus
    |> click(css("#text-1"))
    |> assert_has(css("#text-1", text: "Field 1 value = abc"))
    |> assert_has(css("#text-2", text: "Field 2 value = xyz"))
  end

  feature "click event", %{session: session} do
    session
    |> visit("/e2e/page-2")
    |> click(css("#page-2-update-text-button"))
    |> assert_has(css("#page-2-text", text: "text updated by page 2 update button"))
  end

  feature "pointer down event", %{session: session} do
    # for some reason Wallaby doesn't generate pointerdown event on touch down
    js = "document.getElementById('target').dispatchEvent(new Event('pointerdown'))"

    session
    |> visit("/e2e/page-12")
    |> click(css("#target"))
    |> assert_has(css("#text", text: "Event count: 1"))
    |> execute_script(js)
    |> assert_has(css("#text", text: "Event count: 2"))
  end

  feature "pointer up event", %{session: session} do
    # for some reason Wallaby doesn't generate pointerup event on touch down
    js = "document.getElementById('target').dispatchEvent(new Event('pointerup'))"

    session
    |> visit("/e2e/page-13")
    |> click(css("#target"))
    |> assert_has(css("#text", text: "Event count: 1"))
    |> execute_script(js)
    |> assert_has(css("#text", text: "Event count: 2"))
  end

  feature "submit event", %{session: session} do
    session
    |> visit("/e2e/page-8")
    |> fill_in(css("#input-1"), with: "abc")
    |> fill_in(css("#input-2"), with: "xyz")
    |> click(css("#submit-button"))
    |> assert_has(css("#text-1", text: "Field 1 value = abc"))
    |> assert_has(css("#text-2", text: "Field 2 value = xyz"))
  end
end


<|start_filename|>lib/hologram/compiler/builder.ex<|end_filename|>
defmodule Hologram.Compiler.Builder do
  alias Hologram.Compiler.{Context, JSEncoder, Opts, Pruner}
  require Logger

  def build(module, module_defs, call_graph) do
    Pruner.prune(module, module_defs, call_graph)
    |> encode_module_defs()
  end

  defp encode_module_defs(module_defs) do
    Enum.reduce(module_defs, "", fn {_, module_def}, acc ->
      acc <> "\n" <> JSEncoder.encode(module_def, %Context{}, %Opts{})
    end)
  end
end


<|start_filename|>assets/js/hologram/utils.js<|end_filename|>
"use strict";

// see: https://www.blazemeter.com/blog/the-correct-way-to-import-lodash-libraries-a-benchmark
import cloneDeep from "lodash/cloneDeep";
import isEqual from "lodash/isEqual";

export default class Utils {
  static clone(obj) {
    return cloneDeep(obj)
  }

  static eval(code, immutable = true) {
    const result = (new Function(`return (${code});`)());
    return immutable ? Utils.freeze(result) : result
  }

  static exec(code) {
    (new Function(`${code};`)());
  }

  // based on deepFreeze() from: https://developer.mozilla.org/pl/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
  static freeze(obj) {
    const props = Object.getOwnPropertyNames(obj);
    
    for (const prop of props) {
      const val = obj[prop];

      if (val && typeof val === "object") {
        Utils.freeze(val);
      }
    }

    return Object.freeze(obj);
  }

  static isEqual(left, right) {
    return isEqual(left, right)
  }
}

<|start_filename|>lib/hologram/compiler/pattern_deconstructor/fallback.ex<|end_filename|>
alias Hologram.Compiler.PatternDeconstructor

defimpl PatternDeconstructor, for: Any do
  def deconstruct(_, _), do: []
end


<|start_filename|>test/unit/hologram/commons/encoder_test.exs<|end_filename|>
defmodule Hologram.Commons.EncoderTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Commons.Encoder
  alias Hologram.Compiler.{Context, Opts}
  alias Hologram.Compiler.IR.{AtomType, Block, IntegerType, Variable}

  describe "encode_args/3" do
    test "non-variable arg" do
      args = [%IntegerType{value: 1}]
      result = Encoder.encode_args(args, %Context{}, %Opts{})
      expected = "{ type: 'integer', value: 1 }"

      assert result == expected
    end

    test "variable arg" do
      args = [%Variable{name: :test}]
      result = Encoder.encode_args(args, %Context{}, %Opts{})
      expected = "test"

      assert result == expected
    end

    test "multiple args" do
      args = [%IntegerType{value: 1}, %IntegerType{value: 2}]
      result = Encoder.encode_args(args, %Context{}, %Opts{})
      expected = "{ type: 'integer', value: 1 }, { type: 'integer', value: 2 }"

      assert result == expected
    end
  end

  describe "encode_as_array/3" do
    test "empty list encoding" do
      data = []
      result = Encoder.encode_as_array(data, %Context{}, %Opts{})

      assert result == "[]"
    end

    test "non-empty list encoding" do
      data = [%IntegerType{value: 1}, %IntegerType{value: 2}]
      result = Encoder.encode_as_array(data, %Context{}, %Opts{})
      expected = "[ { type: 'integer', value: 1 }, { type: 'integer', value: 2 } ]"

      assert result == expected
    end
  end

  describe "encode_as_arrow_function/3" do
    test "block" do
      body = %Block{
        expressions: [
          %IntegerType{value: 1},
          %IntegerType{value: 2}
        ]
      }

      result = Encoder.encode_as_arrow_function(body, %Context{}, %Opts{})

      expected = """
      () => {
      { type: 'integer', value: 1 };
      return { type: 'integer', value: 2 };
      }\
      """

      assert result == expected
    end

    test "single expression" do
      expr = %IntegerType{value: 1}
      result = Encoder.encode_as_arrow_function(expr, %Context{}, %Opts{})

      expected = """
      () => {
      return { type: 'integer', value: 1 };
      }\
      """

      assert result == expected
    end
  end

  describe "encode_identifier/1" do
    test "string" do
      result = Encoder.encode_identifier("test")
      assert result == "test"
    end

    test "atom" do
      result = Encoder.encode_identifier(:test)
      assert result == "test"
    end

    test "question mark" do
      result = Encoder.encode_identifier("test?")
      assert result == "test$question"
    end

    test "exclamation mark" do
      result = Encoder.encode_identifier("test!")
      assert result == "test$bang"
    end
  end

  describe "encode_map_data/3" do
    test "empty data" do
      data = []

      result = Encoder.encode_map_data(data, %Context{}, %Opts{})
      expected = "{}"

      assert result == expected
    end

    test "non-empty data" do
      data = [
        {%AtomType{value: :a}, %IntegerType{value: 1}},
        {%AtomType{value: :b}, %IntegerType{value: 2}}
      ]

      result = Encoder.encode_map_data(data, %Context{}, %Opts{})

      expected =
        "{ '~atom[a]': { type: 'integer', value: 1 }, '~atom[b]': { type: 'integer', value: 2 } }"

      assert result == expected
    end
  end

  test "encode_primitive_key/2" do
    result = Encoder.encode_primitive_key(:atom, :test)
    assert result == "~atom[test]"
  end

  test "encode_primitive_type/2" do
    result = Encoder.encode_primitive_type(:atom, "'test'")
    assert result == "{ type: 'atom', value: 'test' }"
  end

  test "encode_vars/3" do
    code = "fn x, %{a: y} -> 1 end"
    %{bindings: bindings} = ir(code)

    result = Encoder.encode_vars(bindings, %Context{}, %Opts{})

    expected = """
    let x = arguments[0];
    let y = arguments[1].data['~atom[a]'];\
    """

    assert result == expected
  end

  describe "wrap_with_array/1" do
    test "empty data" do
      assert Encoder.wrap_with_array("") == "[]"
    end

    test "non-empty data" do
      assert Encoder.wrap_with_array("test") == "[ test ]"
    end
  end

  describe "wrap_with_object/1" do
    test "empty data" do
      assert Encoder.wrap_with_object("") == "{}"
    end

    test "non-empty data" do
      assert Encoder.wrap_with_object("test") == "{ test }"
    end
  end
end


<|start_filename|>lib/hologram/template/evaluators/text_node.ex<|end_filename|>
alias Hologram.Template.VDOM.TextNode
alias Hologram.Template.Evaluator

defimpl Evaluator, for: TextNode do
  def evaluate(%{content: content}, _) do
    content
  end
end


<|start_filename|>lib/hologram/compiler/formatter.ex<|end_filename|>
defmodule Hologram.Compiler.Formatter do
  def append(output, str) do
    output <> str
  end

  def append_line_break(output) do
    output <> "\n"
  end

  def maybe_append_new_expression(output, "") do
    output
  end

  def maybe_append_new_expression(output, str) do
    separator = if String.ends_with?(output, " "), do: "", else: " "
    output <> separator <> str
  end

  def maybe_append_new_line(output, "") do
    output
  end

  def maybe_append_new_line(output, str) do
    separator = if String.ends_with?(output, "\n"), do: "", else: "\n"
    output <> separator <> str
  end

  def maybe_append_new_section(output, "") do
    output
  end

  def maybe_append_new_section(output, str) do
    separator = if String.ends_with?(output, "\n\n"), do: "", else: "\n\n"
    output <> separator <> str
  end
end


<|start_filename|>lib/hologram/compiler/module_def_store.ex<|end_filename|>
defmodule Hologram.Compiler.ModuleDefStore do
  use Hologram.Commons.MemoryStore

  alias Hologram.Compiler.IR.ModuleDefinition
  alias Hologram.Compiler.Reflection

  @impl true
  def table_name, do: :hologram_module_def_store

  def get_if_not_exists(module) do
    GenServer.call(__MODULE__, {:get_if_not_exists, module}, :infinity)
  end

  def handle_call({:get_if_not_exists, module}, _, state) do
    result =
      case get(module) do
        {:ok, _} ->
          nil

        :error ->
          module_def = %ModuleDefinition{} = Reflection.module_definition(module)
          put(module, module_def)
          module_def
      end

    {:reply, result, state}
  end
end


<|start_filename|>test/unit/fixtures/runtime/channel/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Runtime.Channel.Module2 do
  use Hologram.Page

  route "/test-route-2"

  def init(_params, _conn) do
    %{}
  end

  def template do
    ~H"""
    """
  end

  def command(:test_command, params) do
    params =
      Enum.map(params, fn {key, value} -> {key, 10 * value} end)
      |> Enum.into([])

    {:test_action, params}
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/addition_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.AdditionOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, AdditionOperatorTransformer}
  alias Hologram.Compiler.IR.{AdditionOperator, IntegerType, Variable}

  test "transform/3" do
    code = "a + 2"
    ast = ast(code)

    result = AdditionOperatorTransformer.transform(ast, %Context{})

    expected = %AdditionOperator{
      left: %Variable{name: :a},
      right: %IntegerType{value: 2}
    }

    assert result == expected
  end
end


<|start_filename|>test/js/elixir/string_test.js<|end_filename|>
"use strict";

import { assert, assertFrozen, cleanup } from "../support/commons";
beforeEach(() => cleanup());

import String from "../../../assets/js/hologram/elixir/string";
import Type from "../../../assets/js/hologram/type";

describe("to_atom()", () => {
  let result;

  beforeEach(() => {
    const arg = Type.string("test");
    result = String.to_atom(arg);
  });

  it("converts boxed string to boxed atom", () => {
    const expected = Type.atom("test");
    assert.deepStrictEqual(result, expected);
  });

  it("returns frozen object", () => {
    assertFrozen(result);
  });
});


<|start_filename|>lib/hologram/compiler/module_def_aggregators/function_call.ex<|end_filename|>
alias Hologram.Compiler.IR.FunctionCall
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: FunctionCall do
  def aggregate(%{module: module, args: args}) do
    ModuleDefAggregator.aggregate(module)
    ModuleDefAggregator.aggregate(args)
  end
end


<|start_filename|>test/unit/hologram/compiler/expander_test.exs<|end_filename|>
defmodule Hologram.Compiler.ExpanderTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.Expander
  alias Hologram.Compiler.IR.{MacroDefinition, RequireDirective}

  @module_2 Hologram.Test.Fixtures.Compiler.Expander.Module2
  @module_4 Hologram.Test.Fixtures.Compiler.Expander.Module4
  @module_5 Hologram.Test.Fixtures.Compiler.Expander.Module5
  @module_6 Hologram.Test.Fixtures.Compiler.Expander.Module6
  @module_segs_1 [:Hologram, :Test, :Fixtures, :Compiler, :Expander, :Module1]
  @module_segs_3 [:Hologram, :Test, :Fixtures, :Compiler, :Expander, :Module3]
  @requires [%RequireDirective{module: Hologram.Test.Fixtures.Compiler.Expander.Module6}]

  describe "expand_macro/2" do
    test "single expression / no params" do
      macro_def = %MacroDefinition{module: @module_6, name: :test_macro_1}

      result = Expander.expand_macro(macro_def, [])
      expected = [{:abc, [], @module_6}]

      assert result == expected
    end

    test "multiple expressions" do
      macro_def = %MacroDefinition{module: @module_6, name: :test_macro_2}

      result = Expander.expand_macro(macro_def, [])

      expected = [
        {:abc, [], @module_6},
        {:bcd, [], @module_6}
      ]

      assert result == expected
    end

    test "params / unquote calculated value" do
      macro_def = %MacroDefinition{module: @module_6, name: :test_macro_3}

      result = Expander.expand_macro(macro_def, [5, 6])
      expected = [11]

      assert result == expected
    end

    test "unquote param" do
      macro_def = %MacroDefinition{module: @module_6, name: :test_macro_4}

      result = Expander.expand_macro(macro_def, [5, 6])

      expected = [
        {:+, [context: @module_6, import: Kernel],
         [{:+, [context: @module_6, import: Kernel], [{:z, [], @module_6}, 5]}, 6]}
      ]

      assert result == expected
    end

    test "__using__ macro" do
      macro_def = %MacroDefinition{module: @module_6, name: :__using__}

      result = Expander.expand_macro(macro_def, [nil])

      expected = [
        {:import, [context: @module_6],
         [
           {:__aliases__, [alias: false],
            [:Hologram, :Test, :Fixtures, :Compiler, :Expander, :Module5]}
         ]}
      ]

      assert result == expected
    end

    test "function definition, single expression" do
      macro_def = %MacroDefinition{module: @module_6, name: :test_macro_5}

      result = Expander.expand_macro(macro_def, [])

      expected = [
        {:def, [context: @module_6, import: Kernel],
         [
           {:test_function, [context: @module_6], @module_6},
           [do: {:__block__, [], [123]}]
         ]}
      ]

      assert result == expected
    end

    test "function definition, multiple expressions" do
      macro_def = %MacroDefinition{module: @module_6, name: :test_macro_6}

      result = Expander.expand_macro(macro_def, [])

      expected = [
        {:def, [context: @module_6, import: Kernel],
         [
           {:test_function, [context: @module_6], @module_6},
           [do: {:__block__, [], [1, 2]}]
         ]}
      ]

      assert result == expected
    end
  end

  test "expand_macro/3" do
    result = Expander.expand_macro(@module_6, :test_macro_3, [5, 6])
    expected = [11]

    assert result == expected
  end

  describe "expand_macros/2" do
    test "no macros / non-expandable expressions only" do
      code = """
      defmodule Test do
        1
        2
      end
      """

      ast = ast(code)
      result = Expander.expand_macros(ast, @requires)

      assert result == ast
    end

    test "single macro" do
      code = """
      defmodule Test do
        test_macro_1
      end
      """

      ast = ast(code)

      result = Expander.expand_macros(ast, @requires)

      expected =
        {:defmodule, [line: 1],
         [
           {:__aliases__, [line: 1], [:Test]},
           [
             do: {:__block__, [], [{:abc, [], @module_6}]}
           ]
         ]}

      assert result == expected
    end

    test "multiple macros" do
      code = """
      defmodule Test do
        test_macro_1
        test_macro_7
      end
      """

      ast = ast(code)

      result = Expander.expand_macros(ast, @requires)

      expected =
        {:defmodule, [line: 1],
         [
           {:__aliases__, [line: 1], [:Test]},
           [
             do: {:__block__, [], [{:abc, [], @module_6}, 777]}
           ]
         ]}

      assert result == expected
    end

    test "multiple expressions expanded" do
      code = """
      defmodule Test do
        test_macro_2
      end
      """

      ast = ast(code)

      result = Expander.expand_macros(ast, @requires)

      expected =
        {:defmodule, [line: 1],
         [
           {:__aliases__, [line: 1], [:Test]},
           [
             do:
               {:__block__, [],
                [
                  {:abc, [], @module_6},
                  {:bcd, [], @module_6}
                ]}
           ]
         ]}

      assert result == expected
    end

    test "macro with params" do
      code = """
      defmodule Test do
        test_macro_4(1, 2)
      end
      """

      ast = ast(code)

      result = Expander.expand_macros(ast, @requires)

      expected =
        {:defmodule, [line: 1],
         [
           {:__aliases__, [line: 1], [:Test]},
           [
             do:
               {:__block__, [],
                [
                  {:+, [context: @module_6, import: Kernel],
                   [
                     {:+, [context: @module_6, import: Kernel],
                      [
                        {:z, [], @module_6},
                        1
                      ]},
                     2
                   ]}
                ]}
           ]
         ]}

      assert result == expected
    end
  end

  test "expand_module_pseudo_variable/1" do
    code = """
    defmodule Abc.Bcd do
      def test(a, b) do
        __MODULE__.some_fun(1, 2)
      end
    end
    """

    ast = ast(code)
    result = Expander.expand_module_pseudo_variable(ast)

    expected =
      {:defmodule, [line: 1],
       [
         {:__aliases__, [line: 1], [:Abc, :Bcd]},
         [
           do:
             {:__block__, [],
              [
                {:def, [line: 2],
                 [
                   {:test, [line: 2], [{:a, [line: 2], nil}, {:b, [line: 2], nil}]},
                   [
                     do:
                       {:__block__, [],
                        [
                          {{:., [line: 3], [{:__aliases__, [line: 3], [:Abc, :Bcd]}, :some_fun]},
                           [line: 3], [1, 2]}
                        ]}
                   ]
                 ]}
              ]}
         ]
       ]}

    assert result == expected
  end

  describe "expand_use_directives/1" do
    test "no use directives / non-expandable expressions only" do
      code = """
      defmodule Test do
        1
        2
      end
      """

      ast = ast(code)
      result = Expander.expand_use_directives(ast)

      assert result == ast
    end

    test "single use directive" do
      code = """
      defmodule Test do
        use Hologram.Test.Fixtures.Compiler.Expander.Module2
      end
      """

      ast = ast(code)

      result = Expander.expand_use_directives(ast)

      expected =
        {:defmodule, [line: 1],
         [
           {:__aliases__, [line: 1], [:Test]},
           [
             do:
               {:__block__, [],
                [
                  {:import, [context: @module_2],
                   [
                     {:__aliases__, [alias: false], @module_segs_1}
                   ]}
                ]}
           ]
         ]}

      assert result == expected
    end

    test "multiple use directives" do
      code = """
      defmodule Test do
        use Hologram.Test.Fixtures.Compiler.Expander.Module2
        use Hologram.Test.Fixtures.Compiler.Expander.Module4
      end
      """

      ast = ast(code)

      result = Expander.expand_use_directives(ast)

      expected =
        {:defmodule, [line: 1],
         [
           {:__aliases__, [line: 1], [:Test]},
           [
             do:
               {:__block__, [],
                [
                  {:import, [context: @module_2],
                   [
                     {:__aliases__, [alias: false], @module_segs_1}
                   ]},
                  {:import, [context: @module_4],
                   [
                     {:__aliases__, [alias: false], @module_segs_3}
                   ]}
                ]}
           ]
         ]}

      assert result == expected
    end

    test "multiple expressions expanded" do
      code = """
      defmodule Test do
        use Hologram.Test.Fixtures.Compiler.Expander.Module5
      end
      """

      ast = ast(code)

      result = Expander.expand_use_directives(ast)

      expected =
        {:defmodule, [line: 1],
         [
           {:__aliases__, [line: 1], [:Test]},
           [
             do:
               {:__block__, [],
                [
                  {:import, [context: @module_5],
                   [
                     {:__aliases__, [alias: false], @module_segs_1}
                   ]},
                  {:import, [context: @module_5],
                   [
                     {:__aliases__, [alias: false], @module_segs_3}
                   ]}
                ]}
           ]
         ]}

      assert result == expected
    end
  end
end


<|start_filename|>test/js/elixir/ecto/changeset_test.js<|end_filename|>
"use strict";

import { assert, assertBoxedFalse, assertBoxedTrue, cleanup } from "../../support/commons"
beforeEach(() => cleanup());

import Changeset from "../../../../assets/js/hologram/elixir/ecto/changeset";
import Map from "../../../../assets/js/hologram/elixir/map";
import Type from "../../../../assets/js/hologram/type";

describe("cast()", () => {
  let data, result;

  beforeEach(() => {
    data = Type.map()
    data = Map.put(data, Type.atom("a"), Type.integer(1))
    data = Map.put(data, Type.atom("b"), Type.integer(2))

    let types = Type.map()
    types = Map.put(types, Type.atom("k"), Type.atom("integer"))
    types = Map.put(types, Type.atom("l"), Type.atom("string"))
    types = Map.put(types, Type.atom("m"), Type.atom("integer"))
    types = Map.put(types, Type.atom("n"), Type.atom("float"))

    let params = Type.map()
    params = Map.put(params, Type.atom("k"), Type.integer(9))
    params = Map.put(params, Type.atom("l"), Type.integer(8))
    params = Map.put(params, Type.atom("m"), Type.integer(7))
    params = Map.put(params, Type.atom("n"), Type.string("test_string"))
    params = Map.put(params, Type.atom("o"), Type.atom("test_atom"))

    const permitted = Type.list([
      Type.atom("k"),
      Type.atom("l"),
      Type.atom("m"),
      Type.atom("n")
    ])

    result = Changeset.cast(Type.tuple([data, types]), params, permitted)
  })

  it("sets action field", () => {
    const actionField = Map.get(result, Type.atom("action"))
    assert.deepStrictEqual(actionField, Type.nil())
  })

  it("sets changes field", () => {
    let expected = Type.map()
    expected = Map.put(expected, Type.atom("k"), Type.integer(9))
    expected = Map.put(expected, Type.atom("m"), Type.integer(7))

    const changesField = Map.get(result, Type.atom("changes"))

    assert.deepStrictEqual(changesField, expected)
  })

  it("sets data field", () => {
    const dataField = Map.get(result, Type.atom("data"))
    assert.deepStrictEqual(dataField, data)
  })

  it("sets errors field", () => {
    const expected = Type.list([
      Type.tuple([
        Type.atom("l"),
        Type.tuple([
          Type.string("is invalid"),
          Type.list([
            Type.tuple([Type.atom("type"), Type.atom("string")]),
            Type.tuple([Type.atom("validation"), Type.atom("cast")])
          ])
        ])
      ]),
      Type.tuple([
        Type.atom("n"),
        Type.tuple([
          Type.string("is invalid"),
          Type.list([
            Type.tuple([Type.atom("type"), Type.atom("float")]),
            Type.tuple([Type.atom("validation"), Type.atom("cast")])
          ])
        ])
      ]),
    ])

    const errorsField = Map.get(result, Type.atom("errors"))
    assert.deepStrictEqual(errorsField, expected)
  })

  it("sets valid? field in valid changeset", () => {
    const validField = Map.get(result, Type.atom("valid?"))
    assertBoxedFalse(validField)
  })
})

describe("validate_acceptance()", () => {
  let data, permitted, types;

  beforeEach(() => {
    data = Type.map()

    types = Type.map()
    types = Map.put(types, Type.atom("a"), Type.atom("boolean"))

    permitted = Type.list([
      Type.atom("a"),
    ])
  })

  it("doesn't add acceptance error if the field is accepted", () => {
    let params = Type.map()
    params = Map.put(params, Type.atom("a"), Type.boolean(true))

    let changeset = Changeset.cast(Type.tuple([data, types]), params, permitted)
    changeset = Changeset.validate_acceptance(changeset, Type.atom("a"))

    const errorsField = Map.get(changeset, Type.atom("errors"))
    const validField = Map.get(changeset, Type.atom("valid?"))

    assert.deepStrictEqual(errorsField, Type.list())
    assertBoxedTrue(validField)
  })

  it("adds acceptance error if the field is not accepted", () => {
    let params = Type.map()
    params = Map.put(params, Type.atom("a"), Type.boolean(false))

    let changeset = Changeset.cast(Type.tuple([data, types]), params, permitted)
    changeset = Changeset.validate_acceptance(changeset, Type.atom("a"))

    const errorsField = Map.get(changeset, Type.atom("errors"))
    const validField = Map.get(changeset, Type.atom("valid?"))

    const expected = Type.list([
      Type.tuple([
        Type.atom("a"),
        Type.tuple([
          Type.string("must be accepted"),
          Type.list([
            Type.tuple([Type.atom("validation"), Type.atom("acceptance")])
          ])
        ])
      ]),
    ])

    assert.deepStrictEqual(errorsField, expected)
    assertBoxedFalse(validField)
  })
})

describe("validate_required()", () => {
  let data, permitted, types;

  beforeEach(() => {
    data = Type.map()

    types = Type.map()
    types = Map.put(types, Type.atom("a"), Type.atom("string"))
    types = Map.put(types, Type.atom("b"), Type.atom("integer"))

    permitted = Type.list([
      Type.atom("a"),
      Type.atom("b"),
    ])
  })

  it("doesn't add any errors if all required fields are present", () => {
    let params = Type.map()
    params = Map.put(params, Type.atom("a"), Type.string("test"))
    params = Map.put(params, Type.atom("b"), Type.integer(123))

    let changeset = Changeset.cast(Type.tuple([data, types]), params, permitted)
    const fields = Type.list([Type.atom("a"), Type.atom("b")])
    changeset = Changeset.validate_required(changeset, fields)

    const errorsField = Map.get(changeset, Type.atom("errors"))
    const validField = Map.get(changeset, Type.atom("valid?"))

    assert.deepStrictEqual(errorsField, Type.list())
    assertBoxedTrue(validField)
  })

  it("adds required error if a required field is not in params", () => {
    let params = Type.map()
    params = Map.put(params, Type.atom("b"), Type.integer(123))

    let changeset = Changeset.cast(Type.tuple([data, types]), params, permitted)
    const fields = Type.list([Type.atom("a"), Type.atom("b")])
    changeset = Changeset.validate_required(changeset, fields)

    const errorsField = Map.get(changeset, Type.atom("errors"))
    const validField = Map.get(changeset, Type.atom("valid?"))

    const expected = Type.list([
      Type.tuple([
        Type.atom("a"),
        Type.tuple([
          Type.string("can't be blank"),
          Type.list([
            Type.tuple([Type.atom("validation"), Type.atom("required")])
          ])
        ])
      ]),
    ])

    assert.deepStrictEqual(errorsField, expected)
    assertBoxedFalse(validField)
  })

  it("adds required error if a required field is a blank string", () => {
    let params = Type.map()
    params = Map.put(params, Type.atom("a"), Type.string(""))
    params = Map.put(params, Type.atom("b"), Type.integer(123))

    let changeset = Changeset.cast(Type.tuple([data, types]), params, permitted)
    const fields = Type.list([Type.atom("a"), Type.atom("b")])
    changeset = Changeset.validate_required(changeset, fields)

    const errorsField = Map.get(changeset, Type.atom("errors"))
    const validField = Map.get(changeset, Type.atom("valid?"))

    const expected = Type.list([
      Type.tuple([
        Type.atom("a"),
        Type.tuple([
          Type.string("can't be blank"),
          Type.list([
            Type.tuple([Type.atom("validation"), Type.atom("required")])
          ])
        ])
      ]),
    ])

    assert.deepStrictEqual(errorsField, expected)
    assertBoxedFalse(validField)
  })

  it("handles multiple required errors", () => {
    let params = Type.map()

    let changeset = Changeset.cast(Type.tuple([data, types]), params, permitted)
    const fields = Type.list([Type.atom("a"), Type.atom("b")])
    changeset = Changeset.validate_required(changeset, fields)

    const errorsField = Map.get(changeset, Type.atom("errors"))
    const validField = Map.get(changeset, Type.atom("valid?"))

    const expected = Type.list([
      Type.tuple([
        Type.atom("a"),
        Type.tuple([
          Type.string("can't be blank"),
          Type.list([
            Type.tuple([Type.atom("validation"), Type.atom("required")])
          ])
        ])
      ]),
      Type.tuple([
        Type.atom("b"),
        Type.tuple([
          Type.string("can't be blank"),
          Type.list([
            Type.tuple([Type.atom("validation"), Type.atom("required")])
          ])
        ])
      ]),
    ])

    assert.deepStrictEqual(errorsField, expected)
    assertBoxedFalse(validField)
  })
})

<|start_filename|>test/unit/hologram/compiler/pattern_deconstructors/tuple_type_test.exs<|end_filename|>
defmodule Hologram.Compiler.PatternDeconstructor.TupleTypeTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.IR.IntegerType
  alias Hologram.Compiler.IR.TupleAccess
  alias Hologram.Compiler.IR.TupleType
  alias Hologram.Compiler.IR.Variable
  alias Hologram.Compiler.PatternDeconstructor

  test "non-nested tuple without vars" do
    ir = %TupleType{
      data: [
        %IntegerType{value: 1},
        %IntegerType{value: 2}
      ]
    }

    assert PatternDeconstructor.deconstruct(ir) == []
  end

  test "non-nested tuple with single var" do
    ir = %TupleType{
      data: [
        %IntegerType{value: 1},
        %Variable{name: :x}
      ]
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %TupleAccess{index: 1},
        %Variable{name: :x}
      ]
    ]

    assert result == expected
  end

  test "non-nested tuple with multiple vars" do
    ir = %TupleType{
      data: [
        %Variable{name: :x},
        %IntegerType{value: 2},
        %Variable{name: :y}
      ]
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %TupleAccess{index: 0},
        %Variable{name: :x}
      ],
      [
        %TupleAccess{index: 2},
        %Variable{name: :y}
      ]
    ]

    assert result == expected
  end

  test "nested tuple without vars" do
    ir = %TupleType{
      data: [
        %IntegerType{value: 1},
        %IntegerType{value: 2},
        %TupleType{
          data: [
            %IntegerType{value: 3},
            %IntegerType{value: 4}
          ]
        }
      ]
    }

    assert PatternDeconstructor.deconstruct(ir) == []
  end

  test "nested tuple with single var" do
    ir = %TupleType{
      data: [
        %IntegerType{value: 1},
        %TupleType{
          data: [
            %Variable{name: :x},
            %IntegerType{value: 2}
          ]
        }
      ]
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %TupleAccess{index: 1},
        %TupleAccess{index: 0},
        %Variable{name: :x}
      ]
    ]

    assert result == expected
  end

  test "nested tuple with multiple vars" do
    ir = %TupleType{
      data: [
        %IntegerType{value: 1},
        %Variable{name: :x},
        %TupleType{
          data: [
            %Variable{name: :y},
            %IntegerType{value: 2}
          ]
        }
      ]
    }

    result = PatternDeconstructor.deconstruct(ir)

    expected = [
      [
        %TupleAccess{index: 1},
        %Variable{name: :x}
      ],
      [
        %TupleAccess{index: 2},
        %TupleAccess{index: 0},
        %Variable{name: :y}
      ]
    ]

    assert result == expected
  end
end


<|start_filename|>assets/js/hologram/elixir/hologram/runtime/js.js<|end_filename|>
"use strict";

import Kernel from "../../../elixir/kernel"
import Utils from "../../../utils";

export default class JS {
  static exec(boxedCode) {
    Utils.exec(Kernel.to_string(boxedCode).value)
  }
}

<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/page_3.ex<|end_filename|>
defmodule HologramE2E.Page3 do
  use Hologram.Page
  alias HologramE2E.{Component1, Component2}, warn: false

  route "/e2e/page-3"

  def init(_params, _conn) do
    %{
      a: "abc",
      b: "bcd",
      c: "cde"
    }
  end

  def template do
    ~H"""
    <div>
      in page template: {@a}
      <Component1>
        in component 1 slot: {@b}
        <Component2>
          in component 2 slot: {@c}
        </Component2>
      </Component1>
      <button id="update-button" on:click="update_c">Update</button>
    </div>
    """
  end

  def action(:update_c, _params, state) do
    put(state, :c, "xyz")
  end
end


<|start_filename|>lib/hologram/compiler/transformers/anonymous_function_type_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.AnonymousFunctionTypeTransformer do
  alias Hologram.Compiler.{Context, Helpers, Transformer}
  alias Hologram.Compiler.IR.AnonymousFunctionType

  def transform({:fn, _, [{:->, _, [params, body]}]}, %Context{} = context) do
    params = Helpers.transform_params(params, context)
    arity = Enum.count(params)
    bindings = Helpers.aggregate_bindings_from_params(params)
    body = Transformer.transform(body, context)

    %AnonymousFunctionType{arity: arity, params: params, bindings: bindings, body: body}
  end

  # TODO: implement anonymous functions with multiple clauses
  def transform(ast, _), do: %Hologram.Compiler.IR.NotSupportedExpression{ast: ast, type: :multi_clause_anonymous_function_type}
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/types/anonymous_function_page.ex<|end_filename|>
defmodule HologramE2E.Types.AnonymousFunctionPage do
  use Hologram.Page

  route "/e2e/types/anonymous-function"

  def init(_params, _conn) do
    %{
      result: 0
    }
  end

  def template do
    ~H"""
    <button id="button_regular_syntax" on:click="test_regular_syntax">Test regular syntax</button>
    <div id="text">Result = {@result}</div>
    """
  end

  def action(:test_regular_syntax, _params, state) do
    test_fun = fn x -> x * x end
    result = is_function(test_fun)

    Map.put(state, :result, result)
  end
end


<|start_filename|>test/unit/hologram/template/evaluators/module_attribute_operator_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.ModuleAttributeOperatorTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.ModuleAttributeOperator
  alias Hologram.Template.Evaluator

  @bindings %{a: 123}

  test "existing binding" do
    ir = %ModuleAttributeOperator{name: :a}
    result = Evaluator.evaluate(ir, @bindings)

    assert result == 123
  end

  test "non-existing binding" do
    ir = %ModuleAttributeOperator{name: :b}

    assert_raise KeyError, "key :b not found in: %{a: 123}", fn ->
      Evaluator.evaluate(ir, @bindings)
    end
  end
end


<|start_filename|>test/js/action_test.js<|end_filename|>
"use strict";

import { assert, fixtureOperationParamsKeyword, cleanup } from "./support/commons";
beforeEach(() => cleanup())

import Action from "../../assets/js/hologram/action"
import Runtime from "../../assets/js/hologram/runtime"
import Target from "../../assets/js/hologram/target"
import Type from "../../assets/js/hologram/type"

const state = Type.map()
const targetId = Type.atom("test_target_id")
const commandName = Type.atom("test_command")
const params = Type.list()

describe("getCommandNameFromActionResult()", () => {
  it("returns null if the action result is a boxed map", () => {
    const actionResult = Type.map()
    const result = Action.getCommandNameFromActionResult(actionResult)

    assert.isNull(result)
  })

  it("fetches the command name from an action result that is a 4-element boxed tuple", () => {
    const actionResult = Type.tuple([
      state,
      targetId,
      commandName,
      params
    ])

    const result = Action.getCommandNameFromActionResult(actionResult)

    assert.deepStrictEqual(result, commandName)
  })

  it("fetches the command name from an action result that is a 3-element boxed tuple that contains target", () => {
    const actionResult = Type.tuple([
      state,
      targetId,
      commandName
    ])

    const result = Action.getCommandNameFromActionResult(actionResult)

    assert.deepStrictEqual(result, commandName)
  })

  it("fetches the command name from an action result that is a 3-element boxed tuple that doesn't contain target", () => {
    const actionResult = Type.tuple([
      state,
      commandName,
      params
    ])

    const result = Action.getCommandNameFromActionResult(actionResult)

    assert.deepStrictEqual(result, commandName)
  })

  it("fetches the command name from an action result that is a 2-element boxed tuple", () => {
    const actionResult = Type.tuple([
      state,
      commandName
    ])

    const result = Action.getCommandNameFromActionResult(actionResult)

    assert.deepStrictEqual(result, commandName)
  })

  it("returns null if the action result is a boxed tuple that doesn't contain command name", () => {
    const actionResult = Type.tuple([
      Type.map(),
    ])

    const result = Action.getCommandNameFromActionResult(actionResult)

    assert.isNull(result)
  })
})

describe("getParamsFromActionResult()", () => {
  const paramsKeyword = fixtureOperationParamsKeyword()

  it("returns empty keyword list if the action result is a boxed map", () => {
    const actionResult = Type.map()
    const result = Action.getParamsFromActionResult(actionResult)

    assert.deepStrictEqual(result, Type.list())
  })

  it("fetches the command params from an action result that is a 4-element boxed tuple", () => {
    const actionResult = Type.tuple([
      state,
      targetId,
      commandName,
      paramsKeyword
    ])

    const result = Action.getParamsFromActionResult(actionResult)

    assert.deepStrictEqual(result, paramsKeyword)
  })

  it("fetches the command params from an action result that is a 3-element boxed tuple that doesn't contain target", () => {
    const actionResult = Type.tuple([
      state,
      commandName,
      paramsKeyword
    ])

    const result = Action.getParamsFromActionResult(actionResult)

    assert.deepStrictEqual(result, paramsKeyword)
  })

  it("returns empty keyword list if the  action result is a 3-element boxed tuple that contains target", () => {
    const actionResult = Type.tuple([
      state,
      targetId,
      commandName
    ])

    const result = Action.getParamsFromActionResult(actionResult)

    assert.deepStrictEqual(result, Type.list())
  })

  it("returns empty keyword list if the action result is a 2-element boxed tuple", () => {
    const actionResult = Type.tuple([
      state,
      commandName
    ])

    const result = Action.getParamsFromActionResult(actionResult)

    assert.deepStrictEqual(result, Type.list())
  })

  it("returns empty keyword list if the action result is a 1-element boxed tuple", () => {
    const actionResult = Type.tuple([state])
    const result = Action.getParamsFromActionResult(actionResult)

    assert.deepStrictEqual(result, Type.list())
  })
})

describe("getStateFromActionResult()", () => {
  it("fetches the state from an action result that is a boxed map", () => {
    const actionResult = Type.map()
    const result = Action.getStateFromActionResult(actionResult)

    assert.equal(result, actionResult)
  })

  it("fetches the state from an action result that is a boxed tuple", () => {
    const actionResult = Type.tuple([
      state,
      commandName
    ])

    const result = Action.getStateFromActionResult(actionResult)

    assert.deepStrictEqual(result, state)
  })
})

describe("getTargetIdFromActionResult()", () => {
  it("returns null if the action result is a boxed map", () => {
    const actionResult = Type.map()
    const result = Action.getTargetIdFromActionResult(actionResult)

    assert.isNull(result)
  })

  it("fetches the target id from an action result that is a 4-element boxed tuple", () => {
    const actionResult = Type.tuple([
      state,
      targetId,
      commandName,
      params
    ])

    const result = Action.getTargetIdFromActionResult(actionResult)

    assert.deepStrictEqual(result, targetId)
  })

  it("fetches the target id from an action result that is a 3-element boxed tuple that contains target id", () => {
    const actionResult = Type.tuple([
      state,
      targetId,
      commandName
    ])

    const result = Action.getTargetIdFromActionResult(actionResult)

    assert.deepStrictEqual(result, targetId)
  })

  it("returns null if the action result is a 2-element boxed tuple", () => {
    const actionResult = Type.tuple([
      state,
      commandName
    ])

    const result = Action.getTargetIdFromActionResult(actionResult)

    assert.isNull(result)
  })

  it("returns null if the action result is a 1-element boxed tuple", () => {
    const actionResult = Type.tuple([state])
    const result = Action.getTargetIdFromActionResult(actionResult)

    assert.isNull(result)
  })
})

describe("resolveCommandTarget()", () => {
  it("creates Target object when command target is present in action result", () => {
    const commandTargetId = Type.atom("test_command_target_id")

    const actionResult = Type.tuple([
      state,
      commandTargetId,
      commandName
    ])

    const TestComponentClass1 = class {}
    Runtime.registerComponentClass(targetId.value, TestComponentClass1)
    const actionTarget = new Target(targetId.value)

    const TestComponentClass2 = class {}
    Runtime.registerComponentClass(commandTargetId.value, TestComponentClass2)

    const result = Action.resolveCommandTarget(actionResult, actionTarget)
    
    assert.isTrue(result instanceof Target)
    assert.equal(result.id, commandTargetId.value)
  })

  it("creates Target object when command target is not present in action result", () => {
    const actionResult = Type.tuple([state])

    const TestComponentClass = class {}
    Runtime.registerComponentClass(targetId.value, TestComponentClass)
    const actionTarget = new Target(targetId.value)

    const result = Action.resolveCommandTarget(actionResult, actionTarget)
    
    assert.isTrue(result instanceof Target)
    assert.equal(result.id, targetId.value)
  })
})

<|start_filename|>test/js/elixir/kernel_test.js<|end_filename|>
"use strict";

import {
  assert,
  assertBoxedFalse,
  assertBoxedTrue,
  assertFrozen,
  cleanup,
} from "../support/commons";
beforeEach(() => cleanup());

import { HologramNotImplementedError } from "../../../assets/js/hologram/errors";
import Kernel from "../../../assets/js/hologram/elixir/kernel";
import Map from "../../../assets/js/hologram/elixir/map";
import Type from "../../../assets/js/hologram/type";

describe("apply()", () => {
  let functionName, module;

  beforeEach(() => {
    module = Type.module("ModuleStub1");
    functionName = Type.atom("test");
  });

  // apply/3
  it("invokes the function on the module with the args", () => {
    const args = Type.list([Type.integer(1), Type.integer(2)]);

    const result = Kernel.apply(module, functionName, args);
    const expected = Type.integer(3);

    assert.deepStrictEqual(result, expected);
  });

  // apply/2
  it("throws an error if number of args is different than 3", () => {
    const expectedMessage =
      'Kernel.apply(): arguments = {"0":{"type":"module","className":"ModuleStub1"},"1":{"type":"atom","value":"test"}}';
    assert.throw(
      () => {
        Kernel.apply(module, functionName);
      },
      HologramNotImplementedError,
      expectedMessage
    );
  });
});

describe("elem()", () => {
  it("gets the element at the zero-based index in tuple", () => {
    const tuple = Type.tuple([
      Type.integer(1),
      Type.integer(2),
      Type.integer(3),
    ]);
    const index = 1;

    const result = Kernel.elem(tuple, index);
    const expected = Type.integer(2);

    assert.deepStrictEqual(result, expected);
  });
});

describe("hd()", () => {
  it("returns the head of the list", () => {
    const list = Type.list([Type.integer(1), Type.integer(2), Type.integer(3)])
    const result = Kernel.hd(list)
    const expected = Type.integer(1)

    assert.deepStrictEqual(result, expected);
  })
})

describe("is_function()", () => {
  it("returns boxed true if the term is of boxed anonymous function type", () => {
    const term = Type.anonymousFunction(() => { return Type.integer(1) })
    const result = Kernel.is_function(term)

    assertBoxedTrue(result)
  })

  it("returns boxed false if the term is not of boxed anonymous function type", () => {
    const term = Type.integer(1)
    const result = Kernel.is_function(term)

    assertBoxedFalse(result)
  })
})

describe("put_in()", () => {
  it("puts value nested 1 level deep", () => {
    let data = Type.map();
    data = Map.put(data, Type.atom("test_key"), Type.string("abc"));

    const keys = Type.list([Type.atom("test_key")]);
    const result = Kernel.put_in(data, keys, Type.string("xyz"));

    let expected = Type.map();
    expected = Map.put(expected, Type.atom("test_key"), Type.string("xyz"));

    assert.deepStrictEqual(result, expected);
  });

  it("puts value nested more than 1 level deep", () => {
    let level2Data = Type.map();
    level2Data = Map.put(
      level2Data,
      Type.atom("test_key_2"),
      Type.string("abc")
    );

    let level1Data = Type.map();
    level1Data = Map.put(level1Data, Type.atom("test_key_1"), level2Data);

    const keys = Type.list([Type.atom("test_key_1"), Type.atom("test_key_2")]);
    const result = Kernel.put_in(level1Data, keys, Type.string("xyz"));

    let expectedLevel2Data = Type.map();
    expectedLevel2Data = Map.put(
      expectedLevel2Data,
      Type.atom("test_key_2"),
      Type.string("xyz")
    );

    let expectedLevel1Data = Type.map();
    expectedLevel1Data = Map.put(
      expectedLevel1Data,
      Type.atom("test_key_1"),
      expectedLevel2Data
    );

    assert.deepStrictEqual(result, expectedLevel1Data);
  });
});

describe("tl()", () => {
  it("returns the tail of the list", () => {
    const list = Type.list([Type.integer(1), Type.integer(2), Type.integer(3)])
    const result = Kernel.tl(list)
    const expected = Type.list([Type.integer(2), Type.integer(3)])

    assert.deepStrictEqual(result, expected);
  })
})

describe("to_string()", () => {
  it("converts boxed atom to boxed string", () => {
    const value = Type.atom("abc");

    const result = Kernel.to_string(value);
    const expected = Type.string("abc");

    assert.deepStrictEqual(result, expected);
  });

  it("converts boxed boolean to boxed string", () => {
    const value = Type.boolean(true);

    const result = Kernel.to_string(value);
    const expected = Type.string("true");

    assert.deepStrictEqual(result, expected);
  });

  it("converts boxed float to boxed string", () => {
    const value = Type.float(1.234);

    const result = Kernel.to_string(value);
    const expected = Type.string("1.234");

    assert.deepStrictEqual(result, expected);
  });

  it("converts boxed integer to boxed string", () => {
    const value = Type.integer(1);

    const result = Kernel.to_string(value);
    const expected = Type.string("1");

    assert.deepStrictEqual(result, expected);
  });

  it("returns the given arg if it is a boxed string", () => {
    const value = Type.string("abc");
    const result = Kernel.to_string(value);

    assert.deepStrictEqual(result, value);
  });

  it("converts boxed binary to boxed string", () => {
    const value = Type.binary([Type.string("abc"), Type.string("xyz")]);

    const result = Kernel.to_string(value);
    const expected = Type.string("abcxyz");

    assert.deepStrictEqual(result, expected);
  });

  it("converts boxed nil to boxed string", () => {
    const value = Type.nil();

    const result = Kernel.to_string(value);
    const expected = Type.string("");

    assert.deepStrictEqual(result, expected);
  });

  it("throws an error for not implemented types", () => {
    const value = { type: "not implemented", value: "test" };
    const expectedMessage =
      'Kernel.to_string(): boxedValue = {"type":"not implemented","value":"test"}';

    assert.throw(
      () => {
        Kernel.to_string(value);
      },
      HologramNotImplementedError,
      expectedMessage
    );
  });
});


<|start_filename|>test/unit/fixtures/compiler/pruner/module_60.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module60 do
  def test_fun_60a do
    :ok
  end

  def test_fun_60b do
    :ok
  end
end


<|start_filename|>lib/hologram/template/encoders/component.ex<|end_filename|>
alias Hologram.Compiler.Helpers
alias Hologram.Template.VDOM.Component
alias Hologram.Template.Encoder

defimpl Encoder, for: Component do
  use Hologram.Commons.Encoder

  def encode(%{module: module, children: children, props: props}) do
    class_name = Helpers.class_name(module)
    encoded_children = Encoder.encode(children)
    encoded_props = encode_props(props)

    "{ type: 'component', className: '#{class_name}', children: #{encoded_children}, props: #{encoded_props} }"
  end

  defp encode_prop(name, value) do
    "'#{name}': #{Encoder.encode(value)}"
  end

  defp encode_props(props) do
    props
    |> Enum.map(fn {name, value} -> encode_prop(name, value) end)
    |> Enum.join(", ")
    |> wrap_with_object()
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/unary_positive_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.UnaryPositiveOperator
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: UnaryPositiveOperator do
  def aggregate(%{value: value}) do
    ModuleDefAggregator.aggregate(value)
  end
end


<|start_filename|>test/unit/fixtures/compiler/transformer/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Transformer.Module1 do
end


<|start_filename|>lib/hologram/compiler/js_encoders/function_definition.ex<|end_filename|>
alias Hologram.Compiler.{Context, Formatter, JSEncoder, Opts}
alias Hologram.Compiler.IR.FunctionDefinitionVariants

defimpl JSEncoder, for: FunctionDefinitionVariants do
  use Hologram.Commons.Encoder

  def encode(%{name: name, variants: variants}, %Context{} = context, %Opts{} = opts) do
    body = encode_function_body(variants, context, opts)
    name = encode_identifier(name)

    "static #{name}() {#{body}"
    |> Formatter.maybe_append_new_line("}")
    |> Formatter.append_line_break()
  end

  defp encode_function_body(variants, context, opts) do
    invalid_case = encode_function_body_invalid_case()

    encode_function_body_valid_cases(variants, context, opts)
    |> Formatter.maybe_append_new_line(invalid_case)
  end

  defp encode_function_body_invalid_case do
    """
    else {
    console.debug(arguments)
    throw 'No match for the function call'
    }\
    """
  end

  defp encode_function_body_valid_cases(variants, context, opts) do
    Enum.reduce(variants, "", fn variant, acc ->
      statement = if acc == "", do: "if", else: "else if"
      params = encode_function_params(variant, context)
      vars = encode_vars(variant.bindings, context, opts)
      body = JSEncoder.encode(variant.body, context, opts)

      acc
      |> Formatter.maybe_append_new_line(
        "#{statement} (Hologram.Interpreter.isFunctionArgsPatternMatched(#{params}, arguments)) {"
      )
      |> Formatter.maybe_append_new_line(vars)
      |> Formatter.maybe_append_new_line(body)
      |> Formatter.maybe_append_new_line("}")
    end)
  end

  defp encode_function_params(variant, context) do
    Enum.map(variant.params, &JSEncoder.encode(&1, context, %Opts{placeholder: true}))
    |> Enum.join(", ")
    |> wrap_with_array()
  end
end


<|start_filename|>lib/hologram/template/encoders/text_node.ex<|end_filename|>
alias Hologram.Template.VDOM.TextNode
alias Hologram.Template.Encoder

defimpl Encoder, for: TextNode do
  def encode(%{content: content}) do
    content =
      content
      |> String.replace("\n", "\\n")
      |> String.replace("'", "\\'")
      |> String.replace("\\{", "{")
      |> String.replace("\\}", "}")

    "{ type: 'text', content: '#{content}' }"
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module1 do
  use Hologram.Page

  route "/test-route-1"

  def template do
    ~H"""
    """
  end

  def action(:test_1a, _params, _state) do
    :ok
  end

  def action(:test_1b, _a, _b, _c) do
    :ok
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/layouts/default_layout.ex<|end_filename|>
defmodule HologramE2E.DefaultLayout do
  use Hologram.Layout

  def init(_conn) do
    %{
      text: "",
      value: :dl
    }
  end

  def template do
    ~H"""
    <!DOCTYPE html>
    <html>
      <head>
        <title>Hologram E2E</title>
        <Hologram.UI.Runtime />
      </head>
      <body>
        <div id="text-default-layout">{@text}</div>
        default layout:
        <slot />
      </body>
    </html>
    """
  end

  def action(:default_layout_action_1, _params, state) do
    {put(state, :text, "text updated by default_layout_action_1, state.value = #{state.value}")}
  end

  def action(:default_layout_action_2_b, _params, state) do
    {put(
       state,
       :text,
       "text updated by default_layout_action_2_b, state.value = #{state.value}"
     )}
  end

  def command(:default_layout_command_1, _params) do
    :default_layout_action_2_b
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_45.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module45 do
  use Hologram.Page
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module46, warn: false

  route "/test-route-45"

  def template do
    ~H"""
    <Module46 />
    """
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/variable.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Compiler.IR.Variable

defimpl CallGraphBuilder, for: Variable do
  def build(_, _, _, _), do: nil
end


<|start_filename|>lib/hologram/runtime/application.ex<|end_filename|>
defmodule Hologram.Runtime.Application do
  @moduledoc false

  use Application

  alias Hologram.Runtime.{
    PageDigestStore,
    RouterBuilder,
    StaticDigestStore,
    TemplateStore,
    Watcher
  }

  @env Application.fetch_env!(:hologram, :env)

  @impl true
  def start(_type, _args) do
    opts = [strategy: :one_for_one, name: Hologram.Runtime.Supervisor]
    Supervisor.start_link(children(@env), opts)
  end

  defp children(:dev) do
    children(:prod) ++ [Watcher]
  end

  defp children(_) do
    [
      PageDigestStore,
      StaticDigestStore,
      RouterBuilder,
      TemplateStore
    ]
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/variable_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.VariableTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}
  alias Hologram.Compiler.IR.Variable

  describe "encode/3" do
    test "placeholder encoding" do
      ir = %Variable{name: :test}

      result = JSEncoder.encode(ir, %Context{}, %Opts{placeholder: true})
      expected = "{ type: 'placeholder' }"

      assert result == expected
    end

    test "non-placeholder encoding" do
      ir = %Variable{name: :test}

      result = JSEncoder.encode(ir, %Context{}, %Opts{})
      expected = "test"

      assert result == expected
    end
  end
end


<|start_filename|>test/unit/hologram/compiler/call_graph_builder/subtraction_operator_test.exs<|end_filename|>
defmodule Hologram.Compiler.CallGraphBuilder.SubtractionOperatorTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.{CallGraph, CallGraphBuilder, Reflection}
  alias Hologram.Compiler.IR.{FunctionCall, SubtractionOperator}

  alias Hologram.Test.Fixtures.{
    PlaceholderModule1,
    PlaceholderModule2,
    PlaceholderModule3
  }

  @module_defs %{
    PlaceholderModule1 => Reflection.module_definition(PlaceholderModule1),
    PlaceholderModule2 => Reflection.module_definition(PlaceholderModule2),
    PlaceholderModule3 => Reflection.module_definition(PlaceholderModule3)
  }

  @templates %{}

  setup do
    CallGraph.run()
    :ok
  end

  test "build/4" do
    from_vertex = PlaceholderModule1

    ir = %SubtractionOperator{
      left: %FunctionCall{module: PlaceholderModule2, function: :test_fun_2a},
      right: %FunctionCall{module: PlaceholderModule3, function: :test_fun_3a}
    }

    CallGraphBuilder.build(ir, @module_defs, @templates, from_vertex)

    assert CallGraph.has_edge?(PlaceholderModule1, {PlaceholderModule2, :test_fun_2a})
    assert CallGraph.has_edge?(PlaceholderModule1, {PlaceholderModule3, :test_fun_3a})
  end
end


<|start_filename|>lib/hologram/commons/typespecs.ex<|end_filename|>
defmodule Hologram.Typespecs do
  alias Hologram.Compiler.IR.ModuleDefinition
  alias Hologram.Template.VDOM.{Component, ElementNode, Expression, TextNode}

  @type vdom_node :: %Component{} | %ElementNode{} | %Expression{} | %TextNode{}

  @type function_name :: atom()

  @typedoc """
  e.g. MapSet.new([{Hologram.Compiler.Processor, :compile}])
  """
  @type function_set :: MapSet.t({module, function_name})

  @typedoc """
  e.g. %{Hologram.Typespecs => %ModuleDefinition{}}
  """
  @type module_definitions_map :: %{module() => %ModuleDefinition{}}

  @typedoc """
  e.g. [:Hologram, :Typespecs]
  """
  @type module_name_segments :: list(atom())
end


<|start_filename|>lib/hologram/compiler.ex<|end_filename|>
# TODO: refactor & test

defmodule Hologram.Compiler do
  alias Hologram.Compiler.{
    Builder,
    CallGraph,
    CallGraphBuilder,
    ModuleDefAggregator,
    ModuleDefStore,
    Reflection
  }

  alias Hologram.Runtime.TemplateStore
  alias Hologram.Template.Builder, as: TemplateBuilder
  alias Hologram.Utils

  def compile(opts) do
    output_path = resolve_output_path()
    File.mkdir_p!(output_path)

    Reflection.root_priv_path()
    |> File.mkdir_p!()

    ModuleDefStore.run()
    CallGraph.run()

    templatables = Reflection.list_templatables(opts)

    templatables =
      if opts[:templatables], do: templatables ++ opts[:templatables], else: templatables

    templates = TemplateBuilder.build_all(templatables)
    dump_template_store(templates)
    TemplateStore.run()

    pages = Reflection.list_pages(opts)
    dump_page_list(pages)

    module_defs = aggregate_module_defs(pages)
    call_graph = build_call_graph(pages, module_defs, templates)

    build_pages(pages, output_path, module_defs, call_graph)
    |> dump_page_digest_store()

    TemplateStore.stop()
    CallGraph.stop()
    ModuleDefStore.stop()

    %{
      call_graph: call_graph,
      module_defs: module_defs,
      pages: pages,
      templatables: templatables,
      templates: templates
    }
  end

  defp aggregate_module_defs(pages) do
    pages
    |> Utils.map_async(&ModuleDefAggregator.aggregate/1)
    |> Utils.await_tasks()

    ModuleDefStore.get_all()
  end

  defp build_call_graph(pages, module_defs, templates) do
    pages
    |> Utils.map_async(&CallGraphBuilder.build(&1, module_defs, templates, nil))
    |> Utils.await_tasks()

    CallGraph.get()
  end

  defp build_page(page, output_path, module_defs, call_graph) do
    js = Builder.build(page, module_defs, call_graph)

    output = """
    "use strict";

    #{js}

    window.hologramPageScriptLoaded = true;

    // CAUTION: related code is in assets/js/hologram.js
    if (window.hologramRuntimeScriptLoaded && window.hologramPageScriptLoaded && !window.hologramPageMounted) {
      window.hologramPageMounted = true
      Hologram.run(window.hologramArgs)
    }
    """

    digest =
      :crypto.hash(:md5, output)
      |> Base.encode16()
      |> String.downcase()

    "#{output_path}/page-#{digest}.js"
    |> File.write!(output)

    {page, digest}
  end

  defp build_pages(pages, output_path, module_defs, call_graph) do
    pages
    |> Utils.map_async(&build_page(&1, output_path, module_defs, call_graph))
    |> Utils.await_tasks()
  end

  defp dump_page_digest_store(page_digests) do
    data =
      page_digests
      |> Utils.serialize()

    Reflection.root_page_digest_store_path()
    |> File.write!(data)
  end

  defp dump_page_list(pages) do
    data = Utils.serialize(pages)

    Reflection.root_page_list_path()
    |> File.write!(data)
  end

  defp dump_template_store(templates) do
    data = Utils.serialize(templates)

    Reflection.root_template_store_path()
    |> File.write!(data)
  end

  defp resolve_output_path do
    Reflection.root_path() <> "/priv/static/hologram"
  end
end


<|start_filename|>test/unit/hologram/runtime/component_test.exs<|end_filename|>
defmodule Hologram.Runtime.ComponentTest do
  use Hologram.Test.UnitCase, async: true

  test "is_component?/0" do
    module = Hologram.Test.Fixtures.Runtime.Component.Module1
    assert module.is_component?()
  end
end


<|start_filename|>lib/mix/tasks/holo.assets.build.ex<|end_filename|>
defmodule Mix.Tasks.Holo.Assets.Build do
  def run(_) do
    Mix.Shell.cmd("npm install", [cd: "deps/hologram/assets", quiet: true], & &1)
    Mix.Task.run("esbuild", ["hologram", "--minify"])
  end
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/type_operator_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.TypeOperatorTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}
  alias Hologram.Compiler.IR.{IntegerType, TypeOperator}

  test "encode/3" do
    ir = %TypeOperator{
      left: %IntegerType{value: 1},
      right: :binary
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})
    expected = "Hologram.Interpreter.$type_operator({ type: 'integer', value: 1 }, 'binary')"

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_16.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module16 do
  use Hologram.Page

  layout Hologram.Test.Fixtures.Compiler.Pruner.Module17

  route "/test-route-16"

  def template do
    ~H"""
    """
  end
end


<|start_filename|>test/unit/hologram/compiler/decoder_test.exs<|end_filename|>
defmodule Hologram.Compiler.DecoderTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Compiler.Decoder

  test "atom" do
    input = %{"type" => "atom", "value" => "test"}
    result = Decoder.decode(input)
    assert result === :test
  end

  test "boolean" do
    input = %{"type" => "boolean", "value" => true}
    result = Decoder.decode(input)
    assert result === true
  end

  test "float" do
    input = %{"type" => "float", "value" => 1.23}
    result = Decoder.decode(input)
    assert result === 1.23
  end

  test "integer" do
    input = %{"type" => "integer", "value" => 1}
    result = Decoder.decode(input)
    assert result === 1
  end

  test "list" do
    input = %{
      "type" => "list",
      "data" => [
        %{"type" => "integer", "value" => 1},
        %{"type" => "integer", "value" => 2}
      ]
    }

    result = Decoder.decode(input)
    assert is_list(result)
  end

  test "map" do
    input = %{
      "type" => "map",
      "data" => %{
        "~string[test_key]" => %{
          "type" => "string",
          "value" => "test_value"
        }
      }
    }

    result = Decoder.decode(input)
    assert is_map(result)
  end

  test "module" do
    input = %{"type" => "module", "className" => "Elixir_Hologram_Compiler_DecoderTest"}
    result = Decoder.decode(input)
    assert is_atom(result)
  end

  test "string" do
    input = %{"type" => "string", "value" => "test"}
    result = Decoder.decode(input)
    assert result === "test"
  end

  test "tuple" do
    input = %{
      "type" => "tuple",
      "data" => [
        %{"type" => "integer", "value" => 1},
        %{"type" => "integer", "value" => 2}
      ]
    }

    result = Decoder.decode(input)
    assert is_tuple(result)
  end

  test "nested" do
    input = %{
      "type" => "list",
      "data" => [
        %{"type" => "integer", "value" => 1},
        %{
          "type" => "tuple",
          "data" => [
            %{"type" => "integer", "value" => 2},
            %{"type" => "integer", "value" => 3}
          ]
        }
      ]
    }

    result = Decoder.decode(input)
    expected = [1, {2, 3}]

    assert result == expected
  end
end


<|start_filename|>lib/hologram/commons/encoder.ex<|end_filename|>
defmodule Hologram.Commons.Encoder do
  alias Hologram.Compiler.{Context, JSEncoder, MapKeyEncoder, Opts}
  alias Hologram.Compiler.IR.Block
  alias Hologram.Compiler.IR.Variable

  defmacro __using__(_) do
    quote do
      import Hologram.Commons.Encoder
    end
  end

  def encode_args(args, context, opts) do
    Enum.map(args, fn arg ->
      case arg do
        %Variable{name: name} ->
          name

        _ ->
          JSEncoder.encode(arg, context, opts)
      end
    end)
    |> Enum.join(", ")
  end

  def encode_as_array(data, %Context{} = context, %Opts{} = opts) do
    Enum.map(data, &JSEncoder.encode(&1, context, opts))
    |> Enum.join(", ")
    |> wrap_with_array()
  end

  def encode_as_arrow_function(%Block{} = block, context, opts) do
    """
    () => {
    #{JSEncoder.encode(block, context, opts)}
    }\
    """
  end

  def encode_as_arrow_function(expr, context, opts) do
    %Block{expressions: [expr]}
    |> encode_as_arrow_function(context, opts)
  end

  def encode_identifier(name) do
    to_string(name)
    |> String.replace("?", "$question")
    |> String.replace("!", "$bang")
  end

  def encode_map_data(data, %Context{} = context, %Opts{} = opts) do
    Enum.map(data, fn {k, v} ->
      "'#{MapKeyEncoder.encode(k, context, opts)}': #{JSEncoder.encode(v, context, opts)}"
    end)
    |> Enum.join(", ")
    |> wrap_with_object()
  end

  def encode_primitive_key(type, value) do
    "~#{type}[#{value}]"
  end

  def encode_primitive_type(type, value) do
    "{ type: '#{type}', value: #{value} }"
  end

  def encode_vars(bindings, context, opts) do
    bindings
    |> Enum.map(&JSEncoder.encode(&1, context, opts))
    |> Enum.join("\n")
  end

  def wrap_with_array(data) do
    if data != "", do: "[ #{data} ]", else: "[]"
  end

  def wrap_with_object(data) do
    if data != "", do: "{ #{data} }", else: "{}"
  end
end


<|start_filename|>test/unit/hologram/template/renderers/element_node_test.exs<|end_filename|>
defmodule Hologram.Template.Renderer.ElementNodeTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.IR.BooleanType
  alias Hologram.Compiler.IR.IntegerType
  alias Hologram.Compiler.IR.ModuleAttributeOperator
  alias Hologram.Compiler.IR.NilType
  alias Hologram.Compiler.IR.TupleType
  alias Hologram.Runtime
  alias Hologram.Template.Renderer
  alias Hologram.Template.VDOM.Component
  alias Hologram.Template.VDOM.ElementNode
  alias Hologram.Template.VDOM.Expression
  alias Hologram.Template.VDOM.TextNode
  alias Hologram.Test.Fixtures.Template.ElementNodeRenderer.Module1
  alias Hologram.Test.Fixtures.Template.ElementNodeRenderer.Module2

  @attrs %{
    attr_2: %{
      value: [
        %TextNode{content: "test_text_2"}
      ],
      modifiers: []
    },
    attr_1: %{
      value: [
        %Expression{
          ir: %TupleType{
            data: [
              %ModuleAttributeOperator{name: :a}
            ]
          }
        }
      ],
      modifiers: []
    }
  }

  @bindings %{
    __context__: %{},
    a: 123
  }

  @children [
    %TextNode{content: "abc"},
    %Expression{
      ir: %TupleType{
        data: [
          %ModuleAttributeOperator{name: :a}
        ]
      }
    },
    %TextNode{content: "xyz"}
  ]

  @conn %Hologram.Conn{}

  test "non-void element without attributes or children" do
    element_node = %ElementNode{attrs: %{}, children: [], tag: "div"}

    result = Renderer.render(element_node, @conn, @bindings)
    expected = {"<div></div>", %{}}

    assert result == expected
  end

  test "non-void element with attributes" do
    element_node = %ElementNode{attrs: @attrs, children: [], tag: "div"}

    result = Renderer.render(element_node, @conn, @bindings)
    expected = {"<div attr_1=\"123\" attr_2=\"test_text_2\"></div>", %{}}

    assert result == expected
  end

  test "non-void element with children" do
    element_node = %ElementNode{attrs: %{}, children: @children, tag: "div"}

    result = Renderer.render(element_node, @conn, @bindings)
    expected = {"<div>abc123xyz</div>", %{}}

    assert result == expected
  end

  test "non-void element with children and attributes" do
    element_node = %ElementNode{attrs: @attrs, children: @children, tag: "div"}

    result = Renderer.render(element_node, @conn, @bindings)
    expected = {"<div attr_1=\"123\" attr_2=\"test_text_2\">abc123xyz</div>", %{}}

    assert result == expected
  end

  test "void element without attributes" do
    element_node = %ElementNode{attrs: %{}, children: [], tag: "input"}

    result = Renderer.render(element_node, @conn, @bindings)
    expected = {"<input />", %{}}

    assert result == expected
  end

  test "void element with attributes" do
    element_node = %ElementNode{attrs: @attrs, children: [], tag: "input"}

    result = Renderer.render(element_node, @conn, @bindings)
    expected = {"<input attr_1=\"123\" attr_2=\"test_text_2\" />", %{}}

    assert result == expected
  end

  test "pruned attrs" do
    attrs = %{
      non_pruned_attr: %{
        value: [
          %TextNode{content: "test_non_pruned_attr_content"}
        ],
        modifiers: []
      },
      "on:click": %{
        value: [
          %TextNode{content: "test_pruned_attr_content"}
        ],
        modifiers: []
      }
    }

    element_node = %ElementNode{attrs: attrs, children: [], tag: "input"}

    result = Renderer.render(element_node, @conn, @bindings)
    expected = {"<input non_pruned_attr=\"test_non_pruned_attr_content\" />", %{}}

    assert result == expected
  end

  test "'if' attribute which evaluates to a truthy value" do
    attrs = %{
      if: %{
        value: [
          %Expression{
            ir: %TupleType{
              data: [%IntegerType{value: 123}]
            }
          }
        ],
        modifiers: []
      }
    }

    element_node = %ElementNode{attrs: attrs, children: [], tag: "div"}

    result = Renderer.render(element_node, @conn, @bindings)
    expected = {"<div></div>", %{}}

    assert result == expected
  end

  test "'if' attribute which evaluates to a falsy value" do
    attrs = %{
      if: %{
        value: [
          %Expression{
            ir: %TupleType{
              data: [%NilType{}]
            }
          }
        ],
        modifiers: []
      }
    }

    element_node = %ElementNode{attrs: attrs, children: [], tag: "div"}

    result = Renderer.render(element_node, @conn, @bindings)
    expected = {"", %{}}

    assert result == expected
  end

  test "boolean attribute" do
    attrs = %{
      test_attr: %{
        value: nil,
        modifiers: []
      }
    }

    element_node = %ElementNode{attrs: attrs, children: [], tag: "div"}

    result = Renderer.render(element_node, @conn, @bindings)
    expected = {"<div test_attr></div>", %{}}

    assert result == expected
  end

  test "expression attribute which evaluates to false" do
    attrs = %{
      test_attr: %{
        value: [
          %Expression{
            ir: %TupleType{
              data: [%BooleanType{value: false}]
            }
          }
        ],
        modifiers: []
      }
    }

    element_node = %ElementNode{attrs: attrs, children: [], tag: "div"}

    result = Renderer.render(element_node, @conn, @bindings)
    expected = {"<div></div>", %{}}

    assert result == expected
  end

  test "expression attribute which evaluates to nil" do
    attrs = %{
      test_attr: %{
        value: [
          %Expression{
            ir: %TupleType{
              data: [%NilType{}]
            }
          }
        ],
        modifiers: []
      }
    }

    element_node = %ElementNode{attrs: attrs, children: [], tag: "div"}

    result = Renderer.render(element_node, @conn, @bindings)
    expected = {"<div test_attr></div>", %{}}

    assert result == expected
  end

  test "default slot" do
    element_node = %ElementNode{attrs: %{}, children: @children, tag: "slot"}

    slot_bindings = %{a: 987}
    slots = {slot_bindings, default: @children}

    result = Renderer.render(element_node, @conn, @bindings, slots)
    expected = {"abc987xyz", %{}}

    assert result == expected
  end

  test "nested components state" do
    [app_path: "#{@fixtures_path}/template/renderers/element_node_renderer"]
    |> compile()

    Runtime.run()

    component_1 = %Component{
      module: Module1,
      props: %{
        id: [
          %TextNode{content: "component_1_id"},
        ]
      }
    }

    component_2 = %Component{
      module: Module2,
      props: %{
        id: [
          %TextNode{content: "component_2_id"},
        ]
      }
    }

    children = [
      %TextNode{content: "abc"},
      component_1,
      %TextNode{content: "xyz"},
      component_2
    ]

    element_node = %ElementNode{attrs: %{}, children: children, tag: "div"}

    result = Renderer.render(element_node, @conn, @bindings)

    expected_initial_state = %{
      component_1_id: %{component_1_state_key: "component_1_state_value"},
      component_2_id: %{component_2_state_key: "component_2_state_value"},
    }

    expected_html = "<div>abc(in component 1)xyz(in component 2)</div>"
    expected = {expected_html, expected_initial_state}

    assert result == expected
  end
end


<|start_filename|>test/js/elixir/keyword_test.js<|end_filename|>
"use strict";

import {
  assert,
  assertBoxedFalse,
  assertBoxedTrue,
  assertFrozen,
  cleanup,
} from "../support/commons";
beforeEach(() => cleanup());

import Keyword from "../../../assets/js/hologram/elixir/keyword";
import Type from "../../../assets/js/hologram/type";

describe("delete()", () => {
  const key = Type.atom("a");

  const keywords1 = Type.list([
    Type.tuple([key, Type.integer(1)]),
    Type.tuple([Type.atom("b"), Type.integer(2)]),
  ]);

  const keywords2 = Type.list([Type.tuple([Type.atom("b"), Type.integer(2)])]);

  it("deletes the entry in the keyword list for specific a key when there is one matching entry", () => {
    const result = Keyword.delete(keywords1, key);
    assert.deepStrictEqual(result, keywords2);
  });

  it("deletes the entries in the keyword list for specific a key when there are multiple matching entries", () => {
    const keywords1 = Type.list([
      Type.tuple([key, Type.integer(1)]),
      Type.tuple([Type.atom("b"), Type.integer(2)]),
      Type.tuple([key, Type.integer(3)]),
    ]);

    const result = Keyword.delete(keywords1, key);

    assert.deepStrictEqual(result, keywords2);
  });

  it("returns the keyword list unchanged if there are no entries matching the given key", () => {
    const result = Keyword.delete(keywords2, key);
    assert.deepStrictEqual(result, keywords2);
  });

  it("returns frozen object", () => {
    const result = Keyword.delete(keywords1, key);
    assertFrozen(result);
  });
});

describe("get()", () => {
  const keywordElems = [
    Type.tuple([Type.atom("a"), Type.integer(1)]),
    Type.tuple([Type.atom("b"), Type.integer(2)]),
  ];

  const keywords = Type.list(keywordElems);

  it("gets the value for a specific key in keyword list if the given key exists in the given keyword list", () => {
    const result = Keyword.get(keywords, Type.atom("b"));
    assert.deepStrictEqual(result, Type.integer(2));
  });

  it("returns boxed nil by default if the given key doesn't exist in the given keyword list", () => {
    const result = Keyword.get(keywords, Type.atom("c"));
    assert.deepStrictEqual(result, Type.nil());
  });

  it("it returns the default_value arg if the given key doesn't exist in the given keyword list and the default_value param is specified", () => {
    const result = Keyword.get(keywords, Type.atom("c"), Type.integer(9));
    assert.deepStrictEqual(result, Type.integer(9));
  });
});

describe("has_key$question()", () => {
  let keywords;

  beforeEach(() => {
    keywords = Type.list([
      Type.tuple([Type.atom("a"), Type.integer(1)]),
      Type.tuple([Type.atom("b"), Type.integer(2)]),
    ]);
  });

  it("returns boxed true if the keyword list has the key", () => {
    const key = Type.atom("b");
    const result = Keyword.has_key$question(keywords, key);

    assertBoxedTrue(result);
  });

  it("returns boxed false if the keyword list doesn't have the key", () => {
    const key = Type.atom("c");
    const result = Keyword.has_key$question(keywords, key);

    assertBoxedFalse(result);
  });
});

describe("put()", () => {
  it("inserts the given key-value pair at the beginning of the keyword list", () => {
    const keywords = Type.list([Type.tuple([Type.atom("a"), Type.integer(1)])]);

    const result = Keyword.put(keywords, Type.atom("b"), Type.integer(2));

    const expected = Type.list([
      Type.tuple([Type.atom("b"), Type.integer(2)]),
      Type.tuple([Type.atom("a"), Type.integer(1)]),
    ]);

    assert.deepStrictEqual(result, expected);
  });

  it("removes the previous matching entry when the keyword list already has a single entry with the given key", () => {
    const keywords = Type.list([
      Type.tuple([Type.atom("a"), Type.integer(1)]),
      Type.tuple([Type.atom("b"), Type.integer(2)]),
    ]);

    const result = Keyword.put(keywords, Type.atom("a"), Type.integer(3));

    const expected = Type.list([
      Type.tuple([Type.atom("a"), Type.integer(3)]),
      Type.tuple([Type.atom("b"), Type.integer(2)]),
    ]);

    assert.deepStrictEqual(result, expected);
  });

  it("removes the previous matching entries when the keyword list already has multiple entries with the given key", () => {
    const keywords = Type.list([
      Type.tuple([Type.atom("a"), Type.integer(1)]),
      Type.tuple([Type.atom("b"), Type.integer(2)]),
      Type.tuple([Type.atom("a"), Type.integer(4)]),
    ]);

    const result = Keyword.put(keywords, Type.atom("a"), Type.integer(3));

    const expected = Type.list([
      Type.tuple([Type.atom("a"), Type.integer(3)]),
      Type.tuple([Type.atom("b"), Type.integer(2)]),
    ]);

    assert.deepStrictEqual(result, expected);
  });

  it("returns frozen object", () => {
    const result = Keyword.put(Type.list(), Type.atom("a"), Type.integer(1));
    assertFrozen(result);
  });
});


<|start_filename|>test/unit/fixtures/compiler/pruner/module_28.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module28 do
  def test_28a do
    :ok
  end
end


<|start_filename|>lib/hologram/compiler/call_graph_builder/expression.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.CallGraphBuilder
alias Hologram.Template.VDOM.Expression

defimpl CallGraphBuilder, for: Expression do
  def build(%{ir: ir}, module_defs, templates, from_vertex) do
    CallGraphBuilder.build(ir, module_defs, templates, from_vertex)
  end
end


<|start_filename|>test/unit/hologram/compiler/decoders/list_type_decoder_test.exs<|end_filename|>
defmodule Hologram.Compiler.ListTypeDecoderTest do
  use Hologram.Test.UnitCase, async: true
  alias Hologram.Compiler.ListTypeDecoder

  test "decode/1" do
    value = %{
      "type" => "list",
      "data" => [
        %{"type" => "integer", "value" => 1},
        %{"type" => "atom", "value" => "test"}
      ]
    }

    result = ListTypeDecoder.decode(value)
    expected = [1, :test]

    assert result == expected
  end
end


<|start_filename|>test/e2e/config/dev.exs<|end_filename|>
import Config

config :hologram_e2e, HologramE2EWeb.Endpoint,
  check_origin: false,
  code_reloader: false,
  debug_errors: true,
  http: [ip: {127, 0, 0, 1}, port: 4000],
  watchers: [
    esbuild: {Esbuild, :install_and_run, [:hologram, ~w(--sourcemap=inline --watch)]}
  ]

config :logger, :console, format: "[$level] $message\n"

config :phoenix, :stacktrace_depth, 20

config :phoenix, :plug_init_mode, :runtime


<|start_filename|>lib/hologram/compiler/transformers/strict_boolean_and_operator_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.StrictBooleanAndOperatorTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.StrictBooleanAndOperator

  def transform({:and, _, [left, right]}, %Context{} = context) do
    %StrictBooleanAndOperator{
      left: Transformer.transform(left, context),
      right: Transformer.transform(right, context)
    }
  end
end


<|start_filename|>test/e2e/test/commands_test.exs<|end_filename|>
defmodule HologramE2E.CommandsTest do
  use HologramE2E.TestCase, async: false

  describe "command spec" do
    feature "command spec without target and params specified as text", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-1"))
      |> assert_has(css("#text-page-4", text: "text updated by action_1_b, state.value = p4"))
    end

    feature "command spec without target and params specified as 1-element tuple", %{
      session: session
    } do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-2"))
      |> assert_has(css("#text-page-4", text: "text updated by action_2_b, state.value = p4"))
    end

    feature "command spec with params", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-3"))
      |> assert_has(
        css("#text-page-4",
          text: "text updated by action_3_b, params.a = 50, params.b = 60, state.value = p4"
        )
      )
    end

    feature "command spec with target ID", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-4"))
      |> assert_has(
        css("#text-component-4", text: "text updated by component_4_action_1_b, state.value = c4")
      )
    end

    feature "command spec with targetID and params", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-5"))
      |> assert_has(
        css("#text-component-4",
          text:
            "text updated by component_4_action_2_b, params.a = 50, params.b = 60, state.value = c4"
        )
      )
    end
  end

  describe "command result" do
    feature "command returning action only, not wrapped in tuple", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-6"))
      |> assert_has(css("#text-page-4", text: "text updated by action_4_b, state.value = p4"))
    end

    feature "command returning action only, wrapped in tuple", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-7"))
      |> assert_has(css("#text-page-4", text: "text updated by action_5_b, state.value = p4"))
    end

    feature "command returning action and params", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-8"))
      |> assert_has(
        css("#text-page-4",
          text: "text updated by action_6_b, params.a = 50, params.b = 60, state.value = p4"
        )
      )
    end

    feature "command returning target ID and action", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-9"))
      |> assert_has(
        css("#text-component-4", text: "text updated by component_4_action_3_b, state.value = c4")
      )
    end

    feature "command returning target ID, action and params", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-10"))
      |> assert_has(
        css("#text-component-4",
          text:
            "text updated by component_4_action_4_b, params.a = 50, params.b = 60, state.value = c4"
        )
      )
    end
  end

  describe "target" do
    feature "default target", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-11"))
      |> assert_has(css("#text-page-4", text: "text updated by action_9_b, state.value = p4"))
    end

    feature "component target", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-12"))
      |> assert_has(
        css("#text-component-4", text: "text updated by component_4_action_5_b, state.value = c4")
      )
    end

    feature "page target", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#component-4-button-1"))
      |> assert_has(css("#text-page-4", text: "text updated by action_10_b, state.value = p4"))
    end

    feature "layout target", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-13"))
      |> assert_has(
        css("#text-default-layout",
          text: "text updated by default_layout_action_2_b, state.value = dl"
        )
      )
    end
  end

  describe "command trigger" do
    feature "command triggered by event", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-14"))
      |> assert_has(css("#text-page-4", text: "text updated by action_11_b, state.value = p4"))
    end

    feature "command trigerred by action", %{session: session} do
      session
      |> visit("/e2e/page-4")
      |> click(css("#page-4-button-15"))
      |> assert_has(css("#text-page-4", text: "text updated by action_12_b, state.value = p4"))
    end
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregator.ex<|end_filename|>
defprotocol Hologram.Compiler.ModuleDefAggregator do
  # TODO: disable fallback
  @fallback_to_any true

  def aggregate(ir)
end


<|start_filename|>test/unit/fixtures/placeholder_module_4.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.PlaceholderModule4 do
  def test_fun_4a, do: nil
  def test_fun_4b, do: nil
end


<|start_filename|>test/unit/hologram/compiler/js_encoders/relaxed_boolean_and_operator_test.exs<|end_filename|>
defmodule Hologram.Compiler.JSEncoder.RelaxedBooleanAndOperatorTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, JSEncoder, Opts}
  alias Hologram.Compiler.IR.{IntegerType, RelaxedBooleanAndOperator}

  test "encode/3" do
    ir = %RelaxedBooleanAndOperator{
      left: %IntegerType{value: 1},
      right: %IntegerType{value: 2}
    }

    result = JSEncoder.encode(ir, %Context{}, %Opts{})

    expected =
      "Hologram.Interpreter.$relaxed_boolean_and_operator({ type: 'integer', value: 1 }, { type: 'integer', value: 2 })"

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/runtime/layout/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Runtime.Layout.Module1 do
  use Hologram.Layout
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/runtime/actions_page.ex<|end_filename|>
defmodule HologramE2E.Runtime.ActionsPage do
  use Hologram.Page
  alias HologramE2E.Component3, warn: false

  route "/e2e/runtime/actions"

  def init(_params, _conn) do
    %{
      text: "",
      value: :p1,
      x: 0
    }
  end

  def template do
    ~H"""
    <button id="button-1" on:click="action_1">Action 1</button>
    <button id="button-2" on:click={:action_2}>Action 2</button>
    <button id="button-3" on:click={:action_3, a: 5, b: 6}>Action 3</button>
    <button id="button-4" on:click={:component_3_id, :component_3_action_1}>Component 3 Action 1</button>
    <button id="button-5" on:click={:component_3_id, :component_3_action_2, a: 5, b: 6}>Component 3 Action 2</button>
    <button id="button-6" on:click="action_4">Action 4</button>
    <button id="button-7" on:click="action_5">Action 5</button>
    <button id="button-8" on:click="action_6">Action 6</button>
    <button id="button-9" on:click="action_7">Action 7</button>
    <button id="button-10" on:click="action_8">Action 8</button>
    <button id="button-11" on:click="action_9">Action 9</button>
    <button id="button-12" on:click="action_10">Action 10</button>
    <button id="button-13" on:click={:component_3_id, :component_3_action_3}>Component 3 Action 3</button>
    <button id="button-14" on:click={:page, :action_11}>Action 11</button>
    <button id="button-15" on:click={:layout, :default_layout_action_1}>Layout Action 1</button>
    <button id="button-16" on:click="action_12">Action 12</button>
    <button id="button-17" on:click="action_13">Action 13</button>
    <br />

    <div id="text">{@text}</div><br />

    <Component3 id="component_3_id" /><br />
    """
  end

  def action(:action_1, _params, state) do
    put(state, :text, "text updated by action_1, state.value = #{state.value}")
  end

  def action(:action_2, _params, state) do
    put(state, :text, "text updated by action_2, state.value = #{state.value}")
  end

  def action(:action_3, params, state) do
    put(
      state,
      :text,
      "text updated by action_3, params.a = #{params.a}, params.b = #{params.b}, state.value = #{state.value}"
    )
  end

  def action(:action_4, _params, state) do
    {put(state, :text, "text updated by action_4, state.value = #{state.value}")}
  end

  def action(:action_5, _params, state) do
    {state, :command_1}
  end

  def action(:action_5_b, _params, state) do
    put(
      state,
      :text,
      "text updated by action_5_b triggered by command_1, state.value = #{state.value}"
    )
  end

  def action(:action_6, _params, state) do
    {state, :command_2, a: 1, b: 2}
  end

  def action(:action_6_b, params, state) do
    put(
      state,
      :text,
      "text updated by action_6_b triggered by command_2, params.a = #{params.a}, params.b = #{params.b}, state.value = #{state.value}"
    )
  end

  def action(:action_7, _params, state) do
    {state, :component_3_id, :component_3_command_1}
  end

  def action(:action_7_b, _params, state) do
    put(
      state,
      :text,
      "text updated by action_7_b triggered by component_3_command_1, state.value = #{state.value}"
    )
  end

  def action(:action_8, _params, state) do
    {state, :component_3_id, :component_3_command_2, a: 1, b: 2}
  end

  def action(:action_8_b, params, state) do
    put(
      state,
      :text,
      "text updated by action_8_b triggered by component_3_command_2, params.a = #{params.a}, params.b = #{params.b}, state.value = #{state.value}"
    )
  end

  def action(:action_9, _params, state) do
    put(state, :text, "text updated by action_9, state.value = #{state.value}")
  end

  def action(:action_10, _params, state) do
    put(state, :text, "text updated by action_10, state.value = #{state.value}")
  end

  def action(:action_11, _params, state) do
    put(state, :text, "text updated by action_11, state.value = #{state.value}")
  end

  def action(:action_12, _params, state) do
    put(state, :text, "text updated by action_12, state.value = #{state.value}")
  end

  def action(:action_13, _params, state) do
    {state, :command_3}
  end

  def action(:action_13_b, _params, state) do
    put(state, :text, "text updated by action_13_b, state.value = #{state.value}")
  end

  def command(:command_1, _params) do
    :action_5_b
  end

  def command(:command_2, params) do
    {:action_6_b, a: params.a * 10, b: params.b * 10}
  end

  def command(:command_3, _params) do
    :action_13_b
  end
end


<|start_filename|>test/e2e/lib/hologram_e2e_web/pages/page_6.ex<|end_filename|>
defmodule HologramE2E.Page6 do
  use Hologram.Page

  route "/e2e/page-6"

  def init(_params, _conn) do
    %{}
  end

  def template do
    ~H"""
    <h1>Page 6</h1>
    <Link to={HologramE2E.Page7} class="test-class" id="test-id">
      Anchor text
    </Link>
    """
  end
end


<|start_filename|>test/unit/fixtures/compiler/reflection/module_3.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Reflection.Module3 do
  def template, do: nil
end


<|start_filename|>lib/hologram/compiler/js_encoders/module_type.ex<|end_filename|>
alias Hologram.Compiler.{Helpers, JSEncoder}
alias Hologram.Compiler.IR.ModuleType

defimpl JSEncoder, for: ModuleType do
  def encode(%{module: module}, _, _) do
    "{ type: 'module', className: '#{Helpers.class_name(module)}' }"
  end
end


<|start_filename|>test/unit/hologram/template/evaluators/atom_type_test.exs<|end_filename|>
defmodule Hologram.Template.Evaluator.AtomTypeTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.IR.AtomType
  alias Hologram.Template.Evaluator

  test "evaluate/2" do
    ir = %AtomType{value: :a}

    result = Evaluator.evaluate(ir, %{})
    expected = :a

    assert result == expected
  end
end


<|start_filename|>lib/mix/tasks/holo.assets.clean.ex<|end_filename|>
defmodule Mix.Tasks.Holo.Assets.Clean do
  def run(_) do
    Mix.Task.run("phx.digest.clean", ["--all"])
  end
end


<|start_filename|>lib/hologram/compiler/transformers/cons_operator_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.ConsOperatorTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.ConsOperator

  def transform([{:|, _, [head, tail]}], %Context{} = context) do
    %ConsOperator{
      head: Transformer.transform(head, context),
      tail: Transformer.transform(tail, context)
    }
  end
end


<|start_filename|>lib/hologram/compiler/decoders/list_type_decoder.ex<|end_filename|>
defmodule Hologram.Compiler.ListTypeDecoder do
  alias Hologram.Compiler.Decoder

  def decode(%{"data" => data}) do
    Enum.map(data, &Decoder.decode/1)
  end
end


<|start_filename|>test/e2e/test/test_helper.exs<|end_filename|>
alias Hologram.Compiler.Reflection
alias HologramE2EWeb.Endpoint

ExUnit.start()

(Reflection.release_static_path() <> "/hologram/runtime*")
|> Path.wildcard()
|> Enum.each(&File.rm!/1)

Mix.Task.run("holo.assets.build")

{:ok, _} = Application.ensure_all_started(:wallaby)
Application.put_env(:wallaby, :base_url, Endpoint.url())


<|start_filename|>test/unit/fixtures/compiler/pruner/module_13.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module13 do
  def test_fun_13a do
    :ok
  end
end


<|start_filename|>lib/hologram/compiler/js_encoders/function_call.ex<|end_filename|>
alias Hologram.Compiler.{Context, Helpers, JSEncoder, Opts}
alias Hologram.Compiler.IR.FunctionCall
alias Hologram.Template.{Parser, Transformer}
alias Hologram.Template.Encoder, as: TemplateEncoder

defimpl JSEncoder, for: FunctionCall do
  use Hologram.Commons.Encoder

  def encode(%{function: :sigil_H} = ir, %Context{} = context, _) do
    ir
    |> Map.get(:args)
    |> hd()
    |> Map.get(:parts)
    |> hd()
    |> Map.get(:value)
    |> String.trim()
    |> Parser.parse!()
    |> Transformer.transform(context)
    |> TemplateEncoder.encode()
  end

  def encode(
        %{module: module, function: function, args: args},
        %Context{} = context,
        %Opts{} = opts
      ) do
    class_name = Helpers.class_name(module)
    function = encode_identifier(function)
    args = encode_args(args, context, opts)

    "#{class_name}.#{function}(#{args})"
  end
end


<|start_filename|>lib/hologram/compiler/transformers/pipe_operator_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.PipeOperatorTransformer do
  alias Hologram.Compiler.{Context, Transformer}

  # based on: https://ianrumford.github.io/elixir/pipe/clojure/thread-first/macro/2016/07/24/writing-your-own-elixir-pipe-operator.html
  def transform(ast, %Context{} = context) do
    [{first_ast, _index} | rest_tuples] = Macro.unpipe(ast)

    rest_tuples
    |> Enum.reduce(first_ast, fn {rest_ast, rest_index}, this_ast ->
      Macro.pipe(this_ast, rest_ast, rest_index)
    end)
    |> Transformer.transform(context)
  end
end


<|start_filename|>test/unit/fixtures/template/builder/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.Builder.Module1 do
  use Hologram.Component
  alias Hologram.Test.Fixtures.Template.Builder.Module2, warn: false

  def template do
    ~H"""
    <div>test template</div>
    <Module2></Module2>
    """
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/map_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.MapType
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: MapType do
  def aggregate(%{data: data}) do
    ModuleDefAggregator.aggregate(data)
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/function_call_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.FunctionCallTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, FunctionCallTransformer}

  alias Hologram.Compiler.IR.{
    AtomType,
    FunctionCall,
    IntegerType,
    ListType,
    ModuleAttributeOperator,
    NotSupportedExpression,
    Variable
  }

  test "function without params called on module" do
    code = "Hologram.Compiler.FunctionCallTransformerTest.test()"
    ast = ast(code)

    expected = %FunctionCall{
      module: Hologram.Compiler.FunctionCallTransformerTest,
      function: :test,
      args: []
    }

    result = FunctionCallTransformer.transform(ast, %Context{})
    assert result == expected
  end

  test "function with params called on module" do
    code = "Hologram.Compiler.FunctionCallTransformerTest.test(1, 2)"
    ast = ast(code)

    expected = %FunctionCall{
      module: Hologram.Compiler.FunctionCallTransformerTest,
      function: :test,
      args: [
        %IntegerType{value: 1},
        %IntegerType{value: 2}
      ]
    }

    result = FunctionCallTransformer.transform(ast, %Context{})
    assert result == expected
  end

  test "function without params called without module" do
    code = "make_ref()"
    ast = ast(code)

    expected = %FunctionCall{
      module: Kernel,
      function: :make_ref,
      args: []
    }

    result = FunctionCallTransformer.transform(ast, %Context{})
    assert result == expected
  end

  test "function with params called without module" do
    code = "max(1, 2)"
    ast = ast(code)

    expected = %FunctionCall{
      module: Kernel,
      function: :max,
      args: [
        %IntegerType{value: 1},
        %IntegerType{value: 2}
      ]
    }

    result = FunctionCallTransformer.transform(ast, %Context{})
    assert result == expected
  end

  test "string interpolation" do
    code = ~S("#{test}")
    {_, _, [{_, _, [ast, _]}]} = ast(code)

    expected = %FunctionCall{
      module: Kernel,
      function: :to_string,
      args: [%Variable{name: :test}]
    }

    result = FunctionCallTransformer.transform(ast, %Context{})
    assert result == expected
  end

  test "function call on __MODULE__ macro result" do
    code = "__MODULE__.test(1, 2)"
    ast = ast(code)
    context = %Context{module: Hologram.Test.Fixtures.PlaceholderModule1}

    result = FunctionCallTransformer.transform(ast, context)

    expected = %FunctionCall{
      function: :test,
      module: Hologram.Test.Fixtures.PlaceholderModule1,
      args: [
        %IntegerType{value: 1},
        %IntegerType{value: 2}
      ]
    }

    assert result == expected
  end

  test "Erlang function call" do
    code = ":timer.sleep(1_000)"
    ast = ast(code)

    result = FunctionCallTransformer.transform(ast, %Context{})
    expected = %NotSupportedExpression{ast: ast, type: :erlang_function_call}

    assert result == expected
  end

  test "function called on expression" do
    code = "@test.abc(1, 2)"
    ast = ast(code)

    result = FunctionCallTransformer.transform(ast, %Context{})

    expected = %FunctionCall{
      function: :apply,
      module: Kernel,
      args: [
        %ModuleAttributeOperator{name: :test},
        %AtomType{value: :abc},
        %ListType{
          data: [
            %IntegerType{value: 1},
            %IntegerType{value: 2}
          ]
        }
      ]
    }

    assert result == expected
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_27.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module27 do
  use Hologram.Component
  alias Hologram.Test.Fixtures.Compiler.Pruner.Module28, warn: false

  def template do
    ~H"""
    {Module28.test_28a()}
    """
  end
end


<|start_filename|>assets/js/hologram/elixir/map.js<|end_filename|>
"use strict";

import Type from "../type";
import Utils from "../utils"

export default class Map {
  static get(map, key, default_value = Type.nil()) {
    if (Map.has_key$question(map, key).value) {
      return map.data[Type.encodedKey(key)]
      
    } else {
      return default_value
    }
  }

  static has_key$question(map, key) {
    if (map.data.hasOwnProperty(Type.encodedKey(key))) {
      return Type.boolean(true)

    } else {
      return Type.boolean(false)
    }
  }

  static keys(map) {
    const keysData = Object.keys(map.data).reduce((acc, key) => {
      acc.push(Type.decodeKey(key))
      return acc
    }, [])

    return Type.list(keysData)
  }

  static put(map, key, value) {
    let newMap = Utils.clone(map)
    newMap.data[Type.encodedKey(key)] = value

    return Utils.freeze(newMap);
  }

  static to_list(map) {
    const data = Object.keys(map.data).reduce((acc, encodedKey) => {
      const key = Type.decodeKey(encodedKey)
      const value = map.data[encodedKey]
      acc.push(Type.tuple([key, value]))
      return acc
    }, [])

    return Type.list(data)
  }
}

<|start_filename|>lib/hologram/template/encoders/expression.ex<|end_filename|>
alias Hologram.Compiler.{Context, Opts}
alias Hologram.Compiler.JSEncoder
alias Hologram.Template.VDOM.Expression
alias Hologram.Template.Encoder

defimpl Encoder, for: Expression do
  def encode(%{ir: ir}) do
    # DEFER: pass actual %Context{} struct received from compiler
    callback_return = JSEncoder.encode(ir, %Context{}, %Opts{template: true})

    "{ type: 'expression', callback: ($bindings) => { return #{callback_return} } }"
  end
end


<|start_filename|>test/unit/fixtures/compiler/module_def_aggregators/component/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.ModuleDefAggregators.Component.Module2 do
  def test_fun_2a, do: nil
end


<|start_filename|>lib/hologram/compiler/transformers/function_definition_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.FunctionDefinitionTransformer do
  alias Hologram.Compiler.{Context, Helpers, Transformer}
  alias Hologram.Compiler.IR.{FunctionDefinition, FunctionHead}

  def transform( {def_type, _, [{name, _, params}, [do: body]]}, %Context{} = context) when is_list(params) do
    build_function_definition(name, params, body, def_type, context)
  end

  def transform({def_type, _, [{name, _, _}, [do: body]]}, %Context{} = context) do
    build_function_definition(name, [], body, def_type, context)
  end

  # DEFER: implement
  def transform(_, %Context{}) do
    %FunctionHead{}
  end

  defp build_function_definition(name, params, body, def_type, %{module: module} = context) do
    params = Helpers.transform_params(params, context)
    arity = Enum.count(params)
    bindings = Helpers.aggregate_bindings_from_params(params)
    body = Transformer.transform(body, context)
    visibility = if def_type == :def, do: :public, else: :private

    %FunctionDefinition{
      module: module,
      name: name,
      arity: arity,
      params: params,
      bindings: bindings,
      body: body,
      visibility: visibility
    }
  end
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_22.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module22 do
  def test_22a do
    :ok
  end
end


<|start_filename|>lib/hologram/template/evaluators/integer_type.ex<|end_filename|>
alias Hologram.Compiler.IR.IntegerType
alias Hologram.Template.Evaluator

defimpl Evaluator, for: IntegerType do
  def evaluate(%{value: value}, _) do
    value
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/module_attribute_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.ModuleAttributeOperator
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: ModuleAttributeOperator do
  def aggregate(_), do: nil
end


<|start_filename|>lib/hologram/compiler/js_encoders/binary_type.ex<|end_filename|>
alias Hologram.Compiler.{Context, JSEncoder, Opts}
alias Hologram.Compiler.IR.BinaryType

defimpl JSEncoder, for: BinaryType do
  use Hologram.Commons.Encoder

  def encode(%{parts: parts}, %Context{} = context, %Opts{} = opts) do
    "{ type: 'binary', data: #{encode_as_array(parts, context, opts)} }"
  end
end


<|start_filename|>test/unit/fixtures/compiler/transformers/alias_directive_transformer/module_1.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.AliasDirectiveTransformer.Module1 do
end


<|start_filename|>lib/hologram/compiler/normalizer.ex<|end_filename|>
defmodule Hologram.Compiler.Normalizer do
  def normalize({:case, line, [condition, [do: clauses]]}) do
    {:case, line, [condition, [do: normalize(clauses)]]}
  end

  def normalize({:->, line, [pattern, {:__block__, [], exprs}]}) do
    {:->, line, [pattern, {:__block__, [], normalize(exprs)}]}
  end

  def normalize({:->, line, [pattern, expr]}) do
    {:->, line, [pattern, {:__block__, [], [normalize(expr)]}]}
  end

  def normalize({:if, line, [condition, [do: do_exprs]]}) do
    normalize({:if, line, [condition, [do: do_exprs, else: nil]]})
  end

  def normalize({:if, line, [condition, [do: do_exprs, else: else_exprs]]}) do
    {:if, line, [condition, normalize(do: do_exprs) ++ normalize(else: else_exprs)]}
  end

  def normalize(do: {:__block__, [], exprs}) do
    [do: {:__block__, [], Enum.map(exprs, &normalize/1)}]
  end

  def normalize(do: expr) do
    [do: {:__block__, [], [normalize(expr)]}]
  end

  def normalize(else: {:__block__, [], exprs}) do
    [else: {:__block__, [], Enum.map(exprs, &normalize/1)}]
  end

  def normalize(else: expr) do
    [else: {:__block__, [], [normalize(expr)]}]
  end

  def normalize(ast) when is_list(ast) do
    Enum.map(ast, &normalize/1)
  end

  def normalize({elem_1, elem_2, elem_3}) do
    {
      normalize(elem_1),
      normalize(elem_2),
      normalize(elem_3)
    }
  end

  def normalize(ast), do: ast
end


<|start_filename|>test/unit/fixtures/template/builder/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Template.Builder.Module2 do
  use Hologram.Component

  def template do
    ~H"""
    <div>test template</div>
    """
  end
end


<|start_filename|>assets/webpack.config.test.js<|end_filename|>
const nodeExternals = require("webpack-node-externals");

module.exports = {
  target: "node", // Webpack should emit Node.js compatible code
  externals: [nodeExternals()], // in order to ignore all modules in node_modules folder from bundling
};

<|start_filename|>test/js/target_test.js<|end_filename|>
"use strict";

import { assert, cleanup } from "./support/commons";
beforeEach(() => cleanup())

import Runtime from "../../assets/js/hologram/runtime"
import Target from "../../assets/js/hologram/target"
import Type from "../../assets/js/hologram/type"

describe("constructor()", () => {
  it("constructs Target object", () => {
    const id = "test_id"
    const TestComponentClass = class {}
    Runtime.registerComponentClass(id, TestComponentClass)

    const result = new Target(id)

    assert.isTrue(result instanceof Target)
    assert.equal(result.id, id)
    assert.equal(result.class, TestComponentClass)
    assert.deepStrictEqual(result.module, Type.module("TestComponentClass"))
  })
})

<|start_filename|>lib/hologram/compiler/transformers/type_operator_transformer.ex<|end_filename|>
defmodule Hologram.Compiler.TypeOperatorTransformer do
  alias Hologram.Compiler.{Context, Transformer}
  alias Hologram.Compiler.IR.TypeOperator

  def transform({:"::", _, [left, {right, _, _}]}, %Context{} = context) do
    left = Transformer.transform(left, context)
    %TypeOperator{left: left, right: right}
  end
end


<|start_filename|>lib/hologram/runtime/template_store.ex<|end_filename|>
defmodule Hologram.Runtime.TemplateStore do
  use Hologram.Commons.MemoryStore
  alias Hologram.Compiler.Reflection

  @impl true
  def populate_table do
    Reflection.release_template_store_path()
    |> populate_table_from_file()
  end

  @impl true
  def table_name, do: :hologram_template_store
end


<|start_filename|>lib/hologram/compiler/js_encoders/module_attribute_definition.ex<|end_filename|>
alias Hologram.Compiler.{Context, JSEncoder, Opts}
alias Hologram.Compiler.IR.ModuleAttributeDefinition

defimpl JSEncoder, for: ModuleAttributeDefinition do
  def encode(%{name: name, value: value}, %Context{} = context, %Opts{} = opts) do
    value = JSEncoder.encode(value, context, opts)
    "static $#{name} = #{value};"
  end
end


<|start_filename|>lib/hologram/template/renderers/node_list.ex<|end_filename|>
alias Hologram.Template.Renderer

defimpl Renderer, for: List do
  def render(nodes, conn, bindings, slots) do
    Enum.reduce(nodes, {"", %{}}, fn node, {html, initial_state} ->
      {node_html, node_initial_state} = Renderer.render(node, conn, bindings, slots)
      {html <> node_html, Map.merge(initial_state, node_initial_state)}
    end)
  end
end


<|start_filename|>test/js/elixir/enum_test.js<|end_filename|>
"use strict";

import {
  assert,
  assertBoxedFalse,
  assertBoxedTrue,
  assertFrozen,
  cleanup,
} from "../support/commons";
beforeEach(() => cleanup());

import Enum from "../../../assets/js/hologram/elixir/enum";
import { HologramNotImplementedError } from "../../../assets/js/hologram/errors";
import Interpreter from "../../../assets/js/hologram/interpreter";
import Map from "../../../assets/js/hologram/elixir/map";
import Type from "../../../assets/js/hologram/type";

describe("concat()", () => {
  it("concatanates 2 enumerables to a boxed list", () => {
    let map = Type.map();
    map = Map.put(map, Type.atom("a"), Type.integer(1));
    map = Map.put(map, Type.atom("b"), Type.integer(2));

    const list = Type.list([Type.atom("c"), Type.integer(3)]);

    const result = Enum.concat(map, list);

    const expected = Type.list([
      Type.tuple([Type.atom("a"), Type.integer(1)]),
      Type.tuple([Type.atom("b"), Type.integer(2)]),
      Type.atom("c"),
      Type.integer(3),
    ]);

    assert.deepStrictEqual(result, expected);
    assertFrozen(result);
  });
});

describe("count()", () => {
  it("returns the size of the enumerable", () => {
    const enumerable = Type.list([Type.integer(1), Type.integer(2), Type.integer(3)])
    const result = Enum.count(enumerable)
    const expected = Type.integer(3)

    assert.deepStrictEqual(result, expected)
  })
})

describe("member$question()", () => {
  let list;

  beforeEach(() => {
    const elems = [Type.integer(1), Type.integer(2)];
    list = Type.list(elems);
  });

  it("returns boxed true boolean value if the list contains the element", () => {
    const elem = Type.integer(2);
    const result = Enum.member$question(list, elem);

    assertBoxedTrue(result);
  });

  it("returns boxed false boolean value if the list doesn't contain the element", () => {
    const elem = Type.integer(3);
    const result = Enum.member$question(list, elem);

    assertBoxedFalse(result);
  });

  it("uses strictly equal to operator", () => {
    const elems = [Type.float(1.0), Type.float(2.0), Type.float(3.0)];
    const list = Type.list(elems);

    const result1 = Enum.member$question(list, Type.integer(2));
    assertBoxedFalse(result1);

    const result2 = Enum.member$question(list, Type.float(2.0));
    assertBoxedTrue(result2);
  });

  it("throws an error for not implemented enumerable types", () => {
    const enumerable = { type: "not implemented", value: "test" };
    const elem = Type.integer(1);
    const expectedMessage =
      'Enum.member$question(): enumerable = {"type":"not implemented","value":"test"}, elem = {"type":"integer","value":1}';

    assert.throw(
      () => {
        Enum.member$question(enumerable, elem);
      },
      HologramNotImplementedError,
      expectedMessage
    );
  });
});

describe("reduce()", () => {
  it("invokes fun for each element in the enumerable with the accumulator", () => {
    const enumerable = Type.list([Type.integer(1), Type.integer(2), Type.integer(3)])
    const acc = Type.integer(10)

    const fun = Type.anonymousFunction(function(elem, acc) {
      return Interpreter.$addition_operator(acc, Interpreter.$multiplication_operator(elem, elem))
    })

    const result = Enum.reduce(enumerable, acc, fun)
    const expected = Type.integer(24)

    assert.deepStrictEqual(result, expected)
  })
})

describe("to_list()", () => {
  it("returns the given arg if it is a boxed list", () => {
    const list = Type.list([Type.atom("a"), Type.integer(2)]);
    const result = Enum.to_list(list);

    assert.deepStrictEqual(result, list);
    assertFrozen(result);
  });

  it("converts a boxed map to a boxed list", () => {
    let map = Type.map();
    map = Map.put(map, Type.atom("a"), Type.integer(1));
    map = Map.put(map, Type.atom("b"), Type.integer(2));

    const result = Enum.to_list(map);

    const expected = Type.list([
      Type.tuple([Type.atom("a"), Type.integer(1)]),
      Type.tuple([Type.atom("b"), Type.integer(2)]),
    ]);

    assert.deepStrictEqual(result, expected);
    assertFrozen(result);
  });

  it("throws an error for not implemented enumerable types", () => {
    const enumerable = { type: "not implemented", value: "test" };
    const expectedMessage =
      'Enum.to_list(): enumerable = {"type":"not implemented","value":"test"}';
    assert.throw(
      () => {
        Enum.to_list(enumerable);
      },
      HologramNotImplementedError,
      expectedMessage
    );
  });
});


<|start_filename|>test/unit/fixtures/compiler/expander/module_4.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Expander.Module4 do
  defmacro __using__(_) do
    quote do
      import Hologram.Test.Fixtures.Compiler.Expander.Module3
    end
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/match_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.MatchOperator
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: MatchOperator do
  def aggregate(%{right: right}) do
    ModuleDefAggregator.aggregate(right)
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/elixir_list.ex<|end_filename|>
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: List do
  def aggregate(list) do
    Enum.each(list, &ModuleDefAggregator.aggregate/1)
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/cons_operator.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.ConsOperator
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: ConsOperator do
  def aggregate(%{head: head, tail: tail}) do
    ModuleDefAggregator.aggregate(head)
    ModuleDefAggregator.aggregate(tail)
  end
end


<|start_filename|>test/unit/fixtures/runtime/channel/module_7.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Runtime.Channel.Module7 do
  use Hologram.Page

  route "/test-route-7"

  def init(_params, _conn) do
    %{}
  end

  def template do
    ~H"""
    """
  end

  def command(:test_command, _params) do
    {:test_action_target_id, :test_action}
  end
end


<|start_filename|>test/unit/hologram/compiler/transformers/equal_to_operator_transformer_test.exs<|end_filename|>
defmodule Hologram.Compiler.EqualToOperatorTransformerTest do
  use Hologram.Test.UnitCase, async: true

  alias Hologram.Compiler.{Context, EqualToOperatorTransformer}
  alias Hologram.Compiler.IR.{EqualToOperator, IntegerType}

  test "transform/3" do
    code = "1 == 2"
    ast = ast(code)

    result = EqualToOperatorTransformer.transform(ast, %Context{})

    expected = %EqualToOperator{
      left: %IntegerType{value: 1},
      right: %IntegerType{value: 2}
    }

    assert result == expected
  end
end


<|start_filename|>lib/hologram/compiler/module_def_aggregators/string_type.ex<|end_filename|>
# TODO: test

alias Hologram.Compiler.IR.StringType
alias Hologram.Compiler.ModuleDefAggregator

defimpl ModuleDefAggregator, for: StringType do
  def aggregate(_), do: nil
end


<|start_filename|>test/unit/hologram/compiler/pattern_deconstructors/variable_test.exs<|end_filename|>
defmodule Hologram.Compiler.PatternDeconstructor.VariableTest do
  use Hologram.Test.UnitCase, async: false

  alias Hologram.Compiler.IR.Variable
  alias Hologram.Compiler.PatternDeconstructor

  test "deconstruct/2" do
    ir = %Variable{name: :test}
    result = PatternDeconstructor.deconstruct(ir)
    expected = [[%Variable{name: :test}]]

    assert result == expected
  end
end


<|start_filename|>lib/hologram/template/evaluators/atom_type.ex<|end_filename|>
alias Hologram.Compiler.IR.AtomType
alias Hologram.Template.Evaluator

defimpl Evaluator, for: AtomType do
  def evaluate(%{value: value}, _) do
    value
  end
end


<|start_filename|>lib/hologram/runtime/conn.ex<|end_filename|>
defmodule Hologram.Conn do
  defstruct [params: %{}, session: %{}]
end


<|start_filename|>test/unit/fixtures/compiler/pruner/module_47.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.Pruner.Module47 do
  def test_fun_47a do
    :ok
  end
end


<|start_filename|>lib/hologram/compiler/ir.ex<|end_filename|>
defmodule Hologram.Compiler.IR do
  # TYPES

  defmodule AnonymousFunctionType do
    defstruct arity: nil, params: [], bindings: [], body: nil
  end

  defmodule AtomType do
    defstruct value: nil
  end

  defmodule BinaryType do
    defstruct parts: []
  end

  defmodule BooleanType do
    defstruct value: nil
  end

  defmodule FloatType do
    defstruct value: nil
  end

  defmodule IntegerType do
    defstruct value: nil
  end

  defmodule ListType do
    defstruct data: []
  end

  defmodule MapType do
    defstruct data: []
  end

  defmodule ModuleType do
    defstruct module: nil
  end

  defmodule NilType do
    defstruct []
  end

  defmodule StringType do
    defstruct value: nil
  end

  defmodule StructType do
    defstruct module: nil, data: []
  end

  defmodule TupleType do
    defstruct data: []
  end

  # OPERATORS

  defmodule AccessOperator do
    defstruct data: nil, key: nil
  end

  defmodule AdditionOperator do
    defstruct left: nil, right: nil
  end

  defmodule ConsOperator do
    defstruct head: nil, tail: nil
  end

  defmodule DivisionOperator do
    defstruct left: nil, right: nil
  end

  defmodule DotOperator do
    defstruct left: nil, right: nil
  end

  defmodule EqualToOperator do
    defstruct left: nil, right: nil
  end

  defmodule LessThanOperator do
    defstruct left: nil, right: nil
  end

  defmodule ListConcatenationOperator do
    defstruct left: nil, right: nil
  end

  defmodule ListSubtractionOperator do
    defstruct left: nil, right: nil
  end

  defmodule MatchOperator do
    defstruct bindings: [], left: nil, right: nil
  end

  defmodule MembershipOperator do
    defstruct left: nil, right: nil
  end

  defmodule ModuleAttributeOperator do
    defstruct name: nil
  end

  defmodule MultiplicationOperator do
    defstruct left: nil, right: nil
  end

  defmodule NotEqualToOperator do
    defstruct left: nil, right: nil
  end

  defmodule RelaxedBooleanAndOperator do
    defstruct left: nil, right: nil
  end

  defmodule RelaxedBooleanNotOperator do
    defstruct value: nil
  end

  defmodule RelaxedBooleanOrOperator do
    defstruct left: nil, right: nil
  end

  defmodule StrictBooleanAndOperator do
    defstruct left: nil, right: nil
  end

  defmodule SubtractionOperator do
    defstruct left: nil, right: nil
  end

  defmodule TypeOperator do
    defstruct left: nil, right: nil
  end

  defmodule UnaryNegativeOperator do
    defstruct value: nil
  end

  defmodule UnaryPositiveOperator do
    defstruct value: nil
  end

  # DEFINITIONS

  defmodule FunctionDefinition do
    defstruct module: nil,
              name: nil,
              arity: nil,
              params: [],
              bindings: [],
              body: nil,
              visibility: nil
  end

  defmodule FunctionDefinitionVariants do
    defstruct name: nil, variants: []
  end

  # DEFER: implement
  defmodule FunctionHead do
    defstruct []
  end

  defmodule MacroDefinition do
    defstruct module: nil, name: nil, arity: nil, params: [], bindings: [], body: nil
  end

  defmodule ModuleDefinition do
    defstruct module: nil,
              uses: [],
              imports: [],
              requires: [],
              aliases: [],
              attributes: [],
              functions: [],
              macros: [],
              component?: nil,
              layout?: nil,
              page?: nil,
              templatable?: nil
  end

  defmodule ModuleAttributeDefinition do
    defstruct name: nil, value: nil
  end

  # DEFER: implement
  defmodule ProtocolDefinition do
    defstruct []
  end

  # DIRECTIVES

  defmodule AliasDirective do
    defstruct module: nil, as: nil
  end

  defmodule ImportDirective do
    defstruct module: nil, only: nil
  end

  defmodule RequireDirective do
    defstruct module: nil
  end

  defmodule UseDirective do
    defstruct module: nil, opts: []
  end

  # CONTROL FLOW

  defmodule AnonymousFunctionCall do
    defstruct name: nil, args: []
  end

  defmodule CaseExpression do
    defstruct condition: nil, clauses: []
  end

  defmodule FunctionCall do
    defstruct module: nil, function: nil, args: []
  end

  defmodule IfExpression do
    defstruct condition: nil, do: nil, else: nil, ast: nil
  end

  # BINDINGS

  defmodule Binding do
    defstruct name: nil, access_path: []
  end

  defmodule CaseConditionAccess do
    defstruct []
  end

  defmodule ListIndexAccess do
    defstruct index: nil
  end

  defmodule ListTailAccess do
    defstruct []
  end

  defmodule MapAccess do
    defstruct key: nil
  end

  defmodule MatchAccess do
    defstruct []
  end

  defmodule ParamAccess do
    defstruct index: nil
  end

  defmodule TupleAccess do
    defstruct index: nil
  end

  # OTHER

  defmodule Block do
    defstruct expressions: []
  end

  defmodule Quote do
    defstruct body: nil
  end

  defmodule ModulePseudoVariable do
    defstruct []
  end

  defmodule Unquote do
    defstruct expression: nil
  end

  defmodule Typespec do
    defstruct []
  end

  defmodule Variable do
    defstruct name: nil
  end

  # NOT SUPPORTED

  defmodule NotSupportedExpression do
    defstruct ast: nil, type: nil
  end
end


<|start_filename|>test/unit/hologram/commons/memory_store_test.exs<|end_filename|>
defmodule Hologram.Commons.MemoryStoreTest do
  use Hologram.Test.UnitCase, async: false
  alias Hologram.Test.Fixtures.Commons.MemoryStore

  @dump_path MemoryStore.dump_path()
  @store_content %{key_1: :value_1, key_2: :value_2}
  @table_name MemoryStore.table_name()

  defp dump_store_content(store_content) do
    bin_content = Utils.serialize(store_content)
    File.write!(@dump_path, bin_content)
  end

  defp wait_for_test_cleanup do
    if MemoryStore.running?() || :ets.info(@table_name) != :undefined do
      :timer.sleep(1)
      wait_for_test_cleanup()
    end
  end

  setup do
    wait_for_test_cleanup()
    dump_store_content(@store_content)

    :ok
  end

  describe "run/0" do
    test "process name is registered" do
      {:ok, pid} = MemoryStore.run()
      assert Process.whereis(MemoryStore) == pid
    end

    test "ETS table is created if it doesn't exist yet" do
      MemoryStore.run()
      assert :ets.info(@table_name) |> is_list()
    end

    test "ETS table is not created if it already exists" do
      :ets.new(@table_name, [:public, :named_table])
      MemoryStore.run()

      assert :ets.info(@table_name) |> is_list()
    end

    test "ETS table is truncated" do
      :ets.new(@table_name, [:public, :named_table])
      :ets.insert(@table_name, {:key_3, :value_3})
      MemoryStore.run()

      assert MemoryStore.get_all() == @store_content
    end

    test "ETS table is populated" do
      MemoryStore.run()
      assert MemoryStore.get_all() == @store_content
    end
  end

  describe "get/1" do
    test "key exists" do
      MemoryStore.run()
      assert MemoryStore.get(:key_1) == {:ok, :value_1}
    end

    test "key doesn't exist" do
      MemoryStore.run()
      assert MemoryStore.get(:key_invalid) == :error
    end
  end

  describe "get!/1" do
    test "key exists" do
      MemoryStore.run()
      assert MemoryStore.get!(:key_1) == :value_1
    end

    test "key doesn't exist" do
      MemoryStore.run()

      assert_raise KeyError, "key :key_invalid not found", fn ->
        MemoryStore.get!(:key_invalid)
      end
    end
  end

  test "get_all/0" do
    MemoryStore.run()
    assert MemoryStore.get_all() == @store_content
  end

  describe "maybe_stop/0" do
    test "is running" do
      MemoryStore.run()
      MemoryStore.maybe_stop()

      refute MemoryStore.running?()
    end

    test "is not running" do
      MemoryStore.maybe_stop()
      refute MemoryStore.running?()
    end
  end

  test "put/2" do
    MemoryStore.run()
    MemoryStore.put(:key_3, :value_3)

    assert MemoryStore.get(:key_3) == {:ok, :value_3}
  end

  test "reload/0" do
    MemoryStore.run()

    changed_store_content = %{changed_key: :changed_value}
    dump_store_content(changed_store_content)
    MemoryStore.reload()

    assert MemoryStore.get_all() == changed_store_content
  end

  describe "running?/0" do
    test "is running" do
      MemoryStore.run()
      assert MemoryStore.running?()
    end

    test "is not running" do
      refute MemoryStore.running?()
    end
  end

  test "stop/0 (and terminate/2 implicitely)" do
    MemoryStore.run()
    MemoryStore.stop()

    refute MemoryStore.running?()
    assert MemoryStore.table_name() |> :ets.info() == :undefined
  end
end


<|start_filename|>test/unit/fixtures/compiler/call_graph_builder/module_definition/module_2.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Compiler.CallGraphBuilder.ModuleDefinition.Module2 do
end


<|start_filename|>test/unit/fixtures/runtime/channel/module_5.ex<|end_filename|>
defmodule Hologram.Test.Fixtures.Runtime.Channel.Module5 do
  use Hologram.Page

  route "/test-route-5"
  layout Hologram.Test.Fixtures.Runtime.Channel.Module8

  def init(_params, _conn) do
    %{}
  end

  def template do
    ~H"""
      test_template
    """
  end
end


<|start_filename|>test/e2e/test/pattern_matching_test.exs<|end_filename|>
defmodule HologramE2E.PatternMatchingTest do
  use HologramE2E.TestCase, async: false

  describe "cons operator" do
    feature "match expression", %{session: session} do
      session
      |> visit("/e2e/pattern-matching/cons-operator")
      |> click(css("#button_match_expression"))
      |> assert_has(css("#text", text: "Result = 3"))
    end

    feature "function call", %{session: session} do
      session
      |> visit("/e2e/pattern-matching/cons-operator")
      |> click(css("#button_function_call"))
      |> assert_has(css("#text", text: "Result = 4"))
    end

    feature "case condition", %{session: session} do
      session
      |> visit("/e2e/pattern-matching/cons-operator")
      |> click(css("#button_case_condition"))
      |> assert_has(css("#text", text: "Result = 5"))
    end
  end

  describe "list" do
    feature "match expression", %{session: session} do
      session
      |> visit("/e2e/pattern-matching/list")
      |> click(css("#button_match_expression"))
      |> assert_has(css("#text", text: "Result = 3"))
    end

    feature "function call", %{session: session} do
      session
      |> visit("/e2e/pattern-matching/list")
      |> click(css("#button_function_call"))
      |> assert_has(css("#text", text: "Result = 5"))
    end

    feature "case condition", %{session: session} do
      session
      |> visit("/e2e/pattern-matching/list")
      |> click(css("#button_case_condition"))
      |> assert_has(css("#text", text: "Result = 7"))
    end
  end

  describe "map" do
    feature "match expression", %{session: session} do
      session
      |> visit("/e2e/pattern-matching/map")
      |> click(css("#button_match_expression"))
      |> assert_has(css("#text", text: "Result = 3"))
    end

    feature "function call", %{session: session} do
      session
      |> visit("/e2e/pattern-matching/map")
      |> click(css("#button_function_call"))
      |> assert_has(css("#text", text: "Result = 5"))
    end

    feature "case condition", %{session: session} do
      session
      |> visit("/e2e/pattern-matching/map")
      |> click(css("#button_case_condition"))
      |> assert_has(css("#text", text: "Result = 7"))
    end
  end

  describe "tuple" do
    feature "match expression", %{session: session} do
      session
      |> visit("/e2e/pattern-matching/tuple")
      |> click(css("#button_match_expression"))
      |> assert_has(css("#text", text: "Result = 3"))
    end

    feature "function call", %{session: session} do
      session
      |> visit("/e2e/pattern-matching/tuple")
      |> click(css("#button_function_call"))
      |> assert_has(css("#text", text: "Result = 5"))
    end

    feature "case condition", %{session: session} do
      session
      |> visit("/e2e/pattern-matching/tuple")
      |> click(css("#button_case_condition"))
      |> assert_has(css("#text", text: "Result = 7"))
    end
  end

  # feature "for expression": tested in test/e2e/test/control_flow_test.exs
end
