defmodule JSON.LD.ContextTest do use ExUnit.Case alias RDF.NS.{XSD} doctest JSON.LD.Context describe "create from Hash" do test "extracts @base" do assert JSON.LD.context(%{"@base" => "http://base/"}).base_iri == "http://base/" end test "extracts @language" do assert JSON.LD.context(%{"@language" => "en"}).default_language == "en" end test "extracts @vocab" do assert JSON.LD.context(%{"@vocab" => "http://schema.org/"}).vocab == "http://schema.org/" end test "maps term with IRI value" do c = JSON.LD.context(%{"foo" => "http://example.com/"}) assert c.term_defs["foo"] assert c.term_defs["foo"].iri_mapping == "http://example.com/" end test "maps term with @id" do c = JSON.LD.context(%{"foo" => %{"@id" => "http://example.com/"}}) assert c.term_defs["foo"] assert c.term_defs["foo"].iri_mapping == "http://example.com/" end test "associates @list container mapping with predicate" do c = JSON.LD.context(%{"foo" => %{"@id" => "http://example.com/", "@container" => "@list"}}) assert c.term_defs["foo"] assert c.term_defs["foo"].iri_mapping == "http://example.com/" assert c.term_defs["foo"].container_mapping == "@list" end test "associates @set container mapping with predicate" do c = JSON.LD.context(%{"foo" => %{"@id" => "http://example.com/", "@container" => "@set"}}) assert c.term_defs["foo"] assert c.term_defs["foo"].iri_mapping == "http://example.com/" assert c.term_defs["foo"].container_mapping == "@set" end test "associates @id container mapping with predicate" do c = JSON.LD.context(%{"foo" => %{"@id" => "http://example.com/", "@type" => "@id"}}) assert c.term_defs["foo"] assert c.term_defs["foo"].iri_mapping == "http://example.com/" assert c.term_defs["foo"].type_mapping == "@id" end test "associates type mapping with predicate" do c = JSON.LD.context(%{ "foo" => %{"@id" => "http://example.com/", "@type" => to_string(XSD.string())} }) assert c.term_defs["foo"] assert c.term_defs["foo"].iri_mapping == "http://example.com/" assert c.term_defs["foo"].type_mapping == to_string(XSD.string()) end test "associates language mapping with predicate" do c = JSON.LD.context(%{"foo" => %{"@id" => "http://example.com/", "@language" => "en"}}) assert c.term_defs["foo"] assert c.term_defs["foo"].iri_mapping == "http://example.com/" assert c.term_defs["foo"].language_mapping == "en" end test "expands chains of term definition/use with string values" do assert JSON.LD.context(%{ "foo" => "bar", "bar" => "baz", "baz" => "http://example.com/" }) |> iri_mappings == %{ "foo" => "http://example.com/", "bar" => "http://example.com/", "baz" => "http://example.com/" } end test "expands terms using @vocab" do c = JSON.LD.context(%{ "foo" => "bar", "@vocab" => "http://example.com/" }) assert c.term_defs["foo"] assert c.term_defs["foo"].iri_mapping == "http://example.com/bar" end end describe "create from Array/List" do test "merges definitions from each context" do assert JSON.LD.context([ %{"foo" => "http://example.com/foo"}, %{"bar" => "foo"} ]) |> iri_mappings == %{ "foo" => "http://example.com/foo", "bar" => "http://example.com/foo" } end end describe "term definitions with null values" do test "removes @language if set to null" do assert JSON.LD.context([ %{"@language" => "en"}, %{"@language" => nil} ]).default_language == nil end test "removes @vocab if set to null" do assert JSON.LD.context([ %{"@vocab" => "http://schema.org/"}, %{"@vocab" => nil} ]).vocab == nil end test "removes term if set to null with @vocab" do assert JSON.LD.context([ %{ "@vocab" => "http://schema.org/", "term" => nil } ]) |> iri_mappings == %{ "term" => nil } end test "removes a term definition" do assert JSON.LD.context(%{"name" => nil}).term_defs["name"] == nil end test "loads initial context" do init_ec = JSON.LD.Context.new() nil_ec = JSON.LD.context(nil) assert nil_ec.default_language == init_ec.default_language assert nil_ec |> coercions == init_ec |> coercions assert nil_ec |> containers == init_ec |> containers assert nil_ec |> languages == init_ec |> languages assert nil_ec |> iri_mappings == init_ec |> iri_mappings end end describe "remote contexts" do test "when the remote context is a list" do bypass = Bypass.open() Bypass.expect(bypass, fn conn -> assert "GET" == conn.method assert "/litepub-0.1.jsonld" == conn.request_path context = File.read!("test/fixtures/litepub-0.1.jsonld") Plug.Conn.resp(conn, 200, context) end) assert context = JSON.LD.context("http://localhost:#{bypass.port}/litepub-0.1.jsonld") assert %{ "Emoji" => "http://joinmastodon.org/ns#Emoji", # https://www.w3.org/ns/activitystreams "Accept" => "https://www.w3.org/ns/activitystreams#Accept", # https://w3id.org/security/v1 "CryptographicKey" => "https://w3id.org/security#Key" } = iri_mappings(context) end end describe "JSON.LD.context/2" do @example_context_map %{ "@context" => %{ "givenName" => "http://schema.org/givenName", "familyName" => "http://schema.org/familyName", "homepage" => %{ "@id" => "http://schema.org/url", "@type" => "@id" } } } test "wraps everything under a @context" do assert JSON.LD.context(@example_context_map["@context"]) == JSON.LD.context(@example_context_map) end test "with atom keys" do context_with_atom_keys = %{ givenName: "http://schema.org/givenName", familyName: "http://schema.org/familyName", homepage: %{ "@id": "http://schema.org/url", "@type": "@id" } } assert JSON.LD.context(context_with_atom_keys) == JSON.LD.context(@example_context_map) assert JSON.LD.context(%{"@context": context_with_atom_keys}) == JSON.LD.context(@example_context_map) assert JSON.LD.context(%{"@context" => context_with_atom_keys}) == JSON.LD.context(@example_context_map) end test "with a RDF.PropertyMap" do expected_context = %{ "@context" => %{ "givenName" => "http://schema.org/givenName", "familyName" => "http://schema.org/familyName" } } property_map = RDF.property_map( givenName: "http://schema.org/givenName", familyName: "http://schema.org/familyName" ) assert JSON.LD.context(property_map) == JSON.LD.context(expected_context) end end describe "errors" do %{ "no @id, @type, or @container" => %{ input: %{"foo" => %{}}, exception: JSON.LD.InvalidIRIMappingError }, "value as array" => %{ input: %{"foo" => []}, exception: JSON.LD.InvalidTermDefinitionError }, "@id as object" => %{ input: %{"foo" => %{"@id" => %{}}}, exception: JSON.LD.InvalidIRIMappingError }, "@id as array of object" => %{ input: %{"foo" => %{"@id" => [{}]}}, exception: JSON.LD.InvalidIRIMappingError }, "@id as array of null" => %{ input: %{"foo" => %{"@id" => [nil]}}, exception: JSON.LD.InvalidIRIMappingError }, "@type as object" => %{ input: %{"foo" => %{"@type" => %{}}}, exception: JSON.LD.InvalidTypeMappingError }, "@type as array" => %{ input: %{"foo" => %{"@type" => []}}, exception: JSON.LD.InvalidTypeMappingError }, "@type as @list" => %{ input: %{"foo" => %{"@type" => "@list"}}, exception: JSON.LD.InvalidTypeMappingError }, "@type as @set" => %{ input: %{"foo" => %{"@type" => "@set"}}, exception: JSON.LD.InvalidTypeMappingError }, "@container as object" => %{ input: %{"foo" => %{"@container" => %{}}}, exception: JSON.LD.InvalidIRIMappingError }, "@container as array" => %{ input: %{"foo" => %{"@container" => []}}, exception: JSON.LD.InvalidIRIMappingError }, "@container as string" => %{ input: %{"foo" => %{"@container" => "true"}}, exception: JSON.LD.InvalidIRIMappingError }, "@language as @id" => %{ input: %{"@language" => %{"@id" => "http://example.com/"}}, exception: JSON.LD.InvalidDefaultLanguageError }, "@vocab as @id" => %{ input: %{"@vocab" => %{"@id" => "http://example.com/"}}, exception: JSON.LD.InvalidVocabMappingError } } |> Enum.each(fn {title, data} -> @tag data: data test title, %{data: data} do assert_raise data.exception, fn -> JSON.LD.context(data.input) end end end) (JSON.LD.keywords() -- ~w[@base @language @vocab]) |> Enum.each(fn keyword -> @tag keyword: keyword test "does not redefine #{keyword} as a string", %{keyword: keyword} do assert_raise JSON.LD.KeywordRedefinitionError, fn -> JSON.LD.context(%{"@context" => %{keyword => "http://example.com/"}}) end end @tag keyword: keyword test "does not redefine #{keyword} with an @id", %{keyword: keyword} do assert_raise JSON.LD.KeywordRedefinitionError, fn -> JSON.LD.context(%{"@context" => %{keyword => %{"@id" => "http://example.com/"}}}) end end end) end # TODO: "Furthermore, the term must not be an empty string ("") as not all programming languages are able to handle empty JSON keys." -- https://www.w3.org/TR/json-ld/#terms @tag :skip test "an empty string is not a valid term" # TODO: "To avoid forward-compatibility issues, a term should not start with an @ character as future versions of JSON-LD may introduce additional keywords." -- https://www.w3.org/TR/json-ld/#terms @tag :skip test "warn on terms starting with a @" def iri_mappings(%JSON.LD.Context{term_defs: term_defs}) do Enum.reduce(term_defs, %{}, fn {term, term_def}, iri_mappings -> Map.put(iri_mappings, term, (term_def && term_def.iri_mapping) || nil) end) end def languages(%JSON.LD.Context{term_defs: term_defs}) do Enum.reduce(term_defs, %{}, fn {term, term_def}, language_mappings -> Map.put(language_mappings, term, term_def.language_mapping) end) end def coercions(%JSON.LD.Context{term_defs: term_defs}) do Enum.reduce(term_defs, %{}, fn {term, term_def}, type_mappings -> Map.put(type_mappings, term, term_def.type_mapping) end) end def containers(%JSON.LD.Context{term_defs: term_defs}) do Enum.reduce(term_defs, %{}, fn {term, term_def}, type_mappings -> Map.put(type_mappings, term, term_def.container_mapping) end) end end