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,118 @@
1
+ describe IIIF::V3::Presentation::ImageResource do
2
+ vcr_options = {
3
+ cassette_name: 'pul_loris_cassette_v3',
4
+ record: :new_episodes,
5
+ serialize_with: :json
6
+ }
7
+
8
+ describe 'self#create_image_api_image_resource', vcr: vcr_options do
9
+
10
+ let(:image_server) { 'https://libimages.princeton.edu/loris' }
11
+
12
+ let(:valid_service_id) {
13
+ id = 'pudl0001%2F4612422%2F00000001.jp2'
14
+ "#{image_server}/#{id}"
15
+ }
16
+
17
+ let(:invalid_service_id) {
18
+ id = 'xxxx%2F4612422%2F00000001.jp2'
19
+ "#{image_server}/#{id}"
20
+ }
21
+
22
+ it 'returns an ImageResource' do
23
+ instance = described_class.create_image_api_image_resource(service_id: valid_service_id)
24
+ expect(instance.class).to be described_class
25
+ end
26
+
27
+
28
+ describe 'has expected values from our fixture' do
29
+ it 'when copy_info is false' do
30
+ opts = { service_id: valid_service_id }
31
+ resource = described_class.create_image_api_image_resource(opts)
32
+ expect(resource['id']).to eq 'https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2/full/!200,200/0/default.jpg'
33
+ expect(resource['type']).to eq 'Image'
34
+ expect(resource.format).to eq "image/jpeg"
35
+ expect(resource.width).to eq 3047
36
+ expect(resource.height).to eq 7200
37
+ expect(resource.service.first['id']).to eq 'https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2'
38
+ expect(resource.service.first['profile']).to eq 'http://iiif.io/api/image/2/level2.json'
39
+ end
40
+ it 'copies over all teh infos (when copy_info is true)' do
41
+ opts = { service_id: valid_service_id, copy_info: true }
42
+ resource = described_class.create_image_api_image_resource(opts)
43
+
44
+ expect(resource['id']).to eq 'https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2/full/!200,200/0/default.jpg'
45
+ expect(resource['type']).to eq 'Image'
46
+ expect(resource.format).to eq "image/jpeg"
47
+ expect(resource.width).to eq 3047
48
+ expect(resource.height).to eq 7200
49
+ expect(resource.service.first['profile']).to eq [
50
+ 'http://iiif.io/api/image/2/level2.json',
51
+ {
52
+ 'supports' => [
53
+ 'canonicalLinkHeader', 'profileLinkHeader', 'mirroring',
54
+ 'rotationArbitrary', 'regionSquare', 'sizeAboveFull'
55
+ ],
56
+ 'qualities' => ['default', 'bitonal', 'gray', 'color'],
57
+ 'formats'=>['jpg', 'png', 'gif', 'webp']
58
+ }
59
+ ]
60
+ expect(resource.service.first['tiles']).to eq [ {
61
+ 'width' => 1024,
62
+ 'scaleFactors' => [ 1, 2, 4, 8, 16, 32 ]
63
+ } ]
64
+ expect(resource.service.first['sizes']).to eq [
65
+ {'width' => 96, 'height' => 225 },
66
+ {'width' => 191, 'height' => 450 },
67
+ {'width' => 381, 'height' => 900 },
68
+ {'width' => 762, 'height' => 1800 },
69
+ {'width' => 1524, 'height' => 3600 },
70
+ {'width' => 3047, 'height' => 7200 }
71
+ ]
72
+ expect(resource.service.first['id']).to eq 'https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2'
73
+ expect(resource.service.first).not_to have_key('@id')
74
+ end
75
+ end
76
+
77
+ describe 'respects the params we supply' do
78
+ it ':resource_id' do
79
+ r_id = 'http://example.edu/images/some.jpg'
80
+ opts = { service_id: valid_service_id, resource_id: r_id}
81
+ resource = described_class.create_image_api_image_resource(opts)
82
+ expect(resource['id']).to eq r_id
83
+ end
84
+ it ':width' do
85
+ width = 42
86
+ opts = { service_id: valid_service_id, width: width}
87
+ resource = described_class.create_image_api_image_resource(opts)
88
+ expect(resource.width).to eq width
89
+ end
90
+ it ':height' do
91
+ height = 42
92
+ opts = { service_id: valid_service_id, height: height}
93
+ resource = described_class.create_image_api_image_resource(opts)
94
+ expect(resource.height).to eq height
95
+ end
96
+ it ':profile (service[\'profile\'])' do
97
+ profile = 'http://iiif.io/api/image/2/level1.json'
98
+ opts = { service_id: valid_service_id, profile: profile}
99
+ resource = described_class.create_image_api_image_resource(opts)
100
+ expect(resource.service.first['profile']).to eq profile
101
+ end
102
+ end
103
+
104
+ describe 'errors' do
105
+ it 'raises if :service_id is not included' do
106
+ expect {
107
+ described_class.create_image_api_image_resource
108
+ }.to raise_error
109
+ end
110
+ it 'raises if the info can\'t be pulled in' do
111
+ expect {
112
+ described_class.create_image_api_image_resource(service_id: invalid_service_id)
113
+ }.to raise_error
114
+ end
115
+ end
116
+
117
+ end
118
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,16 @@
1
1
  require 'iiif/presentation'
2
+ require 'iiif/v3/presentation'
2
3
  require 'simplecov'
3
4
  require 'coveralls'
5
+ require 'debug'
4
6
  Dir["#{File.dirname(__FILE__)}/unit/iiif/presentation/shared_examples/*.rb"].each do |f|
5
7
  require f
6
8
  end
9
+ Dir["#{File.dirname(__FILE__)}/unit/iiif/v3/presentation/shared_examples/*.rb"].each do |f|
10
+ require f
11
+ end
7
12
  require 'vcr'
13
+ require 'webmock/rspec'
8
14
 
9
15
  VCR.configure do |c|
10
16
  c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
@@ -43,4 +43,3 @@ describe IIIF::Presentation::Canvas do
43
43
  end
44
44
 
45
45
  end
46
-
@@ -14,7 +14,7 @@ describe IIIF::Presentation::Manifest do
14
14
  'type' => 'a:SubClass',
15
15
  'id' => 'http://example.com/prefix/manifest/123',
16
16
  'context' => IIIF::Presentation::CONTEXT,
17
- 'label' => 'Book 1',
17
+ 'label' => {'en' => ['Book 1']},
18
18
  'description' => 'A longer description of this example book. It should give some real information.',
19
19
  'thumbnail' => {
20
20
  '@id' => 'http://www.example.org/images/book1-page1/full/80,100/0/default.jpg',
@@ -0,0 +1,78 @@
1
+ class AbstractResourceSubClass < IIIF::V3::AbstractResource
2
+ TYPE = 'Ignore'
3
+ # need a property here for type of key not already initialized in AbstractResource
4
+ def array_only_keys
5
+ super + %w{ array my_array }
6
+ end
7
+ def hash_only_keys
8
+ super + %w{ hash my_hash }
9
+ end
10
+ def int_only_keys
11
+ super + %w{ int my_int }
12
+ end
13
+ def numeric_only_keys
14
+ super + %w{ num my_num }
15
+ end
16
+ def uri_only_keys
17
+ super + %w{ uri my_uri }
18
+ end
19
+
20
+ def initialize(hsh={})
21
+ hsh = { 'type' => 'a:SubClass' }
22
+ super(hsh)
23
+ end
24
+ end
25
+
26
+ describe AbstractResourceSubClass do
27
+
28
+ describe "*define_methods_for_any_type_keys" do
29
+ # shared_example expects fixed_values; these are roughly based on Stanford purl code
30
+ # (see https://github.com/sul-dlss/purl/blob/master/app/models/iiif3_presentation_manifest.rb)
31
+ let(:fixed_values) do
32
+ {
33
+ 'label' => 'foo',
34
+ 'description' => 'bar',
35
+ 'thumbnail' => IIIF::V3::Presentation::ImageResource.new(
36
+ 'type' => 'Image',
37
+ 'id' => "http://example.org/full/!400,400/0/default.jpg",
38
+ 'format' => 'image/jpeg'
39
+ ),
40
+ 'attribution' => ['foo'],
41
+ 'logo' => {
42
+ 'id' => 'https://example.org/default.jpg',
43
+ 'service' => IIIF::V3::Presentation::Service.new(
44
+ '@context' => 'http://iiif.io/api/image/2/context.json',
45
+ '@id' => 'http://example.org/1',
46
+ 'id' => 'http://example.org/1',
47
+ 'profile' => 'http://example.org/whatever'
48
+ )
49
+ },
50
+ 'see_also' => {
51
+ 'id' => 'http://example.org/whatever',
52
+ 'format' => 'application/mods+xml'
53
+ },
54
+ 'related' => ['no', 'idea'],
55
+ 'within' => {'foo' => 'bar'}
56
+ }
57
+ end
58
+ it_behaves_like 'it has the appropriate methods for any-type keys v3'
59
+ end
60
+ describe "*define_methods_for_array_only_keys" do
61
+ it_behaves_like 'it has the appropriate methods for array-only keys v3'
62
+ end
63
+ describe "*define_methods_for_hash_only_keys" do
64
+ it_behaves_like 'it has the appropriate methods for hash-only keys v3'
65
+ end
66
+ describe "*define_methods_for_int_only_keys" do
67
+ it_behaves_like 'it has the appropriate methods for integer-only keys v3'
68
+ end
69
+ describe "*define_methods_for_numeric_only_keys" do
70
+ it_behaves_like 'it has the appropriate methods for numeric-only keys v3'
71
+ end
72
+ describe "*define_methods_for_string_only_keys" do
73
+ it_behaves_like 'it has the appropriate methods for string-only keys v3'
74
+ end
75
+ describe "*define_methods_for_uri_only_keys" do
76
+ it_behaves_like 'it has the appropriate methods for uri-only keys v3'
77
+ end
78
+ end
@@ -0,0 +1,293 @@
1
+ require 'active_support/inflector'
2
+ require 'json'
3
+ require_relative '../../../../lib/iiif/hash_behaviours'
4
+
5
+ describe IIIF::V3::AbstractResource do
6
+
7
+ let(:fixtures_dir) { File.join(File.dirname(__FILE__), '../../../fixtures') }
8
+ let(:manifest_from_spec_path) { File.join(fixtures_dir, 'v3/manifests/complete_from_spec.json') }
9
+ let(:abstract_resource_subclass) do
10
+ Class.new(IIIF::V3::AbstractResource) do
11
+ include IIIF::HashBehaviours
12
+
13
+ def initialize(hsh={})
14
+ hsh['type'] = 'a:SubClass' unless hsh.has_key?('type')
15
+ super(hsh)
16
+ end
17
+
18
+ def required_keys
19
+ super + %w{ id }
20
+ end
21
+
22
+ def prohibited_keys
23
+ super + %w{ verboten }
24
+ end
25
+
26
+ def legal_viewing_hint_values
27
+ %w{ viewing_hint1 viewing_hint2 }
28
+ end
29
+ end
30
+ end
31
+ subject do
32
+ instance = abstract_resource_subclass.new
33
+ instance['id'] = 'http://example.com/prefix/manifest/123'
34
+ instance
35
+ end
36
+
37
+ describe '#required_keys' do
38
+ it 'accumulate' do
39
+ expect(subject.required_keys).to eq %w{ type id }
40
+ end
41
+ end
42
+
43
+ describe '#initialize' do
44
+ it 'raises an error if you try to instantiate AbstractResource' do
45
+ expect { IIIF::V3::AbstractResource.new }.to raise_error(RuntimeError)
46
+ end
47
+ it 'sets type' do
48
+ expect(subject['type']).to eq 'a:SubClass'
49
+ end
50
+ it 'can take any old hash' do
51
+ hsh = JSON.parse(IO.read(manifest_from_spec_path))
52
+ new_instance = abstract_resource_subclass.new(hsh)
53
+ expect(new_instance['label']['en']).to include 'Book 1'
54
+ end
55
+ end
56
+
57
+ describe '#validate' do
58
+ it 'raises MissingRequiredKeyError if required key is missing' do
59
+ subject.required_keys.each { |k| subject.delete(k) }
60
+ expect { subject.validate }.to raise_error IIIF::V3::Presentation::MissingRequiredKeyError
61
+ end
62
+ it 'raises ProhibitedKeyError if prohibited key is present' do
63
+ subject['verboten'] = 666
64
+ exp_err_msg = "verboten is a prohibited key in #{subject.class}"
65
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::ProhibitedKeyError, exp_err_msg)
66
+ end
67
+ it 'raises IllegalValueError for bad viewing_direction' do
68
+ subject['viewing_direction'] = 'foo'
69
+ exp_err_msg = "viewingDirection must be one of #{subject.legal_viewing_direction_values}"
70
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
71
+ end
72
+ describe 'viewing_hint' do
73
+ it 'can be a uri' do
74
+ subject['viewing_hint'] = 'https://example.org/viewiing_hint'
75
+ expect { subject.validate }.not_to raise_error
76
+ end
77
+ it 'can be a member of legal_viewing_hint_values' do
78
+ subject['viewing_hint'] = subject.legal_viewing_hint_values.first
79
+ expect { subject.validate }.not_to raise_error
80
+ end
81
+ it 'raises IllegalValueError for bad viewing_hint' do
82
+ subject['viewing_hint'] = 'foo'
83
+ exp_err_msg = "viewingHint for #{subject.class} must be one or more of #{subject.legal_viewing_hint_values} or a URI"
84
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
85
+ end
86
+ it 'can have multiple values' do
87
+ subject['viewing_hint'] = [subject.legal_viewing_hint_values.first, subject.legal_viewing_hint_values.last]
88
+ expect { subject.validate }.not_to raise_error
89
+ end
90
+ end
91
+ describe 'metadata' do
92
+ it 'raises IllegalValueError for entry that is not a Hash' do
93
+ subject['metadata'] = ['error']
94
+ exp_err_msg = "metadata must be an Array with Hash members"
95
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
96
+ end
97
+ it 'does not raise error for entry that contains exactly "label" and "value"' do
98
+ subject['metadata'] = [{ 'label' => 'bar', 'value' => 'foo' }]
99
+ expect { subject.validate }.not_to raise_error
100
+ end
101
+ it 'raises IllegalValueError for entry that does not contain exactly "label" and "value"' do
102
+ subject['metadata'] = [{ 'label' => 'bar', 'bar' => 'foo' }]
103
+ exp_err_msg = "metadata members must be a Hash of keys 'label' and 'value'"
104
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
105
+ end
106
+ end
107
+ describe 'thumbnail' do
108
+ let (:exp_basic_err_msg) { "thumbnail must be an Array with Hash or ImageResource members" }
109
+ let (:exp_keys_msg) { 'thumbnail members must include keys "id" and "type"' }
110
+ it 'raises IllegalValueError for entry that is not a Hash or ImageResource object' do
111
+ subject['thumbnail'] = ['error']
112
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_basic_err_msg)
113
+ end
114
+ it 'does not raise error for entry Hash with "id" and "type"' do
115
+ subject['thumbnail'] = [{ 'id' => 'bar', 'type' => 'foo', 'random' => 'xxx' }]
116
+ expect { subject.validate }.not_to raise_error
117
+ end
118
+ it 'raises IllegalValueError for entry Hash that does not contain "id" and "type"' do
119
+ subject['thumbnail'] = [{ 'id' => 'bar' }]
120
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_keys_msg)
121
+ end
122
+ it 'does not raise error for entry ImageResource with "id"' do
123
+ subject['thumbnail'] = [IIIF::V3::Presentation::ImageResource.new({ 'id' => 'bar', 'label' => 'xxx' })]
124
+ expect { subject.validate }.not_to raise_error
125
+ end
126
+ it 'raises IllegalValueError for entry ImageResource without id' do
127
+ subject['thumbnail'] = [IIIF::V3::Presentation::ImageResource.new]
128
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_keys_msg)
129
+ end
130
+ end
131
+ describe 'nav_date' do
132
+ it 'does not raise error for value of form YYYY-MM-DDThh:mm:ssZ' do
133
+ subject['nav_date'] = '1991-01-02T13:04:27Z'
134
+ expect { subject.validate }.not_to raise_error
135
+ end
136
+ it 'raises IllegalValueError for value not of form YYYY-MM-DDThh:mm:ssZ' do
137
+ subject['nav_date'] = '1991-01-02T13:04:27+0500'
138
+ exp_err_msg = 'nav_date must be of form YYYY-MM-DDThh:mm:ssZ'
139
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
140
+ end
141
+ end
142
+ describe 'rights' do
143
+ it 'raises IllegalValueError for entry that is not a Hash' do
144
+ subject['rights'] = ['error']
145
+ exp_err_msg = "rights must be an Array with Hash members"
146
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
147
+ end
148
+ it 'does not raise error for entry with "id" that is URI' do
149
+ subject['rights'] = [{ 'id' => 'http://example.org/rights', 'format' => 'text/html' }]
150
+ expect { subject.validate }.not_to raise_error
151
+ end
152
+ it 'raises IllegalValueError for entry with "id" that is not URI' do
153
+ subject['rights'] = [{ 'id' => 'bar', 'format' => 'text/html' }]
154
+ exp_err_msg = "id value must be a String containing a URI for #{subject.class}"
155
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
156
+ end
157
+ it 'raises IllegalValueError for entry that does not contain "id"' do
158
+ subject['rights'] = [{ 'whoops' => 'http://example.org/rights' }]
159
+ exp_err_msg = 'rights members must be a Hash including "id"'
160
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
161
+ end
162
+ end
163
+ describe 'rendering' do
164
+ it 'raises IllegalValueError for entry that is not a Hash' do
165
+ subject['rendering'] = ['error']
166
+ exp_err_msg = "rendering must be an Array with Hash members"
167
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
168
+ end
169
+ it 'does not raise error for entry with "label" and "format"' do
170
+ subject['rendering'] = [{ 'label' => 'bar', 'format' => 'foo', 'random' => 'xxx' }]
171
+ expect { subject.validate }.not_to raise_error
172
+ end
173
+ it 'raises IllegalValueError for entry that does not contain "label" and "format"' do
174
+ subject['rendering'] = [{ 'label' => 'bar' }]
175
+ exp_err_msg = 'rendering members must be a Hash including keys "label" and "format"'
176
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
177
+ end
178
+ end
179
+ describe 'startCanvas' do
180
+ it 'raises IllegalValueError for entry that is not URI' do
181
+ subject.startCanvas = 'foo'
182
+ exp_err_msg = "startCanvas value must be a String containing a URI for #{subject.class}"
183
+ expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
184
+ end
185
+ end
186
+ end
187
+
188
+ describe 'A nested object (e.g. self[\'metadata\'])' do
189
+ it 'returns [] if not set' do
190
+ expect(subject.metadata).to eq([])
191
+ end
192
+ it 'is not in #to_ordered_hash at all if we access it but do not append to it' do
193
+ subject.metadata # touch it
194
+ expect(subject.metadata).to eq([])
195
+ expect(subject.to_ordered_hash.has_key?('metadata')).to be_falsey
196
+ end
197
+ it 'gets structured as we\'d expect' do
198
+ subject.metadata << {
199
+ 'label' => 'Author',
200
+ 'value' => 'Anne Author'
201
+ }
202
+ subject.metadata << {
203
+ 'label' => 'Published',
204
+ 'value' => [
205
+ {'@value'=> 'Paris, circa 1400', '@language'=>'en'},
206
+ {'@value'=> 'Paris, environ 14eme siecle', '@language'=>'fr'}
207
+ ]
208
+ }
209
+ expect(subject.metadata[0]['label']).to eq('Author')
210
+ expect(subject.metadata[1]['value'].length).to eq(2)
211
+ expect(subject.metadata[1]['value'].select { |e| e['@language'] == 'fr'}.last['@value']).to eq('Paris, environ 14eme siecle')
212
+ end
213
+
214
+ it 'roundtrips' do
215
+ subject.metadata << {
216
+ 'label' => 'Author',
217
+ 'value' => 'Anne Author'
218
+ }
219
+ subject.metadata << {
220
+ 'label' => 'Published',
221
+ 'value' => [
222
+ {'@value'=> 'Paris, circa 1400', '@language'=>'en'},
223
+ {'@value'=> 'Paris, environ 14eme siecle', '@language'=>'fr'}
224
+ ]
225
+ }
226
+ File.open('/tmp/osullivan-spec.json','w') do |f|
227
+ f.write(subject.to_json)
228
+ end
229
+ parsed = subject.class.parse('/tmp/osullivan-spec.json')
230
+ expect(parsed['metadata'][0]['label']).to eq('Author')
231
+ expect(parsed['metadata'][1]['value'].length).to eq(2)
232
+ expect(parsed['metadata'][1]['value'].select { |e| e['@language'] == 'fr'}.last['@value']).to eq('Paris, environ 14eme siecle')
233
+ File.delete('/tmp/osullivan-spec.json')
234
+ end
235
+ end
236
+
237
+ describe '#to_ordered_hash' do
238
+ describe 'adds the @context' do
239
+ before(:each) { subject.delete('@context') }
240
+ it 'by default' do
241
+ expect(subject.has_key?('@context')).to be_falsey
242
+ expect(subject.to_ordered_hash.has_key?('@context')).to be_truthy
243
+ end
244
+ it 'unless you say not to' do
245
+ expect(subject.has_key?('@context')).to be_falsey
246
+ expect(subject.to_ordered_hash(include_context: false).has_key?('@context')).to be_falsey
247
+ end
248
+ it 'or it\'s already there' do
249
+ different_ctxt = 'http://example.org/context'
250
+ subject['@context'] = different_ctxt
251
+ oh = subject.to_ordered_hash
252
+ expect(oh['@context']).to eq different_ctxt
253
+ end
254
+ end
255
+
256
+ describe 'runs the validations' do
257
+ let(:error) { IIIF::V3::Presentation::MissingRequiredKeyError }
258
+ before(:each) { subject.delete('id') }
259
+ it 'raises exceptions' do
260
+ expect { subject.to_ordered_hash }.to raise_error error
261
+ end
262
+ it 'unless you tell it not to' do
263
+ expect { subject.to_ordered_hash(force: true) }.to_not raise_error
264
+ end
265
+ end
266
+ end
267
+
268
+ describe '*get_descendant_class_by_jld_type' do
269
+ before do
270
+ class DummyClass < IIIF::V3::AbstractResource
271
+ TYPE = "Collection"
272
+ def self.singleton_class?
273
+ true
274
+ end
275
+ end
276
+ end
277
+ after do
278
+ Object.send(:remove_const, :DummyClass)
279
+ end
280
+ it 'gets the right class' do
281
+ klass = described_class.get_descendant_class_by_jld_type('Canvas')
282
+ expect(klass).to eq IIIF::V3::Presentation::Canvas
283
+ end
284
+ context "when there are singleton classes which are returned" do
285
+ it "gets the right class" do
286
+ allow(IIIF::V3::AbstractResource).to receive(:descendants).and_return([DummyClass, IIIF::V3::Presentation::Collection])
287
+ klass = described_class.get_descendant_class_by_jld_type('Collection')
288
+ expect(klass).to eq IIIF::V3::Presentation::Collection
289
+ end
290
+ end
291
+ end
292
+
293
+ end
@@ -0,0 +1,36 @@
1
+ describe IIIF::V3::Presentation::AnnotationCollection do
2
+
3
+ let(:fixed_values) do
4
+ {
5
+ '@context' => [
6
+ 'http://iiif.io/api/presentation/3/context.json',
7
+ 'http://www.w3.org/ns/anno.jsonld'
8
+ ],
9
+ 'id' => 'http://www.example.org/iiif/book1/annoColl/transcription',
10
+ 'type' => 'AnnotationCollection',
11
+ 'label' => 'Diplomatic Transcription',
12
+ 'first' => 'http://www.example.org/iiif/book1/list/l1',
13
+ 'last' => 'http://www.example.org/iiif/book1/list/l4',
14
+ }
15
+ end
16
+
17
+
18
+ describe '#initialize' do
19
+ it 'sets type' do
20
+ expect(subject['type']).to eq 'AnnotationCollection'
21
+ end
22
+ end
23
+
24
+ describe "#{described_class}.define_methods_for_string_only_keys" do
25
+ it_behaves_like 'it has the appropriate methods for string-only keys v3'
26
+ end
27
+
28
+ describe "#{described_class}.define_methods_for_array_only_keys" do
29
+ it_behaves_like 'it has the appropriate methods for array-only keys v3'
30
+ end
31
+
32
+ describe "#{described_class}.define_methods_for_any_type_keys" do
33
+ it_behaves_like 'it has the appropriate methods for any-type keys v3'
34
+ end
35
+
36
+ end