459 lines
15 KiB
Elixir
459 lines
15 KiB
Elixir
defmodule JSON.LD.EncoderTest do
|
|
use ExUnit.Case, async: false
|
|
|
|
doctest JSON.LD.Encoder
|
|
|
|
alias RDF.{Dataset, Graph, Description}
|
|
alias RDF.NS
|
|
|
|
import RDF.Sigils
|
|
|
|
defmodule TestNS do
|
|
use RDF.Vocabulary.Namespace
|
|
defvocab EX, base_iri: "http://example.com/", terms: [], strict: false
|
|
defvocab S, base_iri: "http://schema.org/", terms: [], strict: false
|
|
end
|
|
|
|
alias TestNS.{EX, S}
|
|
|
|
@compile {:no_warn_undefined, JSON.LD.EncoderTest.TestNS.EX}
|
|
@compile {:no_warn_undefined, JSON.LD.EncoderTest.TestNS.S}
|
|
|
|
|
|
def gets_serialized_to(input, output, opts \\ []) do
|
|
data_structs = Keyword.get(opts, :data_structs, [Dataset, Graph])
|
|
Enum.each data_structs, fn data_struct ->
|
|
assert JSON.LD.Encoder.from_rdf!(data_struct.new(input), opts) == output
|
|
end
|
|
end
|
|
|
|
test "pretty printing" do
|
|
dataset = Dataset.new {~I<http://a/b>, ~I<http://a/c>, ~I<http://a/d>}
|
|
|
|
assert JSON.LD.Encoder.encode!(dataset) ==
|
|
"[{\"@id\":\"http://a/b\",\"http://a/c\":[{\"@id\":\"http://a/d\"}]}]"
|
|
|
|
assert JSON.LD.Encoder.encode!(dataset, pretty: true) ==
|
|
"""
|
|
[
|
|
{
|
|
"@id": "http://a/b",
|
|
"http://a/c": [
|
|
{
|
|
"@id": "http://a/d"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
""" |> String.trim()
|
|
end
|
|
|
|
test "an empty RDF.Dataset is serialized to an JSON array string" do
|
|
assert JSON.LD.Encoder.encode!(Dataset.new) == "[]"
|
|
end
|
|
|
|
describe "simple tests" do
|
|
test "One subject IRI object" do
|
|
{~I<http://a/b>, ~I<http://a/c>, ~I<http://a/d>}
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://a/b",
|
|
"http://a/c" => [%{"@id" => "http://a/d"}]
|
|
}], data_structs: [Dataset, Graph, Description])
|
|
end
|
|
|
|
test "should generate object list" do
|
|
[{EX.b, EX.c, EX.d}, {EX.b, EX.c, EX.e}]
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://example.com/b",
|
|
"http://example.com/c" => [
|
|
%{"@id" => "http://example.com/d"},
|
|
%{"@id" => "http://example.com/e"}
|
|
]
|
|
}], data_structs: [Dataset, Graph, Description])
|
|
end
|
|
|
|
test "should generate property list" do
|
|
[{EX.b, EX.c, EX.d}, {EX.b, EX.e, EX.f}]
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://example.com/b",
|
|
"http://example.com/c" => [%{"@id" => "http://example.com/d"}],
|
|
"http://example.com/e" => [%{"@id" => "http://example.com/f"}]
|
|
}], data_structs: [Dataset, Graph, Description])
|
|
end
|
|
|
|
test "serializes multiple subjects" do
|
|
[
|
|
{~I<http://test-cases/0001>, NS.RDF.type, ~I<http://www.w3.org/2006/03/test-description#TestCase>},
|
|
{~I<http://test-cases/0002>, NS.RDF.type, ~I<http://www.w3.org/2006/03/test-description#TestCase>}
|
|
]
|
|
|> gets_serialized_to([
|
|
%{"@id" => "http://test-cases/0001", "@type" => ["http://www.w3.org/2006/03/test-description#TestCase"]},
|
|
%{"@id" => "http://test-cases/0002", "@type" => ["http://www.w3.org/2006/03/test-description#TestCase"]},
|
|
])
|
|
end
|
|
end
|
|
|
|
describe "literal coercion" do
|
|
test "typed literal" do
|
|
{EX.a, EX.b, RDF.literal("foo", datatype: EX.d)}
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => "foo", "@type" => "http://example.com/d"}]
|
|
}], data_structs: [Dataset, Graph, Description])
|
|
end
|
|
|
|
test "integer" do
|
|
{EX.a, EX.b, RDF.literal(1)}
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => 1}]
|
|
}], use_native_types: true)
|
|
end
|
|
|
|
test "integer (non-native)" do
|
|
{EX.a, EX.b, RDF.literal(1)}
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => "1","@type" => "http://www.w3.org/2001/XMLSchema#integer"}]
|
|
}], use_native_types: false)
|
|
end
|
|
|
|
test "boolean" do
|
|
{EX.a, EX.b, RDF.literal(true)}
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => true}]
|
|
}], use_native_types: true)
|
|
end
|
|
|
|
test "boolean (non-native)" do
|
|
{EX.a, EX.b, RDF.literal(true)}
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => "true","@type" => "http://www.w3.org/2001/XMLSchema#boolean"}]
|
|
}], use_native_types: false)
|
|
end
|
|
|
|
@tag skip: "TODO: Is this spec conformant or RDF.rb specific? RDF.rb doesn't use the specified RDF to Object Conversion algorithm but reuses a generalized expand_value algorithm"
|
|
test "decimal" do
|
|
{EX.a, EX.b, RDF.literal(1.0)}
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => "1.0", "@type" => "http://www.w3.org/2001/XMLSchema#decimal"}]
|
|
}], use_native_types: true)
|
|
end
|
|
|
|
test "double" do
|
|
{EX.a, EX.b, RDF.literal(1.0e0)}
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => 1.0E0}]
|
|
}], use_native_types: true)
|
|
end
|
|
|
|
@tag skip: "TODO: Is this spec conformant or RDF.rb specific? RDF.rb doesn't use the specified RDF to Object Conversion algorithm but reuses a generalized expand_value algorithm"
|
|
test "double (non-native)" do
|
|
{EX.a, EX.b, RDF.literal(1.0e0)}
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => "1.0E0", "@type" => "http://www.w3.org/2001/XMLSchema#double"}]
|
|
}], use_native_types: false)
|
|
end
|
|
end
|
|
|
|
describe "datatyped (non-native) literals" do
|
|
%{
|
|
integer: 1,
|
|
unsignedInt: 1,
|
|
nonNegativeInteger: 1,
|
|
float: 1.0,
|
|
nonPositiveInteger: -1,
|
|
negativeInteger: -1,
|
|
}
|
|
|> Enum.each(fn ({type, _} = data) ->
|
|
@tag data: data
|
|
test "#{type}", %{data: {type, value}} do
|
|
{EX.a, EX.b, RDF.literal(value, datatype: apply(NS.XSD, type, []))}
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => "#{value}", "@type" => "http://www.w3.org/2001/XMLSchema##{type}"}]
|
|
}], use_native_types: false)
|
|
end
|
|
end)
|
|
|
|
test "when useNativeTypes" do
|
|
{EX.a, EX.b, RDF.literal("foo", datatype: EX.customType)}
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => "foo", "@type" => to_string(EX.customType)}]
|
|
}], use_native_types: true)
|
|
end
|
|
end
|
|
|
|
test "encodes language literal" do
|
|
{EX.a, EX.b, RDF.literal("foo", language: "en-us")}
|
|
|> gets_serialized_to([%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => "foo", "@language" => "en-us"}]
|
|
}])
|
|
end
|
|
|
|
|
|
describe "blank nodes" do
|
|
test "should generate blank nodes" do
|
|
{RDF.bnode(:a), EX.a, EX.b}
|
|
|> gets_serialized_to([%{
|
|
"@id" => "_:a",
|
|
"http://example.com/a" => [%{"@id" => "http://example.com/b"}]
|
|
}], data_structs: [Dataset, Graph, Description])
|
|
end
|
|
|
|
test "should generate blank nodes as object" do
|
|
[
|
|
{EX.a, EX.b, RDF.bnode(:a)},
|
|
{RDF.bnode(:a), EX.c, EX.d}
|
|
]
|
|
|> gets_serialized_to([
|
|
%{
|
|
"@id" => "_:a",
|
|
"http://example.com/c" => [%{"@id" => "http://example.com/d"}]
|
|
},
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@id" => "_:a"}]
|
|
}
|
|
])
|
|
end
|
|
end
|
|
|
|
describe "lists" do
|
|
%{
|
|
"literal list" => {
|
|
[
|
|
{EX.a, EX.b, RDF.bnode(:e1) },
|
|
{RDF.bnode(:e1), NS.RDF.first, ~L"apple"},
|
|
{RDF.bnode(:e1), NS.RDF.rest, RDF.bnode(:e2)},
|
|
{RDF.bnode(:e2), NS.RDF.first, ~L"banana"},
|
|
{RDF.bnode(:e2), NS.RDF.rest, NS.RDF.nil},
|
|
],
|
|
[%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{
|
|
"@list" => [
|
|
%{"@value" => "apple"},
|
|
%{"@value" => "banana"}
|
|
]
|
|
}]
|
|
}]
|
|
},
|
|
"iri list" => {
|
|
[
|
|
{EX.a, EX.b, RDF.bnode(:list)},
|
|
{RDF.bnode(:list), NS.RDF.first, EX.c},
|
|
{RDF.bnode(:list), NS.RDF.rest, NS.RDF.nil},
|
|
],
|
|
[%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{
|
|
"@list" => [
|
|
%{"@id" => "http://example.com/c"}
|
|
]
|
|
}]
|
|
}]
|
|
},
|
|
"empty list" => {
|
|
[
|
|
{EX.a, EX.b, NS.RDF.nil},
|
|
],
|
|
[%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@list" => []}]
|
|
}]
|
|
},
|
|
"single element list" => {
|
|
[
|
|
{EX.a, EX.b, RDF.bnode(:list)},
|
|
{RDF.bnode(:list), NS.RDF.first, ~L"apple"},
|
|
{RDF.bnode(:list), NS.RDF.rest, NS.RDF.nil},
|
|
],
|
|
[%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@list" => [%{"@value" => "apple"}]}]
|
|
}]
|
|
},
|
|
"single element list without @type" => {
|
|
[
|
|
{EX.a, EX.b, RDF.bnode(:list)},
|
|
{RDF.bnode(:list), NS.RDF.first, RDF.bnode(:a)},
|
|
{RDF.bnode(:list), NS.RDF.rest, NS.RDF.nil},
|
|
{RDF.bnode(:a), EX.b, ~L"foo"},
|
|
],
|
|
[
|
|
%{
|
|
"@id" => "_:a",
|
|
"http://example.com/b" => [%{"@value" => "foo"}]
|
|
},
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@list" => [%{"@id" => "_:a"}]}]
|
|
},
|
|
]
|
|
},
|
|
"multiple graphs with shared BNode" => {
|
|
[
|
|
{EX.z, EX.q, RDF.bnode(:z0), EX.G},
|
|
{RDF.bnode(:z0), NS.RDF.first, ~L"cell-A", EX.G},
|
|
{RDF.bnode(:z0), NS.RDF.rest, RDF.bnode(:z1), EX.G},
|
|
{RDF.bnode(:z1), NS.RDF.first, ~L"cell-B", EX.G},
|
|
{RDF.bnode(:z1), NS.RDF.rest, NS.RDF.nil, EX.G},
|
|
{EX.x, EX.p, RDF.bnode(:z1), EX.G1},
|
|
],
|
|
[%{
|
|
"@id" => "http://www.example.com/G",
|
|
"@graph" => [%{
|
|
"@id" => "_:z0",
|
|
"http://www.w3.org/1999/02/22-rdf-syntax-ns#first" => [%{"@value" => "cell-A"}],
|
|
"http://www.w3.org/1999/02/22-rdf-syntax-ns#rest" => [%{"@id" => "_:z1"}]
|
|
}, %{
|
|
"@id" => "_:z1",
|
|
"http://www.w3.org/1999/02/22-rdf-syntax-ns#first" => [%{"@value" => "cell-B"}],
|
|
"http://www.w3.org/1999/02/22-rdf-syntax-ns#rest" => [%{"@list" => []}]
|
|
}, %{
|
|
"@id" => "http://www.example.com/z",
|
|
"http://www.example.com/q" => [%{"@id" => "_:z0"}]
|
|
}]
|
|
},
|
|
%{
|
|
"@id" => "http://www.example.com/G1",
|
|
"@graph" => [%{
|
|
"@id" => "http://www.example.com/x",
|
|
"http://www.example.com/p" => [%{"@id" => "_:z1"}]
|
|
}]
|
|
}]
|
|
},
|
|
}
|
|
|> Enum.each(fn ({title, data}) ->
|
|
if title == "multiple graphs with shared BNode" do
|
|
@tag skip: "TODO: https://github.com/json-ld/json-ld.org/issues/357"
|
|
end
|
|
@tag data: data
|
|
test title, %{data: {input, output}} do
|
|
input |> gets_serialized_to(output)
|
|
end
|
|
end)
|
|
end
|
|
|
|
describe "quads" do
|
|
%{
|
|
"simple named graph" => %{
|
|
input: {EX.a, EX.b, EX.c, EX.U},
|
|
output: [
|
|
%{
|
|
"@id" => "http://example.com/U",
|
|
"@graph" => [%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@id" => "http://example.com/c"}]
|
|
}]
|
|
},
|
|
]
|
|
},
|
|
"with properties" => %{
|
|
input: [
|
|
{EX.a, EX.b, EX.c, EX.U},
|
|
{EX.U, EX.d, EX.e},
|
|
],
|
|
output: [
|
|
%{
|
|
"@id" => "http://example.com/U",
|
|
"@graph" => [%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@id" => "http://example.com/c"}]
|
|
}],
|
|
"http://example.com/d" => [%{"@id" => "http://example.com/e"}]
|
|
}
|
|
]
|
|
},
|
|
"with lists" => %{
|
|
input: [
|
|
{EX.a, EX.b, RDF.bnode(:a), EX.U},
|
|
{RDF.bnode(:a), NS.RDF.first, EX.c, EX.U},
|
|
{RDF.bnode(:a), NS.RDF.rest, NS.RDF.nil, EX.U},
|
|
{EX.U, EX.d, RDF.bnode(:b)},
|
|
{RDF.bnode(:b), NS.RDF.first, EX.e},
|
|
{RDF.bnode(:b), NS.RDF.rest, NS.RDF.nil},
|
|
],
|
|
output: [
|
|
%{
|
|
"@id" => "http://example.com/U",
|
|
"@graph" => [%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@list" => [%{"@id" => "http://example.com/c"}]}]
|
|
}],
|
|
"http://example.com/d" => [%{"@list" => [%{"@id" => "http://example.com/e"}]}]
|
|
}
|
|
]
|
|
},
|
|
"Two Graphs with same subject and lists" => %{
|
|
input: [
|
|
{EX.a, EX.b, RDF.bnode(:a), EX.U},
|
|
{RDF.bnode(:a), NS.RDF.first, EX.c, EX.U},
|
|
{RDF.bnode(:a), NS.RDF.rest, NS.RDF.nil, EX.U},
|
|
{EX.a, EX.b, RDF.bnode(:b), EX.V},
|
|
{RDF.bnode(:b), NS.RDF.first, EX.e, EX.V},
|
|
{RDF.bnode(:b), NS.RDF.rest, NS.RDF.nil, EX.V},
|
|
],
|
|
output: [
|
|
%{
|
|
"@id" => "http://example.com/U",
|
|
"@graph" => [
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{
|
|
"@list" => [%{"@id" => "http://example.com/c"}]
|
|
}]
|
|
}
|
|
]
|
|
},
|
|
%{
|
|
"@id" => "http://example.com/V",
|
|
"@graph" => [
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{
|
|
"@list" => [%{"@id" => "http://example.com/e"}]
|
|
}]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
}
|
|
|> Enum.each(fn ({title, data}) ->
|
|
@tag data: data
|
|
test title, %{data: %{input: input, output: output}} do
|
|
input |> gets_serialized_to(output, data_structs: [Dataset])
|
|
end
|
|
end)
|
|
end
|
|
|
|
describe "problems" do
|
|
%{
|
|
"xsd:boolean as value" => {
|
|
{~I<http://data.wikia.com/terms#playable>, NS.RDFS.range, NS.XSD.boolean},
|
|
[%{
|
|
"@id" => "http://data.wikia.com/terms#playable",
|
|
"http://www.w3.org/2000/01/rdf-schema#range" => [
|
|
%{ "@id" => "http://www.w3.org/2001/XMLSchema#boolean" }
|
|
]
|
|
}]
|
|
},
|
|
}
|
|
|> Enum.each(fn ({title, data}) ->
|
|
@tag data: data
|
|
test title, %{data: {input, output}} do
|
|
input |> gets_serialized_to(output)
|
|
end
|
|
end)
|
|
end
|
|
|
|
end
|