diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 690c7bb..92ad51c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -55,7 +55,6 @@ Layout/SpaceAroundMethodCallOperator: # AllowedMethods: enums Lint/ConstantDefinitionInBlock: Exclude: - - 'spec/comma/comma_spec.rb' - 'spec/comma/rails/active_record_spec.rb' - 'spec/comma/rails/data_mapper_collection_spec.rb' - 'spec/comma/rails/mongoid_spec.rb' diff --git a/spec/comma/comma_spec.rb b/spec/comma/comma_spec.rb index f0249bf..9014ec0 100644 --- a/spec/comma/comma_spec.rb +++ b/spec/comma/comma_spec.rb @@ -40,8 +40,9 @@ end it 'should extend Array to add a #to_comma method which will return CSV content for objects within the array' do - expected = "Title,Description,Issuer,ISBN-10,ISBN-13\nSmalltalk-80,Language and Implementation,ISBN,123123123,321321321\n" # rubocop:disable Layout/LineLength - expect(@books.to_comma).to eq(expected) + expect_csv(@books.to_comma, + headers: %w[Title Description Issuer ISBN-10 ISBN-13], + rows: [['Smalltalk-80', 'Language and Implementation', 'ISBN', '123123123', '321321321']]) end it 'should return an empty string when generating CSV from an empty array' do @@ -49,7 +50,9 @@ end it 'should change the style when specified' do - expect(@books.to_comma(:brief)).to eq("Name,Description\nSmalltalk-80,Language and Implementation\n") + expect_csv(@books.to_comma(:brief), + headers: %w[Name Description], + rows: [['Smalltalk-80', 'Language and Implementation']]) end describe 'with :filename specified' do @@ -57,31 +60,39 @@ it 'should write to the file' do @books.to_comma(filename: 'comma.csv') - expected = "Title,Description,Issuer,ISBN-10,ISBN-13\nSmalltalk-80,Language and Implementation,ISBN,123123123,321321321\n" # rubocop:disable Layout/LineLength - expect(File.read('comma.csv')).to eq(expected) + expect_csv(File.read('comma.csv'), + headers: %w[Title Description Issuer ISBN-10 ISBN-13], + rows: [['Smalltalk-80', 'Language and Implementation', 'ISBN', '123123123', '321321321']]) end it 'should accept FasterCSV options' do @books.to_comma(filename: 'comma.csv', col_sep: ';', force_quotes: true) - expected = "\"Title\";\"Description\";\"Issuer\";\"ISBN-10\";\"ISBN-13\"\n\"Smalltalk-80\";\"Language and Implementation\";\"ISBN\";\"123123123\";\"321321321\"\n" # rubocop:disable Layout/LineLength - expect(File.read('comma.csv')).to eq(expected) + expect_csv(File.read('comma.csv'), + headers: %w[Title Description Issuer ISBN-10 ISBN-13], + rows: [['Smalltalk-80', 'Language and Implementation', 'ISBN', '123123123', '321321321']], + col_sep: ';', force_quotes: true) end end describe 'with FasterCSV options' do it 'should not change when options are empty' do - expected = "Title,Description,Issuer,ISBN-10,ISBN-13\nSmalltalk-80,Language and Implementation,ISBN,123123123,321321321\n" # rubocop:disable Layout/LineLength - expect(@books.to_comma({})).to eq(expected) + expect_csv(@books.to_comma({}), + headers: %w[Title Description Issuer ISBN-10 ISBN-13], + rows: [['Smalltalk-80', 'Language and Implementation', 'ISBN', '123123123', '321321321']]) end it 'should accept the options in #to_comma and generate the appropriate CSV' do - expected = "\"Title\";\"Description\";\"Issuer\";\"ISBN-10\";\"ISBN-13\"\n\"Smalltalk-80\";\"Language and Implementation\";\"ISBN\";\"123123123\";\"321321321\"\n" # rubocop:disable Layout/LineLength - expect(@books.to_comma(col_sep: ';', force_quotes: true)).to eq(expected) + expect_csv(@books.to_comma(col_sep: ';', force_quotes: true), + headers: %w[Title Description Issuer ISBN-10 ISBN-13], + rows: [['Smalltalk-80', 'Language and Implementation', 'ISBN', '123123123', '321321321']], + col_sep: ';', force_quotes: true) end it 'should change the style when specified' do - expect(@books.to_comma(style: :brief, col_sep: ';', force_quotes: true)) - .to eq("\"Name\";\"Description\"\n\"Smalltalk-80\";\"Language and Implementation\"\n") + expect_csv(@books.to_comma(style: :brief, col_sep: ';', force_quotes: true), + headers: %w[Name Description], + rows: [['Smalltalk-80', 'Language and Implementation']], + col_sep: ';', force_quotes: true) end end end @@ -89,29 +100,29 @@ describe Comma, 'defining CSV descriptions' do describe 'with an unnamed description' do before do - class Foo + @foo_class = define_comma_class do comma do; end end end it 'should name the current description :default if no name has been provided' do - expect(Foo.comma_formats).not_to be_empty - expect(Foo.comma_formats[:default]).not_to be_nil + expect(@foo_class.comma_formats).not_to be_empty + expect(@foo_class.comma_formats[:default]).not_to be_nil end end describe 'with a named description' do before do - class Bar + @bar_class = define_comma_class do comma do; end comma :detailed do; end end end it 'should use the provided name to index the comma format' do - expect(Bar.comma_formats).not_to be_empty - expect(Bar.comma_formats[:default]).not_to be_nil - expect(Bar.comma_formats[:detailed]).not_to be_nil + expect(@bar_class.comma_formats).not_to be_empty + expect(@bar_class.comma_formats[:default]).not_to be_nil + expect(@bar_class.comma_formats[:detailed]).not_to be_nil end end end @@ -119,7 +130,7 @@ class Bar describe Comma, 'to_comma data/headers object extensions' do # rubocop:disable Metrics/BlockLength describe 'with unnamed descriptions' do before do - class Foo + foo_class = define_comma_class do attr_accessor :content comma do; content; end @@ -128,7 +139,7 @@ def initialize(content) end end - @foo = Foo.new('content') + @foo = foo_class.new('content') end it 'should return and array of data content, using the :default CSV description if none requested' do @@ -146,7 +157,7 @@ def initialize(content) describe 'with named descriptions' do before do - class Foo + foo_class = define_comma_class do attr_accessor :content comma :detailed do; content; end @@ -155,7 +166,7 @@ def initialize(content) end end - @foo = Foo.new('content') + @foo = foo_class.new('content') end it 'should return and array of data content, using the :default CSV description if none requested' do @@ -179,7 +190,7 @@ def initialize(content) describe 'with block' do # rubocop:disable Metrics/BlockLength before do - class Foo + foo_class = define_comma_class do attr_accessor :content, :created_at, :updated_at comma do content @@ -199,7 +210,7 @@ def initialize(content, created_at = Time.now, updated_at = Time.now) @time = Time.now @content = 'content ' * 5 - @foo = Foo.new @content, @time, @time + @foo = foo_class.new @content, @time, @time end it 'should return yielded values by block' do @@ -265,7 +276,7 @@ def initialize(content, created_at = Time.now, updated_at = Time.now) describe 'on objects using Single Table Inheritance' do # rubocop:disable Metrics/BlockLength before do - class MySuperClass + super_class = define_comma_class do attr_accessor :content comma do; content end @@ -274,7 +285,7 @@ def initialize(content) end end - class ChildClassComma < MySuperClass + child_class_comma = define_comma_class(super_class) do comma do; content end def initialize(content) @@ -282,11 +293,10 @@ def initialize(content) end end - class ChildClassNoComma < MySuperClass - end + child_class_no_comma = define_comma_class(super_class) {} - @childComma = ChildClassComma.new('content') - @childNoComma = ChildClassNoComma.new('content') + @childComma = child_class_comma.new('content') + @childNoComma = child_class_no_comma.new('content') end it 'should return and array of data content, as defined in comma block in child class' do @@ -298,7 +308,7 @@ class ChildClassNoComma < MySuperClass end it 'should reflect changes to the superclass format made after the subclass was defined' do - class ReopenedSuperClass + reopened_super_class = define_comma_class do attr_accessor :content comma do; content end @@ -307,16 +317,15 @@ def initialize(content) end end - class ReopenedChildNoComma < ReopenedSuperClass - end + reopened_child_no_comma = define_comma_class(reopened_super_class) {} - ReopenedSuperClass.class_eval do + reopened_super_class.class_eval do comma do content(&:upcase) end end - child = ReopenedChildNoComma.new('content') + child = reopened_child_no_comma.new('content') expect(child.to_comma).to eq(%w[SUPER-CONTENT]) end end @@ -324,7 +333,7 @@ class ReopenedChildNoComma < ReopenedSuperClass describe Comma, '__use__ keyword' do before(:all) do - @obj = Class.new(Struct.new(:id, :title, :description)) do + @obj = define_comma_class(Struct.new(:id, :title, :description)) do comma do title __use__ :description @@ -348,7 +357,7 @@ class ReopenedChildNoComma < ReopenedSuperClass describe Comma, '__use__ keyword with a circular reference' do it 'should raise Comma::CircularStyleReference instead of overflowing the stack' do - obj = Class.new(Struct.new(:id, :title)) do + obj = define_comma_class(Struct.new(:id, :title)) do comma :a do title __use__ :b @@ -363,7 +372,7 @@ class ReopenedChildNoComma < ReopenedSuperClass end it 'should raise Comma::CircularStyleReference for direct self-reference' do - obj = Class.new(Struct.new(:id)) do + obj = define_comma_class(Struct.new(:id)) do comma :a do __use__ :a end diff --git a/spec/comma/data_extractor_spec.rb b/spec/comma/data_extractor_spec.rb index 9c195e7..a02d4fb 100644 --- a/spec/comma/data_extractor_spec.rb +++ b/spec/comma/data_extractor_spec.rb @@ -45,7 +45,7 @@ describe Comma::DataExtractor, 'id attribute' do before do - @data = Class.new(Struct.new(:id)) do + @data = define_comma_class(Struct.new(:id)) do comma do id 'ID' do |_id| '42' end end @@ -59,7 +59,7 @@ describe Comma::DataExtractor, 'with static column method' do before do - @data = Class.new(Struct.new(:id, :name)) do + @data = define_comma_class(Struct.new(:id, :name)) do comma do __static_column__ __static_column__ 'STATIC' @@ -76,7 +76,7 @@ describe Comma::DataExtractor, 'nil value' do before do - @data = Class.new(Struct.new(:id, :name)) do + @data = define_comma_class(Struct.new(:id, :name)) do comma do name name 'Name' diff --git a/spec/comma/header_extractor_spec.rb b/spec/comma/header_extractor_spec.rb index 7e3b5dd..127042d 100644 --- a/spec/comma/header_extractor_spec.rb +++ b/spec/comma/header_extractor_spec.rb @@ -46,7 +46,7 @@ describe Comma::HeaderExtractor, 'with static column method' do before do - @headers = Class.new(Struct.new(:id, :name)) do + @headers = define_comma_class(Struct.new(:id, :name)) do comma do __static_column__ __static_column__ 'STATIC' diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 122e44d..db6e71c 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -37,13 +37,9 @@ expect(response.media_type).to eq 'text/csv' expect(response.header['Content-Disposition']).to include('filename="data.csv"') - expected_content = <<-CSV.gsub(/^\s+/, '') - First name,Last name,Name - Fred,Flintstone,Fred Flintstone - Wilma,Flintstone,Wilma Flintstone - CSV - - expect(response.body).to eq expected_content + expect_csv(response.body, + headers: ['First name', 'Last name', 'Name'], + rows: [['Fred', 'Flintstone', 'Fred Flintstone'], ['Wilma', 'Flintstone', 'Wilma Flintstone']]) end describe 'with comma options' do @@ -53,13 +49,9 @@ get :with_custom_style, format: :csv - expected_content = <<-CSV.gsub(/^\s+/, '') - First name,Last name - Fred,Flintstone - Wilma,Flintstone - CSV - - expect(response.body).to eq expected_content + expect_csv(response.body, + headers: ['First name', 'Last name'], + rows: [%w[Fred Flintstone], %w[Wilma Flintstone]]) end end @@ -131,13 +123,9 @@ def get_(name, **args) expect(response.status).to eq 200 expect(response.media_type).to eq 'text/csv' - expected_content = <<-CSV.gsub(/^\s+/, '') - First name,Last name,Name - Fred,Flintstone,Fred Flintstone - Wilma,Flintstone,Wilma Flintstone - CSV - - expect(response.body).to eq expected_content + expect_csv(response.body, + headers: ['First name', 'Last name', 'Name'], + rows: [['Fred', 'Flintstone', 'Fred Flintstone'], ['Wilma', 'Flintstone', 'Wilma Flintstone']]) end it 'should allow toggling off' do @@ -161,13 +149,10 @@ def get_(name, **args) expect(response.status).to eq 200 expect(response.media_type).to eq 'text/csv' - expected_content = <<-CSV.gsub(/^\s+/, '') - "First name","Last name","Name" - "Fred","Flintstone","Fred Flintstone" - "Wilma","Flintstone","Wilma Flintstone" - CSV - - expect(response.body).to eq expected_content + expect_csv(response.body, + headers: ['First name', 'Last name', 'Name'], + rows: [['Fred', 'Flintstone', 'Fred Flintstone'], ['Wilma', 'Flintstone', 'Wilma Flintstone']], + force_quotes: true) end it 'should allow combinations of options' do diff --git a/spec/support/comma_class_helper.rb b/spec/support/comma_class_helper.rb new file mode 100644 index 0000000..11aafb0 --- /dev/null +++ b/spec/support/comma_class_helper.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Defines an anonymous class for use in a single example, avoiding +# constants leaking into the global namespace across the spec suite. +# Pass `base` to build on a superclass (e.g. a Struct). +# +# book_class = define_comma_class(Struct.new(:title)) do +# comma { title } +# end +def define_comma_class(base = Object, &block) + Class.new(base, &block) +end diff --git a/spec/support/csv_expectation_helper.rb b/spec/support/csv_expectation_helper.rb new file mode 100644 index 0000000..c28f464 --- /dev/null +++ b/spec/support/csv_expectation_helper.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Builds the expected CSV string from headers/rows so specs don't hand-roll +# comma-joined strings, then asserts it against the actual output. +# `csv_options` are forwarded to CSV.generate (e.g. col_sep:, force_quotes:). +# +# expect_csv(books.to_comma, headers: %w[Title Author], rows: [['Smalltalk-80', 'Kay']]) +def expect_csv(output, headers:, rows:, **csv_options) + expected = CSV.generate(**csv_options) do |csv| + csv << headers + rows.each { |row| csv << row } + end + expect(output).to eq(expected) +end