defmodule RDF.ListTest do use RDF.Test.Case doctest RDF.List import RDF.Sigils alias RDF.{IRI, BlankNode, Literal, Graph} use RDF.Vocabulary.Namespace defvocab EX, base_iri: "http://example.org/#", terms: [], strict: false setup do {:ok, empty: RDF.List.new(RDF.nil(), Graph.new()), one: RDF.List.from([EX.element()], head: ~B), abc: RDF.List.from(~w[a b c], head: ~B), ten: RDF.List.from(Enum.to_list(1..10), head: ~B), nested: RDF.List.from(["foo", [1, 2], "bar"], head: ~B)} end describe "new/2" do ####################################################################### # success cases test "valid head list node" do graph = Graph.new( ~B |> RDF.first(1) |> RDF.rest(~B) ) |> Graph.add( ~B |> RDF.first(2) |> RDF.rest(RDF.nil()) ) assert %RDF.List{} = list = RDF.List.new(~B, graph) assert list.head == ~B assert list.graph == graph end test "with non-blank list nodes" do graph = Graph.new( EX.Foo |> RDF.first(1) |> RDF.rest(RDF.nil()) ) assert %RDF.List{} = list = RDF.List.new(EX.Foo, graph) assert list.head == iri(EX.Foo) end test "with other properties on its nodes" do assert RDF.List.new( ~B, Graph.new( ~B |> EX.other(EX.Property) |> RDF.first(1) |> RDF.rest(~B) ) |> Graph.add( ~B |> EX.other(EX.Property2) |> RDF.first(2) |> RDF.rest(RDF.nil()) ) ) |> RDF.List.valid?() == true end ####################################################################### # failure cases test "when given list node doesn't exist in the given graph" do assert RDF.List.new(RDF.bnode(), RDF.Graph.new()) == nil end test "When the given head node is not a list" do assert RDF.List.new(42, RDF.Graph.new()) == nil assert RDF.List.new(EX.Foo, RDF.Graph.new({EX.Foo, EX.bar(), EX.Baz})) == nil assert RDF.List.new(EX.Foo, RDF.Graph.new({EX.Foo, RDF.first(), EX.Baz})) == nil end test "when list nodes are incomplete" do assert RDF.List.new(EX.Foo, RDF.Graph.new({EX.Foo, RDF.first(), EX.Baz})) == nil assert RDF.List.new(EX.Foo, RDF.Graph.new({EX.Foo, RDF.rest(), RDF.nil()})) == nil end test "when head node has multiple rdf:first objects" do assert RDF.List.new( ~B, Graph.new( ~B |> RDF.first(1, 2) |> RDF.rest(RDF.nil()) ) ) == nil end test "when later list nodes have multiple rdf:first objects" do assert RDF.List.new( ~B, Graph.new( ~B |> RDF.first(1) |> RDF.rest(~B) ) |> Graph.add( ~B |> RDF.first(2, 3) |> RDF.rest(RDF.nil()) ) ) == nil end test "when list nodes have multiple rdf:rest objects" do assert RDF.List.new( ~B, Graph.new( ~B |> RDF.first(1) |> RDF.rest(~B, ~B) ) |> Graph.add( ~B |> RDF.first(2) |> RDF.rest(RDF.nil()) ) |> Graph.add( ~B |> RDF.first(3) |> RDF.rest(RDF.nil()) ) ) == nil assert RDF.List.new( ~B, Graph.new( ~B |> RDF.first(1) |> RDF.rest(~B) ) |> Graph.add( ~B |> RDF.first(2) |> RDF.rest(RDF.nil(), ~B) ) |> Graph.add( ~B |> RDF.first(3) |> RDF.rest(RDF.nil()) ) ) == nil end test "when the list is cyclic" do assert RDF.List.new( ~B, Graph.new( ~B |> RDF.first(1) |> RDF.rest(~B) ) |> Graph.add( ~B |> RDF.first(2) |> RDF.rest(~B) ) ) == nil end end describe "from/1" do test "an empty list", %{empty: empty} do assert RDF.List.from([]) == empty end test "an empty list with named head node", %{empty: empty} do assert RDF.List.from([], name: ~B) == empty end %{ "IRI" => iri(EX.Foo), "blank node" => ~B, "literal" => ~L, "string" => "Foo", "integer" => 42, "float" => 3.14, "true" => true, "false" => false, "unresolved namespace-qualified name" => EX.Foo } |> Enum.each(fn {type, element} -> @tag element: element test "list with #{type} element", %{element: element} do with {bnode, graph_with_list} = one_element_list(element) do assert RDF.List.from([element], head: bnode) == RDF.List.new(bnode, graph_with_list) end end end) test "nested list" do assert %RDF.List{head: bnode, graph: graph_with_list} = RDF.List.from([[1]]) assert [nested] = get_in(graph_with_list, [bnode, RDF.first()]) assert get_in(graph_with_list, [bnode, RDF.rest()]) == [RDF.nil()] assert get_in(graph_with_list, [nested, RDF.first()]) == [XSD.integer(1)] assert get_in(graph_with_list, [nested, RDF.rest()]) == [RDF.nil()] assert %RDF.List{head: bnode, graph: graph_with_list} = RDF.List.from(["foo", [1, 2], "bar"]) assert get_in(graph_with_list, [bnode, RDF.first()]) == [~L"foo"] assert [second] = get_in(graph_with_list, [bnode, RDF.rest()]) assert [nested] = get_in(graph_with_list, [second, RDF.first()]) assert get_in(graph_with_list, [nested, RDF.first()]) == [XSD.integer(1)] assert [nested_second] = get_in(graph_with_list, [nested, RDF.rest()]) assert get_in(graph_with_list, [nested_second, RDF.first()]) == [XSD.integer(2)] assert get_in(graph_with_list, [nested_second, RDF.rest()]) == [RDF.nil()] assert [third] = get_in(graph_with_list, [second, RDF.rest()]) assert get_in(graph_with_list, [third, RDF.first()]) == [~L"bar"] assert get_in(graph_with_list, [third, RDF.rest()]) == [RDF.nil()] end %{ "preserve order" => [3, 2, 1], "different types" => [1, "foo", true, false, 3.14, EX.foo(), EX.Foo, ~B] } |> Enum.each(fn {desc, list} -> @tag list: list test "list with multiple elements: #{desc}", %{list: list} do assert %RDF.List{head: bnode, graph: graph_with_list} = RDF.List.from(list) assert RDF.nil() == Enum.reduce(list, bnode, fn element, list_node -> case element do %IRI{} -> assert get_in(graph_with_list, [list_node, RDF.first()]) == [element] %BlankNode{} -> assert get_in(graph_with_list, [list_node, RDF.first()]) == [element] %Literal{} -> assert get_in(graph_with_list, [list_node, RDF.first()]) == [element] element when is_boolean(element) -> assert get_in(graph_with_list, [list_node, RDF.first()]) == [ RDF.Literal.new(element) ] element when is_atom(element) -> assert get_in(graph_with_list, [list_node, RDF.first()]) == [ RDF.iri(element) ] _ -> assert get_in(graph_with_list, [list_node, RDF.first()]) == [ RDF.Literal.new(element) ] end [next] = get_in(graph_with_list, [list_node, RDF.rest()]) unless next == RDF.nil() do assert %BlankNode{} = next end next end) end end) test "an enumerable" do assert RDF.List.from(MapSet.new([42]), head: ~B) == RDF.List.from([42], head: ~B) end test "head option with unresolved namespace-qualified name" do assert RDF.List.from([42], head: EX.Foo).head == iri(EX.Foo) end end describe "values/1" do test "the empty list", %{empty: empty} do assert RDF.List.values(empty) == [] end test "list with one element", %{one: one} do assert RDF.List.values(one) == [EX.element()] end test "list with multiple elements", %{abc: abc, ten: ten} do assert RDF.List.values(abc) == ~w[a b c] |> Enum.map(&Literal.new/1) assert RDF.List.values(ten) == 1..10 |> Enum.to_list() |> Enum.map(&Literal.new/1) end test "list with non-blank list nodes" do assert RDF.List.from([EX.element()], head: EX.Foo) |> RDF.List.values() == [EX.element()] end test "nested list", %{nested: nested} do assert RDF.List.values(nested) == [~L"foo", [XSD.integer(1), XSD.integer(2)], ~L"bar"] assert RDF.list(["foo", [1, 2]]) |> RDF.List.values() == [~L"foo", [XSD.integer(1), XSD.integer(2)]] assert RDF.list([[1, 2], "foo"]) |> RDF.List.values() == [[XSD.integer(1), XSD.integer(2)], ~L"foo"] inner_list = RDF.list([1, 2], head: ~B) assert RDF.list(["foo", ~B], graph: inner_list.graph) |> RDF.List.values() == [~L"foo", [XSD.integer(1), XSD.integer(2)]] end end describe "nodes/1" do test "the empty list", %{empty: empty} do assert RDF.List.nodes(empty) == [] end test "non-empty list", %{one: one} do assert RDF.List.nodes(one) == [~B] end test "nested list", %{nested: nested} do assert RDF.list([[1, 2, 3]], head: ~B) |> RDF.List.nodes() == [~B] assert [~B, _, _] = RDF.List.nodes(nested) end end describe "valid?/2" do test "the empty list", %{empty: empty} do assert RDF.List.valid?(empty) end test "valid list with one element", %{one: one} do assert RDF.List.valid?(one) == true end test "valid list with multiple elements", %{abc: abc, ten: ten} do assert RDF.List.valid?(abc) == true assert RDF.List.valid?(ten) == true end test "valid nested list", %{nested: nested} do assert RDF.List.valid?(nested) == true end test "a non-blank list node is not valid" do assert RDF.list([EX.element()], head: EX.Foo) |> RDF.List.valid?() == false end test "a non-blank list node on later nodes makes the whole list invalid" do assert RDF.List.new( ~B, Graph.new( ~B |> RDF.first(1) |> RDF.rest(EX.Foo) ) |> Graph.add( EX.Foo |> RDF.first(2) |> RDF.rest(RDF.nil()) ) ) |> RDF.List.valid?() == false end end describe "node?" do test "the empty list", %{empty: empty} do assert RDF.List.node?(empty.head, empty.graph) == true end test "list with one element", %{one: one} do assert RDF.List.node?(one.head, one.graph) == true end test "list with multiple elements", %{abc: abc, ten: ten} do assert RDF.List.node?(abc.head, abc.graph) == true assert RDF.List.node?(ten.head, ten.graph) == true end test "nested list", %{nested: nested} do assert RDF.List.node?(nested.head, nested.graph) == true end test "unresolved namespace-qualified name" do assert RDF.List.node?( EX.Foo, RDF.List.from([EX.element()], head: EX.Foo).graph ) == true end test "when given list node doesn't exist in the given graph" do assert RDF.List.node?(RDF.bnode(), RDF.Graph.new()) == false end test "literal" do assert RDF.List.node?(~L"Foo", RDF.Graph.new()) == false assert RDF.List.node?(42, RDF.Graph.new()) == false assert RDF.List.node?(true, RDF.Graph.new()) == false assert RDF.List.node?(false, RDF.Graph.new()) == false assert RDF.List.node?(nil, RDF.Graph.new()) == false end test "non-list node" do assert RDF.List.node?(EX.Foo, RDF.Graph.new({EX.Foo, EX.bar(), EX.Baz})) == false end test "incomplete list nodes" do assert RDF.List.node?(EX.Foo, RDF.Graph.new({EX.Foo, RDF.first(), EX.Baz})) == false assert RDF.List.node?(EX.Foo, RDF.Graph.new({EX.Foo, RDF.rest(), RDF.nil()})) == false end end describe "Enumerable.reduce" do test "the empty list", %{empty: empty} do assert Enum.reduce(empty, [], fn description, acc -> [description | acc] end) == [] end test "a valid list", %{one: one} do assert [one.graph[one.head]] == Enum.reduce(one, [], fn description, acc -> [description | acc] end) end end defp one_element_list(element), do: one_element_list(element, RDF.bnode()) defp one_element_list(element, bnode) do {bnode, Graph.new( bnode |> RDF.first(element) |> RDF.rest(RDF.nil()) )} end end