Merge pull request #229 from jekyll/drop-it-like-its-hot
Convert author, image, and JSON-LD to dedicated drops
This commit is contained in:
commit
9dc5b01b45
2
Gemfile
2
Gemfile
|
@ -1,6 +1,4 @@
|
|||
source "https://rubygems.org"
|
||||
require "json"
|
||||
require "open-uri"
|
||||
|
||||
gemspec
|
||||
|
||||
|
|
|
@ -3,9 +3,13 @@ require "jekyll-seo-tag/version"
|
|||
|
||||
module Jekyll
|
||||
class SeoTag < Liquid::Tag
|
||||
autoload :JSONLD, "jekyll-seo-tag/json_ld"
|
||||
autoload :Drop, "jekyll-seo-tag/drop"
|
||||
autoload :Filters, "jekyll-seo-tag/filters"
|
||||
autoload :JSONLD, "jekyll-seo-tag/json_ld"
|
||||
autoload :AuthorDrop, "jekyll-seo-tag/author_drop"
|
||||
autoload :ImageDrop, "jekyll-seo-tag/image_drop"
|
||||
autoload :JSONLDDrop, "jekyll-seo-tag/json_ld_drop"
|
||||
autoload :UrlHelper, "jekyll-seo-tag/url_helper"
|
||||
autoload :Drop, "jekyll-seo-tag/drop"
|
||||
autoload :Filters, "jekyll-seo-tag/filters"
|
||||
|
||||
attr_accessor :context
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
module Jekyll
|
||||
class SeoTag
|
||||
# A drop representing the current page's author
|
||||
#
|
||||
# Author name will be pulled from:
|
||||
#
|
||||
# 1. The page's `author` key
|
||||
# 2. The first author in the page's `authors` key
|
||||
# 3. The `author` key in the site config
|
||||
#
|
||||
# If the result from the name search is a string, we'll also check
|
||||
# for additional author metadata in `site.data.authors`
|
||||
class AuthorDrop < Jekyll::Drops::Drop
|
||||
# Initialize a new AuthorDrop
|
||||
#
|
||||
# page - The page hash (e.g., Page#to_liquid)
|
||||
# site - The Jekyll::Drops::SiteDrop
|
||||
def initialize(page: nil, site: nil)
|
||||
raise ArgumentError unless page && site
|
||||
@mutations = {}
|
||||
@page = page
|
||||
@site = site
|
||||
end
|
||||
|
||||
# AuthorDrop#to_s should return name, allowing the author drop to safely
|
||||
# replace `page.author`, if necessary, and remain backwards compatible
|
||||
def name
|
||||
author_hash["name"]
|
||||
end
|
||||
alias_method :to_s, :name
|
||||
|
||||
def twitter
|
||||
return @twitter if defined? @twitter
|
||||
twitter = author_hash["twitter"] || author_hash["name"]
|
||||
@twitter = twitter.is_a?(String) ? twitter.sub(%r!^@!, "") : nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :page
|
||||
attr_reader :site
|
||||
|
||||
# Finds the page author in the page.author, page.authors, or site.author
|
||||
#
|
||||
# Returns a string or hash representing the author
|
||||
def resolved_author
|
||||
return @resolved_author if defined? @resolved_author
|
||||
sources = [page["author"]]
|
||||
sources << page["authors"].first if page["authors"].is_a?(Array)
|
||||
sources << site["author"]
|
||||
@resolved_author = sources.find { |s| !s.to_s.empty? }
|
||||
end
|
||||
|
||||
# If resolved_author is a string, attempts to find coresponding author
|
||||
# metadata in `site.data.authors`
|
||||
#
|
||||
# Returns a hash representing additional metadata or an empty hash
|
||||
def site_data_hash
|
||||
@site_data_hash ||= begin
|
||||
return {} unless resolved_author.is_a?(String)
|
||||
return {} unless site.data["authors"].is_a?(Hash)
|
||||
author_hash = site.data["authors"][resolved_author]
|
||||
author_hash.is_a?(Hash) ? author_hash : {}
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the normalized author hash representing the page author,
|
||||
# including site-wide metadata if the author is provided as a string,
|
||||
# or an empty hash, if the author cannot be resolved
|
||||
def author_hash
|
||||
if resolved_author.is_a? Hash
|
||||
resolved_author
|
||||
elsif resolved_author.is_a? String
|
||||
{ "name" => resolved_author }.merge(site_data_hash)
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
# Since author_hash is aliased to fallback_data, any values in the hash
|
||||
# will be exposed via the drop, allowing support for arbitrary metadata
|
||||
alias_method :fallback_data, :author_hash
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
module Jekyll
|
||||
class SeoTag
|
||||
class Drop < Jekyll::Drops::Drop
|
||||
include Jekyll::SeoTag::JSONLD
|
||||
include Jekyll::SeoTag::UrlHelper
|
||||
|
||||
TITLE_SEPARATOR = " | ".freeze
|
||||
FORMAT_STRING_METHODS = %i[
|
||||
|
@ -72,29 +72,21 @@ module Jekyll
|
|||
end
|
||||
end
|
||||
|
||||
# Returns a nil or a hash representing the author
|
||||
# Author name will be pulled from:
|
||||
#
|
||||
# 1. The `author` key, if the key is a string
|
||||
# 2. The first author in the `authors` key
|
||||
# 3. The `author` key in the site config
|
||||
#
|
||||
# If the result from the name search is a string, we'll also check
|
||||
# to see if the author exists in `site.data.authors`
|
||||
# A drop representing the page author
|
||||
def author
|
||||
@author ||= begin
|
||||
return if author_string_or_hash.to_s.empty?
|
||||
@author ||= AuthorDrop.new(:page => page, :site => site)
|
||||
end
|
||||
|
||||
author = if author_string_or_hash.is_a?(String)
|
||||
author_hash(author_string_or_hash)
|
||||
else
|
||||
author_string_or_hash
|
||||
end
|
||||
# A drop representing the JSON-LD output
|
||||
def json_ld
|
||||
@json_ld ||= JSONLDDrop.new(self)
|
||||
end
|
||||
|
||||
author["twitter"] ||= author["name"]
|
||||
author["twitter"].delete! "@" if author["twitter"]
|
||||
author.to_liquid
|
||||
end
|
||||
# Returns a Drop representing the page's image
|
||||
# Returns nil if the image has no path, to preserve backwards compatability
|
||||
def image
|
||||
@image ||= ImageDrop.new(:page => page, :context => @context)
|
||||
@image if @image.path
|
||||
end
|
||||
|
||||
def date_modified
|
||||
|
@ -149,35 +141,6 @@ module Jekyll
|
|||
end
|
||||
end
|
||||
|
||||
# Returns nil or a hash representing the page image
|
||||
# The image hash will always contain a path, pulled from:
|
||||
#
|
||||
# 1. The `image` key if it's a string
|
||||
# 2. The `image.path` key if it's a hash
|
||||
# 3. The `image.facebook` key
|
||||
# 4. The `image.twitter` key
|
||||
#
|
||||
# The resulting path is always an absolute URL
|
||||
def image
|
||||
return @image if defined?(@image)
|
||||
|
||||
image = page["image"]
|
||||
return @image = nil unless image
|
||||
|
||||
image = { "path" => image } if image.is_a?(String)
|
||||
image["path"] ||= image["facebook"] || image["twitter"]
|
||||
return @image = nil unless image["path"]
|
||||
|
||||
# absolute_url? will return nil for an invalid URL
|
||||
if absolute_url?(image["path"]) == false
|
||||
image["path"] = filters.absolute_url image["path"]
|
||||
end
|
||||
|
||||
image["path"] = filters.uri_escape image["path"]
|
||||
|
||||
@image = image.to_liquid
|
||||
end
|
||||
|
||||
def page_lang
|
||||
@page_lang ||= page["lang"] || site["lang"] || "en_US"
|
||||
end
|
||||
|
@ -216,13 +179,6 @@ module Jekyll
|
|||
@fallback_data ||= {}
|
||||
end
|
||||
|
||||
def absolute_url?(string)
|
||||
return unless string
|
||||
Addressable::URI.parse(string).absolute?
|
||||
rescue Addressable::URI::InvalidURIError
|
||||
nil
|
||||
end
|
||||
|
||||
def format_string(string)
|
||||
string = FORMAT_STRING_METHODS.reduce(string) do |memo, method|
|
||||
filters.public_send(method, memo)
|
||||
|
@ -231,35 +187,6 @@ module Jekyll
|
|||
string unless string.empty?
|
||||
end
|
||||
|
||||
def author_string_or_hash
|
||||
@author_string_or_hash ||= begin
|
||||
author = page["author"]
|
||||
author = page["authors"][0] if author.to_s.empty? && page["authors"]
|
||||
author = site["author"] if author.to_s.empty?
|
||||
author
|
||||
end
|
||||
end
|
||||
|
||||
# Given a string representing the current document's author, return
|
||||
# a normalized hash representing that author. Will try to pull from
|
||||
# site.authors if present and in the proper format.
|
||||
def author_hash(author_string)
|
||||
site_author_hash(author_string) || { "name" => author_string }
|
||||
end
|
||||
|
||||
# Given a string representing the current document's author, attempt
|
||||
# to retrieve additional metadata from site.data.authors, if present
|
||||
#
|
||||
# Returns the author hash
|
||||
def site_author_hash(author_string)
|
||||
return unless site.data["authors"] && site.data["authors"].is_a?(Hash)
|
||||
author_hash = site.data["authors"][author_string]
|
||||
return unless author_hash.is_a?(Hash)
|
||||
author_hash["name"] ||= author_string
|
||||
author_hash["twitter"] ||= author_string
|
||||
author_hash
|
||||
end
|
||||
|
||||
def seo_name
|
||||
@seo_name ||= format_string(page_seo["name"]) if page_seo["name"]
|
||||
end
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
module Jekyll
|
||||
class SeoTag
|
||||
# A drop representing the page image
|
||||
# The image path will be pulled from:
|
||||
#
|
||||
# 1. The `image` key if it's a string
|
||||
# 2. The `image.path` key if it's a hash
|
||||
# 3. The `image.facebook` key
|
||||
# 4. The `image.twitter` key
|
||||
class ImageDrop < Jekyll::Drops::Drop
|
||||
include Jekyll::SeoTag::UrlHelper
|
||||
|
||||
# Initialize a new ImageDrop
|
||||
#
|
||||
# page - The page hash (e.g., Page#to_liquid)
|
||||
# context - the Liquid::Context
|
||||
def initialize(page: nil, context: nil)
|
||||
raise ArgumentError unless page && context
|
||||
@mutations = {}
|
||||
@page = page
|
||||
@context = context
|
||||
end
|
||||
|
||||
# Called path for backwards compatability, this is really
|
||||
# the escaped, absolute URL representing the page's image
|
||||
# Returns nil if no image path can be determined
|
||||
def path
|
||||
@path ||= filters.uri_escape(absolute_url) if absolute_url
|
||||
end
|
||||
alias_method :to_s, :path
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :page
|
||||
attr_accessor :context
|
||||
|
||||
# The normalized image hash with a `path` key (which may be nil)
|
||||
def image_hash
|
||||
@image_hash ||= if page["image"].is_a?(Hash)
|
||||
{ "path" => nil }.merge(page["image"])
|
||||
elsif page["image"].is_a?(String)
|
||||
{ "path" => page["image"] }
|
||||
else
|
||||
{ "path" => nil }
|
||||
end
|
||||
end
|
||||
alias_method :fallback_data, :image_hash
|
||||
|
||||
def raw_path
|
||||
@raw_path ||= begin
|
||||
image_hash["path"] || image_hash["facebook"] || image_hash["twitter"]
|
||||
end
|
||||
end
|
||||
|
||||
def absolute_url
|
||||
return unless raw_path
|
||||
return @absolute_url if defined? @absolute_url
|
||||
@absolute_url = if raw_path.is_a?(String) && absolute_url?(raw_path) == false
|
||||
filters.absolute_url raw_path
|
||||
else
|
||||
raw_path
|
||||
end
|
||||
end
|
||||
|
||||
def filters
|
||||
@filters ||= Jekyll::SeoTag::Filters.new(context)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +1,8 @@
|
|||
module Jekyll
|
||||
class SeoTag
|
||||
# This module is deprecated, but is included in the Gem to avoid a breaking
|
||||
# change and should be removed at the next major version bump
|
||||
module JSONLD
|
||||
|
||||
# A hash of instance methods => key in resulting JSON-LD hash
|
||||
METHODS_KEYS = {
|
||||
:json_context => "@context",
|
||||
:type => "@type",
|
||||
|
@ -19,60 +19,10 @@ module Jekyll
|
|||
:canonical_url => "url",
|
||||
}.freeze
|
||||
|
||||
# Self should be a Jekyll::SeoTag::Drop instance (when extending the module)
|
||||
def json_ld
|
||||
@json_ld ||= begin
|
||||
output = {}
|
||||
METHODS_KEYS.each do |method, key|
|
||||
value = send(method)
|
||||
output[key] = value unless value.nil?
|
||||
end
|
||||
output
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def json_context
|
||||
"http://schema.org"
|
||||
end
|
||||
|
||||
def json_author
|
||||
return unless author
|
||||
{
|
||||
"@type" => "Person",
|
||||
"name" => author["name"],
|
||||
}
|
||||
end
|
||||
|
||||
def json_image
|
||||
return unless image
|
||||
return image["path"] if image.length == 1
|
||||
|
||||
hash = image.dup
|
||||
hash["url"] = hash.delete("path")
|
||||
hash["@type"] = "imageObject"
|
||||
hash
|
||||
end
|
||||
|
||||
def publisher
|
||||
return unless logo
|
||||
output = {
|
||||
"@type" => "Organization",
|
||||
"logo" => {
|
||||
"@type" => "ImageObject",
|
||||
"url" => logo,
|
||||
},
|
||||
}
|
||||
output["name"] = author["name"] if author
|
||||
output
|
||||
end
|
||||
|
||||
def main_entity
|
||||
return unless %w(BlogPosting CreativeWork).include?(type)
|
||||
{
|
||||
"@type" => "WebPage",
|
||||
"@id" => canonical_url,
|
||||
}
|
||||
Jekyll.logger.warn "Jekyll::SeoTag::JSONLD is deprecated"
|
||||
@json_ld ||= JSONLDDrop.new(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
module Jekyll
|
||||
class SeoTag
|
||||
class JSONLDDrop < Jekyll::Drops::Drop
|
||||
extend Forwardable
|
||||
|
||||
def_delegator :page_drop, :name, :name
|
||||
def_delegator :page_drop, :description, :description
|
||||
def_delegator :page_drop, :canonical_url, :url
|
||||
def_delegator :page_drop, :page_title, :headline
|
||||
def_delegator :page_drop, :date_modified, :dateModified
|
||||
def_delegator :page_drop, :date_published, :datePublished
|
||||
def_delegator :page_drop, :links, :sameAs
|
||||
def_delegator :page_drop, :logo, :logo
|
||||
def_delegator :page_drop, :type, :type
|
||||
|
||||
# Expose #type and #logo as private methods and #@type as a public method
|
||||
alias_method :"@type", :type
|
||||
private :type
|
||||
private :logo
|
||||
|
||||
# page_drop should be an instance of Jekyll::SeoTag::Drop
|
||||
def initialize(page_drop)
|
||||
@mutations = {}
|
||||
@page_drop = page_drop
|
||||
end
|
||||
|
||||
def fallback_data
|
||||
{
|
||||
"@context" => "http://schema.org",
|
||||
}
|
||||
end
|
||||
|
||||
def author
|
||||
return unless page_drop.author["name"]
|
||||
{
|
||||
"@type" => "Person",
|
||||
"name" => page_drop.author["name"],
|
||||
}
|
||||
end
|
||||
|
||||
def image
|
||||
return unless page_drop.image
|
||||
return page_drop.image.path if page_drop.image.keys.length == 1
|
||||
|
||||
hash = page_drop.image.to_h
|
||||
hash["url"] = hash.delete("path")
|
||||
hash["@type"] = "imageObject"
|
||||
hash
|
||||
end
|
||||
|
||||
def publisher
|
||||
return unless logo
|
||||
output = {
|
||||
"@type" => "Organization",
|
||||
"logo" => {
|
||||
"@type" => "ImageObject",
|
||||
"url" => logo,
|
||||
},
|
||||
}
|
||||
output["name"] = page_drop.author.name if page_drop.author.name
|
||||
output
|
||||
end
|
||||
|
||||
def main_entity
|
||||
return unless %w(BlogPosting CreativeWork).include?(type)
|
||||
{
|
||||
"@type" => "WebPage",
|
||||
"@id" => page_drop.canonical_url,
|
||||
}
|
||||
end
|
||||
alias_method :mainEntityOfPage, :main_entity
|
||||
private :main_entity
|
||||
|
||||
private
|
||||
|
||||
attr_reader :page_drop
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
module Jekyll
|
||||
class SeoTag
|
||||
# Mixin to share common URL-related methods between class
|
||||
module UrlHelper
|
||||
private
|
||||
|
||||
# Determines if the given string is an absolute URL
|
||||
#
|
||||
# Returns true if an absolute URL.
|
||||
# Retruns false if it's a relative URL
|
||||
# Returns nil if it is not a string or can't be parsed as a URL
|
||||
def absolute_url?(string)
|
||||
return unless string
|
||||
Addressable::URI.parse(string).absolute?
|
||||
rescue Addressable::URI::InvalidURIError
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,162 @@
|
|||
RSpec.describe Jekyll::SeoTag::AuthorDrop do
|
||||
let(:data) { {} }
|
||||
let(:config) { { "author" => "site_author" } }
|
||||
let(:site) do
|
||||
site = make_site(config)
|
||||
site.data = data
|
||||
site
|
||||
end
|
||||
let(:site_payload) { site.site_payload["site"] }
|
||||
|
||||
let(:name) { "foo" }
|
||||
let(:twitter) { "foo" }
|
||||
let(:picture) { nil }
|
||||
let(:expected_hash) do
|
||||
{
|
||||
"name" => name,
|
||||
"twitter" => twitter,
|
||||
}
|
||||
end
|
||||
|
||||
let(:page_meta) { { "title" => "page title" } }
|
||||
let(:page) { make_page(page_meta) }
|
||||
subject { described_class.new(:page => page.to_liquid, :site => site_payload.to_liquid) }
|
||||
|
||||
before do
|
||||
Jekyll.logger.log_level = :error
|
||||
end
|
||||
|
||||
it "returns the author's name for #to_s" do
|
||||
expect(subject.to_s).to eql("site_author")
|
||||
end
|
||||
|
||||
context "with site.authors as an array" do
|
||||
let("data") { { "authors" => %w(foo bar) } }
|
||||
let(:page_meta) { { "author" => "foo" } }
|
||||
|
||||
it "doesn't error" do
|
||||
expect(subject.to_h).to eql(expected_hash)
|
||||
end
|
||||
end
|
||||
|
||||
context "with site.authors[author] as string" do
|
||||
let("data") { { "authors" => { "foo" => "bar" } } }
|
||||
let(:page_meta) { { "author" => "foo" } }
|
||||
|
||||
it "doesn't error" do
|
||||
expect(subject.to_h).to eql(expected_hash)
|
||||
end
|
||||
end
|
||||
|
||||
%i[with without].each do |site_data_type|
|
||||
context "#{site_data_type} site.author data" do
|
||||
let(:data) do
|
||||
if site_data_type == :with
|
||||
{
|
||||
"authors" => {
|
||||
"author" => { "name" => "data_author", "image" => "author.png" },
|
||||
"array_author" => { "image" => "author.png" },
|
||||
"string_author" => { "image" => "author.png" },
|
||||
"site_author" => { "image" => "author.png" },
|
||||
},
|
||||
}
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
:string => { "author" => "string_author" },
|
||||
:array => { "authors" => %w(array_author author2) },
|
||||
:empty_string => { "author" => "" },
|
||||
:nil => { "author" => nil },
|
||||
:hash => { "author" => { "name" => "hash_author" } },
|
||||
}.each do |author_type, data|
|
||||
context "with author as #{author_type}" do
|
||||
let(:page_meta) { data }
|
||||
let(:expected_author) do
|
||||
"#{author_type}_author".sub("nil_", "site_").sub("empty_string_", "site_")
|
||||
end
|
||||
|
||||
it "returns the name" do
|
||||
expect(subject["name"]).to eql(expected_author)
|
||||
end
|
||||
|
||||
it "returns the twitter handle" do
|
||||
expect(subject["twitter"]).to eql(expected_author)
|
||||
end
|
||||
|
||||
if site_data_type == :with && author_type != :hash
|
||||
it "returns arbitrary metadata" do
|
||||
expect(subject["image"]).to eql("author.png")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with author as a front matter default" do
|
||||
let(:config) do
|
||||
{
|
||||
"defaults" => [
|
||||
{
|
||||
"scope" => { "path" => "" },
|
||||
"values" => { "author" => "front matter default" },
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it "uses the author from the front matter default" do
|
||||
expect(subject["name"]).to eql("front matter default")
|
||||
end
|
||||
end
|
||||
|
||||
context "twitter" do
|
||||
let(:page_meta) { { "author" => "author" } }
|
||||
|
||||
it "pulls the handle from the author" do
|
||||
expect(subject["twitter"]).to eql("author")
|
||||
end
|
||||
|
||||
context "with an @" do
|
||||
let(:page_meta) do
|
||||
{
|
||||
"author" => {
|
||||
"name" => "author",
|
||||
"twitter" => "@twitter",
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
it "strips the @" do
|
||||
expect(subject["twitter"]).to eql("twitter")
|
||||
end
|
||||
end
|
||||
|
||||
# See https://github.com/jekyll/jekyll-seo-tag/issues/202
|
||||
context "without an author name or handle" do
|
||||
let(:page_meta) { { "author" => { "foo" => "bar" } } }
|
||||
|
||||
it "dosen't blow up" do
|
||||
expect(subject["twitter"]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "with an explicit handle" do
|
||||
let(:page_meta) do
|
||||
{
|
||||
"author" => {
|
||||
"name" => "author",
|
||||
"twitter" => "twitter",
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
it "pulls the handle from the hash" do
|
||||
expect(subject["twitter"]).to eql("twitter")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -223,85 +223,20 @@ RSpec.describe Jekyll::SeoTag::Drop do
|
|||
end
|
||||
|
||||
context "author" do
|
||||
let(:data) { {} }
|
||||
let(:config) { { "author" => "site_author" } }
|
||||
let(:site) do
|
||||
site = make_site(config)
|
||||
site.data = data
|
||||
site
|
||||
let(:page_meta) { { "author" => "foo" } }
|
||||
|
||||
it "returns an AuthorDrop" do
|
||||
expect(subject.author).to be_a(Jekyll::SeoTag::AuthorDrop)
|
||||
end
|
||||
|
||||
context "with site.authors as an array" do
|
||||
let("data") { { "authors" => %w(foo bar) } }
|
||||
let(:page_meta) { { "author" => "foo" } }
|
||||
|
||||
it "doesn't error" do
|
||||
expect(subject.author).to eql({ "name" => "foo", "twitter" => "foo" })
|
||||
end
|
||||
end
|
||||
|
||||
context "with site.authors[author] as string" do
|
||||
let("data") { { "authors" => { "foo" => "bar" } } }
|
||||
let(:page_meta) { { "author" => "foo" } }
|
||||
|
||||
it "doesn't error" do
|
||||
expect(subject.author).to eql({ "name" => "foo", "twitter" => "foo" })
|
||||
end
|
||||
end
|
||||
|
||||
%i[with without].each do |site_data_type|
|
||||
context "#{site_data_type} site.author data" do
|
||||
let(:data) do
|
||||
if site_data_type == :with
|
||||
{
|
||||
"authors" => {
|
||||
"author" => { "name" => "data_author", "image" => "author.png" },
|
||||
"array_author" => { "image" => "author.png" },
|
||||
"string_author" => { "image" => "author.png" },
|
||||
"site_author" => { "image" => "author.png" },
|
||||
},
|
||||
}
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
:string => { "author" => "string_author" },
|
||||
:array => { "authors" => %w(array_author author2) },
|
||||
:empty_string => { "author" => "" },
|
||||
:nil => { "author" => nil },
|
||||
:hash => { "author" => { "name" => "hash_author" } },
|
||||
}.each do |author_type, data|
|
||||
context "with author as #{author_type}" do
|
||||
let(:page_meta) { data }
|
||||
let(:expected_author) do
|
||||
"#{author_type}_author".sub("nil_", "site_").sub("empty_string_", "site_")
|
||||
end
|
||||
|
||||
it "returns a hash" do
|
||||
expect(subject.author).to be_a(Hash)
|
||||
end
|
||||
|
||||
it "returns the name" do
|
||||
expect(subject.author["name"]).to eql(expected_author)
|
||||
end
|
||||
|
||||
it "returns the twitter handle" do
|
||||
expect(subject.author["twitter"]).to eql(expected_author)
|
||||
end
|
||||
|
||||
if site_data_type == :with && author_type != :hash
|
||||
it "returns the image" do
|
||||
expect(subject.author["image"]).to eql("author.png")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
it "passes page information" do
|
||||
expect(subject.author.name).to eql("foo")
|
||||
end
|
||||
|
||||
# Regression test to ensure to_liquid is called on site and page
|
||||
# before being passed to AuthorDrop
|
||||
context "with author as a front matter default" do
|
||||
let(:page_meta) { {} }
|
||||
let(:config) do
|
||||
{
|
||||
"defaults" => [
|
||||
|
@ -317,53 +252,6 @@ RSpec.describe Jekyll::SeoTag::Drop do
|
|||
expect(subject.author["name"]).to eql("front matter default")
|
||||
end
|
||||
end
|
||||
|
||||
context "twitter" do
|
||||
let(:page_meta) { { "author" => "author" } }
|
||||
|
||||
it "pulls the handle from the author" do
|
||||
expect(subject.author["twitter"]).to eql("author")
|
||||
end
|
||||
|
||||
context "with an @" do
|
||||
let(:page_meta) do
|
||||
{
|
||||
"author" => {
|
||||
"name" => "author",
|
||||
"twitter" => "@twitter",
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
it "strips the @" do
|
||||
expect(subject.author["twitter"]).to eql("twitter")
|
||||
end
|
||||
end
|
||||
|
||||
# See https://github.com/jekyll/jekyll-seo-tag/issues/202
|
||||
context "without an author name or handle" do
|
||||
let(:page_meta) { { "author" => { "foo" => "bar" } } }
|
||||
|
||||
it "dosen't blow up" do
|
||||
expect(subject.author["twitter"]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "with an explicit handle" do
|
||||
let(:page_meta) do
|
||||
{
|
||||
"author" => {
|
||||
"name" => "author",
|
||||
"twitter" => "twitter",
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
it "pulls the handle from the hash" do
|
||||
expect(subject.author["twitter"]).to eql("twitter")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -514,85 +402,15 @@ RSpec.describe Jekyll::SeoTag::Drop do
|
|||
end
|
||||
|
||||
context "image" do
|
||||
let(:image) { "foo.png" }
|
||||
let(:page_meta) { { "image" => image } }
|
||||
|
||||
context "with image as a string" do
|
||||
let(:image) { "image.png" }
|
||||
|
||||
it "returns a hash" do
|
||||
expect(subject.image).to be_a(Hash)
|
||||
end
|
||||
|
||||
it "returns the image" do
|
||||
expect(subject.image["path"]).to eql("/image.png")
|
||||
end
|
||||
|
||||
context "with site.url" do
|
||||
let(:config) { { "url" => "http://example.com" } }
|
||||
|
||||
it "makes the path absolute" do
|
||||
expect(subject.image["path"]).to eql("http://example.com/image.png")
|
||||
end
|
||||
end
|
||||
|
||||
context "with a URL-escaped path" do
|
||||
let(:image) { "some image.png" }
|
||||
|
||||
it "URL-escapes the image" do
|
||||
expect(subject.image["path"]).to eql("/some%20image.png")
|
||||
end
|
||||
end
|
||||
it "returns a Drop" do
|
||||
expect(subject.image).to be_a(Jekyll::SeoTag::ImageDrop)
|
||||
end
|
||||
|
||||
context "with image as a hash" do
|
||||
context "with a path" do
|
||||
let(:image) { { "path" => "image.png" } }
|
||||
|
||||
it "returns the image" do
|
||||
expect(subject.image["path"]).to eql("/image.png")
|
||||
end
|
||||
end
|
||||
|
||||
context "with facebook" do
|
||||
let(:image) { { "facebook" => "image.png" } }
|
||||
|
||||
it "returns the image" do
|
||||
expect(subject.image["path"]).to eql("/image.png")
|
||||
end
|
||||
end
|
||||
|
||||
context "with twitter" do
|
||||
let(:image) { { "twitter" => "image.png" } }
|
||||
|
||||
it "returns the image" do
|
||||
expect(subject.image["path"]).to eql("/image.png")
|
||||
end
|
||||
end
|
||||
|
||||
context "with some random hash" do
|
||||
let(:image) { { "foo" => "bar" } }
|
||||
|
||||
it "returns nil" do
|
||||
expect(subject.image).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "with an invalid path" do
|
||||
let(:image) { ":" }
|
||||
|
||||
it "returns nil" do
|
||||
expect(subject.image["path"]).to eql(":")
|
||||
end
|
||||
end
|
||||
|
||||
context "with height and width" do
|
||||
let(:image) { { "path" => "image.png", "height" => 5, "width" => 10 } }
|
||||
|
||||
it "returns the height and width" do
|
||||
expect(subject.image["height"]).to eql(5)
|
||||
expect(subject.image["width"]).to eql(10)
|
||||
end
|
||||
end
|
||||
it "returns the image" do
|
||||
expect(subject.image["path"]).to eql("/foo.png")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -661,4 +479,8 @@ RSpec.describe Jekyll::SeoTag::Drop do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "exposes the JSON-LD drop" do
|
||||
expect(subject.json_ld).to be_a(Jekyll::SeoTag::JSONLDDrop)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
RSpec.describe Jekyll::SeoTag::ImageDrop do
|
||||
let(:config) { { "title" => "site title" } }
|
||||
let(:image) { nil }
|
||||
let(:page_meta) { { "image" => image } }
|
||||
let(:page) { make_page(page_meta) }
|
||||
let(:site) { make_site(config) }
|
||||
let(:context) { make_context(:page => page, :site => site) }
|
||||
let(:text) { "" }
|
||||
subject { described_class.new(:page => page.to_liquid, :context => context) }
|
||||
|
||||
before do
|
||||
Jekyll.logger.log_level = :error
|
||||
end
|
||||
|
||||
context "with image as a string" do
|
||||
let(:image) { "image.png" }
|
||||
|
||||
it "returns the image" do
|
||||
expect(subject["path"]).to eql("/image.png")
|
||||
end
|
||||
|
||||
context "with site.url" do
|
||||
let(:config) { { "url" => "http://example.com" } }
|
||||
|
||||
it "makes the path absolute" do
|
||||
expect(subject["path"]).to eql("http://example.com/image.png")
|
||||
end
|
||||
end
|
||||
|
||||
context "with a URL-escaped path" do
|
||||
let(:image) { "some image.png" }
|
||||
|
||||
it "URL-escapes the image" do
|
||||
expect(subject["path"]).to eql("/some%20image.png")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with image as a hash" do
|
||||
context "with a path" do
|
||||
let(:image) { { "path" => "image.png" } }
|
||||
|
||||
it "returns the image" do
|
||||
expect(subject["path"]).to eql("/image.png")
|
||||
end
|
||||
end
|
||||
|
||||
context "with facebook" do
|
||||
let(:image) { { "facebook" => "image.png" } }
|
||||
|
||||
it "returns the image" do
|
||||
expect(subject["path"]).to eql("/image.png")
|
||||
end
|
||||
end
|
||||
|
||||
context "with twitter" do
|
||||
let(:image) { { "twitter" => "image.png" } }
|
||||
|
||||
it "returns the image" do
|
||||
expect(subject["path"]).to eql("/image.png")
|
||||
end
|
||||
end
|
||||
|
||||
context "with some random hash" do
|
||||
let(:image) { { "foo" => "bar" } }
|
||||
|
||||
it "returns nil" do
|
||||
expect(subject["path"]).to be_nil
|
||||
end
|
||||
|
||||
it "returns arbitrary values" do
|
||||
expect(subject["foo"]).to eql("bar")
|
||||
end
|
||||
end
|
||||
|
||||
context "with an invalid path" do
|
||||
let(:image) { ":" }
|
||||
|
||||
it "returns the path" do
|
||||
expect(subject["path"]).to eql(":")
|
||||
end
|
||||
end
|
||||
|
||||
context "with height and width" do
|
||||
let(:image) { { "path" => "image.png", "height" => 5, "width" => 10 } }
|
||||
|
||||
it "returns the height and width" do
|
||||
expect(subject["height"]).to eql(5)
|
||||
expect(subject["width"]).to eql(10)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +1,4 @@
|
|||
RSpec.describe Jekyll::SeoTag::JSONLD do
|
||||
before do
|
||||
Jekyll.logger.log_level = :error
|
||||
end
|
||||
|
||||
RSpec.describe Jekyll::SeoTag::JSONLDDrop do
|
||||
let(:author) { "author" }
|
||||
let(:image) { "image" }
|
||||
let(:metadata) do
|
||||
|
@ -28,7 +24,12 @@ RSpec.describe Jekyll::SeoTag::JSONLD do
|
|||
let(:page) { make_page(metadata) }
|
||||
let(:site) { make_site(config) }
|
||||
let(:context) { make_context(:page => page, :site => site) }
|
||||
subject { Jekyll::SeoTag::Drop.new("", context).json_ld }
|
||||
let(:page_drop) { Jekyll::SeoTag::Drop.new("", context) }
|
||||
subject { described_class.new(page_drop) }
|
||||
|
||||
before do
|
||||
Jekyll.logger.log_level = :error
|
||||
end
|
||||
|
||||
it "returns the context" do
|
||||
expect(subject).to have_key("@context")
|
||||
|
|
|
@ -329,10 +329,7 @@ EOS
|
|||
end
|
||||
|
||||
it "minifies JSON-LD" do
|
||||
expected = <<-EOS.strip
|
||||
{"@context":"http://schema.org","@type":"BlogPosting","headline":"post",
|
||||
EOS
|
||||
expect(output).to match(expected)
|
||||
expect(output).to_not match(%r!{.*?\s.*?}!)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,15 @@ require "jekyll"
|
|||
require "jekyll-seo-tag"
|
||||
require "html-proofer"
|
||||
|
||||
# Monkey patch Jekyll::Drops::Drop so Rspec's `have_key` works as expected
|
||||
module Jekyll
|
||||
module Drops
|
||||
class Drop
|
||||
alias_method :has_key?, :key?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ENV["JEKYLL_LOG_LEVEL"] = "error"
|
||||
|
||||
def dest_dir
|
||||
|
|
Loading…
Reference in New Issue