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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +2 -2
- data/.gitignore +1 -0
- data/Gemfile +2 -0
- data/README.md +22 -1
- data/VERSION +1 -1
- data/iiif-presentation.gemspec +2 -1
- data/lib/iiif/presentation/canvas.rb +4 -0
- data/lib/iiif/presentation/service.rb +12 -0
- data/lib/iiif/presentation.rb +5 -4
- data/lib/iiif/service.rb +38 -103
- data/lib/iiif/v3/abstract_resource.rb +491 -0
- data/lib/iiif/v3/presentation/annotation.rb +74 -0
- data/lib/iiif/v3/presentation/annotation_collection.rb +38 -0
- data/lib/iiif/v3/presentation/annotation_page.rb +53 -0
- data/lib/iiif/v3/presentation/canvas.rb +82 -0
- data/lib/iiif/v3/presentation/choice.rb +51 -0
- data/lib/iiif/v3/presentation/collection.rb +52 -0
- data/lib/iiif/v3/presentation/image_resource.rb +110 -0
- data/lib/iiif/v3/presentation/manifest.rb +82 -0
- data/lib/iiif/v3/presentation/nav_place.rb +109 -0
- data/lib/iiif/v3/presentation/range.rb +39 -0
- data/lib/iiif/v3/presentation/resource.rb +30 -0
- data/lib/iiif/v3/presentation/sequence.rb +66 -0
- data/lib/iiif/v3/presentation/service.rb +51 -0
- data/lib/iiif/v3/presentation.rb +37 -0
- data/spec/fixtures/v3/manifests/complete_from_spec.json +195 -0
- data/spec/fixtures/v3/manifests/minimal.json +49 -0
- data/spec/fixtures/v3/manifests/service_only.json +14 -0
- data/spec/fixtures/vcr_cassettes/pul_loris_cassette.json +1 -1
- data/spec/fixtures/vcr_cassettes/pul_loris_cassette_v3.json +1 -0
- data/spec/integration/iiif/presentation/image_resource_spec.rb +0 -1
- data/spec/integration/iiif/service_spec.rb +17 -32
- data/spec/integration/iiif/v3/abstract_resource_spec.rb +202 -0
- data/spec/integration/iiif/v3/presentation/image_resource_spec.rb +118 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/iiif/presentation/canvas_spec.rb +0 -1
- data/spec/unit/iiif/presentation/manifest_spec.rb +1 -1
- data/spec/unit/iiif/v3/abstract_resource_define_methods_for_spec.rb +78 -0
- data/spec/unit/iiif/v3/abstract_resource_spec.rb +293 -0
- data/spec/unit/iiif/v3/presentation/annotation_collection_spec.rb +36 -0
- data/spec/unit/iiif/v3/presentation/annotation_page_spec.rb +131 -0
- data/spec/unit/iiif/v3/presentation/annotation_spec.rb +389 -0
- data/spec/unit/iiif/v3/presentation/canvas_spec.rb +337 -0
- data/spec/unit/iiif/v3/presentation/choice_spec.rb +120 -0
- data/spec/unit/iiif/v3/presentation/collection_spec.rb +55 -0
- data/spec/unit/iiif/v3/presentation/image_resource_spec.rb +189 -0
- data/spec/unit/iiif/v3/presentation/manifest_spec.rb +370 -0
- data/spec/unit/iiif/v3/presentation/nav_place_spec.rb +80 -0
- data/spec/unit/iiif/v3/presentation/range_spec.rb +54 -0
- data/spec/unit/iiif/v3/presentation/resource_spec.rb +174 -0
- data/spec/unit/iiif/v3/presentation/sequence_spec.rb +222 -0
- data/spec/unit/iiif/v3/presentation/service_spec.rb +220 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/abstract_resource_only_keys.rb +41 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/any_type_keys.rb +31 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/array_only_keys.rb +40 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/hash_only_keys.rb +40 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/int_only_keys.rb +45 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/numeric_only_keys.rb +45 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/string_only_keys.rb +26 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/uri_only_keys.rb +31 -0
- metadata +93 -5
@@ -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'
|
@@ -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
|