iiif-presentation 1.0.0 → 1.2.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +35 -0
  3. data/.gitignore +1 -0
  4. data/Gemfile +2 -0
  5. data/README.md +22 -2
  6. data/VERSION +1 -1
  7. data/iiif-presentation.gemspec +1 -1
  8. data/lib/iiif/hash_behaviours.rb +1 -1
  9. data/lib/iiif/presentation/canvas.rb +4 -0
  10. data/lib/iiif/presentation/service.rb +12 -0
  11. data/lib/iiif/presentation.rb +5 -4
  12. data/lib/iiif/service.rb +40 -105
  13. data/lib/iiif/v3/abstract_resource.rb +491 -0
  14. data/lib/iiif/v3/presentation/annotation.rb +74 -0
  15. data/lib/iiif/v3/presentation/annotation_collection.rb +38 -0
  16. data/lib/iiif/v3/presentation/annotation_page.rb +53 -0
  17. data/lib/iiif/v3/presentation/canvas.rb +82 -0
  18. data/lib/iiif/v3/presentation/choice.rb +51 -0
  19. data/lib/iiif/v3/presentation/collection.rb +52 -0
  20. data/lib/iiif/v3/presentation/image_resource.rb +110 -0
  21. data/lib/iiif/v3/presentation/manifest.rb +82 -0
  22. data/lib/iiif/v3/presentation/range.rb +39 -0
  23. data/lib/iiif/v3/presentation/resource.rb +30 -0
  24. data/lib/iiif/v3/presentation/sequence.rb +66 -0
  25. data/lib/iiif/v3/presentation/service.rb +51 -0
  26. data/lib/iiif/v3/presentation.rb +36 -0
  27. data/spec/fixtures/v3/manifests/complete_from_spec.json +195 -0
  28. data/spec/fixtures/v3/manifests/minimal.json +49 -0
  29. data/spec/fixtures/v3/manifests/service_only.json +14 -0
  30. data/spec/fixtures/vcr_cassettes/pul_loris_cassette.json +1 -1
  31. data/spec/fixtures/vcr_cassettes/pul_loris_cassette_v3.json +1 -0
  32. data/spec/integration/iiif/presentation/image_resource_spec.rb +0 -1
  33. data/spec/integration/iiif/service_spec.rb +17 -32
  34. data/spec/integration/iiif/v3/abstract_resource_spec.rb +202 -0
  35. data/spec/integration/iiif/v3/presentation/image_resource_spec.rb +118 -0
  36. data/spec/spec_helper.rb +6 -0
  37. data/spec/unit/iiif/presentation/canvas_spec.rb +0 -1
  38. data/spec/unit/iiif/presentation/manifest_spec.rb +1 -1
  39. data/spec/unit/iiif/v3/abstract_resource_define_methods_for_spec.rb +78 -0
  40. data/spec/unit/iiif/v3/abstract_resource_spec.rb +293 -0
  41. data/spec/unit/iiif/v3/presentation/annotation_collection_spec.rb +36 -0
  42. data/spec/unit/iiif/v3/presentation/annotation_page_spec.rb +131 -0
  43. data/spec/unit/iiif/v3/presentation/annotation_spec.rb +389 -0
  44. data/spec/unit/iiif/v3/presentation/canvas_spec.rb +337 -0
  45. data/spec/unit/iiif/v3/presentation/choice_spec.rb +120 -0
  46. data/spec/unit/iiif/v3/presentation/collection_spec.rb +55 -0
  47. data/spec/unit/iiif/v3/presentation/image_resource_spec.rb +189 -0
  48. data/spec/unit/iiif/v3/presentation/manifest_spec.rb +370 -0
  49. data/spec/unit/iiif/v3/presentation/range_spec.rb +54 -0
  50. data/spec/unit/iiif/v3/presentation/resource_spec.rb +174 -0
  51. data/spec/unit/iiif/v3/presentation/sequence_spec.rb +222 -0
  52. data/spec/unit/iiif/v3/presentation/service_spec.rb +220 -0
  53. data/spec/unit/iiif/v3/presentation/shared_examples/abstract_resource_only_keys.rb +41 -0
  54. data/spec/unit/iiif/v3/presentation/shared_examples/any_type_keys.rb +31 -0
  55. data/spec/unit/iiif/v3/presentation/shared_examples/array_only_keys.rb +40 -0
  56. data/spec/unit/iiif/v3/presentation/shared_examples/hash_only_keys.rb +40 -0
  57. data/spec/unit/iiif/v3/presentation/shared_examples/int_only_keys.rb +45 -0
  58. data/spec/unit/iiif/v3/presentation/shared_examples/numeric_only_keys.rb +45 -0
  59. data/spec/unit/iiif/v3/presentation/shared_examples/string_only_keys.rb +26 -0
  60. data/spec/unit/iiif/v3/presentation/shared_examples/uri_only_keys.rb +31 -0
  61. metadata +82 -11
  62. data/.travis.yml +0 -11
@@ -0,0 +1,189 @@
1
+ describe IIIF::V3::Presentation::ImageResource do
2
+
3
+ describe '#initialize' do
4
+ it 'sets type to Image' do
5
+ expect(subject['type']).to eq 'Image'
6
+ end
7
+ end
8
+
9
+ describe 'realistic examples' do
10
+ let(:img_id) { 'https://example.org/image/iiif/abc666' }
11
+ let(:image_v2_service) {
12
+ IIIF::V3::Presentation::Service.new(
13
+ '@context' => 'http://iiif.io/api/image/2/context.json',
14
+ '@id' => img_id,
15
+ 'id' => img_id,
16
+ 'profile' => 'http://iiif.io/api/image/2/level1.json'
17
+ )
18
+ }
19
+ let(:img_mimetype) { 'image/jpeg' }
20
+ let(:width) { 999 }
21
+ let(:height) { 666 }
22
+ describe 'stanford' do
23
+ describe 'thumbnail per purl code' do
24
+ let(:thumb_id) { "#{img_id}/full/!400,400/0/default.jpg" }
25
+ let(:thumb_object) {
26
+ thumb = IIIF::V3::Presentation::ImageResource.new
27
+ thumb['type'] = 'Image'
28
+ thumb['id'] = thumb_id
29
+ thumb.format = img_mimetype
30
+ thumb.service = [image_v2_service]
31
+ thumb
32
+ }
33
+ it 'validates' do
34
+ expect{thumb_object.validate}.not_to raise_error
35
+ end
36
+ it 'has expected required values' do
37
+ expect(thumb_object.type).to eq 'Image'
38
+ expect(thumb_object.id).to eq thumb_id
39
+ end
40
+ it 'has expected additional values' do
41
+ expect(thumb_object.format).to eq img_mimetype
42
+ expect(thumb_object.service.first).to eq image_v2_service
43
+ end
44
+ end
45
+ describe 'full size per purl code' do
46
+ let(:full_id) { "#{img_id}/full/full/0/default.jpg" }
47
+ let(:image_object) {
48
+ img = IIIF::V3::Presentation::ImageResource.new
49
+ img['id'] = full_id
50
+ img.format = img_mimetype
51
+ img.height = height
52
+ img.width = width
53
+ img.service = [image_v2_service]
54
+ img
55
+ }
56
+ describe 'world visible' do
57
+ it 'validates' do
58
+ expect{image_object.validate}.not_to raise_error
59
+ end
60
+ it 'has expected required values' do
61
+ expect(image_object.type).to eq 'Image'
62
+ expect(image_object.id).to eq full_id
63
+ end
64
+ it 'has expected additional values' do
65
+ expect(image_object.format).to eq img_mimetype
66
+ expect(image_object.height).to eq height
67
+ expect(image_object.width).to eq width
68
+ expect(image_object.service.first).to eq image_v2_service
69
+ end
70
+ it 'has expected service value' do
71
+ img_service_obj = image_object.service.first
72
+ expect(img_service_obj.class).to eq IIIF::V3::Presentation::Service
73
+ expect(img_service_obj.keys.size).to eq 4
74
+ expect(img_service_obj.id).to eq img_id
75
+ expect(img_service_obj['@id']).to eq img_id
76
+ expect(img_service_obj.profile).to eq IIIF::V3::Presentation::Service::IIIF_IMAGE_V2_LEVEL1_PROFILE
77
+ end
78
+ end
79
+ describe 'requires login' do
80
+ let(:service_label) { 'login message' }
81
+ let(:token_service_id) { 'https://example.org/iiif/token' }
82
+ let(:login_service) {
83
+ IIIF::V3::Presentation::Service.new(
84
+ 'id' => 'https://example.org/auth/iiif',
85
+ 'profile' => 'http://iiif.io/api/auth/1/login',
86
+ 'label' => service_label,
87
+ 'service' => [{
88
+ 'id' => token_service_id,
89
+ 'profile' => 'http://iiif.io/api/auth/1/token'
90
+ }]
91
+ )
92
+ }
93
+ let(:image_object_w_login) {
94
+ img = image_object
95
+ img.service.first['service'] = [login_service]
96
+ img
97
+ }
98
+ it 'validates' do
99
+ expect{image_object_w_login.validate}.not_to raise_error
100
+ end
101
+ it 'has expected service value' do
102
+ img_service_obj = image_object_w_login.service.first
103
+ expect(img_service_obj.class).to eq IIIF::V3::Presentation::Service
104
+ expect(img_service_obj.keys.size).to eq 5
105
+ expect(img_service_obj.id).to eq img_id
106
+ expect(img_service_obj['@id']).to eq img_id
107
+ expect(img_service_obj.profile).to eq IIIF::V3::Presentation::Service::IIIF_IMAGE_V2_LEVEL1_PROFILE
108
+ expect(img_service_obj.service.class).to eq Array
109
+ expect(img_service_obj.service.size).to eq 1
110
+
111
+ login_service_obj = img_service_obj.service.first
112
+ expect(login_service_obj.keys.size).to eq 4
113
+ expect(login_service.id).to eq 'https://example.org/auth/iiif'
114
+ expect(login_service.profile).to eq IIIF::V3::Presentation::Service::IIIF_AUTHENTICATION_V1_LOGIN_PROFILE
115
+ expect(login_service.label).to eq service_label
116
+ expect(login_service.service.class).to eq Array
117
+ expect(login_service.service.size).to eq 1
118
+
119
+ token_service_obj = login_service_obj.service.first
120
+ expect(token_service_obj['id']).to eq token_service_id
121
+ expect(token_service_obj['profile']).to eq IIIF::V3::Presentation::Service::IIIF_AUTHENTICATION_V1_TOKEN_PROFILE
122
+ end
123
+ end
124
+ end
125
+ end
126
+ describe 'image examples from http://prezi3.iiif.io/api/presentation/3.0' do
127
+ let(:image_object) {
128
+ IIIF::V3::Presentation::ImageResource.new({
129
+ 'id' => "#{img_id}/full/full/0/default.jpg",
130
+ 'type' => 'dctypes:Image',
131
+ 'format' => img_mimetype,
132
+ 'height' => height,
133
+ 'width' => width,
134
+ 'service' => image_v2_service
135
+ })
136
+ }
137
+ describe 'simpler' do
138
+ it 'validates' do
139
+ expect{image_object.validate}.not_to raise_error
140
+ end
141
+ it 'has expected required values' do
142
+ expect(image_object.id).to eq "#{img_id}/full/full/0/default.jpg"
143
+ expect(image_object.type).to eq 'dctypes:Image'
144
+ end
145
+ it 'has expected additional values' do
146
+ expect(image_object.format).to eq img_mimetype
147
+ expect(image_object.height).to eq height
148
+ expect(image_object.width).to eq width
149
+ expect(image_object.service).to eq image_v2_service
150
+ end
151
+ end
152
+ describe 'height and width in service' do
153
+ # {
154
+ # "id": "http://example.org/images/book1-page2/full/1500,2000/0/default.jpg",
155
+ # "type": "dctypes:Image",
156
+ # "format": "image/jpeg",
157
+ # "height":2000,
158
+ # "width":1500,
159
+ # "service": {
160
+ # "@context": "http://iiif.io/api/image/2/context.json",
161
+ # "id": "http://example.org/images/book1-page2",
162
+ # "profile": "http://iiif.io/api/image/2/level1.json",
163
+ # "height":8000,
164
+ # "width":6000,
165
+ # "tiles": [{"width": 512, "scaleFactors": [1,2,4,8,16]}]
166
+ # }
167
+ # }
168
+ let(:img_obj) {
169
+ img = image_object
170
+ img.service['height'] = 6666
171
+ img.service['width'] = 9999
172
+ img.service['tiles'] = [{"width" => 512, "scaleFactors" => [1,2,4,8,16]}]
173
+ img
174
+ }
175
+ it 'validates' do
176
+ expect{img_obj.validate}.not_to raise_error
177
+ end
178
+ it 'has expected service value' do
179
+ service_obj = img_obj.service
180
+ expect(service_obj.class).to eq IIIF::V3::Presentation::Service
181
+ expect(service_obj.keys.size).to eq 7
182
+ expect(service_obj['height']).to eq 6666
183
+ expect(service_obj['width']).to eq 9999
184
+ expect(service_obj['tiles']).to eq [{"width" => 512, "scaleFactors" => [1,2,4,8,16]}]
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,370 @@
1
+ describe IIIF::V3::Presentation::Manifest do
2
+
3
+ describe '#required_keys' do
4
+ # NOTE: relaxing requirement for items as Universal Viewer currently only accepts sequences
5
+ # see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
6
+ # %w{ type id label items }.each do |k|
7
+ %w{ type id label }.each do |k|
8
+ it k do
9
+ expect(subject.required_keys).to include(k)
10
+ end
11
+ end
12
+ end
13
+
14
+ describe '#prohibited_keys' do
15
+ it 'contains the expected key names' do
16
+ keys = described_class::CONTENT_RESOURCE_PROPERTIES +
17
+ described_class::PAGING_PROPERTIES +
18
+ %w{
19
+ start_canvas
20
+ content_annotation
21
+ }
22
+ expect(subject.prohibited_keys).to include(*keys)
23
+ end
24
+ end
25
+
26
+ describe '#uri_only_keys' do
27
+ it 'id' do
28
+ expect(subject.uri_only_keys).to include('id')
29
+ end
30
+ end
31
+
32
+ describe '#array_only_keys' do
33
+ # NOTE: also allowing sequences as Universal Viewer currently only accepts sequences
34
+ # see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
35
+ %w{ items sequences structures}.each do |k|
36
+ it k do
37
+ expect(subject.array_only_keys).to include(k)
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '#legal_viewing_hint_values' do
43
+ it 'contains the expected values' do
44
+ expect(subject.legal_viewing_hint_values).to contain_exactly('individuals', 'paged', 'continuous', 'auto-advance')
45
+ end
46
+ end
47
+
48
+ describe '#initialize' do
49
+ it 'sets type to Manifest by default' do
50
+ expect(subject['type']).to eq 'Manifest'
51
+ end
52
+ it 'allows subclasses to override type' do
53
+ subclass = Class.new(described_class) do
54
+ def initialize(hsh={})
55
+ hsh = { 'type' => 'a:SubClass' }
56
+ super(hsh)
57
+ end
58
+ end
59
+ sub = subclass.new
60
+ expect(sub['type']).to eq 'a:SubClass'
61
+ end
62
+ end
63
+
64
+ let(:manifest_id) { 'http://www.example.org/iiif/book1/manifest' }
65
+
66
+ describe '#validate' do
67
+ it 'raises an IllegalValueError if id is not http' do
68
+ subject.label = {'en' => ['Book 1']}
69
+ subject['id'] = 'ftp://www.example.org'
70
+ subject['items'] = [IIIF::V3::Presentation::Sequence.new]
71
+ exp_err_msg = "id must be an http(s) URI for #{described_class}"
72
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
73
+ end
74
+
75
+ let(:item_list_err) { 'The items list must have at least one entry (and it must be a IIIF::V3::Presentation::Canvas)' }
76
+ let(:item_entry_err) { 'All entries in the items list must be a IIIF::V3::Presentation::Canvas' }
77
+
78
+ it 'raises MissingRequiredKeyError if no items or sequences key' do
79
+ subject['id'] = manifest_id
80
+ subject.label = {'en' => ['Book 1']}
81
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::MissingRequiredKeyError, item_list_err)
82
+ end
83
+ describe 'items' do
84
+ it 'raises MissingRequiredKeyError for items entry without values' do
85
+ subject['id'] = manifest_id
86
+ subject.label = {'en' => ['Book 1']}
87
+ subject['items'] = []
88
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::MissingRequiredKeyError, item_list_err)
89
+ end
90
+ it 'raises IllegalValueError for items entry that is not a Sequence' do
91
+ subject['id'] = manifest_id
92
+ subject.label = {'en' => ['Book 1']}
93
+ subject['items'] = [IIIF::V3::Presentation::Sequence.new, IIIF::V3::Presentation::Canvas.new]
94
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, item_entry_err)
95
+ end
96
+ it 'raises no error items populated with canvases' do
97
+ subject['items'] = [IIIF::V3::Presentation::Canvas.new]
98
+ subject['id'] = manifest_id
99
+ subject.label = {'en' => ['Book 1']}
100
+ expect { subject.validate }.not_to raise_error
101
+ end
102
+ end
103
+
104
+ it 'raises IllegalValueError for structures entry that is not a Range' do
105
+ subject['id'] = manifest_id
106
+ subject.label = {'en' => ['Book 1']}
107
+ subject['items'] = [IIIF::V3::Presentation::Canvas.new]
108
+ subject['structures'] = [IIIF::V3::Presentation::Sequence.new]
109
+ exp_err_msg = "All entries in the structures list must be a IIIF::V3::Presentation::Range"
110
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
111
+ end
112
+ it 'raises no error when structures entry is a Range' do
113
+ subject['id'] = manifest_id
114
+ subject.label = {'en' => ['Book 1']}
115
+ subject['items'] = [IIIF::V3::Presentation::Canvas.new]
116
+ subject['structures'] = [IIIF::V3::Presentation::Range.new]
117
+ expect { subject.validate }.not_to raise_error
118
+ end
119
+ end
120
+
121
+ describe 'realistic examples' do
122
+ let!(:canvas_object) { IIIF::V3::Presentation::Canvas.new({
123
+ "id" => "https://example.org/abc666/iiif3/canvas/0001",
124
+ "label" => {'en' => ['image']},
125
+ "height" => 7579,
126
+ "width" => 10108,
127
+ "content" => []
128
+ })}
129
+ describe 'realistic(?) minimal manifest' do
130
+ let!(:manifest_object) { described_class.new({
131
+ "@context" => [
132
+ "http://www.w3.org/ns/anno.jsonld",
133
+ "http://iiif.io/api/presentation/3/context.json"
134
+ ],
135
+ "id" => "https://example.org/abc666/iiif3/manifest",
136
+ "label" => {"en" => ["blah"]},
137
+ "requiredStatement" => {
138
+ "label": { "en": [ "Attribution" ] },
139
+ "value": { "en": [ "bleah" ] },
140
+ },
141
+ 'summary' => { 'en' => ['blargh'] },
142
+ "items" => [canvas_object]
143
+ })}
144
+ it 'validates' do
145
+ expect{manifest_object.validate}.not_to raise_error
146
+ end
147
+ it 'has expected required values' do
148
+ expect(manifest_object.type).to eq 'Manifest'
149
+ expect(manifest_object.id).to eq "https://example.org/abc666/iiif3/manifest"
150
+ expect(manifest_object.label['en']).to include "blah"
151
+ expect(manifest_object.items.size).to be 1
152
+ expect(manifest_object.items.first).to eq canvas_object
153
+ end
154
+ it 'has expected context' do
155
+ expect(manifest_object['@context'].size).to be 2
156
+ expect(manifest_object['@context']).to include(*IIIF::V3::Presentation::CONTEXT)
157
+ end
158
+ it 'has other values' do
159
+ expect(manifest_object['required_statement'][:value][:en].first).to eq 'bleah'
160
+ expect(manifest_object.summary['en'].first).to eq 'blargh'
161
+ end
162
+ end
163
+
164
+ describe 'realistic example from Stanford purl manifests' do
165
+ let!(:logo_service) { IIIF::V3::Presentation::Service.new({
166
+ "@id" => "https://example.org/logo",
167
+ "@type" => "ImageService2",
168
+ "id" => "https://example.org/logo",
169
+ "profile" => "http://iiif.io/api/image/2/level1.json"
170
+ })}
171
+ let!(:thumbnail_image_service) { IIIF::V3::Presentation::Service.new({
172
+ "@id" => "https://example.org/image/iiif/abc666_05_0001",
173
+ "id" => "https://example.org/image/iiif/abc666_05_0001",
174
+ "profile" => IIIF::V3::Presentation::Service::IIIF_IMAGE_V2_LEVEL1_PROFILE
175
+ })}
176
+ let!(:thumbnail_image) { IIIF::V3::Presentation::ImageResource.new({
177
+ "id" => "https://example.org/image/iiif/abc666_05_0001/full/!400,400/0/default.jpg",
178
+ "format" => "image/jpeg",
179
+ "service" => thumbnail_image_service
180
+ })}
181
+ let!(:manifest_object) { described_class.new({
182
+ "id" => "https://example.org/abc666/iiif3/manifest",
183
+ "label" => {"en" => ["blah"]},
184
+ "requiredStatement" => {
185
+ "label": { "en": [ "Attribution" ] },
186
+ "value": { "en": [ "bleah" ] },
187
+ },
188
+ 'summary' => { 'en' => ['blargh'] },
189
+ "items" => [canvas_object],
190
+ "logo" => {
191
+ "id" => "https://example.org/logo/full/400,/0/default.jpg",
192
+ "service" => logo_service
193
+ },
194
+ "seeAlso" => {
195
+ "id" => "https://example.org/abc666.mods",
196
+ "format" => "application/mods+xml"
197
+ },
198
+ "viewingHint" => "paged",
199
+ "viewingDirection" => "right-to-left",
200
+ "metadata" => [
201
+ {
202
+ "label" => "Type",
203
+ "value" => "map"
204
+ },
205
+ {
206
+ "label" => "Rights",
207
+ "value" => "stuff"
208
+ }
209
+ ],
210
+ "thumbnail" => [thumbnail_image]
211
+ })}
212
+ it 'thumbnail image object validates' do
213
+ expect{thumbnail_image.validate}.not_to raise_error
214
+ end
215
+ it 'manifest validates' do
216
+ expect{manifest_object.validate}.not_to raise_error
217
+ end
218
+ it 'has expected required values' do
219
+ expect(manifest_object.type).to eq 'Manifest'
220
+ expect(manifest_object.id).to eq "https://example.org/abc666/iiif3/manifest"
221
+ expect(manifest_object.label['en']).to include "blah"
222
+ expect(manifest_object.items.size).to be 1
223
+ expect(manifest_object.items.first).to eq canvas_object
224
+ end
225
+ it 'has expected additional content' do
226
+ expect(manifest_object['required_statement'][:value][:en].first).to eq 'bleah'
227
+ expect(manifest_object.summary['en'].first).to eq 'blargh'
228
+ expect(manifest_object.logo.keys.size).to be 2
229
+ expect(manifest_object.seeAlso.keys.size).to be 2
230
+ expect(manifest_object.metadata.size).to be 2
231
+ expect(manifest_object.thumbnail.size).to be 1
232
+ end
233
+
234
+ describe 'from stanford purl CODE' do
235
+ let!(:manifest_data) {
236
+ {
237
+ "id" => "https://example.org/abc666/iiif3/manifest",
238
+ "label" => {"en" => ["blah"]},
239
+ "requiredStatement" => {
240
+ "label": { "en": [ "Attribution" ] },
241
+ "value": { "en": [ "bleah" ] },
242
+ },
243
+ "logo" => {
244
+ "id" => "https://example.org/logo/full/400,/0/default.jpg",
245
+ "service" => logo_service
246
+ },
247
+ "seeAlso" => {
248
+ "id" => "https://example.org/abc666.mods",
249
+ "format" => "application/mods+xml"
250
+ }
251
+ }
252
+ }
253
+ let!(:manifest_object) {
254
+ m = described_class.new manifest_data
255
+ m.viewingHint = 'paged'
256
+ m.metadata = [
257
+ { 'label' => 'title', 'value' => 'who wants to know?' },
258
+ { 'label' => 'PublishDate', 'value' => 'sometime' }
259
+ ]
260
+ m.summary = { 'en' => ['blargh'] }
261
+ m.thumbnail = [thumbnail_image]
262
+ m.items << canvas_object
263
+ m
264
+ }
265
+ it 'manifest validates' do
266
+ expect{manifest_object.validate}.not_to raise_error
267
+ end
268
+ it 'has expected required values' do
269
+ expect(manifest_object.type).to eq 'Manifest'
270
+ expect(manifest_object.id).to eq "https://example.org/abc666/iiif3/manifest"
271
+ expect(manifest_object.label['en']).to include "blah"
272
+ expect(manifest_object.items.size).to be 1
273
+ expect(manifest_object.items.first).to eq canvas_object
274
+ end
275
+ it 'has expected additional content' do
276
+ expect(manifest_object['required_statement'][:value][:en].first).to eq 'bleah'
277
+ expect(manifest_object.summary['en'].first).to eq 'blargh'
278
+ expect(manifest_object.logo.keys.size).to be 2
279
+ expect(manifest_object.seeAlso.keys.size).to be 2
280
+ expect(manifest_object.metadata.size).to be 2
281
+ expect(manifest_object.thumbnail.size).to be 1
282
+ end
283
+ end
284
+ end
285
+ describe 'example from http://prezi3.iiif.io/api/presentation/3.0' do
286
+ let!(:range_object) { IIIF::V3::Presentation::Range.new({
287
+ "id" => "http://example.org/iiif/book1/range/top",
288
+ "label" => {"en" => ["home, home on the"]},
289
+ "viewingHint" => ["top"]
290
+ })
291
+ }
292
+ let!(:manifest_object) { described_class.new({
293
+ "@context" => [
294
+ "http://www.w3.org/ns/anno.jsonld",
295
+ "http://iiif.io/api/presentation/3/context.json"
296
+ ],
297
+ "id" => "http://example.org/iiif/book1/manifest",
298
+ "label" => {"en" => ["Book 1"]},
299
+ "metadata" => [
300
+ {"label" => {"en" => ["Author"]},
301
+ "value" => {"@none" => ["Anne Author"]}},
302
+ {"label" => {"en" => ["Published"]},
303
+ "value" => {
304
+ "en" => ["Paris, circa 1400"],
305
+ "fr" => ["Paris, environ 1400"]}
306
+ },
307
+ {"label" => {"en" => ["Notes"]},
308
+ "value" => {"en" => ["Text of note 1", "Text of note 2"]}},
309
+ {"label" => {"en" => ["Source"]},
310
+ "value" => {"@none" => ["<span>From: <a href=\"http://example.org/db/1.html\">Some Collection</a></span>"]}}
311
+ ],
312
+ "summary" => {"en" => ["A longer description of this example book. It should give some real information."]},
313
+ "thumbnail" => [{
314
+ "id" => "http://example.org/images/book1-page1/full/80,100/0/default.jpg",
315
+ "type" => "Image",
316
+ "service" => {
317
+ "id" => "http://example.org/images/book1-page1",
318
+ "type" => "ImageService2",
319
+ "profile" => ["http://iiif.io/api/image/2/level1.json"]
320
+ }
321
+ }],
322
+ "viewingDirection" => "right-to-left",
323
+ "viewingHint" => ["paged"],
324
+ "navDate" => "1856-01-01T00:00:00Z",
325
+ "rights" => [{
326
+ "id" =>"http://example.org/license.html",
327
+ "format" => "text/html"}],
328
+ "requiredStatement" => {
329
+ "label": { "en": [ "Attribution" ] },
330
+ "value": { "en": [ "bleah" ] },
331
+ },
332
+ "logo" => {
333
+ "id" => "http://example.org/logos/institution1.jpg",
334
+ "service" => {
335
+ "id" => "http://example.org/service/inst1",
336
+ "type" => "ImageService2",
337
+ "profile" => ["http://iiif.io/api/image/2/profiles/level2.json"]
338
+ }
339
+ },
340
+ "related" => [{
341
+ "id" => "http://example.org/videos/video-book1.mpg",
342
+ "format" => "video/mpeg"
343
+ }],
344
+ "service" => [{
345
+ "id" => "http://example.org/service/example",
346
+ "profile" => ["http://example.org/docs/example-service.html"]
347
+ }],
348
+ "seeAlso" => [{
349
+ "id" => "http://example.org/library/catalog/book1.xml",
350
+ "format" => "text/xml",
351
+ "profile" => ["http://example.org/profiles/bibliographic"]
352
+ }],
353
+ "rendering" => [{
354
+ "id" => "http://example.org/iiif/book1.pdf",
355
+ "label" => {"en" => ["Download as PDF"]},
356
+ "format" => "application/pdf"
357
+ }],
358
+ "within" => [{
359
+ "id" => "http://example.org/collections/books/",
360
+ "type" => "Collection"
361
+ }],
362
+ "items" => [canvas_object],
363
+ "structures" => [range_object]
364
+ })}
365
+ it 'validates' do
366
+ expect{manifest_object.validate}.not_to raise_error
367
+ end
368
+ end
369
+ end
370
+ end
@@ -0,0 +1,54 @@
1
+ describe IIIF::V3::Presentation::Range do
2
+
3
+ let(:fixed_values) do
4
+ {
5
+ 'id' => 'http://www.example.org/iiif/book1/range/r1',
6
+ 'type' => 'Range',
7
+ 'label' => 'Introduction',
8
+ 'members' => [
9
+ {
10
+ "id" => 'http://www.example.org/iiif/book1/range/r1-1',
11
+ "type" => "Range"
12
+ },
13
+ {
14
+ "id" => 'http://www.example.org/iiif/book1/range/r1-2',
15
+ "type" => "Range"
16
+ },
17
+ {
18
+ "id" => 'http://www.example.org/iiif/book1/canvas/p1',
19
+ "type" => "Canvas"
20
+ },
21
+ {
22
+ "id" => 'http://www.example.org/iiif/book1/canvas/p2',
23
+ "type" => "Canvas"
24
+ },
25
+ {
26
+ "id" => 'http://www.example.org/iiif/book1/canvas/p3#xywh=0,0,750,300',
27
+ "type" => "Canvas"
28
+ }
29
+ ]
30
+ }
31
+ end
32
+
33
+ describe '#initialize' do
34
+ it 'sets type to Range by default' do
35
+ expect(subject['type']).to eq 'Range'
36
+ end
37
+ end
38
+
39
+ describe "#{described_class}.define_methods_for_array_only_keys" do
40
+ it_behaves_like 'it has the appropriate methods for array-only keys v3'
41
+ end
42
+
43
+ describe "#{described_class}.define_methods_for_string_only_keys" do
44
+ it_behaves_like 'it has the appropriate methods for string-only keys v3'
45
+ end
46
+
47
+ describe "#{described_class}.define_methods_for_any_type_keys" do
48
+ it_behaves_like 'it has the appropriate methods for any-type keys v3'
49
+ end
50
+
51
+ describe '#validate' do
52
+ end
53
+
54
+ end