iiif-presentation 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +2 -2
  3. data/.gitignore +1 -0
  4. data/Gemfile +2 -0
  5. data/README.md +22 -1
  6. data/VERSION +1 -1
  7. data/iiif-presentation.gemspec +2 -1
  8. data/lib/iiif/presentation/canvas.rb +4 -0
  9. data/lib/iiif/presentation/service.rb +12 -0
  10. data/lib/iiif/presentation.rb +5 -4
  11. data/lib/iiif/service.rb +38 -103
  12. data/lib/iiif/v3/abstract_resource.rb +491 -0
  13. data/lib/iiif/v3/presentation/annotation.rb +74 -0
  14. data/lib/iiif/v3/presentation/annotation_collection.rb +38 -0
  15. data/lib/iiif/v3/presentation/annotation_page.rb +53 -0
  16. data/lib/iiif/v3/presentation/canvas.rb +82 -0
  17. data/lib/iiif/v3/presentation/choice.rb +51 -0
  18. data/lib/iiif/v3/presentation/collection.rb +52 -0
  19. data/lib/iiif/v3/presentation/image_resource.rb +110 -0
  20. data/lib/iiif/v3/presentation/manifest.rb +82 -0
  21. data/lib/iiif/v3/presentation/nav_place.rb +109 -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 +37 -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/nav_place_spec.rb +80 -0
  50. data/spec/unit/iiif/v3/presentation/range_spec.rb +54 -0
  51. data/spec/unit/iiif/v3/presentation/resource_spec.rb +174 -0
  52. data/spec/unit/iiif/v3/presentation/sequence_spec.rb +222 -0
  53. data/spec/unit/iiif/v3/presentation/service_spec.rb +220 -0
  54. data/spec/unit/iiif/v3/presentation/shared_examples/abstract_resource_only_keys.rb +41 -0
  55. data/spec/unit/iiif/v3/presentation/shared_examples/any_type_keys.rb +31 -0
  56. data/spec/unit/iiif/v3/presentation/shared_examples/array_only_keys.rb +40 -0
  57. data/spec/unit/iiif/v3/presentation/shared_examples/hash_only_keys.rb +40 -0
  58. data/spec/unit/iiif/v3/presentation/shared_examples/int_only_keys.rb +45 -0
  59. data/spec/unit/iiif/v3/presentation/shared_examples/numeric_only_keys.rb +45 -0
  60. data/spec/unit/iiif/v3/presentation/shared_examples/string_only_keys.rb +26 -0
  61. data/spec/unit/iiif/v3/presentation/shared_examples/uri_only_keys.rb +31 -0
  62. metadata +93 -5
@@ -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,80 @@
1
+ describe IIIF::V3::Presentation::NavPlace do
2
+ let(:subject) { described_class.new(coordinate_texts: coordinate_texts, base_uri: base_uri) }
3
+ let(:base_uri) { "https://purl.stanford.edu" }
4
+ let(:invalid_coordinates) { ["bogus", "stuff", "is", "here"] }
5
+ let(:valid_coordinates) do
6
+ ["W 23°54'00\"--E 53°36'00\"/N 71°19'00\"--N 33°30'00\"",
7
+ 'E 103°48ʹ/S 3°46ʹ).',
8
+ 'X 103°48ʹ/Y 3°46ʹ).',
9
+ 'In decimal degrees: (E 138.0--W 074.0/N 073.0--N 041.2).']
10
+ end
11
+ let(:nav_place) do
12
+ { id: 'https://purl.stanford.edu/feature-collection/1',
13
+ type: 'FeatureCollection',
14
+ features: [{ id: 'https://purl.stanford.edu/iiif/feature/1',
15
+ type: 'Feature',
16
+ properties: {},
17
+ geometry: { type: 'Polygon',
18
+ coordinates: [[['-23.9', '71.316666'],
19
+ ['53.6', '71.316666'],
20
+ ['53.6', '33.5'],
21
+ ['-23.9', '33.5'],
22
+ ['-23.9', '71.316666']]] } },
23
+ { id: 'https://purl.stanford.edu/iiif/feature/2',
24
+ type: 'Feature',
25
+ properties: {},
26
+ geometry: { type: 'Point', coordinates: ['103.8', '-3.766666'] } }] }
27
+ end
28
+
29
+ describe '#build' do
30
+ context 'when coordinates are valid' do
31
+ let(:coordinate_texts) { valid_coordinates }
32
+
33
+ it 'returns navPlace' do
34
+ expect(subject.build).to eq nav_place
35
+ end
36
+ end
37
+
38
+ context 'when coordinates are not present' do
39
+ let(:coordinate_texts) { [] }
40
+
41
+ it 'raises ArgumentError' do
42
+ expect { subject.build }.to raise_error(ArgumentError)
43
+ end
44
+ end
45
+
46
+ context 'when coordinates are invalid' do
47
+ let(:coordinate_texts) { invalid_coordinates }
48
+
49
+ it 'raises ArgumentError' do
50
+ expect { subject.build }.to raise_error(ArgumentError)
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '#valid' do
56
+ context 'when coordinates are valid' do
57
+ let(:coordinate_texts) { valid_coordinates }
58
+
59
+ it 'returns true' do
60
+ expect(subject.valid?).to be true
61
+ end
62
+ end
63
+
64
+ context 'when coordinates are not present' do
65
+ let(:coordinate_texts) { [] }
66
+
67
+ it 'returns false' do
68
+ expect(subject.valid?).to be false
69
+ end
70
+ end
71
+
72
+ context 'when coordinates are invalid' do
73
+ let(:coordinate_texts) { invalid_coordinates }
74
+
75
+ it 'returns false' do
76
+ expect(subject.valid?).to be false
77
+ end
78
+ end
79
+ end
80
+ end