Login
gpcr/spec/gpcr_spec.cr
Login

[edit]

spec/gpcr_spec.cr @ trunk

spec

require 'spec'
scale = 0.75
fontscale = 1.500
box wid 1.380 ht 0.340 with .nw at (0.035, -0.035) fill 0xCBD5E1 color 0xCBD5E1
box wid 1.380 ht 0.340 with .nw at (0.000, 0.000) fill 0x7C83E6 color 0x7C83E6
box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.022) fill 0xffffff color 0xffffff
line from (0.140, 0.000) to (0.140, -0.340) color white
line from (1.240, 0.000) to (1.240, -0.340) color white
text "require 'spec'" color white at (0.690, -0.170)

crystal source

require "spec"

json block

{
  "kind": "require",
  "name": "spec",
  "doc": null,
  "children": []
}

../src/gpcr

require '../src/gpcr'
scale = 0.75
fontscale = 1.500
box wid 1.783 ht 0.340 with .nw at (0.035, -0.455) fill 0xCBD5E1 color 0xCBD5E1
box wid 1.783 ht 0.340 with .nw at (0.000, -0.420) fill 0x7C83E6 color 0x7C83E6
box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.443) fill 0xffffff color 0xffffff
line from (0.140, -0.420) to (0.140, -0.760) color white
line from (1.643, -0.420) to (1.643, -0.760) color white
text "require '../src/gpcr'" color white at (0.891, -0.590)

crystal source

require "../src/gpcr"

json block

{
  "kind": "require",
  "name": "../src/gpcr",
  "doc": null,
  "children": []
}

GPCR_START = Channel(Nil).new(0)

class ExamplePrimitives
  @[GPCR::Primitive(shape: :hat, label: "on start", category: "Events")]
  def on_start
    GPCR_START.receive
  end

  @[GPCR::Primitive(shape: :command, label: "set user LED #{state}")]
  def set_user_led(state : Bool)
  end

  @[GPCR::Primitive(shape: :reporter, label: "join #{a} and #{b}", category: "Text", returns: :string)]
  def join(a : String, b : String = "") : String
    a + b
  end

  # not a block: no annotation, so nothing is derived for it
  def helper(x : Int32)
  end
end

BLOCKS = GPCR.blocks_of(ExamplePrimitives)

class NewShapePrimitives
  @[GPCR::Primitive(shape: :predefined_process, label: "run subroutine #{name}", category: "Control")]
  def run_subroutine(name : String)
  end

  @[GPCR::Primitive(shape: :document, label: "open file #{path}", category: "File")]
  def open_file(path : String)
    File.open(path)
  end
end

NEW_BLOCKS = GPCR.blocks_of(NewShapePrimitives)

describe GPCR do
  describe ".blocks_of" do
    it "derives a block only for annotated defs" do
      BLOCKS.size.should eq(3)
      BLOCKS.map(&.label).should_not contain("helper")
    end

    it "derives shape, label, and category from the annotation" do
      hat = BLOCKS[0]
      hat.shape.should eq("hat")
      hat.label.should eq("on start")
      hat.category.should eq("Events")
      hat.returns.should be_nil
      hat.inputs.should be_empty
    end

    it "derives inputs from the parameter list" do
      led = BLOCKS[1]
      led.shape.should eq("command")
      led.inputs.size.should eq(1)
      led.inputs[0].name.should eq("state")
      led.inputs[0].type.should eq("boolean")
      led.inputs[0].default.should be_nil
    end

    it "derives returns and parameter defaults" do
      join = BLOCKS[2]
      join.shape.should eq("reporter")
      join.returns.should eq("string")
      join.inputs.map(&.type).should eq(["string", "string"])
      join.inputs[0].default.should be_nil
      join.inputs[1].default.should eq(JSON::Any.new(""))
    end
  end

  describe "derived block form (JSON edit protocol)" do
    it "serializes the derived form as the edit-protocol JSON" do
      json = JSON.parse(BLOCKS[1].to_json)
      json["shape"].should eq("command")
      json["label"].should eq("set user LED \#{state}")
      json["inputs"][0]["name"].should eq("state")
      json["inputs"][0]["type"].should eq("boolean")
    end

    it "round-trips a derived block through JSON" do
      restored = GPCR::Face.from_json(BLOCKS[2].to_json)
      restored.shape.should eq("reporter")
      restored.label.should eq("join \#{a} and \#{b}")
      restored.returns.should eq("string")
      restored.inputs.size.should eq(2)
    end
  end

  describe "new primitive shapes" do
    it "derives predefined_process shape and its category" do
      req = NEW_BLOCKS.find { |b| b.label.includes?("run subroutine") }
      req.should_not be_nil
      req.not_nil!.shape.should eq("predefined_process")
      req.not_nil!.category.should eq("Control")
      req.not_nil!.inputs[0].name.should eq("name")
    end

    it "derives document shape and File category" do
      file = NEW_BLOCKS.find { |b| b.label.includes?("open file") }
      file.should_not be_nil
      file.not_nil!.shape.should eq("document")
      file.not_nil!.category.should eq("File")
      file.not_nil!.inputs[0].name.should eq("path")
    end
  end

  describe ".slot_type" do
    it "maps Crystal restrictions to slot types" do
      GPCR.slot_type("Bool").should eq("boolean")
      GPCR.slot_type("Float64").should eq("number")
      GPCR.slot_type("Int32").should eq("number")
      GPCR.slot_type("String").should eq("string")
    end

    it "treats an untyped parameter as any" do
      GPCR.slot_type(nil).should eq("any")
      GPCR.slot_type("").should eq("any")
    end
  end
end