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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +35 -0
- data/.gitignore +1 -0
- data/Gemfile +2 -0
- data/README.md +22 -2
- data/VERSION +1 -1
- data/iiif-presentation.gemspec +1 -1
- data/lib/iiif/hash_behaviours.rb +1 -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 +40 -105
- 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/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 +36 -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/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 +82 -11
- 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'
|
@@ -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
|