From c7b7caa518b708af5c38a52ee68361d2b1935489 Mon Sep 17 00:00:00 2001 From: Chris Chapman Date: Sat, 23 Nov 2019 00:53:15 -0700 Subject: [PATCH] Updated specs in preparation for issue #88. --- .gitignore | 4 +- .rspec | 2 +- .rubocop_todo.yml | 8 -- Rakefile | 16 +++- spec/helpers/atom_feed_spec.rb | 22 ++++-- spec/helpers/dates_spec.rb | 13 +++- spec/helpers/dictionaries_spec.rb | 44 ++++++----- spec/helpers/link_to_spec.rb | 38 ++++++++-- spec/spec_helper.rb | 121 ++++++++++++++++++++---------- 9 files changed, 183 insertions(+), 85 deletions(-) diff --git a/.gitignore b/.gitignore index 851d8656..ed194841 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,6 @@ items/static/assets/components/ tmp/ .bundle *.gem -/coverage/ -/.yardoc/ +coverage +.yardoc var/company.ldif diff --git a/.rspec b/.rspec index a59484b2..43a58e61 100644 --- a/.rspec +++ b/.rspec @@ -1,3 +1,3 @@ +-r ./spec/spec_helper.rb --format Fuubar --color ---require spec_helper diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 566b090e..55648282 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -269,14 +269,6 @@ RSpec/FilePath: - 'spec/helpers/dictionaries_spec.rb' - 'spec/helpers/link_to_spec.rb' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, each, example -RSpec/HookArgument: - Exclude: - - 'spec/spec_helper.rb' - # Offense count: 17 # Configuration parameters: IgnoreSharedExamples. RSpec/NamedSubject: diff --git a/Rakefile b/Rakefile index ac2c2fa4..1d1e4189 100644 --- a/Rakefile +++ b/Rakefile @@ -1,16 +1,24 @@ -require 'rubocop/rake_task' +# frozen_string_literal: true + +require 'rake/testtask' require 'rspec/core/rake_task' +require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) do |task| task.formatters = ['simple'] task.fail_on_error = false end +Rake::TestTask.new(:test_all) do |task| + task.test_files = Dir['test/**/test_*.rb'] + task.libs << 'test' + task.verbose = false +end + RSpec::Core::RakeTask.new(:spec) do |task| task.verbose = false end -desc 'Run all tests and specs' -task test: [:spec] +task test: %i[spec test_all] -task default: [:test, :rubocop] +task default: %i[test rubocop] diff --git a/spec/helpers/atom_feed_spec.rb b/spec/helpers/atom_feed_spec.rb index 322398aa..bc38121a 100644 --- a/spec/helpers/atom_feed_spec.rb +++ b/spec/helpers/atom_feed_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'helpers/atom_feed' -RSpec.describe LifePreserver::AtomFeed, helper: true do +RSpec.describe LifePreserver::Helpers::AtomFeed, helper: true do before do allow(ctx.dependency_tracker).to receive(:enter) allow(ctx.dependency_tracker).to receive(:exit) @@ -12,9 +14,10 @@ let(:item_attributes) { {} } before do - ctx.item = ctx.create_item('Feed', item_attributes, '/feed/') - ctx.create_rep(ctx.item, '/feed.xml') + ctx.create_item('Feed', item_attributes, '/feed') + ctx.create_rep(ctx.items['/feed'], '/feed.xml') + ctx.item = ctx.items['/feed'] ctx.config[:base_url] = base_url end @@ -40,7 +43,7 @@ end context 'without feed_url' do - it 'returns base_url + path' do + it 'returns base URL + path' do expect(subject).to eql('http://url.base/feed.xml') end end @@ -48,21 +51,22 @@ end describe '#atom_tag_for' do - subject { helper.atom_tag_for(ctx.items['/stuff/']) } + subject { helper.atom_tag_for(ctx.items['/stuff']) } let(:item_attributes) { { created_at: '2015-05-19 12:34:56' } } let(:item_rep_path) { '/stuff.xml' } let(:base_url) { 'http://url.base' } before do - item = ctx.create_item('Stuff', item_attributes, '/stuff/') - ctx.create_rep(item, item_rep_path) + ctx.create_item('Stuff', item_attributes, '/stuff') + ctx.create_rep(ctx.items['/stuff'], item_rep_path) ctx.config[:base_url] = base_url end context 'item with path' do let(:item_rep_path) { '/stuff.xml' } + it { is_expected.to eql('tag:url.base,2015-05-19:/stuff.xml') } end @@ -76,11 +80,13 @@ context 'base URL without subdir' do let(:base_url) { 'http://url.base' } + it { is_expected.to eql('tag:url.base,2015-05-19:/stuff.xml') } end context 'base URL with subdir' do let(:base_url) { 'http://url.base/sub' } + it { is_expected.to eql('tag:url.base,2015-05-19:/sub/stuff.xml') } end @@ -88,6 +94,7 @@ let(:item_attributes) do { created_at: Date.parse('2015-05-19 12:34:56') } end + it { is_expected.to eql('tag:url.base,2015-05-19:/stuff.xml') } end @@ -95,6 +102,7 @@ let(:item_attributes) do { created_at: Time.parse('2015-05-19 12:34:56') } end + it { is_expected.to eql('tag:url.base,2015-05-19:/stuff.xml') } end diff --git a/spec/helpers/dates_spec.rb b/spec/helpers/dates_spec.rb index 4b205768..1dccda30 100644 --- a/spec/helpers/dates_spec.rb +++ b/spec/helpers/dates_spec.rb @@ -1,6 +1,13 @@ +# frozen_string_literal: true + require 'helpers/dates' -RSpec.describe LifePreserver::Dates, helper: true do +RSpec.describe LifePreserver::Helpers::Dates, helper: true do + before do + allow(ctx.dependency_tracker).to receive(:enter) + allow(ctx.dependency_tracker).to receive(:exit) + end + describe '#attribute_to_time' do subject { helper.attribute_to_time(arg) } @@ -13,21 +20,25 @@ context 'with Time instance' do let(:arg) { around_noon_utc } + it { is_expected.to eql(around_noon_utc) } end context 'with Date instance' do let(:arg) { Date.new(2015, 11, 7) } + it { is_expected.to eql(beginning_of_day_utc) } end context 'with DateTime instance' do let(:arg) { DateTime.new(2015, 11, 7, 13, 31, 16) } + it { is_expected.to eql(around_noon_utc) } end context 'with string' do let(:arg) { '2015-11-7 13:31:16' } + it { is_expected.to eql(around_noon_local) } end end diff --git a/spec/helpers/dictionaries_spec.rb b/spec/helpers/dictionaries_spec.rb index c06665ce..4ec026f4 100644 --- a/spec/helpers/dictionaries_spec.rb +++ b/spec/helpers/dictionaries_spec.rb @@ -1,8 +1,10 @@ -require 'helpers/dictionaries' +# frozen_string_literal: true + require 'ffi/hunspell' +require 'helpers/dictionaries' -RSpec.describe LifePreserver::Dictionaries, helper: true, chdir: false do - let(:directories) { ['etc/dictionaries'] } +RSpec.describe LifePreserver::Helpers::Dictionaries, helper: true, chdir: false do + let(:directories) { ['var/dictionaries'] } let(:default_lang) { 'en_US' } before do @@ -20,7 +22,7 @@ after do # clear out dictionary cache - LifePreserver::Dictionaries.class_variable_set(:@@dictionaries, {}) + LifePreserver::Helpers::Dictionaries.class_variable_set(:@@dictionary_cache, {}) end describe '.dictionary' do @@ -40,21 +42,21 @@ end context 'with a hunspell language code' do - subject { helper.dictionary(lang).lang } + subject { helper.dictionary(lang) } context 'with a supported language value' do let(:lang) { 'en_US' } it 'selects the given language' do - expect(subject).to eq(lang) + expect(subject.lang).to eq(lang) end end context 'with an unsupported language value' do let(:lang) { 'zz_YY' } - it 'raises an exception' do - expect { subject }.to raise_error(RuntimeError) + it 'should be nil' do + expect(subject).to be_nil end end @@ -62,7 +64,7 @@ let(:lang) { nil } it 'selects the default dictionary language' do - expect(subject).to eq(FFI::Hunspell.lang) + expect(subject.lang).to eq(FFI::Hunspell.lang) end end @@ -70,18 +72,19 @@ let(:lang) { 'en-Latn-US-x-twain' } it 'maps to the hunspell value' do - expect(subject).to eq('en_US') + expect(subject.lang).to eq('en_US') end end end context 'with a non-existent base dictionary' do - let(:lang) { 'en_UK' } - before do - ctx.create_item('content', { kind: 'extra-dictionary' }, '/dir1/en_UK.dic') + Locale.set_app_language_tags('en_US', 'en_GB', 'fr_FR') + ctx.create_item('content', { kind: 'extra-dictionary' }, '/dir3/fr_FR.dic') end + let(:lang) { 'fr_FR' } + it 'does not accept an extra dictionary as a base dictionary' do expect { subject }.to raise_error(RuntimeError) end @@ -124,31 +127,36 @@ end end - describe '.find_closest_lang' do - subject { helper.find_closest_lang(arg).to_s } + describe '.find_simple_locale' do + subject { helper.find_simple_locale(arg).to_s } context 'using a nil parameter' do let(:arg) { nil } + it { is_expected.to eq(default_lang) } end context 'using an empty string' do let(:arg) { '' } + it { is_expected.to eq(default_lang) } end - context 'passing a simple BCP 47 language tag' do + context 'passing a simple BCP47 language tag' do let(:arg) { 'es' } + it { is_expected.to eq('es_ES') } end - context 'passing a full BCP 47 tag' do + context 'passing a full BCP47 tag' do let(:arg) { 'en-GB' } + it { is_expected.to eq('en_GB') } end - context 'passing an extended BCP 47 tag' do + context 'passing an extended BCP47 tag' do let(:arg) { 'en-US-x-twain' } + it { is_expected.to eq('en_US') } end end diff --git a/spec/helpers/link_to_spec.rb b/spec/helpers/link_to_spec.rb index 6f5d722f..b521447e 100644 --- a/spec/helpers/link_to_spec.rb +++ b/spec/helpers/link_to_spec.rb @@ -1,19 +1,28 @@ +# frozen_string_literal: true + require 'helpers/link_to' -RSpec.describe LifePreserver::LinkTo, helper: true do +RSpec.describe LifePreserver::Helpers::LinkTo, helper: true do describe '#link_to' do subject { helper.link_to(text, target, attributes) } + let(:base_url) { 'http://url.base' } let(:text) { 'Text' } let(:target) { raise 'override me' } let(:attributes) { {} } + before do + ctx.config[:base_url] = base_url + end + context 'with string path' do let(:target) { '/foo/' } + it { is_expected.to eql('Text') } context 'with attributes' do let(:attributes) { { title: 'Donkey' } } + it { is_expected.to eql('Text') } end @@ -29,34 +38,47 @@ context 'evil HTML markup in text' do let(:text) { '' } + it { is_expected.to eql('</a><script>alert(PWNED!!!)</script>') } end context 'special HTML characters in text' do let(:text) { 'Foo & Bar' } + it { is_expected.to eql('Foo & Bar') } + # Not escaped! end context 'special HTML characters in href' do let(:target) { '/r&d/' } + it { is_expected.to eql('Text') } end context 'special HTML characaters in attributes' do let(:attributes) { { title: 'Research & Development' } } + it { is_expected.to eql('Text') } end end context 'with rep' do - let(:item) { ctx.create_item('content', {}, '/target/') } - let(:target) { ctx.create_rep(item, '/target.html') } + before do + ctx.create_item('content', {}, '/target') + ctx.create_rep(ctx.items['/target'], '/target.html') + end + + let(:target) { ctx.items['/target'].reps[:default] } it { is_expected.to eql('Text') } end context 'with item' do - let(:target) { ctx.create_item('content', {}, '/target/') } + before do + ctx.create_item('content', {}, '/target') + end + + let(:target) { ctx.items['/target'] } before do ctx.create_rep(target, '/target.html') @@ -82,8 +104,12 @@ end context 'with nil path' do - let(:item) { ctx.create_item('content', {}, '/target/') } - let(:target) { ctx.create_rep(item, nil) } + before do + ctx.create_item('content', {}, '/target') + ctx.create_rep(ctx.items['/target'], nil) + end + + let(:target) { ctx.items['/target'].reps[:default] } it 'raises' do expect { subject }.to raise_error(RuntimeError) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cdf3080b..ce1db06c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,44 +1,65 @@ require 'simplecov' -SimpleCov.start +SimpleCov.start do + add_group 'Helpers', 'lib/helpers' + add_group 'Filters', 'lib/filters' + add_group 'Data Sources', 'lib/data_sources' + add_group 'Commands', 'commands' +end + +require 'fuubar' +require 'rspec/its' +require 'timecop' +require 'tty-command' +require 'yard' require 'nanoc' require 'nanoc/cli' require 'nanoc/spec' -module Nanoc - module CLI - def self.setup_cleaning_streams; end +RSpec.configure do |c| + c.include(Nanoc::Spec::Helper) + + c.include(Nanoc::Spec::HelperHelper, helper: true) + + # TODO: Only really relevant when using the filesystem data source + c.before(:each, site: true) do + FileUtils.mkdir_p('content') + FileUtils.mkdir_p('layouts') + FileUtils.mkdir_p('lib') + FileUtils.mkdir_p('output') + + File.write('nanoc.yaml', '{}') + + File.write('Rules', 'passthrough "/**/*"') end -end -Nanoc::CLI.setup + c.fuubar_progress_bar_options = { + format: '%c/%C |<%b>%i| %p%%', + } -RSpec.configure do |config| - config.around(:each) do |example| - Nanoc::CLI::ErrorHandler.disable - example.run - Nanoc::CLI::ErrorHandler.enable + c.before(:each, fork: true) do + skip 'fork() is not supported on Windows' if Nanoc::Core.on_windows? end - config.around(:each) do |example| - Dir.mktmpdir('nanoc-test') do |dir| - FileUtils.cd(dir) do - example.run - end - end + c.before do + Nanoc::Core::NotificationCenter.reset end - config.around(:each, chdir: false) do |example| - FileUtils.cd(File.dirname(__FILE__) + '/..') do + c.around do |example| + should_chdir = + !example.metadata.key?(:chdir) || + example.metadata[:chdir] + + if should_chdir + Dir.mktmpdir('nanoc-test') do |dir| + chdir(dir) { example.run } + end + else example.run end end - config.before(:each) do - Nanoc::Int::NotificationCenter.reset - end - - config.around(:each, stdio: true) do |example| + c.around(:each, stdio: true) do |example| orig_stdout = $stdout orig_stderr = $stderr @@ -52,29 +73,53 @@ def self.setup_cleaning_streams; end $stdout = orig_stdout $stderr = orig_stderr end +end - config.before(:each, site: true) do - FileUtils.mkdir_p('content') - FileUtils.mkdir_p('layouts') - FileUtils.mkdir_p('lib') - FileUtils.mkdir_p('output') +module Nanoc + module CLI + def self.setup_cleaning_streams; end + end +end - File.write('nanoc.yaml', '{}') +Nanoc::CLI.setup - File.write('Rules', 'passthrough "/**/*"') +RSpec.configure do |c| + c.around do |example| + Nanoc::CLI::ErrorHandler.disable + example.run + Nanoc::CLI::ErrorHandler.enable + end + + c.before(:each, fork: true) do + skip 'fork() is not supported on Windows' if Nanoc::Core.on_windows? end +end + +RSpec::Matchers.define :raise_wrapped_error do |expected| + supports_block_expectations - config.include(Nanoc::Spec::HelperHelper, helper: true) + include RSpec::Matchers::Composable - config.expect_with :rspec do |expectations| - expectations.include_chain_clauses_in_custom_matcher_descriptions = true + match do |actual| + begin + actual.call + rescue Nanoc::Core::Errors::CompilationError => e + values_match?(expected, e.unwrap) + end end - config.mock_with :rspec do |mocks| - mocks.verify_partial_doubles = true + description do + "raise wrapped error #{expected.inspect}" end - config.shared_context_metadata_behavior = :apply_to_host_groups + failure_message do |_actual| + "expected that proc would raise wrapped error #{expected.inspect}" + end - config.disable_monkey_patching! + failure_message_when_negated do |_actual| + "expected that proc would not raise wrapped error #{expected.inspect}" + end end + +RSpec::Matchers.alias_matcher :some_textual_content, :be_some_textual_content +RSpec::Matchers.alias_matcher :some_binary_content, :be_some_binary_content