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,174 @@
|
|
1
|
+
describe IIIF::V3::Presentation::Resource do
|
2
|
+
describe '#required_keys' do
|
3
|
+
it 'id' do
|
4
|
+
expect(subject.required_keys).to include('id')
|
5
|
+
end
|
6
|
+
it 'type' do
|
7
|
+
expect(subject.required_keys).to include('type')
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#prohibited_keys' do
|
12
|
+
it 'contains the expected key names' do
|
13
|
+
keys = described_class::PAGING_PROPERTIES +
|
14
|
+
%w{
|
15
|
+
nav_date
|
16
|
+
viewing_direction
|
17
|
+
start_canvas
|
18
|
+
content_annotations
|
19
|
+
}
|
20
|
+
expect(subject.prohibited_keys).to include(*keys)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#uri_only_keys' do
|
25
|
+
it 'id' do
|
26
|
+
expect(subject.uri_only_keys).to include('id')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#initialize' do
|
31
|
+
it 'allows subclasses to override type' do
|
32
|
+
subclass = Class.new(described_class) do
|
33
|
+
def initialize(hsh={})
|
34
|
+
hsh = { 'type' => 'a:SubClass' }
|
35
|
+
super(hsh)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
sub = subclass.new
|
39
|
+
expect(sub['type']).to eq 'a:SubClass'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#validate' do
|
44
|
+
it 'raises an IllegalValueError if id is not URI' do
|
45
|
+
subject['id'] = 'foo'
|
46
|
+
subject['type'] = 'image/jpeg'
|
47
|
+
exp_err_msg = "id value must be a String containing a URI for #{described_class}"
|
48
|
+
expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
|
49
|
+
end
|
50
|
+
it 'raises an IllegalValueError if id is not http' do
|
51
|
+
subject['id'] = 'ftp://www.example.org'
|
52
|
+
subject['type'] = 'image/jpeg'
|
53
|
+
exp_err_msg = "id must be an http(s) URI for #{described_class}"
|
54
|
+
expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'realistic examples' do
|
59
|
+
describe 'non-image examples from http://prezi3.iiif.io/api/presentation/3.0' do
|
60
|
+
describe 'audio' do
|
61
|
+
let(:file_id) { 'http://example.org/iiif/book1/res/music.mp3' }
|
62
|
+
let(:file_type) { 'dctypes:Sound' }
|
63
|
+
let(:file_mimetype) { 'audio/mpeg' }
|
64
|
+
let(:resource_object) { IIIF::V3::Presentation::Resource.new({
|
65
|
+
'id' => file_id,
|
66
|
+
'type' => file_type,
|
67
|
+
'format' => file_mimetype
|
68
|
+
})}
|
69
|
+
it 'validates' do
|
70
|
+
expect{resource_object.validate}.not_to raise_error
|
71
|
+
end
|
72
|
+
it 'has expected required values' do
|
73
|
+
expect(resource_object.id).to eq file_id
|
74
|
+
end
|
75
|
+
it 'has expected other values' do
|
76
|
+
expect(resource_object.type).to eq file_type
|
77
|
+
expect(resource_object.format).to eq file_mimetype
|
78
|
+
expect(resource_object.service).to eq []
|
79
|
+
end
|
80
|
+
end
|
81
|
+
describe 'text' do
|
82
|
+
let(:file_id) { 'http://example.org/iiif/book1/res/tei-text-p1.xml' }
|
83
|
+
let(:file_type) { 'dctypes:Text' }
|
84
|
+
let(:file_mimetype) { 'application/tei+xml' }
|
85
|
+
let(:resource_object) { IIIF::V3::Presentation::Resource.new({
|
86
|
+
'id' => file_id,
|
87
|
+
'type' => file_type,
|
88
|
+
'format' => file_mimetype
|
89
|
+
})}
|
90
|
+
it 'validates' do
|
91
|
+
expect{resource_object.validate}.not_to raise_error
|
92
|
+
end
|
93
|
+
it 'has expected required values' do
|
94
|
+
expect(resource_object.id).to eq file_id
|
95
|
+
end
|
96
|
+
it 'has expected other values' do
|
97
|
+
expect(resource_object.type).to eq file_type
|
98
|
+
expect(resource_object.format).to eq file_mimetype
|
99
|
+
expect(resource_object.service).to eq []
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
describe 'stanford' do
|
105
|
+
describe 'non-image resource per purl code' do
|
106
|
+
let(:file_id) { 'https://example.org/file/abc666/ocr.txt' }
|
107
|
+
let(:file_type) { 'Document' }
|
108
|
+
let(:file_mimetype) { 'text/plain' }
|
109
|
+
let(:resource_object) {
|
110
|
+
resource = IIIF::V3::Presentation::Resource.new
|
111
|
+
resource['id'] = file_id
|
112
|
+
resource['type'] = file_type
|
113
|
+
resource.format = file_mimetype
|
114
|
+
resource
|
115
|
+
}
|
116
|
+
describe 'world visible' do
|
117
|
+
it 'validates' do
|
118
|
+
expect{resource_object.validate}.not_to raise_error
|
119
|
+
end
|
120
|
+
it 'has expected required values' do
|
121
|
+
expect(resource_object.id).to eq file_id
|
122
|
+
end
|
123
|
+
it 'has expected other values' do
|
124
|
+
expect(resource_object.type).to eq file_type
|
125
|
+
expect(resource_object.format).to eq file_mimetype
|
126
|
+
expect(resource_object.service).to eq []
|
127
|
+
end
|
128
|
+
end
|
129
|
+
describe 'requires login' do
|
130
|
+
# let(:auth_token_service) {
|
131
|
+
# IIIF::V3::Presentation::Service.new({
|
132
|
+
# 'id' => 'https://example.org/image/iiif/token',
|
133
|
+
# 'profile' => IIIF::V3::Presentation::Service::IIIF_AUTHENTICATION_V1_TOKEN_PROFILE
|
134
|
+
# })}
|
135
|
+
let(:service_label) { 'login message' }
|
136
|
+
let(:token_service_id) { 'https://example.org/iiif/token' }
|
137
|
+
let(:login_service) {
|
138
|
+
IIIF::V3::Presentation::Service.new(
|
139
|
+
'id' => 'https://example.org/auth/iiif',
|
140
|
+
'profile' => 'http://iiif.io/api/auth/1/login',
|
141
|
+
'label' => service_label,
|
142
|
+
'service' => [{
|
143
|
+
'id' => token_service_id,
|
144
|
+
'profile' => 'http://iiif.io/api/auth/1/token'
|
145
|
+
}]
|
146
|
+
)
|
147
|
+
}
|
148
|
+
let(:resource_object_w_login) {
|
149
|
+
resource = resource_object
|
150
|
+
resource.service = [login_service]
|
151
|
+
resource
|
152
|
+
}
|
153
|
+
it 'validates' do
|
154
|
+
expect{resource_object_w_login.validate}.not_to raise_error
|
155
|
+
end
|
156
|
+
it 'has expected service value' do
|
157
|
+
service_obj = resource_object_w_login.service.first
|
158
|
+
expect(service_obj.class).to eq IIIF::V3::Presentation::Service
|
159
|
+
expect(service_obj.keys.size).to eq 4
|
160
|
+
expect(service_obj.id).to eq 'https://example.org/auth/iiif'
|
161
|
+
expect(service_obj.profile).to eq IIIF::V3::Presentation::Service::IIIF_AUTHENTICATION_V1_LOGIN_PROFILE
|
162
|
+
expect(service_obj.label).to eq service_label
|
163
|
+
expect(service_obj.service.class).to eq Array
|
164
|
+
expect(service_obj.service.size).to eq 1
|
165
|
+
expect(service_obj.service.first.keys.size).to eq 2
|
166
|
+
expect(service_obj.service.first['id']).to eq token_service_id
|
167
|
+
expect(service_obj.service.first['profile']).to eq IIIF::V3::Presentation::Service::IIIF_AUTHENTICATION_V1_TOKEN_PROFILE
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
describe IIIF::V3::Presentation::Sequence do
|
2
|
+
|
3
|
+
describe '#required_keys' do
|
4
|
+
# NOTE: relaxing requirement for items as Universal Viewer currently only accepts canvases
|
5
|
+
# see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
|
6
|
+
# %w{ type items }.each do |k|
|
7
|
+
%w{ type }.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
|
+
nav_date
|
20
|
+
content_annotations
|
21
|
+
}
|
22
|
+
expect(subject.prohibited_keys).to include(*keys)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#array_only_keys' do
|
27
|
+
it 'items' do
|
28
|
+
expect(subject.array_only_keys).to include('items')
|
29
|
+
end
|
30
|
+
# NOTE: also allowing sequences as Universal Viewer currently only accepts canvases
|
31
|
+
# see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
|
32
|
+
it 'canvases' do
|
33
|
+
expect(subject.array_only_keys).to include('canvases')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#legal_viewing_hint_values' do
|
38
|
+
it 'contains the expected values' do
|
39
|
+
expect(subject.legal_viewing_hint_values).to contain_exactly('individuals', 'paged', 'continuous', 'auto-advance')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#initialize' do
|
44
|
+
it 'sets type to Sequence by default' do
|
45
|
+
expect(subject['type']).to eq 'Sequence'
|
46
|
+
end
|
47
|
+
it 'allows subclasses to override type' do
|
48
|
+
subclass = Class.new(described_class) do
|
49
|
+
def initialize(hsh={})
|
50
|
+
hsh = { 'type' => 'a:SubClass' }
|
51
|
+
super(hsh)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
sub = subclass.new
|
55
|
+
expect(sub['type']).to eq 'a:SubClass'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#validate' do
|
60
|
+
let(:req_key_msg) { "The (items or canvases) list must have at least one entry (and it must be a IIIF::V3::Presentation::Canvas)" }
|
61
|
+
let(:bad_val_msg) { "All entries in the (items or canvases) list must be a IIIF::V3::Presentation::Canvas" }
|
62
|
+
it 'raises MissingRequiredKeyError if no items or canvases key' do
|
63
|
+
expect { subject.validate }.to raise_error(IIIF::V3::Presentation::MissingRequiredKeyError, req_key_msg)
|
64
|
+
end
|
65
|
+
describe 'items' do
|
66
|
+
it 'raises MissingRequiredKeyError for items as empty Array' do
|
67
|
+
subject['items'] = []
|
68
|
+
expect { subject.validate }.to raise_error(IIIF::V3::Presentation::MissingRequiredKeyError, req_key_msg)
|
69
|
+
end
|
70
|
+
it 'raises IllegalValueError for items entry that is not a Canvas' do
|
71
|
+
subject['items'] = [IIIF::V3::Presentation::Canvas.new, IIIF::V3::Presentation::AnnotationPage.new]
|
72
|
+
expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, bad_val_msg)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
describe 'canvases' do
|
76
|
+
it 'raises MissingRequiredKeyError for canvases as empty Array' do
|
77
|
+
subject['items'] = []
|
78
|
+
expect { subject.validate }.to raise_error(IIIF::V3::Presentation::MissingRequiredKeyError, req_key_msg)
|
79
|
+
end
|
80
|
+
it 'raises IllegalValueError for canvases entry that is not a Canvas' do
|
81
|
+
subject['canvases'] = [IIIF::V3::Presentation::Canvas.new, IIIF::V3::Presentation::AnnotationPage.new]
|
82
|
+
expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, bad_val_msg)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'realistic examples' do
|
88
|
+
let!(:canvas_object) { IIIF::V3::Presentation::Canvas.new({
|
89
|
+
"id" => "https://example.org/abc666/iiif3/canvas/0001",
|
90
|
+
"label" => "p. 1",
|
91
|
+
"height" => 7579,
|
92
|
+
"width" => 10108,
|
93
|
+
"content" => []
|
94
|
+
})}
|
95
|
+
describe 'minimal sequence' do
|
96
|
+
let!(:sequence_object) { described_class.new({
|
97
|
+
"items" => [canvas_object]
|
98
|
+
})}
|
99
|
+
it 'validates' do
|
100
|
+
expect{sequence_object.validate}.not_to raise_error
|
101
|
+
end
|
102
|
+
it 'has expected required values' do
|
103
|
+
expect(sequence_object.type).to eq 'Sequence'
|
104
|
+
expect(sequence_object.items.size).to be 1
|
105
|
+
expect(sequence_object.items.first).to eq canvas_object
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe 'example from Stanford purl' do
|
110
|
+
let(:seq_id) { 'https://example.org/abc666#sequence-1' }
|
111
|
+
let(:sequence_object) {
|
112
|
+
s = described_class.new({
|
113
|
+
"id" => seq_id,
|
114
|
+
"label" => "Current order"
|
115
|
+
})
|
116
|
+
s.canvases << canvas_object
|
117
|
+
s
|
118
|
+
}
|
119
|
+
it 'validates' do
|
120
|
+
expect{sequence_object.validate}.not_to raise_error
|
121
|
+
end
|
122
|
+
it 'has expected required values' do
|
123
|
+
expect(sequence_object.type).to eq 'Sequence'
|
124
|
+
expect(sequence_object.canvases.size).to be 1
|
125
|
+
expect(sequence_object.canvases.first).to eq canvas_object
|
126
|
+
end
|
127
|
+
it 'has expected string values' do
|
128
|
+
expect(sequence_object.id).to eq seq_id
|
129
|
+
expect(sequence_object.label).to eq "Current order"
|
130
|
+
end
|
131
|
+
describe 'with viewingDirection' do
|
132
|
+
let(:sequence_vd) {
|
133
|
+
sequence_object.viewingDirection = 'left-to-right'
|
134
|
+
sequence_object
|
135
|
+
}
|
136
|
+
it 'validates' do
|
137
|
+
expect{sequence_vd.validate}.not_to raise_error
|
138
|
+
end
|
139
|
+
it 'has expected required values' do
|
140
|
+
expect(sequence_vd.type).to eq 'Sequence'
|
141
|
+
expect(sequence_vd.canvases.size).to be 1
|
142
|
+
expect(sequence_vd.canvases.first).to eq canvas_object
|
143
|
+
end
|
144
|
+
it 'has expected string values' do
|
145
|
+
expect(sequence_vd.id).to eq seq_id
|
146
|
+
expect(sequence_vd.label).to eq "Current order"
|
147
|
+
expect(sequence_vd.viewing_direction).to eq 'left-to-right'
|
148
|
+
expect(sequence_vd.viewingDirection).to eq 'left-to-right'
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe 'example from http://prezi3.iiif.io/api/presentation/3.0' do
|
154
|
+
let!(:sequence_object) { described_class.new({
|
155
|
+
"id" => "http://example.org/iiif/book1/sequence/normal",
|
156
|
+
"label" => {"en" => "Current Page Order"},
|
157
|
+
"viewingDirection" => "left-to-right",
|
158
|
+
"viewingHint" => ["paged"],
|
159
|
+
"startCanvas" => canvas_object.id,
|
160
|
+
"items" => [canvas_object]
|
161
|
+
})}
|
162
|
+
it 'validates' do
|
163
|
+
expect{sequence_object.validate}.not_to raise_error
|
164
|
+
end
|
165
|
+
it 'has expected required values' do
|
166
|
+
expect(sequence_object.type).to eq 'Sequence'
|
167
|
+
expect(sequence_object.items.size).to be 1
|
168
|
+
expect(sequence_object.items.first).to eq canvas_object
|
169
|
+
end
|
170
|
+
it 'has expected string values' do
|
171
|
+
expect(sequence_object.id).to eq "http://example.org/iiif/book1/sequence/normal"
|
172
|
+
expect(sequence_object.viewingDirection).to eq "left-to-right"
|
173
|
+
expect(sequence_object.startCanvas).to eq "https://example.org/abc666/iiif3/canvas/0001"
|
174
|
+
end
|
175
|
+
it 'has expected additional content' do
|
176
|
+
expect(sequence_object.viewingHint).to eq ["paged"]
|
177
|
+
expect(sequence_object.label).to eq ({"en" => "Current Page Order"})
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe 'another example' do
|
182
|
+
let!(:sequence_object) { described_class.new({
|
183
|
+
"id" => "http://example.com/prefix/sequence/456",
|
184
|
+
'label' => 'Book 1',
|
185
|
+
'description' => 'A longer description of this example book. It should give some real information.',
|
186
|
+
'thumbnail' => [{
|
187
|
+
'id' => 'http://www.example.org/images/book1-page1/full/80,100/0/default.jpg',
|
188
|
+
'type' => 'Image',
|
189
|
+
'service'=> {
|
190
|
+
'@context' => 'http://iiif.io/api/image/2/context.json',
|
191
|
+
'id' => 'http://www.example.org/images/book1-page1',
|
192
|
+
'profile' => 'http://iiif.io/api/image/2/level1.json'
|
193
|
+
}
|
194
|
+
}],
|
195
|
+
'attribution' => 'Provided by Example Organization',
|
196
|
+
'rights' => [{'id' => 'http://www.example.org/license.html'}],
|
197
|
+
'logo' => 'http://www.example.org/logos/institution1.jpg',
|
198
|
+
'see_also' => 'http://www.example.org/library/catalog/book1.xml',
|
199
|
+
'service' => {
|
200
|
+
'@context' => 'http://example.org/ns/jsonld/context.json',
|
201
|
+
'id' => 'http://example.org/service/example',
|
202
|
+
'profile' => 'http://example.org/docs/example-service.html'
|
203
|
+
},
|
204
|
+
'related' => {
|
205
|
+
'id' => 'http://www.example.org/videos/video-book1.mpg',
|
206
|
+
'format' => 'video/mpeg'
|
207
|
+
},
|
208
|
+
'within' => 'http://www.example.org/collections/books/',
|
209
|
+
# Sequence
|
210
|
+
'metadata' => [{'label'=>'Author', 'value'=>'Anne Author'}],
|
211
|
+
"items" => [canvas_object],
|
212
|
+
'start_canvas' => 'http://www.example.org/iiif/book1/canvas/p2',
|
213
|
+
"viewingDirection" => "left-to-right",
|
214
|
+
"viewingHint" => ["paged"],
|
215
|
+
"startCanvas" => canvas_object.id,
|
216
|
+
})}
|
217
|
+
it 'validates' do
|
218
|
+
expect{sequence_object.validate}.not_to raise_error
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
describe IIIF::V3::Presentation::Service do
|
2
|
+
|
3
|
+
describe '#required_keys' do
|
4
|
+
it '"type" is not required' do
|
5
|
+
expect(subject.required_keys).not_to include('type')
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#prohibited_keys' do
|
10
|
+
it 'contains the expected key names' do
|
11
|
+
keys = described_class::CONTENT_RESOURCE_PROPERTIES +
|
12
|
+
described_class::PAGING_PROPERTIES +
|
13
|
+
%w{
|
14
|
+
nav_date
|
15
|
+
viewing_direction
|
16
|
+
start_canvas
|
17
|
+
content_annotations
|
18
|
+
}
|
19
|
+
expect(subject.prohibited_keys).to include(*keys)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#uri_only_keys' do
|
24
|
+
it '@context' do
|
25
|
+
expect(subject.uri_only_keys).to include('@context')
|
26
|
+
end
|
27
|
+
it '@id' do
|
28
|
+
expect(subject.uri_only_keys).to include('@id')
|
29
|
+
end
|
30
|
+
it 'id' do
|
31
|
+
expect(subject.uri_only_keys).to include('id')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#any_type_keys' do
|
36
|
+
it 'profile' do
|
37
|
+
expect(subject.any_type_keys).to include('profile')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
let(:id_uri) { "https://example.org/image1" }
|
42
|
+
|
43
|
+
describe '#initialize' do
|
44
|
+
it 'assigns hash values passed in' do
|
45
|
+
label_val = 'foo'
|
46
|
+
inner_service_id = 'http://example.org/whatever'
|
47
|
+
inner_service_profile = 'http://iiif.io/api/auth/1/token'
|
48
|
+
inner_service_val = described_class.new({
|
49
|
+
'id' => inner_service_id,
|
50
|
+
'profile' => inner_service_profile
|
51
|
+
})
|
52
|
+
service_obj = described_class.new({
|
53
|
+
'type' => described_class::IIIF_IMAGE_V2_TYPE,
|
54
|
+
'id' => id_uri,
|
55
|
+
'profile' => described_class::IIIF_IMAGE_V2_LEVEL1_PROFILE,
|
56
|
+
'label' => label_val,
|
57
|
+
'service' => [inner_service_val]
|
58
|
+
})
|
59
|
+
expect(service_obj.keys.size).to eq 5
|
60
|
+
expect(service_obj['id']).to eq id_uri
|
61
|
+
expect(service_obj['type']).to eq described_class::IIIF_IMAGE_V2_TYPE
|
62
|
+
expect(service_obj['profile']).to eq described_class::IIIF_IMAGE_V2_LEVEL1_PROFILE
|
63
|
+
expect(service_obj['label']).to eq label_val
|
64
|
+
expect(service_obj['service'][0]['id']).to eq inner_service_id
|
65
|
+
expect(service_obj['service'][0]['profile']).to eq inner_service_profile
|
66
|
+
end
|
67
|
+
it 'allows both "id" and "@id" as keys' do
|
68
|
+
id_uri = "https://stacks.stanford.edu/image/iiif/wy534zh7137%2FSULAIR_rosette"
|
69
|
+
service_obj = described_class.new({
|
70
|
+
'id' => id_uri,
|
71
|
+
'@id' => id_uri
|
72
|
+
})
|
73
|
+
expect(service_obj.keys.size).to eq 2
|
74
|
+
expect(service_obj['id']).to eq id_uri
|
75
|
+
expect(service_obj['@id']).to eq id_uri
|
76
|
+
end
|
77
|
+
it 'allows non-URI profile value' do
|
78
|
+
expect{
|
79
|
+
described_class.new({
|
80
|
+
"profile" => [
|
81
|
+
"http://iiif.io/api/image/2/level2.json",
|
82
|
+
{
|
83
|
+
"formats" => [ "gif", "pdf" ],
|
84
|
+
"qualities" => [ "color", "gray" ],
|
85
|
+
"supports" => [ "canonicalLinkHeader", "rotationArbitrary", "http://example.com/feature" ]
|
86
|
+
}
|
87
|
+
]
|
88
|
+
})
|
89
|
+
}.not_to raise_error
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#validate' do
|
94
|
+
describe 'type = IIIF_IMAGE_API_V2_TYPE' do
|
95
|
+
it 'must have a "@id" or "id"' do
|
96
|
+
service_obj = described_class.new({
|
97
|
+
'type' => described_class::IIIF_IMAGE_V2_TYPE,
|
98
|
+
'nope' => id_uri,
|
99
|
+
'profile' => described_class::IIIF_IMAGE_V2_LEVEL1_PROFILE
|
100
|
+
})
|
101
|
+
exp_err_msg = 'id or @id values are required for IIIF::V3::Presentation::Service with type or @type ImageService2'
|
102
|
+
expect{service_obj.validate}.to raise_error(IIIF::V3::Presentation::MissingRequiredKeyError, exp_err_msg)
|
103
|
+
end
|
104
|
+
it 'should have a profile' do
|
105
|
+
service_obj = described_class.new({
|
106
|
+
'@type' => described_class::IIIF_IMAGE_V2_TYPE,
|
107
|
+
'@id' => id_uri
|
108
|
+
})
|
109
|
+
exp_err_msg = 'profile should be present for IIIF::V3::Presentation::Service with type or @type ImageService2'
|
110
|
+
expect{service_obj.validate}.to raise_error(IIIF::V3::Presentation::MissingRequiredKeyError, exp_err_msg)
|
111
|
+
end
|
112
|
+
it 'should have matching values for "@id" and "id" if both are specified' do
|
113
|
+
service_obj = described_class.new({
|
114
|
+
'@type' => described_class::IIIF_IMAGE_V2_TYPE,
|
115
|
+
'@id' => id_uri,
|
116
|
+
'id' => "#{id_uri}/foo",
|
117
|
+
'profile' => described_class::IIIF_IMAGE_V2_LEVEL1_PROFILE
|
118
|
+
})
|
119
|
+
exp_err_msg = 'id and @id values must match for IIIF::V3::Presentation::Service with type or @type ImageService2'
|
120
|
+
expect{service_obj.validate}.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe 'realistic examples' do
|
126
|
+
describe 'from Stanford purl manifests' do
|
127
|
+
it 'iiif image v2 service' do
|
128
|
+
service_obj = described_class.new({
|
129
|
+
'type' => described_class::IIIF_IMAGE_V2_TYPE,
|
130
|
+
'id' => id_uri,
|
131
|
+
'@id' => id_uri,
|
132
|
+
'profile' => described_class::IIIF_IMAGE_V2_LEVEL1_PROFILE
|
133
|
+
})
|
134
|
+
expect{service_obj.validate}.not_to raise_error
|
135
|
+
expect(service_obj.keys.size).to eq 4
|
136
|
+
expect(service_obj.keys).to include('type', '@id', 'id', 'profile')
|
137
|
+
expect(service_obj['type']).to eq described_class::IIIF_IMAGE_V2_TYPE
|
138
|
+
expect(service_obj['id']).to eq id_uri
|
139
|
+
expect(service_obj['@id']).to eq id_uri
|
140
|
+
expect(service_obj['profile']).to eq described_class::IIIF_IMAGE_V2_LEVEL1_PROFILE
|
141
|
+
end
|
142
|
+
it 'login service' do
|
143
|
+
token_service = described_class.new({
|
144
|
+
'id' => 'https://example.org/image/iiif/token',
|
145
|
+
'profile' => described_class::IIIF_AUTHENTICATION_V1_TOKEN_PROFILE
|
146
|
+
})
|
147
|
+
expect{token_service.validate}.not_to raise_error
|
148
|
+
expect(token_service.class).to eq described_class
|
149
|
+
expect(token_service.keys.size).to eq 2
|
150
|
+
expect(token_service.keys).to include('id', 'profile')
|
151
|
+
expect(token_service['id']).to eq 'https://example.org/image/iiif/token'
|
152
|
+
expect(token_service['profile']).to eq described_class::IIIF_AUTHENTICATION_V1_TOKEN_PROFILE
|
153
|
+
service_obj = described_class.new(
|
154
|
+
'id' => 'https://example.org/auth/iiif',
|
155
|
+
'profile' => described_class::IIIF_AUTHENTICATION_V1_LOGIN_PROFILE,
|
156
|
+
'label' => 'label value',
|
157
|
+
'service' => [token_service]
|
158
|
+
)
|
159
|
+
expect{service_obj.validate}.not_to raise_error
|
160
|
+
expect(service_obj.keys.size).to eq 4
|
161
|
+
expect(service_obj.keys).to include('id', 'profile', 'label', 'service')
|
162
|
+
expect(service_obj['id']).to eq 'https://example.org/auth/iiif'
|
163
|
+
expect(service_obj['profile']).to eq described_class::IIIF_AUTHENTICATION_V1_LOGIN_PROFILE
|
164
|
+
expect(service_obj['label']).to eq 'label value'
|
165
|
+
inner_service = service_obj['service'][0]
|
166
|
+
expect(inner_service.class).to eq described_class
|
167
|
+
expect{inner_service.validate}.not_to raise_error
|
168
|
+
expect(inner_service.keys.size).to eq 2
|
169
|
+
expect(inner_service.keys).to include('id', 'profile')
|
170
|
+
expect(inner_service['id']).to eq 'https://example.org/image/iiif/token'
|
171
|
+
expect(inner_service['profile']).to eq described_class::IIIF_AUTHENTICATION_V1_TOKEN_PROFILE
|
172
|
+
end
|
173
|
+
it 'triply nested service' do
|
174
|
+
inner = described_class.new({
|
175
|
+
'id' => 'https://example.org/image/iiif/token',
|
176
|
+
'profile' => described_class::IIIF_AUTHENTICATION_V1_TOKEN_PROFILE
|
177
|
+
})
|
178
|
+
expect{inner.validate}.not_to raise_error
|
179
|
+
middle = described_class.new({
|
180
|
+
"id" => "https://example.org/auth/iiif",
|
181
|
+
"profile" => described_class::IIIF_AUTHENTICATION_V1_LOGIN_PROFILE,
|
182
|
+
"label" => "Stanford-affiliated? Login to view",
|
183
|
+
"service" => [inner]
|
184
|
+
})
|
185
|
+
expect{middle.validate}.not_to raise_error
|
186
|
+
outer = described_class.new({
|
187
|
+
"@type" => described_class::IIIF_IMAGE_V2_TYPE,
|
188
|
+
"@id" => "https://example.org/iiif/yy816tv6021_img_1",
|
189
|
+
"id" => "https://example.org/iiif/yy816tv6021_img_1",
|
190
|
+
"profile" => described_class::IIIF_IMAGE_V2_LEVEL1_PROFILE,
|
191
|
+
"service" => [middle]
|
192
|
+
})
|
193
|
+
expect{outer.validate}.not_to raise_error
|
194
|
+
end
|
195
|
+
end
|
196
|
+
describe 'example from http://prezi3.iiif.io/api/presentation/3.0' do
|
197
|
+
it 'iiif image v2' do
|
198
|
+
service_obj = described_class.new({
|
199
|
+
"type" => described_class::IIIF_IMAGE_V2_TYPE,
|
200
|
+
"id" => "http://example.org/images/book1-page2",
|
201
|
+
"@id" => "http://example.org/images/book1-page2",
|
202
|
+
"profile" => "http://iiif.io/api/image/2/level1.json",
|
203
|
+
"height" => 8000,
|
204
|
+
"width" => 6000,
|
205
|
+
"tiles" => [{"width" => 512, "scaleFactors" => [1,2,4,8,16]}]
|
206
|
+
})
|
207
|
+
expect(service_obj.keys.size).to eq 7
|
208
|
+
expect(service_obj['type']).to eq described_class::IIIF_IMAGE_V2_TYPE
|
209
|
+
expect(service_obj['id']).to eq 'http://example.org/images/book1-page2'
|
210
|
+
expect(service_obj['@id']).to eq 'http://example.org/images/book1-page2'
|
211
|
+
expect(service_obj['profile']).to eq described_class::IIIF_IMAGE_V2_LEVEL1_PROFILE
|
212
|
+
expect(service_obj['height']).to eq 8000
|
213
|
+
expect(service_obj['width']).to eq 6000
|
214
|
+
expect(service_obj['tiles']).to eq [{"width" => 512, "scaleFactors" => [1,2,4,8,16]}]
|
215
|
+
# TODO: note that this won't validate because "height is a prohibited key in IIIF::V3::Presentation::Service"
|
216
|
+
# expect{service_obj.validate}.not_to raise_error
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
shared_examples 'it has the appropriate methods for abstract_resource_only_keys v3' do
|
2
|
+
|
3
|
+
described_class.new.abstract_resource_only_keys.each do |entry|
|
4
|
+
|
5
|
+
describe "#{entry[:key]}=" do
|
6
|
+
it "sets #{entry[:key]}" do
|
7
|
+
@ex = entry[:type].new
|
8
|
+
subject.send("#{entry[:key]}=", @ex)
|
9
|
+
expect(subject[entry[:key]]).to eq @ex
|
10
|
+
end
|
11
|
+
if entry[:key].camelize(:lower) != entry[:key]
|
12
|
+
it "is aliased as ##{entry[:key].camelize(:lower)}=" do
|
13
|
+
@ex = entry[:type].new
|
14
|
+
subject.send("#{entry[:key].camelize(:lower)}=", @ex)
|
15
|
+
expect(subject[entry[:key]]).to eq @ex
|
16
|
+
end
|
17
|
+
end
|
18
|
+
it "raises an exception when attempting to set it to something other than an #{entry[:type]}" do
|
19
|
+
e = IIIF::V3::Presentation::IllegalValueError
|
20
|
+
expect { subject.send("#{entry[:key]}=", 'Foo') }.to raise_error e
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#{entry[:key]}" do
|
25
|
+
it "gets #{entry[:key]}" do
|
26
|
+
@ex = entry[:type].new
|
27
|
+
subject[entry[:key]] = @ex
|
28
|
+
expect(subject.send(entry[:key])).to eq @ex
|
29
|
+
end
|
30
|
+
if entry[:key].camelize(:lower) != entry[:key]
|
31
|
+
it "is aliased as ##{entry[:key].camelize(:lower)}" do
|
32
|
+
@ex = entry[:type].new
|
33
|
+
subject[entry[:key]] = @ex
|
34
|
+
expect(subject.send("#{entry[:key].camelize(:lower)}")).to eq @ex
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|