From 6671c34e7e0ed5d5517b85f58b8c6633256b32e5 Mon Sep 17 00:00:00 2001 From: Justin Coyne Date: Wed, 7 Jun 2023 05:41:46 -0500 Subject: [PATCH] WIP v3 --- Gemfile | 2 + README.md | 102 +++-- lib/iiif/presentation.rb | 4 +- lib/iiif/presentation/abstract_resource.rb | 2 +- lib/iiif/presentation/annotation.rb | 4 +- lib/iiif/presentation/annotation_list.rb | 6 +- lib/iiif/presentation/canvas.rb | 6 +- lib/iiif/presentation/collection.rb | 8 +- lib/iiif/presentation/image_resource.rb | 14 +- lib/iiif/presentation/layer.rb | 6 +- lib/iiif/presentation/manifest.rb | 10 +- lib/iiif/presentation/range.rb | 10 +- lib/iiif/presentation/repository.rb | 5 + lib/iiif/presentation/resource.rb | 2 +- lib/iiif/presentation/sequence.rb | 35 -- lib/iiif/presentation/service.rb | 8 + lib/iiif/presentation/start.rb | 12 + lib/iiif/service.rb | 35 +- .../manifests/complete_from_spec.json | 380 ++++++++++++------ spec/fixtures/manifests/minimal.json | 56 +-- spec/fixtures/manifests/service_only.json | 18 +- .../vcr_cassettes/pul_loris_cassette.json | 2 +- .../iiif/presentation/image_resource_spec.rb | 18 +- spec/integration/iiif/service_spec.rb | 359 ++++++++++++++--- spec/spec_helper.rb | 2 + .../presentation/abstract_resource_spec.rb | 26 +- spec/unit/iiif/presentation/canvas_spec.rb | 10 +- .../unit/iiif/presentation/collection_spec.rb | 22 +- .../iiif/presentation/image_resource_spec.rb | 4 +- spec/unit/iiif/presentation/layer_spec.rb | 10 +- spec/unit/iiif/presentation/manifest_spec.rb | 30 +- spec/unit/iiif/presentation/range_spec.rb | 8 +- spec/unit/iiif/presentation/sequence_spec.rb | 110 ----- spec/unit/iiif/service_spec.rb | 13 +- 34 files changed, 802 insertions(+), 537 deletions(-) create mode 100644 lib/iiif/presentation/repository.rb delete mode 100644 lib/iiif/presentation/sequence.rb create mode 100644 lib/iiif/presentation/start.rb delete mode 100644 spec/unit/iiif/presentation/sequence_spec.rb diff --git a/Gemfile b/Gemfile index f632211..a5edec2 100644 --- a/Gemfile +++ b/Gemfile @@ -8,3 +8,5 @@ end if ENV['FARADAY_VERSION'] gem 'faraday', ENV['FARADAY_VERSION'] end + +gem 'debug' diff --git a/README.md b/README.md index 94db6c1..9f48b5e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ From the source code do `rake install`, or get the latest release [from RubyGems ## Building New Objects -There is (or will be) a class for all types in [IIIF Presentation API Spec](http://iiif.io/api/presentation/2.0/). +There is (or will be) a class for all types in [IIIF Presentation API Spec v3.0](http://iiif.io/api/presentation/3.0/). @@ -19,7 +19,7 @@ There is (or will be) a class for all types in [IIIF Presentation API Spec](http require 'iiif/presentation' seed = { - '@id' => 'http://example.com/manifest', + 'id' => 'http://example.com/manifest', 'label' => 'My Manifest' } # Any options you add are added to the object @@ -33,7 +33,7 @@ manifest.sequences << sequence canvas = IIIF::Presentation::Canvas.new() # All classes act like `ActiveSupport::OrderedHash`es, for the most part. # Use `[]=` to set JSON-LD properties... -canvas['@id'] = 'http://example.com/canvas' +canvas.id = 'http://example.com/canvas' # ...but there are also accessors and mutators for the properties mentioned in # the spec canvas.width = 10 @@ -41,24 +41,26 @@ canvas.height = 20 canvas.label = 'My Canvas' # Add images -service = IIIF::Presentation::Resource.new('@context' => 'http://iiif.io/api/image/2/context.json', 'profile' => 'http://iiif.io/api/image/2/level2.json', '@id' => "http://images.exampl.com/loris2/my-image") +service = IIIF::Presentation::Resource.new('@context' => 'http://iiif.io/api/image/3/context.json', 'profile' => 'http://iiif.io/api/image/2/level2.json', 'id' => "http://images.exampl.com/loris2/my-image") -image = IIIF::Presentation::ImageResource.new() -i['@id'] = "http://images.exampl.com/loris2/my-image/full/#{canvas.width},#{canvas.height}/0/default.jpg" +image = IIIF::Presentation::ImageResource.new +i.id = "http://images.exampl.com/loris2/my-image/full/#{canvas.width},#{canvas.height}/0/default.jpg" i.format = "image/jpeg" i.width = canvas.width i.height = canvas.height i.service = service -images = IIIF::Presentation::Resource.new('@type' => 'oa:Annotation', 'motivation' => 'sc:painting', '@id' => "#{canvas['@id']}/images", 'resource' => i) +annotation = IIIF::Presentation::Annotation.new('type' => 'Annotation', 'motivation' => 'painting', 'id' => "#{canvas.id}/images", 'resource' => i) -canvas.images << images +annotation_page = IIIF::Presentation::AnnotationPage.new() +annotation_page.items << annotation +canvas.items << annoation_page # Add other content resources oc = IIIF::Presentation::Resource.new('@id' => 'http://example.com/content') canvas.other_content << oc -manifest.sequences.first.canvases << canvas +manifest.canvases << canvas puts manifest.to_json(pretty: true) ``` @@ -92,8 +94,8 @@ manifest.viewing_hint = 'paged' puts manifest.to_json(pretty: true, force: true) # force: true skips validations > { -> "@context": "http://iiif.io/api/presentation/2/context.json", -> "@type": "sc:Manifest", +> "@context": "http://iiif.io/api/presentation/3/context.json", +> "type": "Manifest", > "viewingHint": "paged" > } @@ -102,44 +104,61 @@ puts manifest.to_json(pretty: true, force: true) # force: true skips validations ## Parsing Existing Objects Use `IIIF::Service#parse`. It will figure out what the object -should be, based on `@type`, and fall back to `Hash` when +should be, based on `type`, and fall back to `Hash` when it can't e.g.: ```ruby seed = '{ - "@context": "http://iiif.io/api/presentation/2/context.json", - "@id": "http://example.com/manifest", - "@type": "sc:Manifest", + "@context": "http://iiif.io/api/presentation/3/context.json", + "id": "http://example.com/manifest", + "type": "Manifest", "label": "My Manifest", "service": { "@context": "http://iiif.io/api/image/2/context.json", - "@id":"http://www.example.org/images/book1-page1", + "id":"http://www.example.org/images/book1-page1", "profile":"http://iiif.io/api/image/2/profiles/level2.json" }, "seeAlso": { - "@id": "http://www.example.org/library/catalog/book1.marc", + "id": "http://www.example.org/library/catalog/book1.marc", "format": "application/marc" }, - "sequences": [ + "items": [ { - "@id":"http://www.example.org/iiif/book1/sequence/normal", - "@type":"sc:Sequence", - "label":"Current Page Order", - "viewingDirection":"left-to-right", - "viewingHint":"paged", - "startCanvas": "http://www.example.org/iiif/book1/canvas/p2", - "canvases": [ + "id": "http://example.com/canvas", + "type": "Canvas", + "width": 10, + "height": 20, + "label": "My Canvas", + "items": [ { - "@id": "http://example.com/canvas", - "@type": "sc:Canvas", - "width": 10, - "height": 20, - "label": "My Canvas", - "otherContent": [ + "id": "https://example.org/iiif/book1/page/p1/1", + "type": "AnnotationPage", + "items": [ { - "@id": "http://example.com/content", - "@type":"sc:AnnotationList", - "motivation": "sc:painting" + "id": "https://example.org/iiif/book1/annotation/p0001-image", + "type": "Annotation", + "motivation": "painting", + "body": { + "id": "https://example.org/iiif/book1/page1/full/max/0/default.jpg", + "type": "Image", + "format": "image/jpeg", + "service": [ + { + "id": "https://example.org/iiif/book1/page1", + "type": "ImageService3", + "profile": "level2", + "service": [ + { + "@id": "https://example.org/iiif/auth/login", + "@type": "AuthCookieService1" + } + ] + } + ], + "height": 2000, + "width": 1500 + }, + "target": "https://example.org/iiif/book1/canvas/p1" } ] } @@ -148,6 +167,7 @@ seed = '{ ] }' + obj = IIIF::Service.parse(seed) # can also be a file path or a Hash puts obj.class puts obj.see_also.class @@ -163,15 +183,15 @@ try to set something to a type it should never be: ```ruby manifest = IIIF::Presentation::Manifest.new -manifest.sequences = 'quux' +manifest.items = 'quux' -> [...] sequences must be an Array. (IIIF::Presentation::IllegalValueError) +> [...] items must be an Array. (IIIF::Presentation::IllegalValueError) ``` and also if any required properties are missing when calling `to_json` ```ruby -canvas = IIIF::Presentation::Canvas.new('@id' => 'http://example.com/canvas') +canvas = IIIF::Presentation::Canvas.new('id' => 'http://example.com/canvas') puts canvas.to_json(pretty: true) > A(n) width is required for each IIIF::Presentation::Canvas (IIIF::Presentation::MissingRequiredKeyError) @@ -180,13 +200,13 @@ puts canvas.to_json(pretty: true) but you can skip this validation by adding `force: true`: ```ruby -canvas = IIIF::Presentation::Canvas.new('@id' => 'http://example.com/canvas') +canvas = IIIF::Presentation::Canvas.new('id' => 'http://example.com/canvas') puts canvas.to_json(pretty: true, force: true) > { -> "@context": "http://iiif.io/api/presentation/2/context.json", -> "@id": "http://example.com/canvas", -> "@type": "sc:Canvas" +> "@context": "http://iiif.io/api/presentation/3/context.json", +> "id": "http://example.com/canvas", +> "type": "Canvas" > } ``` This all needs a bit of tidying up, finishing, and refactoring, so expect it to diff --git a/lib/iiif/presentation.rb b/lib/iiif/presentation.rb index 0b6870b..a93eab5 100644 --- a/lib/iiif/presentation.rb +++ b/lib/iiif/presentation.rb @@ -9,9 +9,9 @@ manifest resource image_resource - sequence service range + start }.each do |f| require File.join(File.dirname(__FILE__), 'presentation', f) end @@ -20,7 +20,7 @@ module IIIF module Presentation - CONTEXT ||= 'http://iiif.io/api/presentation/2/context.json' + CONTEXT ||= 'http://iiif.io/api/presentation/3/context.json' class MissingRequiredKeyError < StandardError; end class IllegalValueError < StandardError; end diff --git a/lib/iiif/presentation/abstract_resource.rb b/lib/iiif/presentation/abstract_resource.rb index 67f76f6..ab21cbd 100644 --- a/lib/iiif/presentation/abstract_resource.rb +++ b/lib/iiif/presentation/abstract_resource.rb @@ -7,7 +7,7 @@ class AbstractResource < Service # Every subclass should override the following five methods where # appropriate, see Subclasses for how. def required_keys - %w{ @type } + %w{ type } end def any_type_keys # these are allowed on all classes diff --git a/lib/iiif/presentation/annotation.rb b/lib/iiif/presentation/annotation.rb index 9c98e63..50e7f15 100644 --- a/lib/iiif/presentation/annotation.rb +++ b/lib/iiif/presentation/annotation.rb @@ -15,8 +15,8 @@ def abstract_resource_only_keys end def initialize(hsh={}) - hsh['@type'] = TYPE unless hsh.has_key? '@type' - hsh['motivation'] = 'sc:painting' unless hsh.has_key? 'motivation' + hsh['type'] = TYPE unless hsh.has_key? 'type' + hsh['motivation'] = 'painting' unless hsh.has_key? 'motivation' super(hsh) end diff --git a/lib/iiif/presentation/annotation_list.rb b/lib/iiif/presentation/annotation_list.rb index ac780e9..1aff6ff 100644 --- a/lib/iiif/presentation/annotation_list.rb +++ b/lib/iiif/presentation/annotation_list.rb @@ -4,10 +4,10 @@ module IIIF module Presentation class AnnotationList < AbstractResource - TYPE = 'sc:AnnotationList' + TYPE = 'AnnotationList' def required_keys - super + %w{ @id } + super + %w{ id } end def array_only_keys; @@ -15,7 +15,7 @@ def array_only_keys; end def initialize(hsh={}) - hsh['@type'] = TYPE unless hsh.has_key? '@type' + hsh['type'] = TYPE unless hsh.has_key? 'type' super(hsh) end diff --git a/lib/iiif/presentation/canvas.rb b/lib/iiif/presentation/canvas.rb index a3748d8..e44d361 100644 --- a/lib/iiif/presentation/canvas.rb +++ b/lib/iiif/presentation/canvas.rb @@ -6,10 +6,10 @@ class Canvas < AbstractResource # TODO (?) a simple 'Image Canvas' constructor. - TYPE = 'sc:Canvas' + TYPE = 'Canvas' def required_keys - super + %w{ @id width height label } + super + %w{ id width height label } end def any_type_keys @@ -30,7 +30,7 @@ def legal_viewing_hint_values end def initialize(hsh={}) - hsh['@type'] = TYPE unless hsh.has_key? '@type' + hsh['type'] = TYPE unless hsh.has_key? 'type' super(hsh) end diff --git a/lib/iiif/presentation/collection.rb b/lib/iiif/presentation/collection.rb index d515237..7ad98b2 100644 --- a/lib/iiif/presentation/collection.rb +++ b/lib/iiif/presentation/collection.rb @@ -4,10 +4,10 @@ module IIIF module Presentation class Collection < AbstractResource - TYPE = 'sc:Collection' + TYPE = 'Collection' def required_keys - super + %w{ @id label } + super + %w{ id label } end def array_only_keys @@ -15,13 +15,13 @@ def array_only_keys end def initialize(hsh={}) - hsh['@type'] = TYPE unless hsh.has_key? '@type' + hsh['type'] = TYPE unless hsh.has_key? 'type' super(hsh) end def validate # each member of collections and manifests must be a Hash - # each member of collections and manifests MUST have @id, @type, and label + # each member of collections and manifests MUST have id, type, and label end end diff --git a/lib/iiif/presentation/image_resource.rb b/lib/iiif/presentation/image_resource.rb index 3559f26..a18a7c2 100644 --- a/lib/iiif/presentation/image_resource.rb +++ b/lib/iiif/presentation/image_resource.rb @@ -13,7 +13,7 @@ def int_only_keys end def initialize(hsh={}) - hsh['@type'] = TYPE unless hsh.has_key? '@type' + hsh['type'] = TYPE unless hsh.has_key? 'type' super(hsh) end @@ -22,7 +22,7 @@ class << self IMAGE_API_CONTEXT = 'http://iiif.io/api/image/2/context.json' DEFAULT_FORMAT = 'image/jpeg' # Create a new ImageResource that includes a IIIF Image API Service - # See http://iiif.io/api/presentation/2.0/#image-resources + # See http://iiif.io/api/presentation/3.0/#image-resources # # Params # * :service_id (required) - The base URI for the image on the image @@ -48,12 +48,12 @@ class << self # The result is something like this: # # { - # "@id":"http://www.example.org/iiif/book1/res/page1.jpg", - # "@type":"dctypes:Image", + # "id":"http://www.example.org/iiif/book1/res/page1.jpg", + # "type":"dctypes:Image", # "format":"image/jpeg", # "service": { # "@context": "http://iiif.io/api/image/2/context.json", - # "@id":"http://www.example.org/images/book1-page1", + # "id":"http://www.example.org/images/book1-page1", # "profile":"http://iiif.io/api/image/2/profiles/level2.json", # }, # "height":2000, @@ -76,7 +76,7 @@ def create_image_api_image_resource(params={}) remote_info = get_info(service_id) if !have_whp || copy_info resource = self.new - resource['@id'] = resource_id + resource['id'] = resource_id resource.format = format resource.width = width.nil? ? remote_info['width'] : width resource.height = height.nil? ? remote_info['height'] : height @@ -85,7 +85,7 @@ def create_image_api_image_resource(params={}) resource.service.merge!(remote_info) else resource.service['@context'] = IMAGE_API_CONTEXT - resource.service['@id'] = service_id + resource.service['id'] = service_id if profile.nil? if remote_info['profile'].kind_of?(Array) resource.service['profile'] = remote_info['profile'][0] diff --git a/lib/iiif/presentation/layer.rb b/lib/iiif/presentation/layer.rb index f881fc4..9d7afda 100644 --- a/lib/iiif/presentation/layer.rb +++ b/lib/iiif/presentation/layer.rb @@ -4,10 +4,10 @@ module IIIF module Presentation class Layer < AbstractResource - TYPE = 'sc:Layer' + TYPE = 'Layer' def required_keys - super + %w{ @id label } + super + %w{ id label } end def array_only_keys @@ -19,7 +19,7 @@ def string_only_keys end def initialize(hsh={}) - hsh['@type'] = TYPE unless hsh.has_key? '@type' + hsh['type'] = TYPE unless hsh.has_key? 'type' super(hsh) end diff --git a/lib/iiif/presentation/manifest.rb b/lib/iiif/presentation/manifest.rb index b6ae9c4..249f68a 100644 --- a/lib/iiif/presentation/manifest.rb +++ b/lib/iiif/presentation/manifest.rb @@ -4,10 +4,10 @@ module IIIF module Presentation class Manifest < AbstractResource - TYPE = 'sc:Manifest' + TYPE = 'Manifest' def required_keys - super + %w{ @id label } + super + %w{ id label } end def string_only_keys @@ -15,7 +15,7 @@ def string_only_keys end def array_only_keys - super + %w{ sequences structures } + super + %w{ structures } end def legal_viewing_hint_values @@ -23,12 +23,12 @@ def legal_viewing_hint_values end def initialize(hsh={}) - hsh['@type'] = TYPE unless hsh.has_key? '@type' + hsh['type'] = TYPE unless hsh.has_key? 'type' super(hsh) end def validate - # TODO: check types of sequences and structure members + # TODO: check types of structure members super end diff --git a/lib/iiif/presentation/range.rb b/lib/iiif/presentation/range.rb index e438853..926c2aa 100644 --- a/lib/iiif/presentation/range.rb +++ b/lib/iiif/presentation/range.rb @@ -1,13 +1,11 @@ -require File.join(File.dirname(__FILE__), 'sequence') - module IIIF module Presentation - class Range < Sequence + class Range < AbstractResource - TYPE = 'sc:Range' + TYPE = 'Range' def required_keys - super + %w{ @id label } + super + %w{ id label } end def array_only_keys @@ -19,7 +17,7 @@ def legal_viewing_hint_values end def initialize(hsh={}) - hsh['@type'] = TYPE unless hsh.has_key? '@type' + hsh['type'] = TYPE unless hsh.has_key? 'type' super(hsh) end diff --git a/lib/iiif/presentation/repository.rb b/lib/iiif/presentation/repository.rb new file mode 100644 index 0000000..a8b723b --- /dev/null +++ b/lib/iiif/presentation/repository.rb @@ -0,0 +1,5 @@ +# A datastore of all the entities in the context +class Repository + def find(id) + end +end \ No newline at end of file diff --git a/lib/iiif/presentation/resource.rb b/lib/iiif/presentation/resource.rb index b0ec446..79da606 100644 --- a/lib/iiif/presentation/resource.rb +++ b/lib/iiif/presentation/resource.rb @@ -5,7 +5,7 @@ module Presentation class Resource < AbstractResource def required_keys - %w{ @id } + %w{ id } end def string_only_keys diff --git a/lib/iiif/presentation/sequence.rb b/lib/iiif/presentation/sequence.rb deleted file mode 100644 index e4145d6..0000000 --- a/lib/iiif/presentation/sequence.rb +++ /dev/null @@ -1,35 +0,0 @@ -require File.join(File.dirname(__FILE__), 'abstract_resource') - -module IIIF - module Presentation - class Sequence < AbstractResource - - TYPE = 'sc:Sequence' - - def array_only_keys - super + %w{ canvases } - end - - def string_only_keys - super + %w{ start_canvas viewing_direction } - end - - def legal_viewing_hint_values - %w{ individuals paged continuous } - end - - def initialize(hsh={}) - hsh['@type'] = TYPE unless hsh.has_key? '@type' - super(hsh) - end - - def validate - # * Must be at least one canvas - # * All members of canvases must be a kind of Canvas - super - end - - end - end -end - diff --git a/lib/iiif/presentation/service.rb b/lib/iiif/presentation/service.rb index 7e8ada1..942653c 100644 --- a/lib/iiif/presentation/service.rb +++ b/lib/iiif/presentation/service.rb @@ -6,6 +6,14 @@ class Service < AbstractResource def required_keys [] end + + # def to_ordered_hash(opts={}) + # result = data.except('service') + # if data['service'] + # result['service'] = data['service'].map(&:to_ordered_hash) + # end + # result + # end end end end diff --git a/lib/iiif/presentation/start.rb b/lib/iiif/presentation/start.rb new file mode 100644 index 0000000..5e6609c --- /dev/null +++ b/lib/iiif/presentation/start.rb @@ -0,0 +1,12 @@ +require File.join(File.dirname(__FILE__), 'abstract_resource') + +module IIIF + module Presentation + # https://iiif.io/api/presentation/3.0/#start + class Start < AbstractResource + def required_keys + super + %w{ id } + end + end + end +end diff --git a/lib/iiif/service.rb b/lib/iiif/service.rb index 91d229f..ed08d7c 100644 --- a/lib/iiif/service.rb +++ b/lib/iiif/service.rb @@ -8,7 +8,7 @@ module IIIF class Service include IIIF::HashBehaviours - # Anything goes! SHOULD have @id and profile, MAY have label + # Anything goes! SHOULD have id and profile, MAY have label # Consider subclassing this for typical services... def required_keys; %w{ }; end def any_type_keys; %w{ }; end @@ -106,15 +106,14 @@ def to_json(opts={}) def to_ordered_hash(opts={}) force = opts.fetch(:force, false) sort_json_ld_keys = opts.fetch(:sort_json_ld_keys, true) - unless force self.validate end export_hash = IIIF::OrderedHash.new - + boosted_keys = ['@context', 'id', 'type'] if sort_json_ld_keys - self.keys.select { |k| k.start_with?('@') }.sort!.each do |k| + self.keys.select { |k| boosted_keys.include?(k) }.sort!.each do |k| export_hash[k] = self.data[k] end end @@ -125,8 +124,8 @@ def to_ordered_hash(opts={}) force: force } self.keys.each do |k| - unless sort_json_ld_keys && k.start_with?('@') - if self.data[k].respond_to?(:to_ordered_hash) #.respond_to?(:to_ordered_hash) + unless sort_json_ld_keys && boosted_keys.include?(k) + if self.data[k].respond_to?(:to_ordered_hash) export_hash[k] = self.data[k].to_ordered_hash(sub_opts) elsif self.data[k].kind_of?(Hash) @@ -196,23 +195,28 @@ def to_ordered_hash(opts={}) export_hash end - def self.from_ordered_hash(hsh, default_klass=IIIF::OrderedHash) + def self.from_ordered_hash(hsh, default_klass=IIIF::OrderedHash, forced_class: nil) # Create a new object (new_object) - type = nil - if hsh.has_key?('@type') - type = IIIF::Service.get_descendant_class_by_jld_type(hsh['@type']) - end - new_object = type.nil? ? default_klass.new : type.new - + type = if forced_class + forced_class + elsif hsh.has_key?('type') + IIIF::Service.get_descendant_class_by_jld_type(hsh['type']) || default_klass + else + default_klass + end + new_object = type.new hsh.keys.each do |key| new_key = key.underscore == key ? key : key.underscore + if hsh[key].kind_of?(Array) new_object[new_key] = [] hsh[key].each do |member| - if new_key == 'service' + if hsh['type'] == 'Manifest' && (new_key == 'service' || new_key == 'services') new_object[new_key] << IIIF::Service.from_ordered_hash(member, IIIF::Presentation::Service) elsif new_key == 'resource' new_object[new_key] << IIIF::Service.from_ordered_hash(hsh[key], IIIF::Presentation::Resource) + elsif new_key == 'items' && hsh['type'] == 'Range' && member['type'] == 'Canvas' + new_object[new_key] << IIIF::Service.from_ordered_hash(member, forced_class: IIIF::Presentation::Start) elsif member.kind_of?(Hash) new_object[new_key] << IIIF::Service.from_ordered_hash(member) else @@ -221,7 +225,10 @@ def self.from_ordered_hash(hsh, default_klass=IIIF::OrderedHash) end end elsif new_key == 'service' + raise new_object[new_key] = IIIF::Service.from_ordered_hash(hsh[key], IIIF::Presentation::Service) + elsif new_key == 'start' + new_object[new_key] = IIIF::Service.from_ordered_hash(hsh[key], forced_class: IIIF::Presentation::Start) elsif new_key == 'resource' new_object[new_key] = IIIF::Service.from_ordered_hash(hsh[key], IIIF::Presentation::Resource) elsif hsh[key].kind_of?(Hash) diff --git a/spec/fixtures/manifests/complete_from_spec.json b/spec/fixtures/manifests/complete_from_spec.json index 1f84f5f..7de1e05 100644 --- a/spec/fixtures/manifests/complete_from_spec.json +++ b/spec/fixtures/manifests/complete_from_spec.json @@ -1,171 +1,291 @@ { - "@context": "http://iiif.io/api/presentation/2/context.json", - "@id": "http://www.example.org/iiif/book1/manifest", - "@type": "sc:Manifest", - "label": "Book 1", + "@context": "http://iiif.io/api/presentation/3/context.json", + "id": "https://example.org/iiif/book1/manifest", + "type": "Manifest", + "label": { "en": [ "Book 1" ] }, "metadata": [ { - "label": "Author", - "value": "Anne Author" + "label": { "en": [ "Author" ] }, + "value": { "none": [ "Anne Author" ] } }, { - "label": "Published", - "value": [ - { - "@value": "Paris, circa 1400", - "@language": "en" - }, + "label": { "en": [ "Published" ] }, + "value": { + "en": [ "Paris, circa 1400" ], + "fr": [ "Paris, environ 1400" ] + } + }, + { + "label": { "en": [ "Notes" ] }, + "value": { + "en": [ + "Text of note 1", + "Text of note 2" + ] + } + }, + { + "label": { "en": [ "Source" ] }, + "value": { "none": [ "From: Some Collection" ] } + } + ], + "summary": { "en": [ "Book 1, written be Anne Author, published in Paris around 1400." ] }, + + "thumbnail": [ + { + "id": "https://example.org/iiif/book1/page1/full/80,100/0/default.jpg", + "type": "Image", + "format": "image/jpeg", + "service": [ { - "@value": "Paris, environ 14eme siecle", - "@language": "fr" + "id": "https://example.org/iiif/book1/page1", + "type": "ImageService3", + "profile": "level1" } ] } ], - "description": "A longer description of this example book. It should give some real information.", - "license": "http://www.example.org/license.html", - "attribution": "Provided by Example Organization", - "service": { - "@context": "http://example.org/ns/jsonld/context.json", - "@id": "http://example.org/service/example", - "profile": "http://example.org/docs/example-service.html" + + "viewingDirection": "right-to-left", + "behavior": [ "paged" ], + "navDate": "1856-01-01T00:00:00Z", + + "rights": "https://creativecommons.org/licenses/by/4.0/", + "requiredStatement": { + "label": { "en": [ "Attribution" ] }, + "value": { "en": [ "Provided by Example Organization" ] } }, - "seeAlso": { - "@id": "http://www.example.org/library/catalog/book1.marc", - "format": "application/marc" + + "provider": [ + { + "id": "https://example.org/about", + "type": "Agent", + "label": { "en": [ "Example Organization" ] }, + "homepage": [ + { + "id": "https://example.org/", + "type": "Text", + "label": { "en": [ "Example Organization Homepage" ] }, + "format": "text/html" + } + ], + "logo": [ + { + "id": "https://example.org/service/inst1/full/max/0/default.png", + "type": "Image", + "format": "image/png", + "service": [ + { + "id": "https://example.org/service/inst1", + "type": "ImageService3", + "profile": "level2" + } + ] + } + ], + "seeAlso": [ + { + "id": "https://data.example.org/about/us.jsonld", + "type": "Dataset", + "format": "application/ld+json", + "profile": "https://schema.org/" + } + ] + } + ], + "homepage": [ + { + "id": "https://example.org/info/book1/", + "type": "Text", + "label": { "en": [ "Home page for Book 1" ] }, + "format": "text/html" + } + ], + "service": [ + { + "id": "https://example.org/service/example", + "type": "ExampleExtensionService", + "profile": "https://example.org/docs/example-service.html" + } + ], + "seeAlso": [ + { + "id": "https://example.org/library/catalog/book1.xml", + "type": "Dataset", + "format": "text/xml", + "profile": "https://example.org/profiles/bibliographic" + } + ], + "rendering": [ + { + "id": "https://example.org/iiif/book1.pdf", + "type": "Text", + "label": { "en": [ "Download as PDF" ] }, + "format": "application/pdf" + } + ], + "partOf": [ + { + "id": "https://example.org/collections/books/", + "type": "Collection" + } + ], + "start": { + "id": "https://example.org/iiif/book1/canvas/p2", + "type": "Canvas" }, - "within": "http://www.example.org/collections/books/", - "sequences": [ + + "services": [ + { + "@id": "https://example.org/iiif/auth/login", + "@type": "AuthCookieService1", + "profile": "http://iiif.io/api/auth/1/login", + "label": "Login to Example Institution", + "service": [ + { + "@id": "https://example.org/iiif/auth/token", + "@type": "AuthTokenService1", + "profile": "http://iiif.io/api/auth/1/token" + } + ] + } + ], + + "items": [ { - "@id": "http://www.example.org/iiif/book1/sequence/normal", - "@type": "sc:Sequence", - "label": "Current Page Order", - "viewingDirection": "left-to-right", - "viewingHint": "paged", - "canvases": [ + "id": "https://example.org/iiif/book1/canvas/p1", + "type": "Canvas", + "label": { "none": [ "p. 1" ] }, + "height": 1000, + "width": 750, + "items": [ { - "@id": "http://www.example.org/iiif/book1/canvas/p1", - "@type": "sc:Canvas", - "label": "p. 1", - "height": 1000, - "width": 750, - "images": [ + "id": "https://example.org/iiif/book1/page/p1/1", + "type": "AnnotationPage", + "items": [ { - "@type": "oa:Annotation", - "motivation": "sc:painting", - "resource": { - "@id": "http://www.example.org/iiif/book1/res/page1.jpg", - "@type": "dctypes:Image", + "id": "https://example.org/iiif/book1/annotation/p0001-image", + "type": "Annotation", + "motivation": "painting", + "body": { + "id": "https://example.org/iiif/book1/page1/full/max/0/default.jpg", + "type": "Image", "format": "image/jpeg", - "service": { - "@context": "http://iiif.io/api/image/2/context.json", - "@id": "http://www.example.org/images/book1-page1", - "profile": "http://iiif.io/api/image/2/level1.json" - }, + "service": [ + { + "id": "https://example.org/iiif/book1/page1", + "type": "ImageService3", + "profile": "level2", + "service": [ + { + "@id": "https://example.org/iiif/auth/login", + "@type": "AuthCookieService1" + } + ] + } + ], "height": 2000, "width": 1500 }, - "on": "http://www.example.org/iiif/book1/canvas/p1" - } - ], - "otherContent": [ - { - "@id": "http://www.example.org/iiif/book1/list/p1", - "@type": "sc:AnnotationList" + "target": "https://example.org/iiif/book1/canvas/p1" } ] - }, + } + ], + "annotations": [ + { + "id": "https://example.org/iiif/book1/comments/p1/1", + "type": "AnnotationPage" + } + ] + }, + { + "id": "https://example.org/iiif/book1/canvas/p2", + "type": "Canvas", + "label": { "none": [ "p. 2" ] }, + "height": 1000, + "width": 750, + "items": [ { - "@id": "http://www.example.org/iiif/book1/canvas/p2", - "@type": "sc:Canvas", - "label": "p. 2", - "height": 1000, - "width": 750, - "images": [ + "id": "https://example.org/iiif/book1/page/p2/1", + "type": "AnnotationPage", + "items": [ { - "@type": "oa:Annotation", - "motivation": "sc:painting", - "resource": { - "@id": "http://www.example.org/images/book1-page2/full/1500,2000/0/default.jpg", - "@type": "dctypes:Image", + "id": "https://example.org/iiif/book1/annotation/p0002-image", + "type": "Annotation", + "motivation": "painting", + "body": { + "id": "https://example.org/iiif/book1/page2/full/max/0/default.jpg", + "type": "Image", "format": "image/jpeg", + "service": [ + { + "id": "https://example.org/iiif/book1/page2", + "type": "ImageService3", + "profile": "level2" + } + ], "height": 2000, - "width": 1500, - "service": { - "@context": "http://iiif.io/api/image/2/context.json", - "@id": "http://www.example.org/images/book1-page2", - "profile": "http://iiif.io/api/image/2/level1.json", - "height": 8000, - "width": 6000, - "tiles": [ - { - "width": 512, - "scaleFactors": [ - 1, - 2, - 4, - 8, - 16 - ] - } - ] - } + "width": 1500 }, - "on": "http://www.example.org/iiif/book1/canvas/p2" - } - ], - "otherContent": [ - { - "@id": "http://www.example.org/iiif/book1/list/p2", - "@type": "sc:AnnotationList" + "target": "https://example.org/iiif/book1/canvas/p2" } ] - }, + } + ] + } + ], + + "structures": [ + { + "id": "https://example.org/iiif/book1/range/r0", + "type": "Range", + "label": { "en": [ "Table of Contents" ] }, + "items": [ { - "@id": "http://www.example.org/iiif/book1/canvas/p3", - "@type": "sc:Canvas", - "label": "p. 3", - "height": 1000, - "width": 750, - "images": [ + "id": "https://example.org/iiif/book1/range/r1", + "type": "Range", + "label": { "en": [ "Introduction" ] }, + "supplementary": { + "id": "https://example.org/iiif/book1/annocoll/introTexts", + "type": "AnnotationCollection" + }, + "items": [ { - "@type": "oa:Annotation", - "motivation": "sc:painting", - "resource": { - "@id": "http://www.example.org/iiif/book1/res/page3.jpg", - "@type": "dctypes:Image", - "format": "image/jpeg", - "service": { - "@context": "http://iiif.io/api/image/2/context.json", - "@id": "http://www.example.org/images/book1-page3", - "profile": "http://iiif.io/api/image/2/level1.json" - }, - "height": 2000, - "width": 1500 - }, - "on": "http://www.example.org/iiif/book1/canvas/p3" - } - ], - "otherContent": [ + "id": "https://example.org/iiif/book1/canvas/p1", + "type": "Canvas" + }, { - "@id": "http://www.example.org/iiif/book1/list/p3", - "@type": "sc:AnnotationList" + "type": "SpecificResource", + "source": "https://example.org/iiif/book1/canvas/p2", + "selector": { + "type": "FragmentSelector", + "value": "xywh=0,0,750,300" + } } ] } ] } ], - "structures": [ + + "annotations": [ { - "@id": "http://www.example.org/iiif/book1/range/r1", - "@type": "sc:Range", - "label": "Introduction", - "canvases": [ - "http://www.example.org/iiif/book1/canvas/p1", - "http://www.example.org/iiif/book1/canvas/p2", - "http://www.example.org/iiif/book1/canvas/p3#xywh=0,0,750,300" + "id": "https://example.org/iiif/book1/page/manifest/1", + "type": "AnnotationPage", + "items": [ + { + "id": "https://example.org/iiif/book1/page/manifest/a1", + "type": "Annotation", + "motivation": "commenting", + "body": { + "type": "TextualBody", + "language": "en", + "value": "I love this manifest!" + }, + "target": "https://example.org/iiif/book1/manifest" + } ] } ] -} +} \ No newline at end of file diff --git a/spec/fixtures/manifests/minimal.json b/spec/fixtures/manifests/minimal.json index bc44efa..d07f139 100644 --- a/spec/fixtures/manifests/minimal.json +++ b/spec/fixtures/manifests/minimal.json @@ -1,40 +1,40 @@ { - "@context": "http://iiif.io/api/presentation/2/context.json", - "@type": "sc:Manifest", - "@id": "http://www.example.org/iiif/book1/manifest", - "label": "Book 1", - "sequences": [ + "@context": "http://iiif.io/api/presentation/3/context.json", + "id": "https://example.org/iiif/book1/manifest", + "type": "Manifest", + "label": { "en": [ "Book 1" ] }, + "viewingDirection": "right-to-left", + "behavior": [ "paged" ], + "navDate": "1856-01-01T00:00:00Z", + "items": [ { - "@id": "http://www.example.org/iiif/book1/sequence/normal", - "label": "Current Page Order", - "canvases": [ + "id": "https://example.org/iiif/book1/canvas/p1", + "type": "Canvas", + "label": { "none": [ "p. 1" ] }, + "height": 1000, + "width": 750, + "items": [ { - "@id": "http://www.example.org/iiif/book1/canvas/p1", - "@type": "sc:Canvas", - "label": "p. 1", - "height": 1000, - "width": 750, - "images": [ + "id": "https://example.org/iiif/book1/page/p1/1", + "type": "AnnotationPage", + "items": [ { - "@type": "oa:Annotation", - "motivation": "sc:painting", - "on": "http://www.example.org/iiif/book1/canvas/p1", - "resource": { - "@id": "http://www.example.org/iiif/book1/res/page1.jpg", - "@type": "dctypes:Image", + "id": "https://example.org/iiif/book1/annotation/p0001-image", + "type": "Annotation", + "motivation": "painting", + "body": { + "id": "https://example.org/iiif/book1/page1/full/max/0/default.jpg", + "type": "Image", "format": "image/jpeg", + ] "height": 2000, - "width": 1500, - "service": { - "@context": "http://iiif.io/api/image/2/context.json", - "@id": "http://www.example.org/images/book1-page1", - "profile": "http://iiif.io/api/image/2/level1.json" - } - } + "width": 1500 + }, + "target": "https://example.org/iiif/book1/canvas/p1" } ] } ] } ] -} +} \ No newline at end of file diff --git a/spec/fixtures/manifests/service_only.json b/spec/fixtures/manifests/service_only.json index 188947f..49cb07c 100644 --- a/spec/fixtures/manifests/service_only.json +++ b/spec/fixtures/manifests/service_only.json @@ -1,11 +1,13 @@ { - "@context": "http://iiif.io/api/presentation/2/context.json", - "@type": "sc:Manifest", - "@id": "http://www.example.org/iiif/book1/manifest", + "@context": "http://iiif.io/api/presentation/3/context.json", + "type": "Manifest", + "id": "http://www.example.org/iiif/book1/manifest", "label": "Book 1", - "service": { - "@context": "http://example.org/ns/jsonld/context.json", - "@id": "http://example.org/service/example", - "profile": "http://example.org/docs/example-service.html" - } + "service": [ + { + "id": "https://example.org/service/example", + "type": "ExampleExtensionService", + "profile": "https://example.org/docs/example-service.html" + } + ] } diff --git a/spec/fixtures/vcr_cassettes/pul_loris_cassette.json b/spec/fixtures/vcr_cassettes/pul_loris_cassette.json index b25b518..6da83ae 100644 --- a/spec/fixtures/vcr_cassettes/pul_loris_cassette.json +++ b/spec/fixtures/vcr_cassettes/pul_loris_cassette.json @@ -1 +1 @@ -{"http_interactions":[{"request":{"method":"get","uri":"https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2/info.json","body":{"encoding":"US-ASCII","string":""},"headers":{"User-Agent":["Faraday v1.0.1"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"date":["Wed, 17 Jun 2020 19:38:19 GMT"],"server":["Apache/2.4.18 (Ubuntu)"],"link":[";rel=\"profile\",;rel=\"http://www.w3.org/ns/json-ld#context\";type=\"application/ld+json\""],"access-control-allow-origin":["*"],"access-control-allow-methods":["GET"],"access-control-allow-headers":["X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding"],"last-modified":["Sat, 25 Jan 2020 18:37:32 GMT"],"content-length":["753"],"content-type":["application/json"]},"body":{"encoding":"UTF-8","string":"{\"profile\": [\"http://iiif.io/api/image/2/level2.json\", {\"supports\": [\"canonicalLinkHeader\", \"profileLinkHeader\", \"mirroring\", \"rotationArbitrary\", \"regionSquare\", \"sizeAboveFull\"], \"qualities\": [\"default\", \"bitonal\", \"gray\", \"color\"], \"formats\": [\"jpg\", \"png\", \"gif\", \"webp\"]}], \"tiles\": [{\"width\": 1024, \"scaleFactors\": [1, 2, 4, 8, 16, 32]}], \"protocol\": \"http://iiif.io/api/image\", \"sizes\": [{\"width\": 96, \"height\": 225}, {\"width\": 191, \"height\": 450}, {\"width\": 381, \"height\": 900}, {\"width\": 762, \"height\": 1800}, {\"width\": 1524, \"height\": 3600}, {\"width\": 3047, \"height\": 7200}], \"height\": 7200, \"width\": 3047, \"@context\": \"http://iiif.io/api/image/2/context.json\", \"@id\": \"https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2\"}"},"http_version":null},"recorded_at":"Wed, 17 Jun 2020 19:38:19 GMT"},{"request":{"method":"get","uri":"https://libimages.princeton.edu/loris/xxxx%2F4612422%2F00000001.jp2/info.json","body":{"encoding":"US-ASCII","string":""},"headers":{"User-Agent":["Faraday v1.0.1"]}},"response":{"status":{"code":404,"message":"NOT FOUND"},"headers":{"date":["Wed, 17 Jun 2020 19:38:20 GMT"],"server":["Apache/2.4.18 (Ubuntu)"],"link":[";rel=\"profile\""],"access-control-allow-origin":["*"],"access-control-allow-methods":["GET"],"access-control-allow-headers":["X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding"],"content-length":["86"],"content-type":["text/plain"]},"body":{"encoding":"UTF-8","string":"Not Found: Source image not found for identifier: xxxx%2F4612422%2F00000001.jp2. (404)"},"http_version":null},"recorded_at":"Wed, 17 Jun 2020 19:38:20 GMT"}],"recorded_with":"VCR 5.1.0"} \ No newline at end of file +{"http_interactions":[{"request":{"method":"get","uri":"https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2/info.json","body":{"encoding":"US-ASCII","string":""},"headers":{"User-Agent":["Faraday v1.0.1"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"date":["Wed, 17 Jun 2020 19:38:19 GMT"],"server":["Apache/2.4.18 (Ubuntu)"],"link":[";rel=\"profile\",;rel=\"http://www.w3.org/ns/json-ld#context\";type=\"application/ld+json\""],"access-control-allow-origin":["*"],"access-control-allow-methods":["GET"],"access-control-allow-headers":["X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding"],"last-modified":["Sat, 25 Jan 2020 18:37:32 GMT"],"content-length":["753"],"content-type":["application/json"]},"body":{"encoding":"UTF-8","string":"{\"profile\": [\"http://iiif.io/api/image/2/level2.json\", {\"supports\": [\"canonicalLinkHeader\", \"profileLinkHeader\", \"mirroring\", \"rotationArbitrary\", \"regionSquare\", \"sizeAboveFull\"], \"qualities\": [\"default\", \"bitonal\", \"gray\", \"color\"], \"formats\": [\"jpg\", \"png\", \"gif\", \"webp\"]}], \"tiles\": [{\"width\": 1024, \"scaleFactors\": [1, 2, 4, 8, 16, 32]}], \"protocol\": \"http://iiif.io/api/image\", \"sizes\": [{\"width\": 96, \"height\": 225}, {\"width\": 191, \"height\": 450}, {\"width\": 381, \"height\": 900}, {\"width\": 762, \"height\": 1800}, {\"width\": 1524, \"height\": 3600}, {\"width\": 3047, \"height\": 7200}], \"height\": 7200, \"width\": 3047, \"@context\": \"http://iiif.io/api/image/2/context.json\", \"id\": \"https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2\"}"},"http_version":null},"recorded_at":"Wed, 17 Jun 2020 19:38:19 GMT"},{"request":{"method":"get","uri":"https://libimages.princeton.edu/loris/xxxx%2F4612422%2F00000001.jp2/info.json","body":{"encoding":"US-ASCII","string":""},"headers":{"User-Agent":["Faraday v1.0.1"]}},"response":{"status":{"code":404,"message":"NOT FOUND"},"headers":{"date":["Wed, 17 Jun 2020 19:38:20 GMT"],"server":["Apache/2.4.18 (Ubuntu)"],"link":[";rel=\"profile\""],"access-control-allow-origin":["*"],"access-control-allow-methods":["GET"],"access-control-allow-headers":["X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding"],"content-length":["86"],"content-type":["text/plain"]},"body":{"encoding":"UTF-8","string":"Not Found: Source image not found for identifier: xxxx%2F4612422%2F00000001.jp2. (404)"},"http_version":null},"recorded_at":"Wed, 17 Jun 2020 19:38:20 GMT"}],"recorded_with":"VCR 5.1.0"} \ No newline at end of file diff --git a/spec/integration/iiif/presentation/image_resource_spec.rb b/spec/integration/iiif/presentation/image_resource_spec.rb index e19a8af..e1463e6 100644 --- a/spec/integration/iiif/presentation/image_resource_spec.rb +++ b/spec/integration/iiif/presentation/image_resource_spec.rb @@ -1,4 +1,4 @@ -describe IIIF::Presentation::ImageResource do +RSpec.describe IIIF::Presentation::ImageResource do vcr_options = { cassette_name: 'pul_loris_cassette', record: :new_episodes, @@ -30,27 +30,27 @@ it 'when copy_info is false' do opts = { service_id: valid_service_id } resource = described_class.create_image_api_image_resource(opts) - # expect(resource['@context']).to eq 'http://iiif.io/api/presentation/2/context.json' + # expect(resource['@context']).to eq 'http://iiif.io/api/presentation/3/context.json' # @context is only added when we call to_json... - expect(resource['@id']).to eq 'https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2/full/!200,200/0/default.jpg' - expect(resource['@type']).to eq 'dctypes:Image' + expect(resource['id']).to eq 'https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2/full/!200,200/0/default.jpg' + expect(resource['type']).to eq 'dctypes:Image' expect(resource.format).to eq "image/jpeg" expect(resource.width).to eq 3047 expect(resource.height).to eq 7200 expect(resource.service['@context']).to eq 'http://iiif.io/api/image/2/context.json' - expect(resource.service['@id']).to eq 'https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2' + expect(resource.service['id']).to eq 'https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2' expect(resource.service['profile']).to eq 'http://iiif.io/api/image/2/level2.json' end it 'copies over all the info (when copy_info is true)' do opts = { service_id: valid_service_id, copy_info: true } resource = described_class.create_image_api_image_resource(opts) - expect(resource['@id']).to eq 'https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2/full/!200,200/0/default.jpg' - expect(resource['@type']).to eq 'dctypes:Image' + expect(resource['id']).to eq 'https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2/full/!200,200/0/default.jpg' + expect(resource['type']).to eq 'dctypes:Image' expect(resource.format).to eq "image/jpeg" expect(resource.width).to eq 3047 expect(resource.height).to eq 7200 expect(resource.service['@context']).to eq 'http://iiif.io/api/image/2/context.json' - expect(resource.service['@id']).to eq 'https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2' + expect(resource.service['id']).to eq 'https://libimages.princeton.edu/loris/pudl0001%2F4612422%2F00000001.jp2' expect(resource.service['profile']).to eq [ 'http://iiif.io/api/image/2/level2.json', { @@ -83,7 +83,7 @@ opts = { service_id: valid_service_id, resource_id: r_id} resource = described_class.create_image_api_image_resource(opts) - expect(resource['@id']).to eq r_id + expect(resource['id']).to eq r_id end it ':width' do width = 42 diff --git a/spec/integration/iiif/service_spec.rb b/spec/integration/iiif/service_spec.rb index 48e085a..ade46bd 100644 --- a/spec/integration/iiif/service_spec.rb +++ b/spec/integration/iiif/service_spec.rb @@ -3,29 +3,29 @@ let(:fixtures_dir) { File.join(File.dirname(__FILE__), '../../fixtures') } let(:manifest_from_spec_path) { File.join(fixtures_dir, 'manifests/complete_from_spec.json') } - describe 'self.parse' do + describe '.parse' do it 'works from a file' do s = described_class.parse(manifest_from_spec_path) - expect(s['label']).to eq 'Book 1' + expect(s['label']).to eq("en" => ["Book 1"]) end it 'works from a string of JSON' do file = File.open(manifest_from_spec_path, 'rb') json_string = file.read file.close s = described_class.parse(json_string) - expect(s['label']).to eq 'Book 1' + expect(s['label']).to eq("en" => ["Book 1"]) end describe 'works from a hash' do it 'plain old' do h = JSON.parse(IO.read(manifest_from_spec_path)) s = described_class.parse(h) - expect(s['label']).to eq 'Book 1' + expect(s['label']).to eq("en" => ["Book 1"]) end it 'IIIF::OrderedHash' do h = JSON.parse(IO.read(manifest_from_spec_path)) oh = IIIF::OrderedHash[h] s = described_class.parse(oh) - expect(s['label']).to eq 'Book 1' + expect(s['label']).to eq("en" => ["Book 1"]) end end it 'turns camels to snakes' do @@ -37,53 +37,302 @@ describe 'self#from_ordered_hash' do subject(:parsed) { described_class.from_ordered_hash(fixture) } - let(:fixture) { JSON.parse('{ - "@context": "http://iiif.io/api/presentation/2/context.json", - "@id": "http://example.com/manifest", - "@type": "sc:Manifest", - "label": "My Manifest", - "service": { - "@context": "http://iiif.io/api/image/2/context.json", - "@id":"http://www.example.org/images/book1-page1", - "profile":"http://iiif.io/api/image/2/profiles/level2.json" - }, - "some_other_thing": { - "foo" : "bar" + + let(:fixture) do + JSON.parse( + <<~JSON + { + "@context": "http://iiif.io/api/presentation/3/context.json", + "id": "https://example.org/iiif/book1/manifest", + "type": "Manifest", + "label": { "en": [ "Book 1" ] }, + "metadata": [ + { + "label": { "en": [ "Author" ] }, + "value": { "none": [ "Anne Author" ] } + }, + { + "label": { "en": [ "Published" ] }, + "value": { + "en": [ "Paris, circa 1400" ], + "fr": [ "Paris, environ 1400" ] + } + }, + { + "label": { "en": [ "Notes" ] }, + "value": { + "en": [ + "Text of note 1", + "Text of note 2" + ] + } + }, + { + "label": { "en": [ "Source" ] }, + "value": { "none": [ "From: Some Collection" ] } + } + ], + "summary": { "en": [ "Book 1, written be Anne Author, published in Paris around 1400." ] }, + + "thumbnail": [ + { + "id": "https://example.org/iiif/book1/page1/full/80,100/0/default.jpg", + "type": "Image", + "format": "image/jpeg", + "service": [ + { + "id": "https://example.org/iiif/book1/page1", + "type": "ImageService3", + "profile": "level1" + } + ] + } + ], + + "viewingDirection": "right-to-left", + "behavior": [ "paged" ], + "navDate": "1856-01-01T00:00:00Z", + + "rights": "https://creativecommons.org/licenses/by/4.0/", + "requiredStatement": { + "label": { "en": [ "Attribution" ] }, + "value": { "en": [ "Provided by Example Organization" ] } }, - "seeAlso": { - "@id": "http://www.example.org/library/catalog/book1.marc", - "format": "application/marc" + + "provider": [ + { + "id": "https://example.org/about", + "type": "Agent", + "label": { "en": [ "Example Organization" ] }, + "homepage": [ + { + "id": "https://example.org/", + "type": "Text", + "label": { "en": [ "Example Organization Homepage" ] }, + "format": "text/html" + } + ], + "logo": [ + { + "id": "https://example.org/service/inst1/full/max/0/default.png", + "type": "Image", + "format": "image/png", + "service": [ + { + "id": "https://example.org/service/inst1", + "type": "ImageService3", + "profile": "level2" + } + ] + } + ], + "seeAlso": [ + { + "id": "https://data.example.org/about/us.jsonld", + "type": "Dataset", + "format": "application/ld+json", + "profile": "https://schema.org/" + } + ] + } + ], + "homepage": [ + { + "id": "https://example.org/info/book1/", + "type": "Text", + "label": { "en": [ "Home page for Book 1" ] }, + "format": "text/html" + } + ], + "service": [ + { + "id": "https://example.org/service/example", + "type": "ExampleExtensionService", + "profile": "https://example.org/docs/example-service.html" + } + ], + "seeAlso": [ + { + "id": "https://example.org/library/catalog/book1.xml", + "type": "Dataset", + "format": "text/xml", + "profile": "https://example.org/profiles/bibliographic" + } + ], + "rendering": [ + { + "id": "https://example.org/iiif/book1.pdf", + "type": "Text", + "label": { "en": [ "Download as PDF" ] }, + "format": "application/pdf" + } + ], + "partOf": [ + { + "id": "https://example.org/collections/books/", + "type": "Collection" + } + ], + "start": { + "id": "https://example.org/iiif/book1/canvas/p2", + "type": "Canvas" }, - "sequences": [ + + "services": [ { - "@id":"http://www.example.org/iiif/book1/sequence/normal", - "@type":"sc:Sequence", - "label":"Current Page Order", - - "viewingDirection":"left-to-right", - "viewingHint":"paged", - "startCanvas": "http://www.example.org/iiif/book1/canvas/p2", - - "canvases": [ + "@id": "https://example.org/iiif/auth/login", + "@type": "AuthCookieService1", + "profile": "http://iiif.io/api/auth/1/login", + "label": "Login to Example Institution", + "service": [ { - "@id": "http://example.com/canvas", - "@type": "sc:Canvas", - "width": 10, - "height": 20, - "label": "My Canvas", - "otherContent": [ + "@id": "https://example.org/iiif/auth/token", + "@type": "AuthTokenService1", + "profile": "http://iiif.io/api/auth/1/token" + } + ] + } + ], + + "items": [ + { + "id": "https://example.org/iiif/book1/canvas/p1", + "type": "Canvas", + "label": { "none": [ "p. 1" ] }, + "height": 1000, + "width": 750, + "items": [ + { + "id": "https://example.org/iiif/book1/page/p1/1", + "type": "AnnotationPage", + "items": [ { - "@id": "http://example.com/content", - "@type":"sc:AnnotationList", - "motivation": "sc:painting" + "id": "https://example.org/iiif/book1/annotation/p0001-image", + "type": "Annotation", + "motivation": "painting", + "body": { + "id": "https://example.org/iiif/book1/page1/full/max/0/default.jpg", + "type": "Image", + "format": "image/jpeg", + "service": [ + { + "id": "https://example.org/iiif/book1/page1", + "type": "ImageService3", + "profile": "level2", + "service": [ + { + "@id": "https://example.org/iiif/auth/login", + "@type": "AuthCookieService1" + } + ] + } + ], + "height": 2000, + "width": 1500 + }, + "target": "https://example.org/iiif/book1/canvas/p1" + } + ] + } + ], + "annotations": [ + { + "id": "https://example.org/iiif/book1/comments/p1/1", + "type": "AnnotationPage" + } + ] + }, + { + "id": "https://example.org/iiif/book1/canvas/p2", + "type": "Canvas", + "label": { "none": [ "p. 2" ] }, + "height": 1000, + "width": 750, + "items": [ + { + "id": "https://example.org/iiif/book1/page/p2/1", + "type": "AnnotationPage", + "items": [ + { + "id": "https://example.org/iiif/book1/annotation/p0002-image", + "type": "Annotation", + "motivation": "painting", + "body": { + "id": "https://example.org/iiif/book1/page2/full/max/0/default.jpg", + "type": "Image", + "format": "image/jpeg", + "service": [ + { + "id": "https://example.org/iiif/book1/page2", + "type": "ImageService3", + "profile": "level2" + } + ], + "height": 2000, + "width": 1500 + }, + "target": "https://example.org/iiif/book1/canvas/p2" } ] } ] } + ], + "structures": [ + { + "id": "https://example.org/iiif/book1/range/r0", + "type": "Range", + "label": { "en": [ "Table of Contents" ] }, + "items": [ + { + "id": "https://example.org/iiif/book1/range/r1", + "type": "Range", + "label": { "en": [ "Introduction" ] }, + "supplementary": { + "id": "https://example.org/iiif/book1/annocoll/introTexts", + "type": "AnnotationCollection" + }, + "items": [ + { + "id": "https://example.org/iiif/book1/canvas/p1", + "type": "Canvas" + }, + { + "type": "SpecificResource", + "source": "https://example.org/iiif/book1/canvas/p2", + "selector": { + "type": "FragmentSelector", + "value": "xywh=0,0,750,300" + } + } + ] + } + ] + } + ], + "annotations": [ + { + "id": "https://example.org/iiif/book1/page/manifest/1", + "type": "AnnotationPage", + "items": [ + { + "id": "https://example.org/iiif/book1/page/manifest/a1", + "type": "Annotation", + "motivation": "commenting", + "body": { + "type": "TextualBody", + "language": "en", + "value": "I love this manifest!" + }, + "target": "https://example.org/iiif/book1/manifest" + } + ] + } ] - }') - } + } + JSON + ) + end it 'doesn\'t raise a NoMethodError when we check the keys' do expect { parsed }.to_not raise_error end @@ -92,17 +341,13 @@ expect(parsed).to be_a IIIF::Presentation::Manifest end - it 'turns keys without "@type" into an OrderedHash' do - expect(parsed['some_other_thing']).to be_a IIIF::OrderedHash + it 'turns keys without "type" into an OrderedHash' do + parsed = described_class.from_ordered_hash(fixture) + expect(parsed['summary'].class).to be IIIF::OrderedHash end it 'turns services into Services' do - expect(parsed['service']).to be_a IIIF::Presentation::Service - end - - it 'works with arrays of services' do - fixture['service'] = [fixture['service']] - expect(parsed['service'].first).to be_a IIIF::Presentation::Service + expect(parsed['service']).to all be_kind_of IIIF::Presentation::Service end it 'round-trips' do @@ -117,23 +362,17 @@ expect(from_file.to_ordered_hash.to_a - parsed.to_ordered_hash.to_a).to eq [] end - it 'turns each memeber of "sequences" into an instance of Sequence' do - expect(parsed['sequences']).to all be_a IIIF::Presentation::Sequence - end - - it 'turns each member of sequences/canvaes in an instance of Canvas' do - parsed['sequences'].each do |s| - expect(s.canvases).to all be_a IIIF::Presentation::Canvas - end + it 'turns each member of items into an instance of Canvas' do + expect(parsed['items']).to all be_a IIIF::Presentation::Canvas end it 'turns the keys into snakes' do - expect(parsed.has_key?('seeAlso')).to be false - expect(parsed.has_key?('see_also')).to be true + expect(parsed.has_key?('seeAlso')).to be_falsey + expect(parsed.has_key?('see_also')).to be_truthy end it 'copies over plain-old key-values' do - expect(parsed['label']).to eq 'My Manifest' + expect(parsed['rights']).to eq 'https://creativecommons.org/licenses/by/4.0/' end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7465a83..555a2ea 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,8 @@ require 'iiif/presentation' require 'simplecov' require 'coveralls' +require 'debug' + Dir["#{File.dirname(__FILE__)}/unit/iiif/presentation/shared_examples/*.rb"].each do |f| require f end diff --git a/spec/unit/iiif/presentation/abstract_resource_spec.rb b/spec/unit/iiif/presentation/abstract_resource_spec.rb index ec41361..93558a0 100644 --- a/spec/unit/iiif/presentation/abstract_resource_spec.rb +++ b/spec/unit/iiif/presentation/abstract_resource_spec.rb @@ -1,10 +1,4 @@ -require 'active_support/inflector' -require 'json' -require File.join(File.dirname(__FILE__), '../../../../lib/iiif/hash_behaviours') - - -describe IIIF::Presentation::AbstractResource do - +RSpec.describe IIIF::Presentation::AbstractResource do let(:fixtures_dir) { File.join(File.dirname(__FILE__), '../../../fixtures') } let(:manifest_from_spec_path) { File.join(fixtures_dir, 'manifests/complete_from_spec.json') } let(:abstract_resource_subclass) do @@ -12,19 +6,19 @@ include IIIF::HashBehaviours def initialize(hsh={}) - hsh['@type'] = 'a:SubClass' unless hsh.has_key?('@type') + hsh['type'] = 'a:SubClass' unless hsh.has_key?('type') super(hsh) end def required_keys - super + %w{ @id } + super + %w{ id } end end end subject do instance = abstract_resource_subclass.new - instance['@id'] = 'http://example.com/prefix/manifest/123' + instance['id'] = 'http://example.com/prefix/manifest/123' instance end @@ -32,13 +26,15 @@ def required_keys it 'raises an error if you try to instantiate AbstractResource' do expect { IIIF::Presentation::AbstractResource.new }.to raise_error(RuntimeError) end - it 'sets @type' do - expect(subject['@type']).to eq 'a:SubClass' + + it 'sets type' do + expect(subject['type']).to eq 'a:SubClass' end + it 'can take any old hash' do hsh = JSON.parse(IO.read(manifest_from_spec_path)) new_instance = abstract_resource_subclass.new(hsh) - expect(new_instance['label']).to eq 'Book 1' + expect(new_instance['label']).to eq("en"=>["Book 1"]) end end @@ -93,7 +89,7 @@ def required_keys describe '#required_keys' do it 'accumulates' do - expect(subject.required_keys).to eq %w{ @type @id } + expect(subject.required_keys).to eq %w{ type id } end end @@ -119,7 +115,7 @@ def required_keys describe 'runs the validations' do # Test this here because there's nothing to validate on the superclass (Subject) let(:error) { IIIF::Presentation::MissingRequiredKeyError } - before(:each) { subject.delete('@id') } + before(:each) { subject.delete('id') } it 'raises exceptions' do expect { subject.to_ordered_hash }.to raise_error error end diff --git a/spec/unit/iiif/presentation/canvas_spec.rb b/spec/unit/iiif/presentation/canvas_spec.rb index fb65955..27eae43 100644 --- a/spec/unit/iiif/presentation/canvas_spec.rb +++ b/spec/unit/iiif/presentation/canvas_spec.rb @@ -2,9 +2,9 @@ let(:fixed_values) do { - "@context" => "http://iiif.io/api/presentation/2/context.json", - "@id" => "http://www.example.org/iiif/book1/canvas/p1", - "@type" => "sc:Canvas", + "@context" => "http://iiif.io/api/presentation/3/context.json", + "id" => "http://www.example.org/iiif/book1/canvas/p1", + "type" => "Canvas", "label" => "p. 1", "height" => 1000, "width" => 750, @@ -15,8 +15,8 @@ describe '#initialize' do - it 'sets @type' do - expect(subject['@type']).to eq 'sc:Canvas' + it 'sets type' do + expect(subject['type']).to eq 'Canvas' end end diff --git a/spec/unit/iiif/presentation/collection_spec.rb b/spec/unit/iiif/presentation/collection_spec.rb index 4fc1a68..e2fcf55 100644 --- a/spec/unit/iiif/presentation/collection_spec.rb +++ b/spec/unit/iiif/presentation/collection_spec.rb @@ -2,26 +2,26 @@ let(:fixed_values) do { - '@context' => 'http://iiif.io/api/presentation/2/context.json', - '@id' => 'http://example.org/iiif/collection/top', - '@type' => 'sc:Collection', + '@context' => 'http://iiif.io/api/presentation/3/context.json', + 'id' => 'http://example.org/iiif/collection/top', + 'type' => 'Collection', 'label' => 'Top Level Collection for Example Organization', 'description' => 'Description of Collection', 'attribution' => 'Provided by Example Organization', 'collections' => [ - { '@id' => 'http://example.org/iiif/collection/part1', - '@type' => 'sc:Collection', + { 'id' => 'http://example.org/iiif/collection/part1', + 'type' => 'Collection', 'label' => 'Sub Collection 1' }, - { '@id' => 'http://example.org/iiif/collection/part2', - '@type' => 'sc:Collection', + { 'id' => 'http://example.org/iiif/collection/part2', + 'type' => 'Collection', 'label' => 'Sub Collection 2' } ], 'manifests' => [ - { '@id' => 'http://example.org/iiif/book1/manifest', - '@type' => 'sc:Manifest', + { 'id' => 'http://example.org/iiif/book1/manifest', + 'type' => 'Manifest', 'label' => 'Book 1' } ] @@ -29,8 +29,8 @@ end describe '#initialize' do - it 'sets @type to sc:Collection by default' do - expect(subject['@type']).to eq 'sc:Collection' + it 'sets type to Collection by default' do + expect(subject['type']).to eq 'Collection' end end diff --git a/spec/unit/iiif/presentation/image_resource_spec.rb b/spec/unit/iiif/presentation/image_resource_spec.rb index 70013e8..4f5a5ec 100644 --- a/spec/unit/iiif/presentation/image_resource_spec.rb +++ b/spec/unit/iiif/presentation/image_resource_spec.rb @@ -1,8 +1,8 @@ describe IIIF::Presentation::ImageResource do describe '#initialize' do - it 'sets @type to dctypes:Image' do - expect(subject['@type']).to eq 'dctypes:Image' + it 'sets type to dctypes:Image' do + expect(subject['type']).to eq 'dctypes:Image' end end diff --git a/spec/unit/iiif/presentation/layer_spec.rb b/spec/unit/iiif/presentation/layer_spec.rb index e9da757..da7c67d 100644 --- a/spec/unit/iiif/presentation/layer_spec.rb +++ b/spec/unit/iiif/presentation/layer_spec.rb @@ -2,9 +2,9 @@ let(:fixed_values) do { - '@context' => 'http://iiif.io/api/presentation/2/context.json', - '@id' => 'http://www.example.org/iiif/book1/layer/transcription', - '@type' => 'sc:Layer', + '@context' => 'http://iiif.io/api/presentation/3/context.json', + 'id' => 'http://www.example.org/iiif/book1/layer/transcription', + 'type' => 'Layer', 'label' => 'Diplomatic Transcription', 'otherContent' => [ 'http://www.example.org/iiif/book1/list/l1', @@ -17,8 +17,8 @@ describe '#initialize' do - it 'sets @type' do - expect(subject['@type']).to eq 'sc:Layer' + it 'sets type' do + expect(subject['type']).to eq 'Layer' end end diff --git a/spec/unit/iiif/presentation/manifest_spec.rb b/spec/unit/iiif/presentation/manifest_spec.rb index 4aa60a8..d128b89 100644 --- a/spec/unit/iiif/presentation/manifest_spec.rb +++ b/spec/unit/iiif/presentation/manifest_spec.rb @@ -3,7 +3,7 @@ let(:subclass_subject) do Class.new(IIIF::Presentation::Manifest) do def initialize(hsh={}) - hsh = { '@type' => 'a:SubClass' } + hsh = { 'type' => 'a:SubClass' } super(hsh) end end @@ -17,10 +17,10 @@ def initialize(hsh={}) 'label' => 'Book 1', 'description' => 'A longer description of this example book. It should give some real information.', 'thumbnail' => { - '@id' => 'http://www.example.org/images/book1-page1/full/80,100/0/default.jpg', + 'id' => 'http://www.example.org/images/book1-page1/full/80,100/0/default.jpg', 'service'=> { '@context' => 'http://iiif.io/api/image/2/context.json', - '@id' => 'http://www.example.org/images/book1-page1', + 'id' => 'http://www.example.org/images/book1-page1', 'profile' => 'http://iiif.io/api/image/2/level1.json' } }, @@ -30,11 +30,11 @@ def initialize(hsh={}) 'see_also' => 'http://www.example.org/library/catalog/book1.xml', 'service' => { '@context' => 'http://example.org/ns/jsonld/context.json', - '@id' => 'http://example.org/service/example', + 'id' => 'http://example.org/service/example', 'profile' => 'http://example.org/docs/example-service.html' }, 'related' => { - '@id' => 'http://www.example.org/videos/video-book1.mpg', + 'id' => 'http://www.example.org/videos/video-book1.mpg', 'format' => 'video/mpeg' }, 'within' => 'http://www.example.org/collections/books/', @@ -42,34 +42,34 @@ def initialize(hsh={}) end describe '#initialize' do - it 'sets @type to sc:Manifest by default' do - expect(subject['@type']).to eq 'sc:Manifest' + it 'sets type to Manifest by default' do + expect(subject['type']).to eq 'Manifest' end - it 'allows subclasses to override @type' do + it 'allows subclasses to override type' do sub = subclass_subject.new - expect(sub['@type']).to eq 'a:SubClass' + expect(sub['type']).to eq 'a:SubClass' end end describe '#required_keys' do it 'accumulates' do - expect(subject.required_keys).to eq %w{ @type @id label } + expect(subject.required_keys).to eq %w{ type id label } end end describe '#validate' do - it 'raises an error if there is no @id' do + it 'raises an error if there is no id' do subject.label = 'Book 1' expect { subject.validate }.to raise_error IIIF::Presentation::MissingRequiredKeyError end it 'raises an error if there is no label' do - subject['@id'] = 'http://www.example.org/iiif/book1/manifest' + subject['id'] = 'http://www.example.org/iiif/book1/manifest' expect { subject.validate }.to raise_error IIIF::Presentation::MissingRequiredKeyError end - it 'raises an error if there is no @type' do - subject.delete('@type') + it 'raises an error if there is no type' do + subject.delete('type') subject.label = 'Book 1' - subject['@id'] = 'http://www.example.org/iiif/book1/manifest' + subject['id'] = 'http://www.example.org/iiif/book1/manifest' expect { subject.validate }.to raise_error IIIF::Presentation::MissingRequiredKeyError end end diff --git a/spec/unit/iiif/presentation/range_spec.rb b/spec/unit/iiif/presentation/range_spec.rb index 1cb257a..0ff059e 100644 --- a/spec/unit/iiif/presentation/range_spec.rb +++ b/spec/unit/iiif/presentation/range_spec.rb @@ -2,8 +2,8 @@ let(:fixed_values) do { - '@id' => 'http://www.example.org/iiif/book1/range/r1', - '@type' => 'sc:Range', + 'id' => 'http://www.example.org/iiif/book1/range/r1', + 'type' => 'Range', 'label' => 'Introduction', 'ranges' => [ 'http://www.example.org/iiif/book1/range/r1-1', @@ -18,8 +18,8 @@ end describe '#initialize' do - it 'sets @type to sc:Range by default' do - expect(subject['@type']).to eq 'sc:Range' + it 'sets type to Range by default' do + expect(subject['type']).to eq 'Range' end end diff --git a/spec/unit/iiif/presentation/sequence_spec.rb b/spec/unit/iiif/presentation/sequence_spec.rb deleted file mode 100644 index 526f99d..0000000 --- a/spec/unit/iiif/presentation/sequence_spec.rb +++ /dev/null @@ -1,110 +0,0 @@ -describe IIIF::Presentation::Sequence do - - let(:subclass_subject) do - Class.new(IIIF::Presentation::Sequence) do - def initialize(hsh={}) - hsh = { '@type' => 'a:SubClass' } - super(hsh) - end - end - end - - let(:fixed_values) do - { - '@type' => 'sc:Sequence', - '@id' => 'http://example.com/prefix/sequence/456', - '@context' => IIIF::Presentation::CONTEXT, - 'label' => 'Book 1', - 'description' => 'A longer description of this example book. It should give some real information.', - 'thumbnail' => { - '@id' => 'http://www.example.org/images/book1-page1/full/80,100/0/default.jpg', - 'service'=> { - '@context' => 'http://iiif.io/api/image/2/context.json', - '@id' => 'http://www.example.org/images/book1-page1', - 'profile' => 'http://iiif.io/api/image/2/level1.json' - } - }, - 'attribution' => 'Provided by Example Organization', - 'license' => 'http://www.example.org/license.html', - 'logo' => 'http://www.example.org/logos/institution1.jpg', - 'see_also' => 'http://www.example.org/library/catalog/book1.xml', - 'service' => { - '@context' => 'http://example.org/ns/jsonld/context.json', - '@id' => 'http://example.org/service/example', - 'profile' => 'http://example.org/docs/example-service.html' - }, - 'related' => { - '@id' => 'http://www.example.org/videos/video-book1.mpg', - 'format' => 'video/mpeg' - }, - 'within' => 'http://www.example.org/collections/books/', - # Sequence - 'metadata' => [{'label'=>'Author', 'value'=>'Anne Author'}], - 'canvases' => [{ - '@id' => 'http://www.example.org/iiif/book1/canvas/p1', - '@type' => 'sc:Canvas', - 'label' => 'p. 1', - 'height' => 1000, - 'width' => 750, - 'images'=> [] - }], - 'viewing_hint' => 'paged', - 'start_canvas' => 'http://www.example.org/iiif/book1/canvas/p2', - 'viewing_direction' => 'right-to-left', - } - end - - describe '#initialize' do - it 'sets @type to sc:Sequence by default' do - expect(subject['@type']).to eq 'sc:Sequence' - end - it 'allows subclasses to override @type' do - sub = subclass_subject.new - expect(sub['@type']).to eq 'a:SubClass' - end - end - - describe '#required_keys' do - it 'accumulates from the superclass' do - expect(subject.required_keys).to eq %w{ @type } - end - end - - describe '#string_only_keys' do - it 'accumulates from the superclass' do - expect(subject.string_only_keys).to eq %w{ viewing_hint start_canvas viewing_direction } - end - end - - describe '#array_only_keys' do - it 'accumulates from the superclass' do - expect(subject.array_only_keys).to eq %w{ metadata canvases } - end - end - - describe "#{described_class}.define_methods_for_array_only_keys" do - it_behaves_like 'it has the appropriate methods for array-only keys' - end - - describe "#{described_class}.define_methods_for_string_only_keys" do - it_behaves_like 'it has the appropriate methods for string-only keys' - end - - describe "#{described_class}.define_methods_for_any_type_keys" do - it_behaves_like 'it has the appropriate methods for any-type keys' - end - - describe '#validate' do - it 'raises an error if viewing_hint isn\'t an allowable value' do - subject['viewing_hint'] = 'foo' - expect { subject.validate }.to raise_error IIIF::Presentation::IllegalValueError - end - it 'raises an error if viewing_directon isn\'t an allowable value' do - subject['viewing_direction'] = 'foo-to-bar' - expect { subject.validate }.to raise_error IIIF::Presentation::IllegalValueError - end - end - - -end - diff --git a/spec/unit/iiif/service_spec.rb b/spec/unit/iiif/service_spec.rb index be7d1ca..2bbddf0 100644 --- a/spec/unit/iiif/service_spec.rb +++ b/spec/unit/iiif/service_spec.rb @@ -1,9 +1,8 @@ -describe IIIF::Service do - - describe 'self#get_descendant_class_by_jld_type' do +RSpec.describe IIIF::Service do + describe '.get_descendant_class_by_jld_type' do before do class DummyClass < IIIF::Service - TYPE = "sc:Collection" + TYPE = "Collection" def self.singleton_class? true end @@ -13,16 +12,16 @@ def self.singleton_class? Object.send(:remove_const, :DummyClass) end it 'gets the right class' do - klass = described_class.get_descendant_class_by_jld_type('sc:Canvas') + klass = described_class.get_descendant_class_by_jld_type('Canvas') expect(klass).to be IIIF::Presentation::Canvas end + context "when there are singleton classes which are returned" do it "gets the right class" do allow(IIIF::Service).to receive(:descendants).and_return([DummyClass, IIIF::Presentation::Collection]) - klass = described_class.get_descendant_class_by_jld_type('sc:Collection') + klass = described_class.get_descendant_class_by_jld_type('Collection') expect(klass).to be IIIF::Presentation::Collection end end end - end