Merge branch 'master' into pr/title-optional
This commit is contained in:
commit
6bc8a57315
|
@ -0,0 +1,10 @@
|
|||
Metrics/LineLength:
|
||||
Exclude:
|
||||
- spec/**/*
|
||||
- jekyll-seo-tag.gemspec
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
Style/FileName:
|
||||
Enabled: false
|
14
Gemfile
14
Gemfile
|
@ -1,6 +1,16 @@
|
|||
source 'https://rubygems.org'
|
||||
require 'json'
|
||||
require 'open-uri'
|
||||
|
||||
# Specify your gem's dependencies in jekyll_seo_tags.gemspec
|
||||
gemspec
|
||||
|
||||
gem 'github-pages'
|
||||
group :development, :test do
|
||||
versions = JSON.parse(open('https://pages.github.com/versions.json').read)
|
||||
versions.delete('ruby')
|
||||
versions.delete('jekyll-seo-tag')
|
||||
versions.delete('github-pages')
|
||||
|
||||
versions.each do |dep, version|
|
||||
gem dep, version
|
||||
end
|
||||
end
|
||||
|
|
72
README.md
72
README.md
|
@ -2,7 +2,7 @@
|
|||
|
||||
A Jekyll plugin to add metadata tags for search engines and social networks to better index and display your site's content.
|
||||
|
||||
[![Gem Version](https://badge.fury.io/rb/jekyll-seo-tag.svg)](https://badge.fury.io/rb/jekyll-seo-tag) [![Build Status](https://travis-ci.org/benbalter/jekyll-seo-tag.svg)](https://travis-ci.org/benbalter/jekyll-seo-tag)
|
||||
[![Gem Version](https://badge.fury.io/rb/jekyll-seo-tag.svg)](https://badge.fury.io/rb/jekyll-seo-tag) [![Build Status](https://travis-ci.org/benbalter/jekyll-seo-tag.svg)](https://travis-ci.org/benbalter/jekyll-seo-tag)
|
||||
|
||||
## What it does
|
||||
|
||||
|
@ -52,6 +52,7 @@ The SEO tag will respect any of the following if included in your site's `_confi
|
|||
* `title` - Your site's title (e.g., Ben's awesome site, The GitHub Blog, etc.)
|
||||
* `description` - A short description (e.g., A blog dedicated to reviewing cat gifs)
|
||||
* `url` - The full URL to your site. Note: `site.github.url` will be used by default.
|
||||
* `author` - global author information (see below)
|
||||
* `twitter:username` - The site's Twitter handle. You'll want to describe it like so:
|
||||
|
||||
```yml
|
||||
|
@ -59,15 +60,80 @@ The SEO tag will respect any of the following if included in your site's `_confi
|
|||
username: benbalter
|
||||
```
|
||||
|
||||
* `facebook:app_id` (A Facebook app ID for Facebook insights), and/or `facebook:publisher` (A Facebook page URL or ID of the publishing entity). You'll want to describe one or both like so:
|
||||
|
||||
```yml
|
||||
facebook:
|
||||
app_id: 1234
|
||||
publisher: 1234
|
||||
```
|
||||
|
||||
* `logo` - Relative URL to a site-wide logo (e.g., `assets/your-company-logo.png`)
|
||||
* `social` - For [specifying social profiles](https://developers.google.com/structured-data/customize/social-profiles). The following properties are available:
|
||||
* `type` - Either `person` or `organization` (defaults to `person`)
|
||||
* `name` - If the user or organization name differs from the site's name
|
||||
* `links` - An array of links to social media profiles.
|
||||
* `google_site_verification` for verifying ownership via Google webmaster tools
|
||||
|
||||
The SEO tag will respect the following YAML front matter if included in a post, page, or document:
|
||||
|
||||
* `title` - The title of the post, page, or document
|
||||
* `description` - A short description of the page's content
|
||||
* `image` - The absolute URL to an image that should be associated with the post, page, or document
|
||||
* `author` - The username of the post, page, or document author
|
||||
* `image` - Relative URL to an image associated with the post, page, or document (e.g., `assets/page-pic.jpg`)
|
||||
* `author` - Page-, post-, or document-specific author information (see below)
|
||||
|
||||
### Author information
|
||||
|
||||
Author information is used to propagate the `creator` field of Twitter summary cards. This is should be an author-specific, not site-wide Twitter handle (the site-wide username be stored as `site.twitter.username`).
|
||||
|
||||
*TL;DR: In most cases, put `author: [your Twitter handle]` in the document's front matter, for sites with multiple authors. If you need something more complicated, read on.*
|
||||
|
||||
There are several ways to convey this author-specific information. Author information is found in the following order of priority:
|
||||
|
||||
1. An `author` object, in the documents's front matter, e.g.:
|
||||
|
||||
```yml
|
||||
author:
|
||||
twitter: benbalter
|
||||
```
|
||||
|
||||
2. An `author` object, in the site's `_config.yml`, e.g.:
|
||||
|
||||
```yml
|
||||
author:
|
||||
twitter: benbalter
|
||||
```
|
||||
|
||||
3. `site.data.authors[author]`, if an author is specified in the document's front matter, and a corresponding key exists in `site.data.authors`. E.g., you have the following in the document's front matter:
|
||||
|
||||
```yml
|
||||
author: benbalter
|
||||
```
|
||||
|
||||
And you have the following in `_data/authors.yml`:
|
||||
|
||||
```yml
|
||||
benbalter:
|
||||
picture: /img/benbalter.png
|
||||
twitter: jekyllrb
|
||||
|
||||
potus:
|
||||
picture: /img/potus.png
|
||||
twitter: whitehouse
|
||||
```
|
||||
|
||||
In the above example, the author `benbalter`'s Twitter handle will be resolved to `@jekyllrb`. This allows you to centralize author information in a single `_data/authors` file for site with many authors that require more than just the author's username.
|
||||
|
||||
*Pro-tip: If `authors` is present in the document's front matter as an array (and `author` is not), the plugin will use the first author listed, as Twitter supports only one author.*
|
||||
|
||||
4. An author in the document's front matter (the simplest way), e.g.:
|
||||
|
||||
```yml
|
||||
author: benbalter
|
||||
```
|
||||
|
||||
5. An author in the site's `_config.yml`, e.g.:
|
||||
|
||||
```yml
|
||||
author: benbalter
|
||||
```
|
||||
|
|
6
Rakefile
6
Rakefile
|
@ -1,6 +1,6 @@
|
|||
require "bundler/gem_tasks"
|
||||
require "rspec/core/rake_task"
|
||||
require 'bundler/gem_tasks'
|
||||
require 'rspec/core/rake_task'
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
|
||||
task :default => :spec
|
||||
task default: :spec
|
||||
|
|
|
@ -4,31 +4,31 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|||
require 'jekyll-seo-tag/version'
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "jekyll-seo-tag"
|
||||
spec.name = 'jekyll-seo-tag'
|
||||
spec.version = Jekyll::SeoTag::VERSION
|
||||
spec.authors = ["Ben Balter"]
|
||||
spec.email = ["ben.balter@github.com"]
|
||||
spec.summary = %q{A Jekyll plugin to add metadata tags for search engines and social networks to better index and display your site's content.}
|
||||
spec.homepage = "https://github.com/benbalter/jekyll-seo-tag"
|
||||
spec.license = "MIT"
|
||||
spec.authors = ['Ben Balter']
|
||||
spec.email = ['ben.balter@github.com']
|
||||
spec.summary = "A Jekyll plugin to add metadata tags for search engines and social networks to better index and display your site's content."
|
||||
spec.homepage = 'https://github.com/benbalter/jekyll-seo-tag'
|
||||
spec.license = 'MIT'
|
||||
|
||||
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
||||
# delete this section to allow pushing this gem to any host.
|
||||
if spec.respond_to?(:metadata)
|
||||
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
||||
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
||||
else
|
||||
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
||||
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
|
||||
end
|
||||
|
||||
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
||||
spec.bindir = "exe"
|
||||
spec.bindir = 'exe'
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.add_dependency "jekyll", ">= 2.0"
|
||||
spec.add_development_dependency "bundler", "~> 1.10"
|
||||
spec.add_development_dependency "rake", "~> 10.0"
|
||||
spec.add_development_dependency "rspec", "~> 3.3"
|
||||
spec.add_development_dependency "html-proofer", "~> 2.5"
|
||||
spec.require_paths = ['lib']
|
||||
|
||||
spec.add_dependency 'jekyll', '>= 2.0'
|
||||
spec.add_development_dependency 'bundler', '~> 1.10'
|
||||
spec.add_development_dependency 'rake', '~> 10.0'
|
||||
spec.add_development_dependency 'rspec', '~> 3.3'
|
||||
spec.add_development_dependency 'html-proofer', '~> 2.5'
|
||||
spec.add_development_dependency 'rubocop', '~> 0.37'
|
||||
end
|
||||
|
|
|
@ -1,36 +1,42 @@
|
|||
require 'jekyll-seo-tag/filters'
|
||||
|
||||
module Jekyll
|
||||
class SeoTag < Liquid::Tag
|
||||
|
||||
attr_accessor :context
|
||||
|
||||
def initialize(_, markup, _)
|
||||
MINIFY_REGEX = /(>\n|[%}]})\s+(<|{[{%])/
|
||||
|
||||
def initialize(_tag_name, text, _tokens)
|
||||
super
|
||||
@options = {
|
||||
"title" => !(markup =~ /title\s*:\s*false/i)
|
||||
}
|
||||
@text = text
|
||||
end
|
||||
|
||||
def render(context)
|
||||
@context = context
|
||||
output = template.render!(payload, info)
|
||||
|
||||
output
|
||||
template.render!(payload, info)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def options
|
||||
{
|
||||
'version' => VERSION,
|
||||
"title" => !(@text =~ /title\s*:\s*false/i)
|
||||
}
|
||||
end
|
||||
|
||||
def payload
|
||||
{
|
||||
"page" => context.registers[:page],
|
||||
"site" => context.registers[:site].site_payload["site"],
|
||||
"seo" => @options
|
||||
'page' => context.registers[:page],
|
||||
'site' => context.registers[:site].site_payload['site'],
|
||||
'seo_tag' => options
|
||||
}
|
||||
end
|
||||
|
||||
def info
|
||||
{
|
||||
:registers => context.registers,
|
||||
:filters => [Jekyll::Filters]
|
||||
registers: context.registers,
|
||||
filters: [Jekyll::Filters, JekyllSeoTag::Filters]
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -39,11 +45,15 @@ module Jekyll
|
|||
end
|
||||
|
||||
def template_contents
|
||||
@template_contents ||= File.read(template_path).gsub(/(>\n|[%}]})\s+(<|{[{%])/,'\1\2').chomp
|
||||
@template_contents ||= begin
|
||||
File.read(template_path).gsub(MINIFY_REGEX, '\1\2').chomp
|
||||
end
|
||||
end
|
||||
|
||||
def template_path
|
||||
@template_path ||= File.expand_path "./template.html", File.dirname(__FILE__)
|
||||
@template_path ||= begin
|
||||
File.expand_path './template.html', File.dirname(__FILE__)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
module JekyllSeoTag
|
||||
module Filters
|
||||
# This is available in Liquid from version 3 which is required by Jekyll 3
|
||||
# Provided here for compatibility with Jekyll 2.x
|
||||
def default(input, default_value = ''.freeze)
|
||||
if !input || input.respond_to?(:empty?) && input.empty?
|
||||
default_value
|
||||
else
|
||||
input
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,6 +3,6 @@ module Liquid; class Tag; end; end
|
|||
|
||||
module Jekyll
|
||||
class SeoTag < Liquid::Tag
|
||||
VERSION = "0.1.4"
|
||||
VERSION = '1.1.0'.freeze
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,50 +1,59 @@
|
|||
<!-- Begin Jekyll SEO tag -->
|
||||
<!-- Begin Jekyll SEO tag v{{ seo_tag.version }} -->
|
||||
|
||||
{% if site.url %}
|
||||
{% assign seo_url = site.url | append: site.baseurl %}
|
||||
{% elsif site.github.url %}
|
||||
{% assign seo_url = site.github.url %}
|
||||
{% endif %}
|
||||
|
||||
{% if site.title %}
|
||||
{% assign seo_site_title = site.title %}
|
||||
{% elsif site.name %}
|
||||
{% assign seo_site_title = site.name %}
|
||||
{% endif %}
|
||||
{% assign seo_url = seo_url | default: site.github.url %}
|
||||
{% assign seo_site_title = site.title | default: site.name %}
|
||||
|
||||
{% if page.title %}
|
||||
{% assign seo_title = page.title %}
|
||||
{% assign seo_page_title = page.title %}
|
||||
|
||||
{% if seo_site_title %}
|
||||
{% assign seo_title = seo_title | append:" - " | append: seo_site_title %}
|
||||
{% endif %}
|
||||
{% elsif seo_site_title %}
|
||||
{% assign seo_title = seo_site_title %}
|
||||
{% assign seo_page_title = seo_site_title %}
|
||||
|
||||
{% if site.description %}
|
||||
{% assign seo_title = seo_title | append:" - " | append: site.description %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if seo_title %}
|
||||
{% assign seo_title = seo_title | markdownify | strip_html | strip_newlines | escape_once %}
|
||||
{% endif %}
|
||||
|
||||
{% if seo_site_title %}
|
||||
{% assign seo_site_title = seo_site_title | markdownify | strip_html | strip_newlines | escape_once %}
|
||||
{% endif %}
|
||||
|
||||
{% if seo_page_title %}
|
||||
{% assign seo_page_title = seo_page_title | markdownify | strip_html | strip_newlines | escape_once %}
|
||||
{% endif %}
|
||||
|
||||
{% if page.description %}
|
||||
{% assign seo_description = page.description %}
|
||||
{% elsif site.description %}
|
||||
{% assign seo_description = site.description %}
|
||||
{% endif %}
|
||||
{% assign seo_description = page.description | default: page.excerpt | default: site.description %}
|
||||
{% if seo_description %}
|
||||
{% assign seo_description = seo_description | markdownify | strip_html | strip_newlines | escape_once %}
|
||||
{% endif %}
|
||||
|
||||
{% if seo_title and seo.title %}
|
||||
{% assign seo_author = page.author | default: page.authors[0] | default: site.author %}
|
||||
{% if seo_author %}
|
||||
{% if seo_author.twitter %}
|
||||
{% assign seo_author_twitter = seo_author.twitter %}
|
||||
{% else %}
|
||||
{% if site.data.authors and site.data.authors[seo_author] %}
|
||||
{% assign seo_author_twitter = site.data.authors[seo_author].twitter %}
|
||||
{% else %}
|
||||
{% assign seo_author_twitter = seo_author %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% assign seo_author_twitter = seo_author_twitter | replace:"@","" %}
|
||||
{% endif %}
|
||||
|
||||
{% if seo_tag.title and seo_title %}
|
||||
<title>{{ seo_title }}</title>
|
||||
{% endif %}
|
||||
|
||||
|
@ -58,7 +67,7 @@
|
|||
{% endif %}
|
||||
|
||||
{% if seo_url %}
|
||||
<link rel="canonical" href="{{ page.url | prepend: seo_url | replace:'/index.html','/' }}" itemprop="url" />
|
||||
<link rel="canonical" href="{{ page.url | prepend: seo_url | replace:'/index.html','/' }}" />
|
||||
<meta property='og:url' content='{{ page.url | prepend: seo_url | replace:'/index.html','/' }}' />
|
||||
{% endif %}
|
||||
|
||||
|
@ -75,17 +84,21 @@
|
|||
{% endif %}
|
||||
|
||||
{% if page.image %}
|
||||
<meta property="og:image" content="{{ page.image }}" />
|
||||
<meta property="og:image" content="{{ page.image | prepend: "/" | prepend: seo_url | escape }}" />
|
||||
{% endif %}
|
||||
|
||||
{% if page.date %}
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="article:published_time" content="{{ page.date | date_to_xmlschema }}" />
|
||||
|
||||
{% if page.next.url %}
|
||||
<link rel="next" href="{{ page.next.url | prepend: seo_url | replace:'/index.html','/' }}" title="{{ page.next.title | escape }}" />
|
||||
{% endif %}
|
||||
|
||||
{% if page.previous.url %}
|
||||
<link rel="prev" href="{{ page.previous.url | prepend: seo_url | replace:'/index.html','/' }}" title="{{ page.previous.title | escape }}" />
|
||||
{% endif %}
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "http://schema.org",
|
||||
|
@ -93,7 +106,7 @@
|
|||
"headline": {{ page.title | jsonify }},
|
||||
"image": {{ page.image | jsonify }},
|
||||
"datePublished": {{ page.date | date_to_xmlschema | jsonify }},
|
||||
"description": {{ page.description | jsonify }}
|
||||
"description": {{ seo_description | jsonify }}
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
@ -103,14 +116,25 @@
|
|||
<meta name="twitter:site" content="@{{ site.twitter.username | replace:"@","" }}" />
|
||||
<meta name="twitter:title" content="{{ seo_title }}" />
|
||||
<meta name="twitter:description" content="{{ seo_description }}" />
|
||||
|
||||
{% if page.image %}
|
||||
<meta name="twitter:image" content="{{ page.image | escape }}" />
|
||||
{% endif %}
|
||||
{% if page.author %}
|
||||
<meta name="twitter:creator" content="@{{ page.author | replace:"@","" }}" />
|
||||
|
||||
{% if seo_author_twitter %}
|
||||
<meta name="twitter:creator" content="@{{ seo_author_twitter }}" />
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if site.facebook %}
|
||||
<meta property="article:publisher" content="{{ site.facebook.publisher }}" />
|
||||
<meta property="fb:app_id" content="{{ site.facebook.app_id }}" />
|
||||
{% endif %}
|
||||
|
||||
{% if site.google_site_verification %}
|
||||
<meta name="google-site-verification" content="{{ site.google_site_verification }}" />
|
||||
{% endif %}
|
||||
|
||||
{% if site.logo %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
set -ex
|
||||
|
||||
bundle exec rake spec
|
||||
bundle exec rubocop -S -D
|
||||
bundle exec gem build jekyll-seo-tag.gemspec
|
||||
|
|
|
@ -3,3 +3,5 @@ title: Some "post" & a test
|
|||
description: A post
|
||||
layout: default
|
||||
---
|
||||
|
||||
Blah blah
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
Blah blah
|
||||
|
|
|
@ -1,184 +1,300 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Jekyll::SeoTag do
|
||||
|
||||
subject { Jekyll::SeoTag.new("seo", nil, nil) }
|
||||
# subject { Jekyll::SeoTag.parse('seo', nil, nil, nil) }
|
||||
let(:page) { make_page }
|
||||
let(:site) { make_site }
|
||||
let(:post) { make_post }
|
||||
let(:context) { make_context(page: page, site: site) }
|
||||
let(:tag) { 'seo' }
|
||||
let(:text) { '' }
|
||||
let(:output) { Liquid::Template.parse("{% #{tag} #{text} %}").render!(context, {}) }
|
||||
let(:json) { output.match(%r{<script type=\"application/ld\+json\">(.*)</script>}m)[1] }
|
||||
let(:json_data) { JSON.parse(json) }
|
||||
|
||||
before do
|
||||
Jekyll.logger.log_level = :error
|
||||
end
|
||||
|
||||
it "builds" do
|
||||
expect(subject.render(context)).to match(/Jekyll SEO tag/i)
|
||||
it 'builds' do
|
||||
expect(output).to match(/Jekyll SEO tag/i)
|
||||
end
|
||||
|
||||
it "builds the title with a page title only" do
|
||||
page = page({"title" => "foo"})
|
||||
context = context({ :page => page })
|
||||
expect(subject.render(context)).to match(/<title>foo<\/title>/)
|
||||
expect(subject.render(context)).to match(/<meta property="og:title" content="foo" \/>/)
|
||||
it 'outputs the plugin version' do
|
||||
version = Jekyll::SeoTag::VERSION
|
||||
expect(output).to match(/Jekyll SEO tag v#{version}/i)
|
||||
end
|
||||
|
||||
it "builds the title with a page title and site title" do
|
||||
page = page({"title" => "foo"})
|
||||
site = site({"title" => "bar"})
|
||||
context = context({ :page => page, :site => site })
|
||||
expect(subject.render(context)).to match(/<title>foo - bar<\/title>/)
|
||||
context 'with page.title' do
|
||||
let(:page) { make_page('title' => 'foo') }
|
||||
|
||||
it 'builds the title with a page title only' do
|
||||
expect(output).to match(%r{<title>foo</title>})
|
||||
expected = %r{<meta property="og:title" content="foo" />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
|
||||
context 'with site.title' do
|
||||
let(:site) { make_site('title' => 'bar') }
|
||||
|
||||
it 'builds the title with a page title and site title' do
|
||||
expect(output).to match(%r{<title>foo - bar</title>})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "builds the title with only a site title" do
|
||||
site = site({"title" => "foo"})
|
||||
context = context({ :site => site })
|
||||
expect(subject.render(context)).to match(/<title>foo<\/title>/)
|
||||
context 'with site.title' do
|
||||
let(:site) { make_site('title' => 'Site title') }
|
||||
|
||||
it 'builds the title with only a site title' do
|
||||
expect(output).to match(%r{<title>Site title</title>})
|
||||
end
|
||||
end
|
||||
|
||||
it "uses the page description" do
|
||||
page = page({"description" => "foo"})
|
||||
context = context({ :page => page })
|
||||
expect(subject.render(context)).to match(/<meta name="description" content="foo" \/>/)
|
||||
expect(subject.render(context)).to match(/<meta property='og:description' content="foo" \/>/)
|
||||
context 'with page.description' do
|
||||
let(:page) { make_page('description' => 'foo') }
|
||||
|
||||
it 'uses the page description' do
|
||||
expect(output).to match(%r{<meta name="description" content="foo" />})
|
||||
expect(output).to match(%r{<meta property='og:description' content="foo" />})
|
||||
end
|
||||
end
|
||||
|
||||
it "uses the site description when no page description exists" do
|
||||
site = site({"description" => "foo"})
|
||||
context = context({ :site => site })
|
||||
expect(subject.render(context)).to match(/<meta name="description" content="foo" \/>/)
|
||||
expect(subject.render(context)).to match(/<meta property='og:description' content="foo" \/>/)
|
||||
context 'with page.excerpt' do
|
||||
let(:page) { make_page('excerpt' => 'foo') }
|
||||
|
||||
it 'uses the page excerpt when no page description exists' do
|
||||
expect(output).to match(%r{<meta name="description" content="foo" />})
|
||||
expect(output).to match(%r{<meta property='og:description' content="foo" />})
|
||||
end
|
||||
end
|
||||
|
||||
it "uses the site url to build the seo url" do
|
||||
site = site({"url" => "http://example.invalid"})
|
||||
context = context({ :site => site })
|
||||
expected = /<link rel="canonical" href="http:\/\/example.invalid\/page.html" itemprop="url" \/>/
|
||||
expect(subject.render(context)).to match(expected)
|
||||
expected = /<meta property='og:url' content='http:\/\/example.invalid\/page.html' \/>/
|
||||
expect(subject.render(context)).to match(expected)
|
||||
context 'with site.description' do
|
||||
let(:site) { make_site('description' => 'foo') }
|
||||
|
||||
it 'uses the site description when no page description nor excerpt exist' do
|
||||
expect(output).to match(%r{<meta name="description" content="foo" />})
|
||||
expect(output).to match(%r{<meta property='og:description' content="foo" />})
|
||||
end
|
||||
end
|
||||
|
||||
it "uses site.github.url to build the seo url" do
|
||||
site = site({"github" => { "url" => "http://example.invalid" }} )
|
||||
context = context({ :site => site })
|
||||
expected = /<link rel="canonical" href="http:\/\/example.invalid\/page.html" itemprop="url" \/>/
|
||||
expect(subject.render(context)).to match(expected)
|
||||
expected = /<meta property='og:url' content='http:\/\/example.invalid\/page.html' \/>/
|
||||
expect(subject.render(context)).to match(expected)
|
||||
context 'with site.url' do
|
||||
let(:site) { make_site('url' => 'http://example.invalid') }
|
||||
|
||||
it 'uses the site url to build the seo url' do
|
||||
expected = %r{<link rel="canonical" href="http://example.invalid/page.html" />}
|
||||
expect(output).to match(expected)
|
||||
expected = %r{<meta property='og:url' content='http://example.invalid/page.html' />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
|
||||
context 'with page.permalink' do
|
||||
let(:page) { make_page('permalink' => '/page/index.html') }
|
||||
|
||||
it "uses replaces '/index.html' with '/'" do
|
||||
expected = %r{<link rel="canonical" href="http://example.invalid/page/" />}
|
||||
expect(output).to match(expected)
|
||||
|
||||
expected = %r{<meta property='og:url' content='http://example.invalid/page/' />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with site.baseurl' do
|
||||
let(:site) { make_site('url' => 'http://example.invalid', 'baseurl' => '/foo') }
|
||||
it 'uses baseurl to build the seo url' do
|
||||
expected = %r{<link rel="canonical" href="http://example.invalid/foo/page.html" />}
|
||||
expect(output).to match(expected)
|
||||
expected = %r{<meta property='og:url' content='http://example.invalid/foo/page.html' />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with page.image' do
|
||||
let(:page) { make_page('image' => 'foo.png') }
|
||||
|
||||
it 'outputs the image' do
|
||||
expected = %r{<meta property="og:image" content="http://example.invalid/foo.png" />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with site.logo' do
|
||||
let(:site) { make_site('logo' => 'logo.png', 'url' => 'http://example.invalid') }
|
||||
|
||||
it 'outputs the logo' do
|
||||
expect(json_data['logo']).to eql('http://example.invalid/logo.png')
|
||||
expect(json_data['url']).to eql('http://example.invalid')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with site.title' do
|
||||
let(:site) { make_site('title' => 'Foo', 'url' => 'http://example.invalid') }
|
||||
|
||||
it 'outputs the site title meta' do
|
||||
expect(output).to match(%r{<meta property="og:site_name" content="Foo" />})
|
||||
expect(json_data['name']).to eql('Foo')
|
||||
expect(json_data['url']).to eql('http://example.invalid')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "uses replaces '/index.html' with '/'" do
|
||||
page = page({ "permalink" => "/page/index.html" })
|
||||
site = site({ "url" => "http://example.invalid" })
|
||||
context = context({ :page => page, :site => site })
|
||||
expected = %r!<link rel="canonical" href="http://example.invalid/page/" itemprop="url" />!
|
||||
expected = %r!<meta property='og:url' content='http://example.invalid/page/' />!
|
||||
expect(subject.render(context)).to match(expected)
|
||||
context 'with site.github.url' do
|
||||
let(:github_namespace) { { 'url' => 'http://example.invalid' } }
|
||||
let(:site) { make_site('github' => github_namespace) }
|
||||
|
||||
it 'uses site.github.url to build the seo url' do
|
||||
expected = %r{<link rel="canonical" href="http://example.invalid/page.html" \/>}
|
||||
expect(output).to match(expected)
|
||||
expected = %r{<meta property='og:url' content='http://example.invalid/page.html' />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
end
|
||||
|
||||
it "uses baseurl to build the seo url" do
|
||||
site = site({ "url" => "http://example.invalid", "baseurl" => "/foo" })
|
||||
context = context({ :site => site })
|
||||
expected = %r!<link rel="canonical" href="http://example.invalid/foo/page.html" itemprop="url" />!
|
||||
expect(subject.render(context)).to match(expected)
|
||||
expected = %r!<meta property='og:url' content='http://example.invalid/foo/page.html' />!
|
||||
expect(subject.render(context)).to match(expected)
|
||||
context 'posts' do
|
||||
context 'with post meta' do
|
||||
let(:meta) do
|
||||
{
|
||||
'title' => 'post',
|
||||
'description' => 'description',
|
||||
'image' => '/img.png'
|
||||
}
|
||||
end
|
||||
let(:page) { make_post(meta) }
|
||||
|
||||
it 'outputs post meta' do
|
||||
expected = %r{<meta property="og:type" content="article" />}
|
||||
expect(output).to match(expected)
|
||||
|
||||
expect(json_data['headline']).to eql('post')
|
||||
expect(json_data['description']).to eql('description')
|
||||
expect(json_data['image']).to eql('/img.png')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "outputs the site title meta" do
|
||||
site = site({"title" => "Foo", "url" => "http://example.invalid"})
|
||||
context = context({ :site => site })
|
||||
output = subject.render(context)
|
||||
context 'twitter' do
|
||||
context 'with site.twitter.username' do
|
||||
let(:site_twitter) { { 'username' => 'jekyllrb' } }
|
||||
let(:site) { make_site('twitter' => site_twitter) }
|
||||
|
||||
expect(output).to match(/<meta property="og:site_name" content="Foo" \/>/)
|
||||
data = output.match(/<script type=\"application\/ld\+json\">(.*)<\/script>/m)[1]
|
||||
context 'with page.author as a string' do
|
||||
let(:page) { make_page('author' => 'benbalter') }
|
||||
|
||||
data = JSON.parse(data)
|
||||
expect(data["name"]).to eql("Foo")
|
||||
expect(data["url"]).to eql("http://example.invalid")
|
||||
it 'outputs twitter card meta' do
|
||||
expected = %r{<meta name="twitter:site" content="@jekyllrb" />}
|
||||
expect(output).to match(expected)
|
||||
|
||||
expected = %r{<meta name="twitter:creator" content="@benbalter" />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
|
||||
context 'with an @' do
|
||||
let(:page) { make_page('author' => '@benbalter') }
|
||||
|
||||
it 'outputs the twitter card' do
|
||||
expected = %r{<meta name="twitter:creator" content="@benbalter" />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with site.data.authors' do
|
||||
let(:author_data) { {} }
|
||||
let(:data) { { 'authors' => author_data } }
|
||||
let(:site) { make_site('data' => data, 'twitter' => site_twitter) }
|
||||
|
||||
context 'with the author in site.data.authors' do
|
||||
let(:author_data) { { 'benbalter' => { 'twitter' => 'test' } } }
|
||||
it 'outputs the twitter card' do
|
||||
expected = %r{<meta name="twitter:creator" content="@test" />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without the author in site.data.authors' do
|
||||
it 'outputs the twitter card' do
|
||||
expected = %r{<meta name="twitter:creator" content="@benbalter" />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with page.author as a hash' do
|
||||
let(:page) { make_page('author' => { 'twitter' => '@test' }) }
|
||||
|
||||
it 'supports author data as a hash' do
|
||||
expected = %r{<meta name="twitter:creator" content="@test" />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with page.authors as an array' do
|
||||
let(:page) { make_page('authors' => %w(test foo)) }
|
||||
|
||||
it 'supports author data as an array' do
|
||||
expected = %r{<meta name="twitter:creator" content="@test" />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with site.author as a hash' do
|
||||
let(:author) { { 'twitter' => '@test' } }
|
||||
let(:site) { make_site('author' => author, 'twitter' => site_twitter) }
|
||||
|
||||
it 'supports author data as an hash' do
|
||||
expected = %r{<meta name="twitter:creator" content="@test" />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "outputs post meta" do
|
||||
post = post({"title" => "post", "description" => "description", "image" => "/img.png" })
|
||||
context = context({ :page => post })
|
||||
output = subject.render(context)
|
||||
expected = /<meta property="og:type" content="article" \/>/
|
||||
expect(output).to match(expected)
|
||||
data = output.match(/<script type=\"application\/ld\+json\">(.*)<\/script>/m)[1]
|
||||
data = JSON.parse(data)
|
||||
context 'with site.social' do
|
||||
let(:links) { ['http://foo.invalid', 'http://bar.invalid'] }
|
||||
let(:social_namespace) { { 'name' => 'Ben', 'links' => links } }
|
||||
let(:site) { make_site('social' => social_namespace) }
|
||||
|
||||
expect(data["headline"]).to eql("post")
|
||||
expect(data["description"]).to eql("description")
|
||||
expect(data["image"]).to eql("/img.png")
|
||||
it 'outputs social meta' do
|
||||
expect(json_data['@type']).to eql('person')
|
||||
expect(json_data['name']).to eql('Ben')
|
||||
expect(json_data['sameAs']).to eql(links)
|
||||
end
|
||||
end
|
||||
|
||||
it "outputs twitter card meta" do
|
||||
site = site({"twitter" => { "username" => "jekyllrb" }})
|
||||
page = page({"author" => "benbalter"})
|
||||
context = context({ :site => site, :page => page })
|
||||
context 'with site.name' do
|
||||
let(:site) { make_site('name' => 'Site name') }
|
||||
|
||||
expected = /<meta name="twitter:site" content="@jekyllrb" \/>/
|
||||
expect(subject.render(context)).to match(expected)
|
||||
it 'uses site.name if site.title is not present' do
|
||||
expected = %r{<meta property="og:site_name" content="Site name" />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
|
||||
expected = /<meta name="twitter:creator" content="@benbalter" \/>/
|
||||
expect(subject.render(context)).to match(expected)
|
||||
context 'with site.title' do
|
||||
let(:site) { make_site('name' => 'Site Name', 'title' => 'Site Title') }
|
||||
|
||||
it 'uses site.tile if both site.title and site.name are present' do
|
||||
expected = %r{<meta property="og:site_name" content="Site Title" />}
|
||||
expect(output).to match(expected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "outputs social meta" do
|
||||
links = ["http://foo.invalid", "http://bar.invalid"]
|
||||
site = site({"social" => { "name" => "Ben", "links" => links }})
|
||||
context = context({ :site => site })
|
||||
output = subject.render(context)
|
||||
data = output.match(/<script type=\"application\/ld\+json\">(.*)<\/script>/m)[1]
|
||||
data = JSON.parse(data)
|
||||
|
||||
expect(data["@type"]).to eql("person")
|
||||
expect(data["name"]).to eql("Ben")
|
||||
expect(data["sameAs"]).to eql(links)
|
||||
context 'with title=false' do
|
||||
it "does not output a <title> tag if title:false" do
|
||||
site = site({"name" => "Site Name", "title" => "Site Title" })
|
||||
context = context({ :site => site })
|
||||
expected = %r!<title>!
|
||||
expect(output).not_to match(expected)
|
||||
end
|
||||
end
|
||||
|
||||
it "outputs the logo" do
|
||||
site = site({"logo" => "logo.png", "url" => "http://example.invalid" })
|
||||
context = context({ :site => site })
|
||||
output = subject.render(context)
|
||||
data = output.match(/<script type=\"application\/ld\+json\">(.*)<\/script>/m)[1]
|
||||
data = JSON.parse(data)
|
||||
|
||||
expect(data["logo"]).to eql("http://example.invalid/logo.png")
|
||||
expect(data["url"]).to eql("http://example.invalid")
|
||||
end
|
||||
|
||||
it "outputs the image" do
|
||||
page = page({"image" => "http://foo.invalid/foo.png"})
|
||||
context = context({ :page => page })
|
||||
expected = /<meta property="og:image" content="http:\/\/foo.invalid\/foo.png" \/>/
|
||||
expect(subject.render(context)).to match(expected)
|
||||
end
|
||||
|
||||
it "uses site.name if site.title is not present" do
|
||||
site = site({"name" => "Site Name", "title" => nil })
|
||||
context = context({ :site => site })
|
||||
expected = %r!<meta property="og:site_name" content="Site Name" />!
|
||||
expect(subject.render(context)).to match(expected)
|
||||
end
|
||||
|
||||
it "uses site.tile if both site.title and site.name are present" do
|
||||
site = site({"name" => "Site Name", "title" => "Site Title" })
|
||||
context = context({ :site => site })
|
||||
expected = %r!<meta property="og:site_name" content="Site Title" />!
|
||||
expect(subject.render(context)).to match(expected)
|
||||
end
|
||||
|
||||
it "does not output a <title> tag if title:false" do
|
||||
site = site({"name" => "Site Name", "title" => "Site Title" })
|
||||
context = context({ :site => site })
|
||||
output = Liquid::Template.parse("{% seo title:false %}").render!(context, {})
|
||||
expected = %r!<title>!
|
||||
expect(output).not_to match(expected)
|
||||
end
|
||||
|
||||
it "outputs valid HTML" do
|
||||
it 'outputs valid HTML' do
|
||||
site.process
|
||||
options = {
|
||||
:check_html => true,
|
||||
:checks_to_ignore => ["ScriptCheck", "LinkCheck", "ImageCheck"]
|
||||
check_html: true,
|
||||
checks_to_ignore: %w(ScriptCheck LinkCheck ImageCheck)
|
||||
}
|
||||
status = HTML::Proofer.new(dest_dir, options).run
|
||||
expect(status).to eql(true)
|
||||
|
|
|
@ -3,39 +3,41 @@ require 'jekyll'
|
|||
require 'jekyll-seo-tag'
|
||||
require 'html/proofer'
|
||||
|
||||
ENV["JEKYLL_LOG_LEVEL"] = "error"
|
||||
ENV['JEKYLL_LOG_LEVEL'] = 'error'
|
||||
|
||||
def dest_dir
|
||||
File.expand_path("../tmp/dest", File.dirname(__FILE__))
|
||||
File.expand_path('../tmp/dest', File.dirname(__FILE__))
|
||||
end
|
||||
|
||||
def source_dir
|
||||
File.expand_path("./fixtures", File.dirname(__FILE__))
|
||||
File.expand_path('./fixtures', File.dirname(__FILE__))
|
||||
end
|
||||
|
||||
CONFIG_DEFAULTS = {
|
||||
"source" => source_dir,
|
||||
"destination" => dest_dir,
|
||||
"gems" => ["jekyll-seo-tag"]
|
||||
}
|
||||
'source' => source_dir,
|
||||
'destination' => dest_dir,
|
||||
'gems' => ['jekyll-seo-tag']
|
||||
}.freeze
|
||||
|
||||
def page(options={})
|
||||
page = Jekyll::Page.new site, CONFIG_DEFAULTS["source"], "", "page.md"
|
||||
def make_page(options = {})
|
||||
page = Jekyll::Page.new site, CONFIG_DEFAULTS['source'], '', 'page.md'
|
||||
page.data = options
|
||||
page
|
||||
end
|
||||
|
||||
def post(options={})
|
||||
page = Jekyll::Post.new site, CONFIG_DEFAULTS["source"], "", "2015-01-01-post.md"
|
||||
page.data = options
|
||||
def make_post(options = {})
|
||||
filename = File.expand_path('2015-01-01-post.md', CONFIG_DEFAULTS['source'])
|
||||
config = { site: site, collection: site.collections['posts'] }
|
||||
page = Jekyll::Document.new filename, config
|
||||
page.merge_data!(options)
|
||||
page
|
||||
end
|
||||
|
||||
def site(options={})
|
||||
def make_site(options = {})
|
||||
config = Jekyll.configuration CONFIG_DEFAULTS.merge(options)
|
||||
Jekyll::Site.new(config)
|
||||
end
|
||||
|
||||
def context(registers={})
|
||||
Liquid::Context.new({}, {}, { :site => site, :page => page }.merge(registers))
|
||||
def make_context(registers = {})
|
||||
Liquid::Context.new({}, {}, { site: site, page: page }.merge(registers))
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue