json-ld 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +1 -1
  3. data/README.md +90 -53
  4. data/UNLICENSE +1 -1
  5. data/VERSION +1 -1
  6. data/bin/jsonld +4 -4
  7. data/lib/json/ld.rb +27 -10
  8. data/lib/json/ld/api.rb +325 -96
  9. data/lib/json/ld/compact.rb +75 -27
  10. data/lib/json/ld/conneg.rb +188 -0
  11. data/lib/json/ld/context.rb +677 -292
  12. data/lib/json/ld/expand.rb +240 -75
  13. data/lib/json/ld/flatten.rb +5 -3
  14. data/lib/json/ld/format.rb +19 -19
  15. data/lib/json/ld/frame.rb +135 -85
  16. data/lib/json/ld/from_rdf.rb +44 -17
  17. data/lib/json/ld/html/nokogiri.rb +151 -0
  18. data/lib/json/ld/html/rexml.rb +186 -0
  19. data/lib/json/ld/reader.rb +25 -5
  20. data/lib/json/ld/resource.rb +2 -2
  21. data/lib/json/ld/streaming_writer.rb +3 -1
  22. data/lib/json/ld/to_rdf.rb +47 -17
  23. data/lib/json/ld/utils.rb +4 -2
  24. data/lib/json/ld/writer.rb +75 -14
  25. data/spec/api_spec.rb +13 -34
  26. data/spec/compact_spec.rb +968 -9
  27. data/spec/conneg_spec.rb +373 -0
  28. data/spec/context_spec.rb +447 -53
  29. data/spec/expand_spec.rb +1872 -416
  30. data/spec/flatten_spec.rb +434 -47
  31. data/spec/frame_spec.rb +979 -344
  32. data/spec/from_rdf_spec.rb +305 -5
  33. data/spec/spec_helper.rb +177 -0
  34. data/spec/streaming_writer_spec.rb +4 -4
  35. data/spec/suite_compact_spec.rb +2 -2
  36. data/spec/suite_expand_spec.rb +14 -2
  37. data/spec/suite_flatten_spec.rb +10 -2
  38. data/spec/suite_frame_spec.rb +3 -2
  39. data/spec/suite_from_rdf_spec.rb +2 -2
  40. data/spec/suite_helper.rb +55 -20
  41. data/spec/suite_html_spec.rb +22 -0
  42. data/spec/suite_http_spec.rb +35 -0
  43. data/spec/suite_remote_doc_spec.rb +2 -2
  44. data/spec/suite_to_rdf_spec.rb +14 -3
  45. data/spec/support/extensions.rb +5 -1
  46. data/spec/test-files/test-4-input.json +3 -3
  47. data/spec/test-files/test-5-input.json +2 -2
  48. data/spec/test-files/test-8-framed.json +14 -18
  49. data/spec/to_rdf_spec.rb +606 -16
  50. data/spec/writer_spec.rb +5 -5
  51. metadata +144 -88
@@ -0,0 +1,373 @@
1
+ # coding: utf-8
2
+ require_relative 'spec_helper'
3
+ require 'rack/linkeddata'
4
+ require 'rack/test'
5
+
6
+ describe JSON::LD::ContentNegotiation do
7
+ include ::Rack::Test::Methods
8
+ let(:logger) {RDF::Spec.logger}
9
+
10
+ let(:app) do
11
+ described_class.new(double("Target Rack Application", :call => [200, {}, @results || "A String"]))
12
+ end
13
+
14
+ describe "#parse_accept_header" do
15
+ {
16
+ "application/n-triples, application/ld+json;q=0.5" => %w(application/ld+json),
17
+ "application/ld+json, application/ld+json;profile=http://www.w3.org/ns/json-ld#compacted" =>
18
+ %w(application/ld+json;profile=http://www.w3.org/ns/json-ld#compacted application/ld+json),
19
+ }.each do |accept, content_types|
20
+ it "returns #{content_types.inspect} given #{accept.inspect}" do
21
+ expect(app.send(:parse_accept_header, accept)).to eq content_types
22
+ end
23
+ end
24
+ end
25
+
26
+ describe "#find_content_type_for_media_range" do
27
+ {
28
+ "*/*" => "application/ld+json",
29
+ "application/*" => "application/ld+json",
30
+ "application/json" => "application/ld+json",
31
+ "application/json;profile=http://www.w3.org/ns/json-ld#compacted" =>
32
+ "application/ld+json;profile=http://www.w3.org/ns/json-ld#compacted",
33
+ "text/plain" => nil
34
+ }.each do |media_range, content_type|
35
+ it "returns #{content_type.inspect} for #{media_range.inspect}" do
36
+ expect(app.send(:find_content_type_for_media_range, media_range)).to eq content_type
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "#call" do
42
+ let(:schema_context) {
43
+ JSON::LD::API::RemoteDocument.new(%q({
44
+ "@context": {
45
+ "@vocab": "http://schema.org/",
46
+ "id": "@id",
47
+ "type": "@type"
48
+ }
49
+ }), documentUrl: "http://schema.org")
50
+ }
51
+ let(:frame) {
52
+ JSON::LD::API::RemoteDocument.new(%q({
53
+ "@context": {
54
+ "dc": "http://purl.org/dc/elements/1.1/",
55
+ "ex": "http://example.org/vocab#"
56
+ },
57
+ "@type": "ex:Library",
58
+ "ex:contains": {
59
+ "@type": "ex:Book",
60
+ "ex:contains": {
61
+ "@type": "ex:Chapter"
62
+ }
63
+ }
64
+ }), documentUrl: "http://conneg.example.com/frame")
65
+ }
66
+ let(:context) {
67
+ JSON::LD::API::RemoteDocument.new(%q({
68
+ "@context": {
69
+ "dc": "http://purl.org/dc/elements/1.1/",
70
+ "ex": "http://example.org/vocab#"
71
+ }
72
+ }), documentUrl: "http://conneg.example.com/context")
73
+ }
74
+
75
+ before(:each) do
76
+ allow(JSON::LD::API).to receive(:documentLoader).with("http://schema.org", any_args).and_yield(schema_context)
77
+ allow(JSON::LD::API).to receive(:documentLoader).with("http://conneg.example.com/context", any_args).and_yield(context)
78
+ allow(JSON::LD::API).to receive(:documentLoader).with("http://conneg.example.com/frame", any_args).and_yield(frame)
79
+ end
80
+
81
+ context "with text result" do
82
+ it "returns text unchanged" do
83
+ get '/'
84
+ expect(last_response.body).to eq 'A String'
85
+ end
86
+ end
87
+
88
+ context "with object result" do
89
+ before(:each) do
90
+ @results = LIBRARY_INPUT
91
+ end
92
+
93
+ it "returns expanded result" do
94
+ get '/'
95
+ expect(JSON.parse(last_response.body)).to produce_jsonld(LIBRARY_INPUT, logger)
96
+ end
97
+
98
+ context "with Accept" do
99
+ {
100
+ "application/n-triples" => "406 Not Acceptable (No appropriate combinaion of media-type and parameters found)\n",
101
+ "application/json" => LIBRARY_INPUT,
102
+ "application/ld+json" => LIBRARY_INPUT,
103
+ %(application/ld+json;profile=http://www.w3.org/ns/json-ld#expanded) =>
104
+ LIBRARY_EXPANDED,
105
+
106
+ %(application/ld+json;profile=http://www.w3.org/ns/json-ld#compacted) =>
107
+ LIBRARY_COMPACTED_DEFAULT,
108
+
109
+ %(application/ld+json;profile=http://www.w3.org/ns/json-ld#flattened) =>
110
+ LIBRARY_FLATTENED_EXPANDED,
111
+ %(application/ld+json;profile="http://www.w3.org/ns/json-ld#flattened http://www.w3.org/ns/json-ld#expanded") =>
112
+ LIBRARY_FLATTENED_EXPANDED,
113
+ %(application/ld+json;profile="http://www.w3.org/ns/json-ld#expanded http://www.w3.org/ns/json-ld#flattened") =>
114
+ LIBRARY_FLATTENED_EXPANDED,
115
+
116
+ %(application/ld+json;profile="http://www.w3.org/ns/json-ld#flattened http://www.w3.org/ns/json-ld#compacted") =>
117
+ LIBRARY_FLATTENED_COMPACTED_DEFAULT,
118
+ %(application/ld+json;profile="http://www.w3.org/ns/json-ld#compacted http://www.w3.org/ns/json-ld#flattened") =>
119
+ LIBRARY_FLATTENED_COMPACTED_DEFAULT,
120
+
121
+ %(application/ld+json;profile=http://www.w3.org/ns/json-ld#framed) =>
122
+ "406 Not Acceptable (framed profile without a frame)\n",
123
+ %(application/ld+json;profile="http://www.w3.org/ns/json-ld#framed http://www.w3.org/ns/json-ld#expanded") =>
124
+ "406 Not Acceptable (framed profile without a frame)\n",
125
+ %(application/ld+json;profile="http://www.w3.org/ns/json-ld#expanded http://www.w3.org/ns/json-ld#framed") =>
126
+ "406 Not Acceptable (framed profile without a frame)\n",
127
+ %(application/ld+json;profile="http://www.w3.org/ns/json-ld#framed http://www.w3.org/ns/json-ld#compacted") =>
128
+ "406 Not Acceptable (framed profile without a frame)\n",
129
+ %(application/ld+json;profile="http://www.w3.org/ns/json-ld#compacted http://www.w3.org/ns/json-ld#framed") =>
130
+ "406 Not Acceptable (framed profile without a frame)\n",
131
+ }.each do |accepts, result|
132
+ context accepts do
133
+ before(:each) do
134
+ get '/', {}, {"HTTP_ACCEPT" => accepts}
135
+ end
136
+
137
+ it "status" do
138
+ expect(last_response.status).to satisfy("be 200 or 406") {|x| [200, 406].include?(x)}
139
+ end
140
+
141
+ it "sets content type" do
142
+ expect(last_response.content_type).to eq(last_response.status == 406 ? 'text/plain' : 'application/ld+json')
143
+ end
144
+
145
+ it "returns serialization" do
146
+ if last_response.status == 406
147
+ expect(last_response.body).to eq result
148
+ else
149
+ expect(JSON.parse(last_response.body)).to produce_jsonld(result, logger)
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ context "with Link" do
157
+ {
158
+ "compacted with context" => {
159
+ accept: %(application/ld+json;profile=http://www.w3.org/ns/json-ld#compacted),
160
+ link: %(<http://conneg.example.com/context> rel="http://www.w3.org/ns/json-ld#context"),
161
+ result: LIBRARY_COMPACTED
162
+ },
163
+ "flattened with context" => {
164
+ accept: %(application/ld+json;profile=http://www.w3.org/ns/json-ld#flattened),
165
+ link: %(<http://conneg.example.com/context> rel="http://www.w3.org/ns/json-ld#context"),
166
+ result: LIBRARY_FLATTENED_COMPACTED
167
+ },
168
+ "flattened and compacted with context" => {
169
+ accept: %(application/ld+json;profile="http://www.w3.org/ns/json-ld#flattened http://www.w3.org/ns/json-ld#compacted"),
170
+ link: %(<http://conneg.example.com/context> rel="http://www.w3.org/ns/json-ld#context"),
171
+ result: LIBRARY_FLATTENED_COMPACTED
172
+ },
173
+ "compacted and flattened with context" => {
174
+ accept: %(application/ld+json;profile="http://www.w3.org/ns/json-ld#compacted http://www.w3.org/ns/json-ld#flattened"),
175
+ link: %(<http://conneg.example.com/context> rel="http://www.w3.org/ns/json-ld#context"),
176
+ result: LIBRARY_FLATTENED_COMPACTED
177
+ },
178
+ "framed with frame" => {
179
+ accept: %(application/ld+json;profile=http://www.w3.org/ns/json-ld#framed),
180
+ link: %(<http://conneg.example.com/frame> rel="http://www.w3.org/ns/json-ld#frame"),
181
+ result: LIBRARY_FRAMED
182
+ },
183
+
184
+ "framed with context" => {
185
+ accept: %(application/ld+json;profile=http://www.w3.org/ns/json-ld#framed),
186
+ link: %(<http://conneg.example.com/context> rel="http://www.w3.org/ns/json-ld#context"),
187
+ result: "406 Not Acceptable (framed profile without a frame)\n"
188
+ },
189
+ }.each do |name, params|
190
+ context name do
191
+ before(:each) do
192
+ get '/', {}, {"HTTP_ACCEPT" => params[:accept], "HTTP_LINK" => params[:link]}
193
+ end
194
+
195
+ it "status" do
196
+ expect(last_response.status).to satisfy("be 200 or 406") {|x| [200, 406].include?(x)}
197
+ end
198
+
199
+ it "sets content type" do
200
+ expect(last_response.content_type).to eq(last_response.status == 406 ? 'text/plain' : 'application/ld+json')
201
+ end
202
+
203
+ it "returns serialization" do
204
+ if last_response.status == 406
205
+ expect(last_response.body).to eq params[:result]
206
+ else
207
+ expect(JSON.parse(last_response.body)).to produce_jsonld(params[:result], logger)
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ describe Rack::LinkedData::ContentNegotiation do
218
+ include ::Rack::Test::Methods
219
+ let(:logger) {RDF::Spec.logger}
220
+
221
+ let(:app) do
222
+ graph = RDF::NTriples::Reader.new(%(
223
+ <http://example.org/library> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/vocab#Library> .
224
+ <http://example.org/library> <http://example.org/vocab#contains> <http://example.org/library/the-republic> .
225
+ <http://example.org/library/the-republic> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/vocab#Book> .
226
+ <http://example.org/library/the-republic> <http://purl.org/dc/elements/1.1/title> "The Republic" .
227
+ <http://example.org/library/the-republic> <http://purl.org/dc/elements/1.1/creator> "Plato" .
228
+ <http://example.org/library/the-republic> <http://example.org/vocab#contains> <http://example.org/library/the-republic#introduction> .
229
+ <http://example.org/library/the-republic#introduction> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/vocab#Chapter> .
230
+ <http://example.org/library/the-republic#introduction> <http://purl.org/dc/elements/1.1/title> "The Introduction" .
231
+ <http://example.org/library/the-republic#introduction> <http://purl.org/dc/elements/1.1/description> "An introductory chapter on The Republic." .
232
+ ))
233
+ Rack::LinkedData::ContentNegotiation.new(double("Target Rack Application", :call => [200, {}, graph]), {})
234
+ end
235
+
236
+ describe "#call" do
237
+ let(:schema_context) {
238
+ JSON::LD::API::RemoteDocument.new(%q({
239
+ "@context": {
240
+ "@vocab": "http://schema.org/",
241
+ "id": "@id",
242
+ "type": "@type"
243
+ }
244
+ }), documentUrl: "http://schema.org")
245
+ }
246
+ let(:frame) {
247
+ JSON::LD::API::RemoteDocument.new(%q({
248
+ "@context": {
249
+ "dc": "http://purl.org/dc/elements/1.1/",
250
+ "ex": "http://example.org/vocab#"
251
+ },
252
+ "@type": "ex:Library",
253
+ "ex:contains": {
254
+ "@type": "ex:Book",
255
+ "ex:contains": {
256
+ "@type": "ex:Chapter"
257
+ }
258
+ }
259
+ }), documentUrl: "http://conneg.example.com/frame")
260
+ }
261
+ let(:context) {
262
+ JSON::LD::API::RemoteDocument.new(%q({
263
+ "@context": {
264
+ "dc": "http://purl.org/dc/elements/1.1/",
265
+ "ex": "http://example.org/vocab#"
266
+ }
267
+ }), documentUrl: "http://conneg.example.com/context")
268
+ }
269
+
270
+ before(:each) do
271
+ allow(JSON::LD::API).to receive(:documentLoader).with("http://schema.org", any_args).and_yield(schema_context)
272
+ allow(JSON::LD::API).to receive(:documentLoader).with("http://conneg.example.com/context", any_args).and_yield(context)
273
+ allow(JSON::LD::API).to receive(:documentLoader).with("http://conneg.example.com/frame", any_args).and_yield(frame)
274
+ end
275
+
276
+ {
277
+ "application/json" => LIBRARY_FLATTENED_EXPANDED,
278
+ "application/ld+json" => LIBRARY_FLATTENED_EXPANDED,
279
+ %(application/ld+json;profile=http://www.w3.org/ns/json-ld#expanded) =>
280
+ LIBRARY_FLATTENED_EXPANDED,
281
+
282
+ %(application/ld+json;profile=http://www.w3.org/ns/json-ld#compacted) =>
283
+ LIBRARY_FLATTENED_COMPACTED_DEFAULT,
284
+
285
+ %(application/ld+json;profile=http://www.w3.org/ns/json-ld#flattened) =>
286
+ LIBRARY_FLATTENED_EXPANDED,
287
+ %(application/ld+json;profile="http://www.w3.org/ns/json-ld#flattened http://www.w3.org/ns/json-ld#expanded") =>
288
+ LIBRARY_FLATTENED_EXPANDED,
289
+ %(application/ld+json;profile="http://www.w3.org/ns/json-ld#expanded http://www.w3.org/ns/json-ld#flattened") =>
290
+ LIBRARY_FLATTENED_EXPANDED,
291
+
292
+ %(application/ld+json;profile="http://www.w3.org/ns/json-ld#flattened http://www.w3.org/ns/json-ld#compacted") =>
293
+ LIBRARY_FLATTENED_COMPACTED_DEFAULT,
294
+ %(application/ld+json;profile="http://www.w3.org/ns/json-ld#compacted http://www.w3.org/ns/json-ld#flattened") =>
295
+ LIBRARY_FLATTENED_COMPACTED_DEFAULT,
296
+
297
+ }.each do |accepts, result|
298
+ context accepts do
299
+ before(:each) do
300
+ get '/', {}, {"HTTP_ACCEPT" => accepts}
301
+ end
302
+
303
+ it "status" do
304
+ expect(last_response.status).to satisfy("200 or 406") {|x| [200, 406].include?(x)}
305
+ end
306
+
307
+ it "sets content type" do
308
+ expect(last_response.content_type).to eq(last_response.status == 406 ? 'text/plain' : 'application/ld+json')
309
+ end
310
+
311
+ it "returns serialization" do
312
+ if last_response.status == 406
313
+ expect(last_response.body).to eq result
314
+ else
315
+ expect(JSON.parse(last_response.body)).to produce_jsonld(result, logger)
316
+ end
317
+ end
318
+ end
319
+ end
320
+
321
+ context "with Link" do
322
+ {
323
+ "with context" => {
324
+ accept: %(application/ld+json),
325
+ link: %(<http://conneg.example.com/context> rel="http://www.w3.org/ns/json-ld#context"),
326
+ result: LIBRARY_FLATTENED_COMPACTED
327
+ },
328
+ "compacted with context" => {
329
+ accept: %(application/ld+json;profile=http://www.w3.org/ns/json-ld#compacted),
330
+ link: %(<http://conneg.example.com/context> rel="http://www.w3.org/ns/json-ld#context"),
331
+ result: LIBRARY_FLATTENED_COMPACTED
332
+ },
333
+ "flattened and compacted with context" => {
334
+ accept: %(application/ld+json;profile="http://www.w3.org/ns/json-ld#flattened http://www.w3.org/ns/json-ld#compacted"),
335
+ link: %(<http://conneg.example.com/context> rel="http://www.w3.org/ns/json-ld#context"),
336
+ result: LIBRARY_FLATTENED_COMPACTED
337
+ },
338
+ "compacted and flattened with context" => {
339
+ accept: %(application/ld+json;profile="http://www.w3.org/ns/json-ld#compacted http://www.w3.org/ns/json-ld#flattened"),
340
+ link: %(<http://conneg.example.com/context> rel="http://www.w3.org/ns/json-ld#context"),
341
+ result: LIBRARY_FLATTENED_COMPACTED
342
+ },
343
+ "framed with frame" => {
344
+ accept: %(application/ld+json;profile=http://www.w3.org/ns/json-ld#framed),
345
+ link: %(<http://conneg.example.com/frame> rel="http://www.w3.org/ns/json-ld#frame"),
346
+ result: LIBRARY_FRAMED
347
+ },
348
+ }.each do |name, params|
349
+ context name do
350
+ before(:each) do
351
+ get '/', {}, {"HTTP_ACCEPT" => params[:accept], "HTTP_LINK" => params[:link]}
352
+ end
353
+
354
+ it "status" do
355
+ expect(last_response.status).to satisfy("be 200 or 406") {|x| [200, 406].include?(x)}
356
+ end
357
+
358
+ it "sets content type" do
359
+ expect(last_response.content_type).to eq(last_response.status == 406 ? 'text/plain' : 'application/ld+json')
360
+ end
361
+
362
+ it "returns serialization" do
363
+ if last_response.status == 406
364
+ expect(last_response.body).to eq params[:result]
365
+ else
366
+ expect(JSON.parse(last_response.body)).to produce_jsonld(params[:result], logger)
367
+ end
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end
373
+ end
@@ -25,14 +25,16 @@ describe JSON::LD::Context do
25
25
  let(:logger) {RDF::Spec.logger}
26
26
  let(:context) {JSON::LD::Context.new(logger: logger, validate: true, processingMode: "json-ld-1.1", compactToRelative: true)}
27
27
  let(:remote_doc) do
28
- JSON::LD::API::RemoteDocument.new("http://example.com/context", %q({
28
+ JSON::LD::API::RemoteDocument.new(%q({
29
29
  "@context": {
30
30
  "xsd": "http://www.w3.org/2001/XMLSchema#",
31
31
  "name": "http://xmlns.com/foaf/0.1/name",
32
32
  "homepage": {"@id": "http://xmlns.com/foaf/0.1/homepage", "@type": "@id"},
33
33
  "avatar": {"@id": "http://xmlns.com/foaf/0.1/avatar", "@type": "@id"}
34
34
  }
35
- }))
35
+ }),
36
+ documentUrl: "http://example.com/context",
37
+ contentType: "application/ld+json")
36
38
  end
37
39
  subject {context}
38
40
 
@@ -78,13 +80,98 @@ describe JSON::LD::Context do
78
80
  }, logger)
79
81
  end
80
82
 
83
+ it "retrieves and parses a remote context document in HTML using the context profile" do
84
+ remote_doc =
85
+ JSON::LD::API::RemoteDocument.new(%q(
86
+ <html><head>
87
+ <script>Not This</script>
88
+ <script type="application/ld+json">
89
+ {
90
+ "@context": {
91
+ "homepage": {"@id": "http://example.com/this-would-be-wrong", "@type": "@id"},
92
+ "avatar": {"@id": "http://example.com/this-would-be-wrong", "@type": "@id"}
93
+ }
94
+ }
95
+ </script>
96
+ <script type="application/ld+json;profile=http://www.w3.org/ns/json-ld#context">
97
+ {
98
+ "@context": {
99
+ "xsd": "http://www.w3.org/2001/XMLSchema#",
100
+ "name": "http://xmlns.com/foaf/0.1/name",
101
+ "homepage": {"@id": "http://xmlns.com/foaf/0.1/homepage", "@type": "@id"},
102
+ "avatar": {"@id": "http://xmlns.com/foaf/0.1/avatar", "@type": "@id"}
103
+ }
104
+ }
105
+ </script>
106
+ <script type="application/ld+json;profile=http://www.w3.org/ns/json-ld#context">
107
+ {
108
+ "@context": {
109
+ "homepage": {"@id": "http://example.com/this-would-also-be-wrong", "@type": "@id"},
110
+ "avatar": {"@id": "http://example.com/this-would-also-be-wrong", "@type": "@id"}
111
+ }
112
+ }
113
+ </script>
114
+ </head></html>
115
+ ),
116
+ documentUrl: "http://example.com/context",
117
+ contentType: "text/html")
118
+
119
+ JSON::LD::Context::PRELOADED.clear
120
+ expect(JSON::LD::API).to receive(:documentLoader).with("http://example.com/context", anything).and_yield(remote_doc)
121
+ ec = subject.parse("http://example.com/context")
122
+ expect(ec.send(:mappings)).to produce({
123
+ "xsd" => "http://www.w3.org/2001/XMLSchema#",
124
+ "name" => "http://xmlns.com/foaf/0.1/name",
125
+ "homepage" => "http://xmlns.com/foaf/0.1/homepage",
126
+ "avatar" => "http://xmlns.com/foaf/0.1/avatar"
127
+ }, logger)
128
+ end
129
+
130
+ it "retrieves and parses a remote context document in HTML" do
131
+ remote_doc =
132
+ JSON::LD::API::RemoteDocument.new(%q(
133
+ <html><head>
134
+ <script>Not This</script>
135
+ <script type="application/ld+json">
136
+ {
137
+ "@context": {
138
+ "xsd": "http://www.w3.org/2001/XMLSchema#",
139
+ "name": "http://xmlns.com/foaf/0.1/name",
140
+ "homepage": {"@id": "http://xmlns.com/foaf/0.1/homepage", "@type": "@id"},
141
+ "avatar": {"@id": "http://xmlns.com/foaf/0.1/avatar", "@type": "@id"}
142
+ }
143
+ }
144
+ </script>
145
+ <script type="application/ld+json">
146
+ {
147
+ "@context": {
148
+ "homepage": {"@id": "http://example.com/this-would-also-be-wrong", "@type": "@id"},
149
+ "avatar": {"@id": "http://example.com/this-would-also-be-wrong", "@type": "@id"}
150
+ }
151
+ }
152
+ </script>
153
+ </head></html>
154
+ ),
155
+ documentUrl: "http://example.com/context",
156
+ contentType: "text/html")
157
+ JSON::LD::Context::PRELOADED.clear
158
+ expect(JSON::LD::API).to receive(:documentLoader).with("http://example.com/context", anything).and_yield(remote_doc)
159
+ ec = subject.parse("http://example.com/context")
160
+ expect(ec.send(:mappings)).to produce({
161
+ "xsd" => "http://www.w3.org/2001/XMLSchema#",
162
+ "name" => "http://xmlns.com/foaf/0.1/name",
163
+ "homepage" => "http://xmlns.com/foaf/0.1/homepage",
164
+ "avatar" => "http://xmlns.com/foaf/0.1/avatar"
165
+ }, logger)
166
+ end
167
+
81
168
  it "notes non-existing @context" do
82
169
  expect {subject.parse(StringIO.new("{}"))}.to raise_error(JSON::LD::JsonLdError::InvalidRemoteContext)
83
170
  end
84
171
 
85
172
  it "parses a referenced context at a relative URI" do
86
173
  JSON::LD::Context::PRELOADED.clear
87
- rd1 = JSON::LD::API::RemoteDocument.new("http://example.com/c1", %({"@context": "context"}))
174
+ rd1 = JSON::LD::API::RemoteDocument.new(%({"@context": "context"}), base_uri: "http://example.com/c1")
88
175
  expect(JSON::LD::API).to receive(:documentLoader).with("http://example.com/c1", anything).and_yield(rd1)
89
176
  expect(JSON::LD::API).to receive(:documentLoader).with("http://example.com/context", anything).and_yield(remote_doc)
90
177
  ec = subject.parse("http://example.com/c1")
@@ -115,8 +202,10 @@ describe JSON::LD::Context do
115
202
  let(:ctx) {"http://example.com/preloaded"}
116
203
  before(:all) {
117
204
  JSON::LD::Context.add_preloaded("http://example.com/preloaded",
118
- JSON::LD::Context.parse({'foo' => "http://example.com/"})
119
- )}
205
+ JSON::LD::Context.parse({'foo' => "http://example.com/"})
206
+ )
207
+ JSON::LD::Context.alias_preloaded("https://example.com/preloaded", "http://example.com/preloaded")
208
+ }
120
209
  after(:all) {JSON::LD::Context::PRELOADED.clear}
121
210
 
122
211
  it "does not load referenced context" do
@@ -124,12 +213,24 @@ describe JSON::LD::Context do
124
213
  subject.parse(ctx)
125
214
  end
126
215
 
216
+ it "does not load aliased context" do
217
+ expect(JSON::LD::API).not_to receive(:documentLoader).with(ctx.sub('http', 'https'), anything)
218
+ subject.parse(ctx.sub('http', 'https'))
219
+ end
220
+
127
221
  it "uses loaded context" do
128
222
  ec = subject.parse(ctx)
129
223
  expect(ec.send(:mappings)).to produce({
130
224
  "foo" => "http://example.com/"
131
225
  }, logger)
132
226
  end
227
+
228
+ it "uses aliased context" do
229
+ ec = subject.parse(ctx.sub('http', 'https'))
230
+ expect(ec.send(:mappings)).to produce({
231
+ "foo" => "http://example.com/"
232
+ }, logger)
233
+ end
133
234
  end
134
235
  end
135
236
 
@@ -149,11 +250,11 @@ describe JSON::LD::Context do
149
250
 
150
251
  it "merges definitions from remote contexts" do
151
252
  expect(JSON::LD::API).to receive(:documentLoader).with("http://example.com/context", anything).and_yield(remote_doc)
152
- rd2 = JSON::LD::API::RemoteDocument.new("http://example.com/c2", %q({
253
+ rd2 = JSON::LD::API::RemoteDocument.new(%q({
153
254
  "@context": {
154
255
  "title": {"@id": "http://purl.org/dc/terms/title"}
155
256
  }
156
- }))
257
+ }), base_uri: "http://example.com/c2")
157
258
  expect(JSON::LD::API).to receive(:documentLoader).with("http://example.com/c2", anything).and_yield(rd2)
158
259
  ec = subject.parse(%w(http://example.com/context http://example.com/c2))
159
260
  expect(ec.send(:mappings)).to produce({
@@ -187,6 +288,16 @@ describe JSON::LD::Context do
187
288
  }, logger)
188
289
  end
189
290
 
291
+ it "maps term with blank node value (with deprecation)" do
292
+ expect do
293
+ expect(subject.parse({
294
+ "foo" => "_:bn"
295
+ }).send(:mappings)).to produce({
296
+ "foo" => RDF::Node("bn")
297
+ }, logger)
298
+ end.to write("[DEPRECATION]").to(:error)
299
+ end
300
+
190
301
  it "maps term with @id" do
191
302
  expect(subject.parse({
192
303
  "foo" => {"@id" => "http://example.com/"}
@@ -195,6 +306,44 @@ describe JSON::LD::Context do
195
306
  }, logger)
196
307
  end
197
308
 
309
+ it "maps term with blank node @id (with deprecation)" do
310
+ expect do
311
+ expect(subject.parse({
312
+ "foo" => {"@id" => "_:bn"}
313
+ }).send(:mappings)).to produce({
314
+ "foo" => RDF::Node("bn")
315
+ }, logger)
316
+ end.to write("[DEPRECATION]").to(:error)
317
+ end
318
+
319
+ it "warns and ignores keyword-like term" do
320
+ expect do
321
+ expect(subject.parse({
322
+ "@foo" => {"@id" => "http://example.org/foo"}
323
+ }).send(:mappings)).to produce({}, logger)
324
+ end.to write("Terms beginning with '@' are reserved").to(:error)
325
+ end
326
+
327
+ it "maps '@' as a term" do
328
+ expect do
329
+ expect(subject.parse({
330
+ "@" => {"@id" => "http://example.org/@"}
331
+ }).send(:mappings)).to produce({
332
+ "@" => "http://example.org/@"
333
+ }, logger)
334
+ end.not_to write.to(:error)
335
+ end
336
+
337
+ it "maps '@foo.bar' as a term" do
338
+ expect do
339
+ expect(subject.parse({
340
+ "@foo.bar" => {"@id" => "http://example.org/foo.bar"}
341
+ }).send(:mappings)).to produce({
342
+ "@foo.bar" => "http://example.org/foo.bar"
343
+ }, logger)
344
+ end.not_to write.to(:error)
345
+ end
346
+
198
347
  it "associates @list container mapping with term" do
199
348
  expect(subject.parse({
200
349
  "foo" => {"@id" => "http://example.com/", "@container" => "@list"}
@@ -227,6 +376,14 @@ describe JSON::LD::Context do
227
376
  }, logger)
228
377
  end
229
378
 
379
+ it "associates @json type mapping with term" do
380
+ expect(subject.parse({
381
+ "foo" => {"@id" => "http://example.com/", "@type" => "@json"}
382
+ }).coercions).to produce({
383
+ "foo" => "@json"
384
+ }, logger)
385
+ end
386
+
230
387
  it "associates type mapping with term" do
231
388
  expect(subject.parse({
232
389
  "foo" => {"@id" => "http://example.com/", "@type" => RDF::XSD.string.to_s}
@@ -310,6 +467,29 @@ describe JSON::LD::Context do
310
467
  expect(subject.parse({"name" => nil}).send(:mapping, "name")).to be_nil
311
468
  end
312
469
  end
470
+
471
+
472
+ context "@propagate" do
473
+ it "generates an InvalidPropagateValue error if not a boolean" do
474
+ expect {subject.parse({'@version' => 1.1, '@propagate' => "String"})}.to raise_error(JSON::LD::JsonLdError::InvalidPropagateValue)
475
+ end
476
+ end
477
+
478
+ context "@import" do
479
+ before(:each) {JSON::LD::Context::PRELOADED.clear}
480
+ it "generates an InvalidImportValue error if not a string" do
481
+ expect {subject.parse({'@version' => 1.1, '@import' => true})}.to raise_error(JSON::LD::JsonLdError::InvalidImportValue)
482
+ end
483
+
484
+ it "retrieves remote context" do
485
+ expect(JSON::LD::API).to receive(:documentLoader).with("http://example.com/context", anything).and_yield(remote_doc)
486
+ ec = subject.parse(JSON.parse %({
487
+ "@version": 1.1,
488
+ "@import": "http://example.com/context"
489
+ }))
490
+ expect(ec.term_definitions).to include("avatar")
491
+ end
492
+ end
313
493
  end
314
494
 
315
495
  describe "Syntax Errors" do
@@ -323,16 +503,28 @@ describe JSON::LD::Context do
323
503
  "@type as object" => {"foo" => {"@type" => {}}},
324
504
  "@type as array" => {"foo" => {"@type" => []}},
325
505
  "@type as @list" => {"foo" => {"@type" => "@list"}},
506
+ "@type as @none" => {"@version": 1.1, "foo" => {"@type" => "@none"}},
326
507
  "@type as @set" => {"foo" => {"@type" => "@set"}},
327
508
  "@container as object" => {"foo" => {"@container" => {}}},
328
509
  "@container as empty array" => {"foo" => {"@container" => []}},
329
510
  "@container as string" => {"foo" => {"@container" => "true"}},
330
511
  "@context which is invalid" => {"foo" => {"@context" => {"bar" => []}}},
331
512
  "@language as @id" => {"@language" => {"@id" => "http://example.com/"}},
513
+ "@direction as foo" => {"@direction" => "foo"},
332
514
  "@vocab as @id" => {"@vocab" => {"@id" => "http://example.com/"}},
333
515
  "@prefix string" => {"foo" => {"@id" => 'http://example.org/', "@prefix" => "str"}},
334
516
  "@prefix array" => {"foo" => {"@id" => 'http://example.org/', "@prefix" => []}},
335
517
  "@prefix object" => {"foo" => {"@id" => 'http://example.org/', "@prefix" => {}}},
518
+ "IRI term expands to different IRI" => {
519
+ "ex" => "http://example.com/",
520
+ "ex2" => "http://example.com/2/",
521
+ "ex:foo" => "ex2:foo"
522
+ },
523
+ "IRI term expands to different IRI (reverse)" => {
524
+ "ex" => "http://example.com/",
525
+ "ex2" => "http://example.com/2/",
526
+ "ex:foo" => {"@reverse" => "ex2:foo"}
527
+ }
336
528
  }.each do |title, context|
337
529
  it title do
338
530
  expect {
@@ -343,12 +535,14 @@ describe JSON::LD::Context do
343
535
  end
344
536
 
345
537
  context "1.0" do
346
- let(:context) {JSON::LD::Context.new(logger: logger, validate: true)}
538
+ let(:context) {JSON::LD::Context.new(logger: logger, validate: true, processingMode: 'json-ld-1.0')}
347
539
  {
348
540
  "@context" => {"foo" => {"@id" => 'http://example.org/', "@context" => {}}},
349
541
  "@container @id" => {"foo" => {"@container" => "@id"}},
350
542
  "@container @type" => {"foo" => {"@container" => "@type"}},
351
543
  "@nest" => {"foo" => {"@id" => 'http://example.org/', "@nest" => "@nest"}},
544
+ "@type as @none" => {"foo" => {"@type" => "@none"}},
545
+ "@type as @json" => {"foo" => {"@type" => "@json"}},
352
546
  "@prefix" => {"foo" => {"@id" => 'http://example.org/', "@prefix" => true}},
353
547
  }.each do |title, context|
354
548
  it title do
@@ -358,9 +552,26 @@ describe JSON::LD::Context do
358
552
  }.to raise_error(JSON::LD::JsonLdError)
359
553
  end
360
554
  end
555
+
556
+ it "generates InvalidContextMember if using @propagate" do
557
+ expect {context.parse({'@propagate' => true})}.to raise_error(JSON::LD::JsonLdError::InvalidContextMember)
558
+ end
559
+
560
+ it "generates InvalidContextMember if using @import" do
561
+ expect {context.parse({'@import' => "location"})}.to raise_error(JSON::LD::JsonLdError::InvalidContextMember)
562
+ end
563
+
564
+ (JSON::LD::KEYWORDS - %w(@base @language @version @protected @propagate @vocab)).each do |kw|
565
+ it "does not redefine #{kw} with an @container" do
566
+ expect {
567
+ ec = subject.parse({kw => {"@container" => "@set"}})
568
+ expect(ec.serialize).to produce({}, logger)
569
+ }.to raise_error(JSON::LD::JsonLdError)
570
+ end
571
+ end
361
572
  end
362
573
 
363
- (JSON::LD::KEYWORDS - %w(@base @language @vocab)).each do |kw|
574
+ (JSON::LD::KEYWORDS - %w(@base @direction @language @protected @propagate @import @version @vocab)).each do |kw|
364
575
  it "does not redefine #{kw} as a string" do
365
576
  expect {
366
577
  ec = subject.parse({kw => "http://example.com/"})
@@ -374,6 +585,18 @@ describe JSON::LD::Context do
374
585
  expect(ec.serialize).to produce({}, logger)
375
586
  }.to raise_error(JSON::LD::JsonLdError)
376
587
  end
588
+
589
+ it "does not redefine #{kw} with an @container" do
590
+ expect {
591
+ ec = subject.parse({"@version" => 1.1, kw => {"@container" => "@set"}})
592
+ expect(ec.serialize).to produce({}, logger)
593
+ }.to raise_error(JSON::LD::JsonLdError)
594
+ end unless kw == '@type'
595
+
596
+ it "redefines #{kw} with an @container" do
597
+ ec = subject.parse({kw => {"@container" => "@set"}})
598
+ expect(ec.as_array('@type')).to be_truthy
599
+ end if kw == '@type'
377
600
  end
378
601
  end
379
602
  end
@@ -609,7 +832,19 @@ describe JSON::LD::Context do
609
832
  }, logger)
610
833
  end
611
834
 
612
- it "CURIE with @type" do
835
+ it "prefix with @type @json" do
836
+ expect(subject.parse({
837
+ "knows" => {"@id" => RDF::Vocab::FOAF.knows.to_s, "@type" => "@json"}
838
+ }).
839
+ send(:clear_provided_context).
840
+ serialize).to produce({
841
+ "@context" => {
842
+ "knows" => {"@id" => RDF::Vocab::FOAF.knows.to_s, "@type" => "@json"}
843
+ }
844
+ }, logger)
845
+ end
846
+
847
+ it "Compact IRI with @type" do
613
848
  expect(subject.parse({
614
849
  "foaf" => RDF::Vocab::FOAF.to_uri.to_s,
615
850
  "foaf:knows" => {
@@ -691,7 +926,7 @@ describe JSON::LD::Context do
691
926
  }, logger)
692
927
  end
693
928
 
694
- it "compacts IRIs to CURIEs" do
929
+ it "compacts IRIs to Compact IRIs" do
695
930
  expect(subject.parse({
696
931
  "ex" => 'http://example.org/',
697
932
  "term" => {"@id" => "ex:term", "@type" => "ex:datatype"}
@@ -756,6 +991,48 @@ describe JSON::LD::Context do
756
991
  end
757
992
  end
758
993
 
994
+ describe "#vocab=" do
995
+ subject {
996
+ context.parse({
997
+ '@base' => 'http://base/resource',
998
+ })
999
+ }
1000
+
1001
+ it "sets vocab from absolute iri" do
1002
+ subject.vocab = "http://example.org/"
1003
+ expect(subject.vocab).to eql RDF::URI("http://example.org/")
1004
+ end
1005
+
1006
+ it "sets vocab from empty string" do
1007
+ subject.vocab = ""
1008
+ expect(subject.vocab).to eql RDF::URI("http://base/resource")
1009
+ end
1010
+
1011
+ it "sets vocab to blank node (with deprecation)" do
1012
+ expect do
1013
+ subject.vocab = "_:bn"
1014
+ end.to write("[DEPRECATION]").to(:error)
1015
+ expect(subject.vocab).to eql "_:bn"
1016
+ end
1017
+
1018
+ it "sets vocab from relative IRI" do
1019
+ subject.vocab = "relative#"
1020
+ expect(subject.vocab).to eql RDF::URI("http://base/relative#")
1021
+ end
1022
+
1023
+ it "sets vocab from relative IRI given an existing vocab" do
1024
+ subject.vocab = "http://example.org/."
1025
+ subject.vocab = "relative#"
1026
+ expect(subject.vocab).to eql RDF::URI("http://example.org/.relative#")
1027
+ end
1028
+
1029
+ it "sets vocab from relative IRI given an existing vocab which is also relative" do
1030
+ subject.vocab = "/rel1"
1031
+ subject.vocab = "rel2#"
1032
+ expect(subject.vocab).to eql RDF::URI("http://base/rel1rel2#")
1033
+ end
1034
+ end
1035
+
759
1036
  describe "#expand_iri" do
760
1037
  subject {
761
1038
  context.parse({
@@ -786,17 +1063,19 @@ describe JSON::LD::Context do
786
1063
  "absolute IRI" => ["http://example.org/", RDF::URI("http://example.org/")],
787
1064
  "term" => ["ex", RDF::URI("ex")],
788
1065
  "prefix:suffix" => ["ex:suffix", RDF::URI("http://example.org/suffix")],
1066
+ "#frag" => ["#frag", RDF::URI("#frag")],
1067
+ "#frag:2" => ["#frag:2", RDF::URI("#frag:2")],
789
1068
  "keyword" => ["@type", "@type"],
790
- "empty" => [":suffix", RDF::URI("http://empty/suffix")],
791
1069
  "unmapped" => ["foo", RDF::URI("foo")],
792
1070
  "relative" => ["foo/bar", RDF::URI("foo/bar")],
793
1071
  "dotseg" => ["../foo/bar", RDF::URI("../foo/bar")],
794
1072
  "empty term" => ["", RDF::URI("")],
795
1073
  "another abs IRI"=>["ex://foo", RDF::URI("ex://foo")],
796
- "absolute IRI looking like a curie" =>
1074
+ "absolute IRI looking like a Compact IRI" =>
797
1075
  ["foo:bar", RDF::URI("foo:bar")],
798
1076
  "bnode" => ["_:t0", RDF::Node("t0")],
799
1077
  "_" => ["_", RDF::URI("_")],
1078
+ "@" => ["@", RDF::URI("@")],
800
1079
  }.each do |title, (input, result)|
801
1080
  it title do
802
1081
  expect(subject.expand_iri(input)).to produce(result, logger)
@@ -809,17 +1088,19 @@ describe JSON::LD::Context do
809
1088
  "absolute IRI" => ["http://example.org/", RDF::URI("http://example.org/")],
810
1089
  "term" => ["ex", RDF::URI("http://base/ex")],
811
1090
  "prefix:suffix" => ["ex:suffix", RDF::URI("http://example.org/suffix")],
1091
+ "#frag" => ["#frag", RDF::URI("http://base/base#frag")],
1092
+ "#frag:2" => ["#frag:2", RDF::URI("http://base/base#frag:2")],
812
1093
  "keyword" => ["@type", "@type"],
813
- "empty" => [":suffix", RDF::URI("http://empty/suffix")],
814
1094
  "unmapped" => ["foo", RDF::URI("http://base/foo")],
815
1095
  "relative" => ["foo/bar", RDF::URI("http://base/foo/bar")],
816
1096
  "dotseg" => ["../foo/bar", RDF::URI("http://base/foo/bar")],
817
1097
  "empty term" => ["", RDF::URI("http://base/base")],
818
1098
  "another abs IRI"=>["ex://foo", RDF::URI("ex://foo")],
819
- "absolute IRI looking like a curie" =>
1099
+ "absolute IRI looking like a compact IRI" =>
820
1100
  ["foo:bar", RDF::URI("foo:bar")],
821
1101
  "bnode" => ["_:t0", RDF::Node("t0")],
822
1102
  "_" => ["_", RDF::URI("http://base/_")],
1103
+ "@" => ["@", RDF::URI("http://base/@")],
823
1104
  }.each do |title, (input, result)|
824
1105
  it title do
825
1106
  expect(subject.expand_iri(input, documentRelative: true)).to produce(result, logger)
@@ -832,17 +1113,19 @@ describe JSON::LD::Context do
832
1113
  "absolute IRI" => ["http://example.org/", RDF::URI("http://example.org/")],
833
1114
  "term" => ["ex", RDF::URI("http://example.org/")],
834
1115
  "prefix:suffix" => ["ex:suffix", RDF::URI("http://example.org/suffix")],
1116
+ "#frag" => ["#frag", RDF::URI("http://vocab/#frag")],
1117
+ "#frag:2" => ["#frag:2", RDF::URI("http://vocab/#frag:2")],
835
1118
  "keyword" => ["@type", "@type"],
836
- "empty" => [":suffix", RDF::URI("http://empty/suffix")],
837
1119
  "unmapped" => ["foo", RDF::URI("http://vocab/foo")],
838
1120
  "relative" => ["foo/bar", RDF::URI("http://vocab/foo/bar")],
839
1121
  "dotseg" => ["../foo/bar", RDF::URI("http://vocab/../foo/bar")],
840
1122
  "empty term" => ["", RDF::URI("http://empty/")],
841
1123
  "another abs IRI"=>["ex://foo", RDF::URI("ex://foo")],
842
- "absolute IRI looking like a curie" =>
1124
+ "absolute IRI looking like a compact IRI" =>
843
1125
  ["foo:bar", RDF::URI("foo:bar")],
844
1126
  "bnode" => ["_:t0", RDF::Node("t0")],
845
1127
  "_" => ["_", RDF::URI("http://underscore/")],
1128
+ "@" => ["@", RDF::URI("http://vocab/@")],
846
1129
  }.each do |title, (input, result)|
847
1130
  it title do
848
1131
  expect(subject.expand_iri(input, vocab: true)).to produce(result, logger)
@@ -864,14 +1147,15 @@ describe JSON::LD::Context do
864
1147
  "absolute IRI" => ["http://example.org/", RDF::URI("http://example.org/")],
865
1148
  "term" => ["ex", RDF::URI("http://example.org/")],
866
1149
  "prefix:suffix" => ["ex:suffix", RDF::URI("http://example.org/suffix")],
1150
+ "#frag" => ["#frag", RDF::URI("http://base/base#frag")],
1151
+ "#frag:2" => ["#frag:2", RDF::URI("http://base/base#frag:2")],
867
1152
  "keyword" => ["@type", "@type"],
868
- "empty" => [":suffix", RDF::URI("http://empty/suffix")],
869
1153
  "unmapped" => ["foo", RDF::URI("http://base/basefoo")],
870
1154
  "relative" => ["foo/bar", RDF::URI("http://base/basefoo/bar")],
871
1155
  "dotseg" => ["../foo/bar", RDF::URI("http://base/base../foo/bar")],
872
1156
  "empty term" => ["", RDF::URI("http://empty/")],
873
1157
  "another abs IRI"=>["ex://foo", RDF::URI("ex://foo")],
874
- "absolute IRI looking like a curie" =>
1158
+ "absolute IRI looking like a compact IRI" =>
875
1159
  ["foo:bar", RDF::URI("foo:bar")],
876
1160
  "bnode" => ["_:t0", RDF::Node("t0")],
877
1161
  "_" => ["_", RDF::URI("http://underscore/")],
@@ -881,6 +1165,14 @@ describe JSON::LD::Context do
881
1165
  end
882
1166
  end
883
1167
  end
1168
+
1169
+ it "expand-0110" do
1170
+ ctx = JSON::LD::Context.parse({
1171
+ "@base" => "http://example.com/some/deep/directory/and/file/",
1172
+ "@vocab" => "/relative"
1173
+ })
1174
+ expect(ctx.expand_iri("#fragment-works", vocab: true)).to produce("http://example.com/relative#fragment-works", logger)
1175
+ end
884
1176
  end
885
1177
  end
886
1178
  end
@@ -897,7 +1189,7 @@ describe JSON::LD::Context do
897
1189
  'lex' => {'@id' => 'ex', '@language' => 'en'},
898
1190
  'tex' => {'@id' => 'ex', '@type' => 'xsd:string'},
899
1191
  'exp' => {'@id' => 'ex:pert'},
900
- 'experts' => {'@id' => 'ex:perts'}
1192
+ 'experts' => {'@id' => 'ex:perts'},
901
1193
  })
902
1194
  logger.clear
903
1195
  c
@@ -908,14 +1200,17 @@ describe JSON::LD::Context do
908
1200
  "absolute IRI" => ["http://example.com/", "http://example.com/"],
909
1201
  "prefix:suffix" => ["ex:suffix", "http://example.org/suffix"],
910
1202
  "keyword" => ["@type", "@type"],
911
- "empty" => [":suffix", "http://empty/suffix"],
912
1203
  "unmapped" => ["foo", "foo"],
913
- "bnode" => ["_:a", RDF::Node("a")],
1204
+ "bnode" => [JSON::LD::JsonLdError:: IRIConfusedWithPrefix, RDF::Node("a")],
914
1205
  "relative" => ["foo/bar", "http://base/foo/bar"],
915
- "odd CURIE" => ["ex:perts", "http://example.org/perts"]
1206
+ "odd Compact IRI"=>["ex:perts", "http://example.org/perts"]
916
1207
  }.each do |title, (result, input)|
917
1208
  it title do
918
- expect(subject.compact_iri(input)).to produce(result, logger)
1209
+ if result.is_a?(Class)
1210
+ expect {subject.compact_iri(input)}.to raise_error(result)
1211
+ else
1212
+ expect(subject.compact_iri(input)).to produce(result, logger)
1213
+ end
919
1214
  end
920
1215
  end
921
1216
 
@@ -924,14 +1219,17 @@ describe JSON::LD::Context do
924
1219
  "absolute IRI" => ["http://example.com/", "http://example.com/"],
925
1220
  "prefix:suffix" => ["ex:suffix", "http://example.org/suffix"],
926
1221
  "keyword" => ["@type", "@type"],
927
- "empty" => [":suffix", "http://empty/suffix"],
928
1222
  "unmapped" => ["foo", "foo"],
929
- "bnode" => ["_:a", RDF::Node("a")],
1223
+ "bnode" => [JSON::LD::JsonLdError:: IRIConfusedWithPrefix, RDF::Node("a")],
930
1224
  "relative" => ["http://base/foo/bar", "http://base/foo/bar"],
931
- "odd CURIE" => ["experts", "http://example.org/perts"]
1225
+ "odd Compact IRI"=> ["experts", "http://example.org/perts"]
932
1226
  }.each do |title, (result, input)|
933
1227
  it title do
934
- expect(subject.compact_iri(input, vocab: true)).to produce(result, logger)
1228
+ if result.is_a?(Class)
1229
+ expect {subject.compact_iri(input, vocab: true)}.to raise_error(result)
1230
+ else
1231
+ expect(subject.compact_iri(input, vocab: true)).to produce(result, logger)
1232
+ end
935
1233
  end
936
1234
  end
937
1235
  end
@@ -943,14 +1241,17 @@ describe JSON::LD::Context do
943
1241
  "absolute IRI" => ["http://example.com/", "http://example.com/"],
944
1242
  "prefix:suffix" => ["suffix", "http://example.org/suffix"],
945
1243
  "keyword" => ["@type", "@type"],
946
- "empty" => [":suffix", "http://empty/suffix"],
947
1244
  "unmapped" => ["foo", "foo"],
948
- "bnode" => ["_:a", RDF::Node("a")],
1245
+ "bnode" => [JSON::LD::JsonLdError:: IRIConfusedWithPrefix, RDF::Node("a")],
949
1246
  "relative" => ["http://base/foo/bar", "http://base/foo/bar"],
950
- "odd CURIE" => ["experts", "http://example.org/perts"]
1247
+ "odd Compact IRI"=> ["experts", "http://example.org/perts"]
951
1248
  }.each do |title, (result, input)|
952
1249
  it title do
953
- expect(subject.compact_iri(input, vocab: true)).to produce(result, logger)
1250
+ if result.is_a?(Class)
1251
+ expect {subject.compact_iri(input, vocab: true)}.to raise_error(result)
1252
+ else
1253
+ expect(subject.compact_iri(input, vocab: true)).to produce(result, logger)
1254
+ end
954
1255
  end
955
1256
  end
956
1257
 
@@ -963,7 +1264,7 @@ describe JSON::LD::Context do
963
1264
 
964
1265
  context "with @vocab: relative" do
965
1266
  before(:each) {
966
- subject.vocab = ""
1267
+ subject.vocab = nil
967
1268
  subject.base = 'http://base/base'
968
1269
  }
969
1270
 
@@ -971,14 +1272,17 @@ describe JSON::LD::Context do
971
1272
  "absolute IRI" => ["http://example.com/", "http://example.com/"],
972
1273
  "prefix:suffix" => ["ex:suffix", "http://example.org/suffix"],
973
1274
  "keyword" => ["@type", "@type"],
974
- "empty" => [":suffix", "http://empty/suffix"],
975
1275
  "unmapped" => ["foo", "foo"],
976
- "bnode" => ["_:a", RDF::Node("a")],
977
- "relative" => ["foo/bar", "http://base/foo/bar"],
978
- "odd CURIE" => ["experts", "http://example.org/perts"]
1276
+ "bnode" => [JSON::LD::JsonLdError:: IRIConfusedWithPrefix, RDF::Node("a")],
1277
+ "relative" => ["http://base/foo/bar", "http://base/foo/bar"],
1278
+ "odd Compact IRI"=> ["experts", "http://example.org/perts"]
979
1279
  }.each do |title, (result, input)|
980
1280
  it title do
981
- expect(subject.compact_iri(input, vocab: true)).to produce(result, logger)
1281
+ if result.is_a?(Class)
1282
+ expect {subject.compact_iri(input, vocab: true)}.to raise_error(result)
1283
+ else
1284
+ expect(subject.compact_iri(input, vocab: true)).to produce(result, logger)
1285
+ end
982
1286
  end
983
1287
  end
984
1288
  end
@@ -990,12 +1294,15 @@ describe JSON::LD::Context do
990
1294
  "xsd" => RDF::XSD.to_s,
991
1295
  "plain" => "http://example.com/plain",
992
1296
  "lang" => {"@id" => "http://example.com/lang", "@language" => "en"},
1297
+ "dir" => {"@id" => "http://example.com/dir", "@direction" => "ltr"},
1298
+ "langdir" => {"@id" => "http://example.com/langdir", "@language" => "en", "@direction" => "ltr"},
993
1299
  "bool" => {"@id" => "http://example.com/bool", "@type" => "xsd:boolean"},
994
1300
  "integer" => {"@id" => "http://example.com/integer", "@type" => "xsd:integer"},
995
1301
  "double" => {"@id" => "http://example.com/double", "@type" => "xsd:double"},
996
1302
  "date" => {"@id" => "http://example.com/date", "@type" => "xsd:date"},
997
1303
  "id" => {"@id" => "http://example.com/id", "@type" => "@id"},
998
1304
  'graph' => {'@id' => 'http://example.com/graph', '@container' => '@graph'},
1305
+ 'json' => {'@id' => 'http://example.com/json', '@type' => '@json'},
999
1306
 
1000
1307
  "list_plain" => {"@id" => "http://example.com/plain", "@container" => "@list"},
1001
1308
  "list_lang" => {"@id" => "http://example.com/lang", "@language" => "en", "@container" => "@list"},
@@ -1031,6 +1338,9 @@ describe JSON::LD::Context do
1031
1338
  "set_integer" => [{"@value" => "1", "@type" => "http://www.w3.org/2001/XMLSchema#integer"}],
1032
1339
  "set_id" => [{"@id" => "http://example.org/id"}],
1033
1340
  "graph" => [{"@graph" => [{"@id" => "http://example.org/id"}]}],
1341
+ 'json' => [{"@value" => {"some" => "json"}, "@type" => "@json"}],
1342
+ 'dir' => [{"@value" => "dir", "@direction" => "ltr"}],
1343
+ 'langdir' => [{"@value" => "lang dir", "@language" => "en", "@direction" => "ltr"}],
1034
1344
  }.each do |prop, values|
1035
1345
  context "uses #{prop}" do
1036
1346
  values.each do |value|
@@ -1074,7 +1384,7 @@ describe JSON::LD::Context do
1074
1384
  end
1075
1385
  end
1076
1386
 
1077
- context "CURIE compaction" do
1387
+ context "Compact IRI compaction" do
1078
1388
  {
1079
1389
  "nil" => [nil, nil],
1080
1390
  "absolute IRI" => ["http://example.com/", "http://example.com/"],
@@ -1082,12 +1392,16 @@ describe JSON::LD::Context do
1082
1392
  "keyword" => ["@type", "@type"],
1083
1393
  "empty" => [":suffix", "http://empty/suffix"],
1084
1394
  "unmapped" => ["foo", "foo"],
1085
- "bnode" => ["_:a", RDF::Node("a")],
1395
+ "bnode" => [JSON::LD::JsonLdError:: IRIConfusedWithPrefix, RDF::Node("a")],
1086
1396
  "relative" => ["foo/bar", "http://base/foo/bar"],
1087
- "odd CURIE" => ["ex:perts", "http://example.org/perts"]
1397
+ "odd Compact IRI"=> ["ex:perts", "http://example.org/perts"]
1088
1398
  }.each do |title, (result, input)|
1089
1399
  it title do
1090
- expect(subject.compact_iri(input)).to produce(result, logger)
1400
+ if result.is_a?(Class)
1401
+ expect {subject.compact_iri(input)}.to raise_error(result)
1402
+ else
1403
+ expect(subject.compact_iri(input)).to produce(result, logger)
1404
+ end
1091
1405
  end
1092
1406
  end
1093
1407
 
@@ -1100,12 +1414,16 @@ describe JSON::LD::Context do
1100
1414
  "keyword" => ["@type", "@type"],
1101
1415
  "empty" => [":suffix", "http://empty/suffix"],
1102
1416
  "unmapped" => ["foo", "foo"],
1103
- "bnode" => ["_:a", RDF::Node("a")],
1417
+ "bnode" => [JSON::LD::JsonLdError:: IRIConfusedWithPrefix, RDF::Node("a")],
1104
1418
  "relative" => ["http://base/foo/bar", "http://base/foo/bar"],
1105
- "odd CURIE" => ["experts", "http://example.org/perts"]
1419
+ "odd Compact IRI"=> ["experts", "http://example.org/perts"]
1106
1420
  }.each do |title, (result, input)|
1107
1421
  it title do
1108
- expect(subject.compact_iri(input, vocab: true)).to produce(result, logger)
1422
+ if result.is_a?(Class)
1423
+ expect {subject.compact_iri(input, vocab: true)}.to raise_error(result)
1424
+ else
1425
+ expect(subject.compact_iri(input, vocab: true)).to produce(result, logger)
1426
+ end
1109
1427
  end
1110
1428
  end
1111
1429
  end
@@ -1256,6 +1574,8 @@ describe JSON::LD::Context do
1256
1574
  "ex:integer" => {"@type" => "xsd:integer"},
1257
1575
  "ex:double" => {"@type" => "xsd:double"},
1258
1576
  "ex:boolean" => {"@type" => "xsd:boolean"},
1577
+ "ex:none" => {"@type" => "@none"},
1578
+ "ex:json" => {"@type" => "@json"}
1259
1579
  })
1260
1580
  logger.clear
1261
1581
  ctx
@@ -1280,7 +1600,6 @@ describe JSON::LD::Context do
1280
1600
  "native integer" => ["foo", 1, {"@value" => 1}],
1281
1601
  "native double" => ["foo", 1.1e1, {"@value" => 1.1E1}],
1282
1602
  "native date" => ["foo", Date.parse("2011-12-27"), {"@value" => "2011-12-27", "@type" => RDF::XSD.date.to_s}],
1283
- "native time" => ["foo", Time.parse("10:11:12Z"), {"@value" => "10:11:12Z", "@type" => RDF::XSD.time.to_s}],
1284
1603
  "native dateTime" =>["foo", DateTime.parse("2011-12-27T10:11:12Z"), {"@value" => "2011-12-27T10:11:12Z", "@type" => RDF::XSD.dateTime.to_s}],
1285
1604
  "rdf boolean" => ["foo", RDF::Literal(true), {"@value" => "true", "@type" => RDF::XSD.boolean.to_s}],
1286
1605
  "rdf integer" => ["foo", RDF::Literal(1), {"@value" => "1", "@type" => RDF::XSD.integer.to_s}],
@@ -1289,7 +1608,17 @@ describe JSON::LD::Context do
1289
1608
  "rdf URI" => ["foo", RDF::URI("foo"), {"@id" => "foo"}],
1290
1609
  "rdf date " => ["foo", RDF::Literal(Date.parse("2011-12-27")), {"@value" => "2011-12-27", "@type" => RDF::XSD.date.to_s}],
1291
1610
  "rdf nonNeg" => ["foo", RDF::Literal::NonNegativeInteger.new(1), {"@value" => "1", "@type" => RDF::XSD.nonNegativeInteger}],
1292
- "rdf float" => ["foo", RDF::Literal::Float.new(1.0), {"@value" => "1.0", "@type" => RDF::XSD.float}],
1611
+ "rdf float" => ["foo", RDF::Literal::Float.new(1.0), {"@value" => "1.0", "@type" => RDF::XSD.float}],
1612
+ "ex:none string" => ["ex:none", "foo", {"@value" => "foo"}],
1613
+ "ex:none boolean" =>["ex:none", true, {"@value" => true}],
1614
+ "ex:none integer" =>["ex:none", 1, {"@value" => 1}],
1615
+ "ex:none double" => ["ex:none", 1.1e1, {"@value" => 1.1E1}],
1616
+ "ex:json string" => ["ex:json", "foo", {"@value" => "foo", "@type" => "@json"}],
1617
+ "ex:json boolean" =>["ex:json", true, {"@value" => true, "@type" => "@json"}],
1618
+ "ex:json integer" =>["ex:json", 1, {"@value" => 1, "@type" => "@json"}],
1619
+ "ex:json double" => ["ex:json", 1.1e1, {"@value" => 1.1e1, "@type" => "@json"}],
1620
+ "ex:json object" => ["ex:json", {"foo" => "bar"}, {"@value" => {"foo" => "bar"}, "@type" => "@json"}],
1621
+ "ex:json array" => ["ex:json", [{"foo" => "bar"}], {"@value" => [{"foo" => "bar"}], "@type" => "@json"}],
1293
1622
  }.each do |title, (key, compacted, expanded)|
1294
1623
  it title do
1295
1624
  expect(subject.expand_value(key, compacted)).to produce(expanded, logger)
@@ -1318,15 +1647,20 @@ describe JSON::LD::Context do
1318
1647
  "boolean-boolean" => ["ex:boolean", true, {"@value" => true, "@type" => RDF::XSD.boolean.to_s}],
1319
1648
  "boolean-integer" => ["ex:integer", true, {"@value" => true, "@type" => RDF::XSD.integer.to_s}],
1320
1649
  "boolean-double" => ["ex:double", true, {"@value" => true, "@type" => RDF::XSD.double.to_s}],
1650
+ "boolean-json" => ["ex:json", true, {"@value" => true, "@type" => '@json'}],
1321
1651
  "double-boolean" => ["ex:boolean", 1.1, {"@value" => 1.1, "@type" => RDF::XSD.boolean.to_s}],
1322
1652
  "double-double" => ["ex:double", 1.1, {"@value" => 1.1, "@type" => RDF::XSD.double.to_s}],
1323
1653
  "double-integer" => ["foaf:age", 1.1, {"@value" => 1.1, "@type" => RDF::XSD.integer.to_s}],
1654
+ "double-json" => ["ex:json", 1.1, {"@value" => 1.1, "@type" => '@json'}],
1655
+ "json-json" => ["ex:json", {"foo" => "bar"}, {"@value" => {"foo" => "bar"}, "@type" => '@json'}],
1324
1656
  "integer-boolean" => ["ex:boolean", 1, {"@value" => 1, "@type" => RDF::XSD.boolean.to_s}],
1325
1657
  "integer-double" => ["ex:double", 1, {"@value" => 1, "@type" => RDF::XSD.double.to_s}],
1326
1658
  "integer-integer" => ["foaf:age", 1, {"@value" => 1, "@type" => RDF::XSD.integer.to_s}],
1659
+ "integer-json" => ["ex:json", 1, {"@value" => 1, "@type" => '@json'}],
1327
1660
  "string-boolean" => ["ex:boolean", "foo", {"@value" => "foo", "@type" => RDF::XSD.boolean.to_s}],
1328
1661
  "string-double" => ["ex:double", "foo", {"@value" => "foo", "@type" => RDF::XSD.double.to_s}],
1329
1662
  "string-integer" => ["foaf:age", "foo", {"@value" => "foo", "@type" => RDF::XSD.integer.to_s}],
1663
+ "string-json" => ["ex:json", "foo", {"@value" => "foo", "@type" => '@json'}],
1330
1664
  }.each do |title, (key, compacted, expanded)|
1331
1665
  it title do
1332
1666
  expect(subject.expand_value(key, compacted)).to produce(expanded, logger)
@@ -1348,6 +1682,7 @@ describe JSON::LD::Context do
1348
1682
  "dc:created" => {"@type" => RDF::XSD.date.to_s},
1349
1683
  "foaf:age" => {"@type" => RDF::XSD.integer.to_s},
1350
1684
  "foaf:knows" => {"@type" => "@id"},
1685
+ "ex:none" => {"@type" => "@none"},
1351
1686
  })
1352
1687
  logger.clear
1353
1688
  c
@@ -1360,16 +1695,19 @@ describe JSON::LD::Context do
1360
1695
  "integer" => ["foaf:age", "54", {"@value" => "54", "@type" => RDF::XSD.integer.to_s}],
1361
1696
  "date " => ["dc:created", "2011-12-27Z", {"@value" => "2011-12-27Z", "@type" => RDF::XSD.date.to_s}],
1362
1697
  "no IRI" => ["foo", {"@id" =>"http://example.com/"},{"@id" => "http://example.com/"}],
1363
- "no IRI (CURIE)" => ["foo", {"@id" => RDF::Vocab::FOAF.Person.to_s}, {"@id" => RDF::Vocab::FOAF.Person.to_s}],
1364
- "no boolean" => ["foo", {"@value" => "true", "@type" => RDF::XSD.boolean.to_s},{"@value" => "true", "@type" => RDF::XSD.boolean.to_s}],
1365
- "no integer" => ["foo", {"@value" => "54", "@type" => RDF::XSD.integer.to_s},{"@value" => "54", "@type" => RDF::XSD.integer.to_s}],
1366
- "no date " => ["foo", {"@value" => "2011-12-27Z", "@type" => RDF::XSD.date.to_s}, {"@value" => "2011-12-27Z", "@type" => RDF::XSD.date.to_s}],
1698
+ "no IRI (Compact IRI)" => ["foo", {"@id" => RDF::Vocab::FOAF.Person.to_s}, {"@id" => RDF::Vocab::FOAF.Person.to_s}],
1699
+ "no boolean" => ["foo", {"@value" => "true", "@type" => "xsd:boolean"},{"@value" => "true", "@type" => RDF::XSD.boolean.to_s}],
1700
+ "no integer" => ["foo", {"@value" => "54", "@type" => "xsd:integer"},{"@value" => "54", "@type" => RDF::XSD.integer.to_s}],
1701
+ "no date " => ["foo", {"@value" => "2011-12-27Z", "@type" => "xsd:date"}, {"@value" => "2011-12-27Z", "@type" => RDF::XSD.date.to_s}],
1367
1702
  "no string " => ["foo", "string", {"@value" => "string"}],
1368
1703
  "no lang " => ["nolang", "string", {"@value" => "string"}],
1369
1704
  "native boolean" => ["foo", true, {"@value" => true}],
1370
1705
  "native integer" => ["foo", 1, {"@value" => 1}],
1371
1706
  "native integer(list)"=>["list", 1, {"@value" => 1}],
1372
1707
  "native double" => ["foo", 1.1e1, {"@value" => 1.1E1}],
1708
+ "ex:none IRI" => ["ex:none", {"@id" => "http://example.com/"}, {"@id" => "http://example.com/"}],
1709
+ "ex:none string" => ["ex:none", {"@value" => "string"}, {"@value" => "string"}],
1710
+ "ex:none integer" =>["ex:none", {"@value" => "54", "@type" => "xsd:integer"}, {"@value" => "54", "@type" => RDF::XSD.integer.to_s}],
1373
1711
  }.each do |title, (key, compacted, expanded)|
1374
1712
  it title do
1375
1713
  expect(subject.compact_value(key, expanded)).to produce(compacted, logger)
@@ -1379,8 +1717,8 @@ describe JSON::LD::Context do
1379
1717
  context "@language" do
1380
1718
  {
1381
1719
  "@id" => ["foo", {"@id" => "foo"}, {"@id" => "foo"}],
1382
- "integer" => ["foo", {"@value" => "54", "@type" => RDF::XSD.integer.to_s}, {"@value" => "54", "@type" => RDF::XSD.integer.to_s}],
1383
- "date" => ["foo", {"@value" => "2011-12-27Z","@type" => RDF::XSD.date.to_s},{"@value" => "2011-12-27Z", "@type" => RDF::XSD.date.to_s}],
1720
+ "integer" => ["foo", {"@value" => "54", "@type" => "xsd:integer"}, {"@value" => "54", "@type" => RDF::XSD.integer.to_s}],
1721
+ "date" => ["foo", {"@value" => "2011-12-27Z","@type" => "xsd:date"},{"@value" => "2011-12-27Z", "@type" => RDF::XSD.date.to_s}],
1384
1722
  "no lang" => ["foo", {"@value" => "foo" }, {"@value" => "foo"}],
1385
1723
  "same lang" => ["foo", "foo", {"@value" => "foo", "@language" => "en"}],
1386
1724
  "other lang" => ["foo", {"@value" => "foo", "@language" => "bar"}, {"@value" => "foo", "@language" => "bar"}],
@@ -1630,6 +1968,62 @@ describe JSON::LD::Context do
1630
1968
  end
1631
1969
  end
1632
1970
 
1971
+ describe "protected contexts" do
1972
+ it "seals a term with @protected true" do
1973
+ ctx = context.parse({
1974
+ "protected" => {"@id" => "http://example.com/protected", "@protected" => true},
1975
+ "unprotected" => {"@id" => "http://example.com/unprotected"},
1976
+ })
1977
+ expect(ctx.term_definitions["protected"]).to be_protected
1978
+ expect(ctx.term_definitions["unprotected"]).not_to be_protected
1979
+ end
1980
+
1981
+ it "seals all term with @protected true in context" do
1982
+ ctx = context.parse({
1983
+ "@protected" => true,
1984
+ "protected" => {"@id" => "http://example.com/protected"},
1985
+ "protected2" => {"@id" => "http://example.com/protected2"},
1986
+ })
1987
+ expect(ctx.term_definitions["protected"]).to be_protected
1988
+ expect(ctx.term_definitions["protected2"]).to be_protected
1989
+ end
1990
+
1991
+ it "does not seal term with @protected: false when context is protected" do
1992
+ ctx = context.parse({
1993
+ "@protected" => true,
1994
+ "protected" => {"@id" => "http://example.com/protected"},
1995
+ "unprotected" => {"@id" => "http://example.com/unprotected", "@protected" => false},
1996
+ })
1997
+ expect(ctx.term_definitions["protected"]).to be_protected
1998
+ expect(ctx.term_definitions["unprotected"]).not_to be_protected
1999
+ end
2000
+
2001
+ it "does not error when redefining an identical term" do
2002
+ c = {
2003
+ "protected" => {"@id" => "http://example.com/protected", "@protected" => true}
2004
+ }
2005
+ ctx = context.parse(c)
2006
+
2007
+ expect {ctx.parse(c)}.not_to raise_error
2008
+ end
2009
+
2010
+ it "errors when redefining a protected term" do
2011
+ ctx = context.parse({
2012
+ "protected" => {"@id" => "http://example.com/protected", "@protected" => true}
2013
+ })
2014
+
2015
+ expect {ctx.parse({"protected" => "http://example.com/different"})}.to raise_error(JSON::LD::JsonLdError::ProtectedTermRedefinition)
2016
+ end
2017
+
2018
+ it "errors when clearing a context having protected terms" do
2019
+ ctx = context.parse({
2020
+ "protected" => {"@id" => "http://example.com/protected", "@protected" => true}
2021
+ })
2022
+
2023
+ expect {ctx.parse(nil)}.to raise_error(JSON::LD::JsonLdError::InvalidContextNullification)
2024
+ end
2025
+ end
2026
+
1633
2027
  describe JSON::LD::Context::TermDefinition do
1634
2028
  context "with nothing" do
1635
2029
  subject {described_class.new("term")}