rails-excel-reporter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CLAUDE.md +94 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +306 -0
- data/README.md +506 -0
- data/lib/generators/report/report_generator.rb +32 -0
- data/lib/generators/report/templates/report.rb.erb +43 -0
- data/lib/rails_excel_reporter/base.rb +210 -0
- data/lib/rails_excel_reporter/configuration.rb +40 -0
- data/lib/rails_excel_reporter/controller_helpers.rb +34 -0
- data/lib/rails_excel_reporter/railtie.rb +35 -0
- data/lib/rails_excel_reporter/streaming.rb +81 -0
- data/lib/rails_excel_reporter/styling.rb +87 -0
- data/lib/rails_excel_reporter/version.rb +3 -0
- data/lib/rails_excel_reporter.rb +19 -0
- data/rails_excel_reporter.gemspec +36 -0
- data/spec/rails_excel_reporter/base_spec.rb +190 -0
- data/spec/rails_excel_reporter/configuration_spec.rb +75 -0
- data/spec/rails_excel_reporter/controller_helpers_spec.rb +121 -0
- data/spec/rails_excel_reporter/streaming_spec.rb +139 -0
- data/spec/rails_excel_reporter/styling_spec.rb +124 -0
- data/spec/rails_excel_reporter_spec.rb +25 -0
- data/spec/spec_helper.rb +30 -0
- metadata +211 -0
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RailsExcelReporter::Base do
|
4
|
+
let(:sample_data) do
|
5
|
+
[
|
6
|
+
OpenStruct.new(id: 1, name: 'John Doe', email: 'john@example.com', created_at: Time.parse('2024-01-01')),
|
7
|
+
OpenStruct.new(id: 2, name: 'Jane Smith', email: 'jane@example.com', created_at: Time.parse('2024-01-02')),
|
8
|
+
OpenStruct.new(id: 3, name: 'Bob Johnson', email: 'bob@example.com', created_at: Time.parse('2024-01-03'))
|
9
|
+
]
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:report_class) do
|
13
|
+
Class.new(RailsExcelReporter::Base) do
|
14
|
+
attributes :id, :name, :email
|
15
|
+
|
16
|
+
def name
|
17
|
+
"#{object.name} (Custom)"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:report) { report_class.new(sample_data) }
|
23
|
+
|
24
|
+
describe 'class methods' do
|
25
|
+
describe '.attributes' do
|
26
|
+
it 'defines attributes for the report' do
|
27
|
+
expect(report_class.attributes).to contain_exactly(
|
28
|
+
{ name: :id, header: 'Id' },
|
29
|
+
{ name: :name, header: 'Name' },
|
30
|
+
{ name: :email, header: 'Email' }
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '.attribute' do
|
36
|
+
it 'adds a single attribute with custom header' do
|
37
|
+
klass = Class.new(RailsExcelReporter::Base)
|
38
|
+
klass.attribute :custom_field, header: 'Custom Header'
|
39
|
+
|
40
|
+
expect(klass.attributes).to contain_exactly(
|
41
|
+
{ name: :custom_field, header: 'Custom Header' }
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'instance methods' do
|
48
|
+
describe '#initialize' do
|
49
|
+
it 'initializes with collection' do
|
50
|
+
expect(report.collection).to eq(sample_data)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'accepts custom worksheet name' do
|
54
|
+
custom_report = report_class.new(sample_data, worksheet_name: 'Custom Sheet')
|
55
|
+
expect(custom_report.worksheet_name).to eq('Custom Sheet')
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'accepts progress callback' do
|
59
|
+
callback = proc { |progress| }
|
60
|
+
custom_report = report_class.new(sample_data, &callback)
|
61
|
+
expect(custom_report.progress_callback).to eq(callback)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#filename' do
|
66
|
+
it 'generates a filename with timestamp' do
|
67
|
+
allow(Time).to receive(:now).and_return(Time.parse('2024-01-15'))
|
68
|
+
expect(report.filename).to match(/report_report_2024_01_15\.xlsx/)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '#to_h' do
|
73
|
+
it 'returns hash representation' do
|
74
|
+
hash = report.to_h
|
75
|
+
expect(hash).to include(
|
76
|
+
:worksheet_name,
|
77
|
+
:attributes,
|
78
|
+
:data,
|
79
|
+
:collection_size,
|
80
|
+
:streaming
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe '#collection_size' do
|
86
|
+
it 'returns the size of the collection' do
|
87
|
+
expect(report.collection_size).to eq(3)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#should_stream?' do
|
92
|
+
it 'returns false for small collections' do
|
93
|
+
expect(report.should_stream?).to be false
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns true for large collections' do
|
97
|
+
large_data = (1..2000).map { |i| OpenStruct.new(id: i, name: "User #{i}") }
|
98
|
+
large_report = report_class.new(large_data)
|
99
|
+
expect(large_report.should_stream?).to be true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe 'Excel generation' do
|
104
|
+
it 'generates Excel file' do
|
105
|
+
file = report.file
|
106
|
+
expect(file).to be_a(Tempfile)
|
107
|
+
expect(file.size).to be > 0
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'generates Excel binary data' do
|
111
|
+
xlsx_data = report.to_xlsx
|
112
|
+
expect(xlsx_data).to be_a(String)
|
113
|
+
expect(xlsx_data.size).to be > 0
|
114
|
+
expect(xlsx_data[0, 4]).to eq("PK\x03\x04")
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'saves to file' do
|
118
|
+
temp_path = '/tmp/test_report.xlsx'
|
119
|
+
report.save_to(temp_path)
|
120
|
+
expect(File.exist?(temp_path)).to be true
|
121
|
+
expect(File.size(temp_path)).to be > 0
|
122
|
+
File.delete(temp_path)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'custom methods' do
|
127
|
+
it 'calls custom methods for attribute values' do
|
128
|
+
# Since we can't easily inspect binary xlsx data, test the underlying data
|
129
|
+
report_data = report.to_h[:data]
|
130
|
+
expect(report_data[0][1]).to eq('John Doe (Custom)')
|
131
|
+
expect(report_data[1][1]).to eq('Jane Smith (Custom)')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe 'error handling' do
|
137
|
+
it 'raises error when no attributes are defined' do
|
138
|
+
empty_class = Class.new(RailsExcelReporter::Base)
|
139
|
+
empty_report = empty_class.new([])
|
140
|
+
|
141
|
+
expect { empty_report.to_xlsx }.to raise_error(RuntimeError, /No attributes defined/)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe 'callbacks' do
|
146
|
+
let(:callback_class) do
|
147
|
+
Class.new(RailsExcelReporter::Base) do
|
148
|
+
attributes :id, :name
|
149
|
+
|
150
|
+
attr_reader :before_render_called, :after_render_called,
|
151
|
+
:before_row_calls, :after_row_calls
|
152
|
+
|
153
|
+
def initialize(*args)
|
154
|
+
super
|
155
|
+
@before_render_called = false
|
156
|
+
@after_render_called = false
|
157
|
+
@before_row_calls = []
|
158
|
+
@after_row_calls = []
|
159
|
+
end
|
160
|
+
|
161
|
+
def before_render
|
162
|
+
@before_render_called = true
|
163
|
+
end
|
164
|
+
|
165
|
+
def after_render
|
166
|
+
@after_render_called = true
|
167
|
+
end
|
168
|
+
|
169
|
+
def before_row(object)
|
170
|
+
@before_row_calls << object
|
171
|
+
end
|
172
|
+
|
173
|
+
def after_row(object)
|
174
|
+
@after_row_calls << object
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
let(:callback_report) { callback_class.new(sample_data) }
|
180
|
+
|
181
|
+
it 'calls all callbacks in correct order' do
|
182
|
+
callback_report.to_xlsx
|
183
|
+
|
184
|
+
expect(callback_report.before_render_called).to be true
|
185
|
+
expect(callback_report.after_render_called).to be true
|
186
|
+
expect(callback_report.before_row_calls).to eq(sample_data)
|
187
|
+
expect(callback_report.after_row_calls).to eq(sample_data)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RailsExcelReporter::Configuration do
|
4
|
+
let(:config) { RailsExcelReporter::Configuration.new }
|
5
|
+
|
6
|
+
describe 'default values' do
|
7
|
+
it 'has default date format' do
|
8
|
+
expect(config.date_format).to eq('%Y-%m-%d')
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'has default streaming threshold' do
|
12
|
+
expect(config.streaming_threshold).to eq(1000)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'has default styles' do
|
16
|
+
expect(config.default_styles).to be_a(Hash)
|
17
|
+
expect(config.default_styles[:header]).to include(:bg_color, :fg_color, :bold)
|
18
|
+
expect(config.default_styles[:cell]).to include(:border)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'has default temp directory' do
|
22
|
+
expect(config.temp_directory).to eq(Dir.tmpdir)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'customization' do
|
27
|
+
it 'allows setting custom date format' do
|
28
|
+
config.date_format = '%d/%m/%Y'
|
29
|
+
expect(config.date_format).to eq('%d/%m/%Y')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'allows setting custom streaming threshold' do
|
33
|
+
config.streaming_threshold = 500
|
34
|
+
expect(config.streaming_threshold).to eq(500)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'allows setting custom temp directory' do
|
38
|
+
config.temp_directory = '/custom/temp'
|
39
|
+
expect(config.temp_directory).to eq('/custom/temp')
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'allows setting custom default styles' do
|
43
|
+
custom_styles = {
|
44
|
+
header: { bg_color: 'FF0000' },
|
45
|
+
cell: { border: { style: :medium } }
|
46
|
+
}
|
47
|
+
config.default_styles = custom_styles
|
48
|
+
expect(config.default_styles).to eq(custom_styles)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
RSpec.describe RailsExcelReporter do
|
54
|
+
describe 'configuration' do
|
55
|
+
it 'returns the same configuration instance' do
|
56
|
+
config1 = RailsExcelReporter.configuration
|
57
|
+
config2 = RailsExcelReporter.configuration
|
58
|
+
expect(config1).to be(config2)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'allows configuration via block' do
|
62
|
+
RailsExcelReporter.configure do |config|
|
63
|
+
config.date_format = '%d-%m-%Y'
|
64
|
+
config.streaming_threshold = 2000
|
65
|
+
end
|
66
|
+
|
67
|
+
expect(RailsExcelReporter.config.date_format).to eq('%d-%m-%Y')
|
68
|
+
expect(RailsExcelReporter.config.streaming_threshold).to eq(2000)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'provides config alias' do
|
72
|
+
expect(RailsExcelReporter.config).to be(RailsExcelReporter.configuration)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RailsExcelReporter::ControllerHelpers do
|
4
|
+
let(:controller_class) do
|
5
|
+
Class.new do
|
6
|
+
include RailsExcelReporter::ControllerHelpers
|
7
|
+
|
8
|
+
attr_accessor :response
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@response = MockResponse.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def send_data(data, options = {})
|
15
|
+
@sent_data = data
|
16
|
+
@send_options = options
|
17
|
+
end
|
18
|
+
|
19
|
+
def response_body=(body)
|
20
|
+
@response.body = body
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :sent_data, :send_options
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:mock_response) do
|
28
|
+
Class.new do
|
29
|
+
attr_accessor :headers, :body
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@headers = {}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:controller) { controller_class.new }
|
38
|
+
|
39
|
+
let(:report_class) do
|
40
|
+
Class.new(RailsExcelReporter::Base) do
|
41
|
+
attributes :id, :name
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:sample_data) do
|
46
|
+
[
|
47
|
+
OpenStruct.new(id: 1, name: 'John'),
|
48
|
+
OpenStruct.new(id: 2, name: 'Jane')
|
49
|
+
]
|
50
|
+
end
|
51
|
+
|
52
|
+
let(:report) { report_class.new(sample_data) }
|
53
|
+
|
54
|
+
before do
|
55
|
+
stub_const('MockResponse', mock_response)
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#send_excel_report' do
|
59
|
+
it 'sends Excel data with default filename' do
|
60
|
+
controller.send_excel_report(report)
|
61
|
+
|
62
|
+
expect(controller.sent_data).to be_a(String)
|
63
|
+
expect(controller.send_options[:filename]).to match(/\.xlsx$/)
|
64
|
+
expect(controller.send_options[:type]).to eq('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
65
|
+
expect(controller.send_options[:disposition]).to eq('attachment')
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'sends Excel data with custom filename' do
|
69
|
+
controller.send_excel_report(report, filename: 'custom_report.xlsx')
|
70
|
+
|
71
|
+
expect(controller.send_options[:filename]).to eq('custom_report.xlsx')
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'sends Excel data with custom disposition' do
|
75
|
+
controller.send_excel_report(report, disposition: 'inline')
|
76
|
+
|
77
|
+
expect(controller.send_options[:disposition]).to eq('inline')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#stream_excel_report' do
|
82
|
+
it 'sets up streaming response headers' do
|
83
|
+
controller.stream_excel_report(report)
|
84
|
+
|
85
|
+
expect(controller.response.headers['Content-Type']).to eq('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
86
|
+
expect(controller.response.headers['Content-Disposition']).to match(/attachment; filename=/)
|
87
|
+
expect(controller.response.headers['Content-Transfer-Encoding']).to eq('binary')
|
88
|
+
expect(controller.response.headers['Last-Modified']).to be_present
|
89
|
+
expect(controller.response.body).to be_a(StringIO)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'uses custom filename in Content-Disposition' do
|
93
|
+
controller.stream_excel_report(report, filename: 'custom_stream.xlsx')
|
94
|
+
|
95
|
+
expect(controller.response.headers['Content-Disposition']).to include('custom_stream.xlsx')
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#excel_report_response' do
|
100
|
+
it 'uses send_excel_report for small reports' do
|
101
|
+
allow(report).to receive(:should_stream?).and_return(false)
|
102
|
+
expect(controller).to receive(:send_excel_report).with(report, {})
|
103
|
+
|
104
|
+
controller.excel_report_response(report)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'uses stream_excel_report for large reports' do
|
108
|
+
allow(report).to receive(:should_stream?).and_return(true)
|
109
|
+
expect(controller).to receive(:stream_excel_report).with(report, {})
|
110
|
+
|
111
|
+
controller.excel_report_response(report)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'passes options to the appropriate method' do
|
115
|
+
allow(report).to receive(:should_stream?).and_return(false)
|
116
|
+
expect(controller).to receive(:send_excel_report).with(report, { filename: 'test.xlsx' })
|
117
|
+
|
118
|
+
controller.excel_report_response(report, filename: 'test.xlsx')
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RailsExcelReporter::Streaming do
|
4
|
+
let(:small_data) do
|
5
|
+
(1..10).map { |i| OpenStruct.new(id: i, name: "User #{i}") }
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:large_data) do
|
9
|
+
(1..2000).map { |i| OpenStruct.new(id: i, name: "User #{i}") }
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:report_class) do
|
13
|
+
Class.new(RailsExcelReporter::Base) do
|
14
|
+
attributes :id, :name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'streaming threshold' do
|
19
|
+
it 'has default streaming threshold' do
|
20
|
+
expect(report_class.streaming_threshold).to eq(RailsExcelReporter.config.streaming_threshold)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'can set custom streaming threshold' do
|
24
|
+
report_class.streaming_threshold = 500
|
25
|
+
expect(report_class.streaming_threshold).to eq(500)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#should_stream?' do
|
30
|
+
it 'returns false for small collections' do
|
31
|
+
report = report_class.new(small_data)
|
32
|
+
expect(report.should_stream?).to be false
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns true for large collections' do
|
36
|
+
report = report_class.new(large_data)
|
37
|
+
expect(report.should_stream?).to be true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#collection_size' do
|
42
|
+
it 'calculates size for arrays' do
|
43
|
+
report = report_class.new(small_data)
|
44
|
+
expect(report.collection_size).to eq(10)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'uses count method when available' do
|
48
|
+
mock_collection = double('Collection')
|
49
|
+
allow(mock_collection).to receive(:count).and_return(100)
|
50
|
+
|
51
|
+
report = report_class.new(mock_collection)
|
52
|
+
expect(report.collection_size).to eq(100)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'falls back to size method' do
|
56
|
+
mock_collection = double('Collection')
|
57
|
+
allow(mock_collection).to receive(:respond_to?).with(:count).and_return(false)
|
58
|
+
allow(mock_collection).to receive(:respond_to?).with(:size).and_return(true)
|
59
|
+
allow(mock_collection).to receive(:size).and_return(50)
|
60
|
+
|
61
|
+
report = report_class.new(mock_collection)
|
62
|
+
expect(report.collection_size).to eq(50)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#stream_data' do
|
67
|
+
it 'yields each item in small collections' do
|
68
|
+
report = report_class.new(small_data)
|
69
|
+
yielded_items = []
|
70
|
+
|
71
|
+
report.stream_data do |item|
|
72
|
+
yielded_items << item
|
73
|
+
end
|
74
|
+
|
75
|
+
expect(yielded_items).to eq(small_data)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'uses find_each for large ActiveRecord-like collections' do
|
79
|
+
mock_collection = double('ActiveRecord Collection')
|
80
|
+
allow(mock_collection).to receive(:respond_to?).with(:count).and_return(true)
|
81
|
+
allow(mock_collection).to receive(:count).and_return(2000)
|
82
|
+
allow(mock_collection).to receive(:respond_to?).with(:find_each).and_return(true)
|
83
|
+
|
84
|
+
yielded_items = []
|
85
|
+
allow(mock_collection).to receive(:find_each).with(batch_size: 1000) do |&block|
|
86
|
+
large_data.each(&block)
|
87
|
+
end
|
88
|
+
|
89
|
+
report = report_class.new(mock_collection)
|
90
|
+
|
91
|
+
report.stream_data do |item|
|
92
|
+
yielded_items << item
|
93
|
+
end
|
94
|
+
|
95
|
+
expect(yielded_items.size).to eq(2000)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'returns enumerator when no block given' do
|
99
|
+
report = report_class.new(small_data)
|
100
|
+
enumerator = report.stream_data
|
101
|
+
|
102
|
+
expect(enumerator).to be_a(Enumerator)
|
103
|
+
expect(enumerator.to_a).to eq(small_data)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe '#with_progress_tracking' do
|
108
|
+
it 'tracks progress and yields items with progress info' do
|
109
|
+
report = report_class.new(small_data)
|
110
|
+
progress_updates = []
|
111
|
+
|
112
|
+
report.with_progress_tracking do |_item, progress|
|
113
|
+
progress_updates << progress
|
114
|
+
end
|
115
|
+
|
116
|
+
expect(progress_updates.size).to eq(10)
|
117
|
+
expect(progress_updates.first.current).to eq(1)
|
118
|
+
expect(progress_updates.first.total).to eq(10)
|
119
|
+
expect(progress_updates.first.percentage).to eq(10.0)
|
120
|
+
expect(progress_updates.last.current).to eq(10)
|
121
|
+
expect(progress_updates.last.percentage).to eq(100.0)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'calls progress callback when provided' do
|
125
|
+
callback_calls = []
|
126
|
+
callback = proc { |progress| callback_calls << progress }
|
127
|
+
|
128
|
+
report = report_class.new(small_data, &callback)
|
129
|
+
|
130
|
+
report.with_progress_tracking do |item, progress|
|
131
|
+
# Just iterate
|
132
|
+
end
|
133
|
+
|
134
|
+
expect(callback_calls.size).to eq(10)
|
135
|
+
expect(callback_calls.first.current).to eq(1)
|
136
|
+
expect(callback_calls.last.current).to eq(10)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RailsExcelReporter::Styling do
|
4
|
+
let(:styled_class) do
|
5
|
+
Class.new(RailsExcelReporter::Base) do
|
6
|
+
attributes :id, :name, :email
|
7
|
+
|
8
|
+
style :header, {
|
9
|
+
bg_color: 'FF0000',
|
10
|
+
fg_color: 'FFFFFF',
|
11
|
+
bold: true
|
12
|
+
}
|
13
|
+
|
14
|
+
style :id, {
|
15
|
+
alignment: { horizontal: :center }
|
16
|
+
}
|
17
|
+
|
18
|
+
style :name, {
|
19
|
+
bold: true,
|
20
|
+
font_size: 12
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:sample_data) do
|
26
|
+
[
|
27
|
+
OpenStruct.new(id: 1, name: 'John', email: 'john@example.com'),
|
28
|
+
OpenStruct.new(id: 2, name: 'Jane', email: 'jane@example.com')
|
29
|
+
]
|
30
|
+
end
|
31
|
+
|
32
|
+
let(:report) { styled_class.new(sample_data) }
|
33
|
+
|
34
|
+
describe 'class methods' do
|
35
|
+
describe '.style' do
|
36
|
+
it 'defines styles for targets' do
|
37
|
+
expect(styled_class.styles[:header]).to eq({
|
38
|
+
bg_color: 'FF0000',
|
39
|
+
fg_color: 'FFFFFF',
|
40
|
+
bold: true
|
41
|
+
})
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'defines styles for columns' do
|
45
|
+
expect(styled_class.styles[:id]).to eq({
|
46
|
+
alignment: { horizontal: :center }
|
47
|
+
})
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '.styles' do
|
52
|
+
it 'returns all defined styles' do
|
53
|
+
expect(styled_class.styles).to include(:header, :id, :name)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'instance methods' do
|
59
|
+
describe '#get_header_style' do
|
60
|
+
it 'returns merged header style with defaults' do
|
61
|
+
header_style = report.get_header_style
|
62
|
+
expect(header_style[:bg_color]).to eq('FF0000')
|
63
|
+
expect(header_style[:bold]).to be true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#get_column_style' do
|
68
|
+
it 'returns merged column style with defaults' do
|
69
|
+
id_style = report.get_column_style(:id)
|
70
|
+
expect(id_style[:alignment]).to eq({ horizontal: :center })
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'returns default style for undefined columns' do
|
74
|
+
email_style = report.get_column_style(:email)
|
75
|
+
expect(email_style).to eq(RailsExcelReporter.config.default_styles[:cell])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#build_caxlsx_style' do
|
80
|
+
it 'converts style options to caxlsx format' do
|
81
|
+
style_options = {
|
82
|
+
bg_color: 'FF0000',
|
83
|
+
fg_color: 'FFFFFF',
|
84
|
+
bold: true,
|
85
|
+
font_size: 12,
|
86
|
+
alignment: { horizontal: :center }
|
87
|
+
}
|
88
|
+
|
89
|
+
caxlsx_style = report.build_caxlsx_style(style_options)
|
90
|
+
|
91
|
+
expect(caxlsx_style[:bg_color]).to eq('FF0000')
|
92
|
+
expect(caxlsx_style[:fg_color]).to eq('FFFFFF')
|
93
|
+
expect(caxlsx_style[:b]).to be true
|
94
|
+
expect(caxlsx_style[:sz]).to eq(12)
|
95
|
+
expect(caxlsx_style[:alignment]).to eq({ horizontal: :center })
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#merge_styles' do
|
100
|
+
it 'merges multiple styles' do
|
101
|
+
styled_class.style :base, { bold: true, font_size: 10 }
|
102
|
+
styled_class.style :override, { font_size: 12, italic: true }
|
103
|
+
|
104
|
+
merged = report.merge_styles(:base, :override)
|
105
|
+
|
106
|
+
expect(merged[:bold]).to be true
|
107
|
+
expect(merged[:font_size]).to eq(12)
|
108
|
+
expect(merged[:italic]).to be true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'inheritance' do
|
114
|
+
it 'inherits styles from parent class' do
|
115
|
+
child_class = Class.new(styled_class) do
|
116
|
+
style :email, { italic: true }
|
117
|
+
end
|
118
|
+
|
119
|
+
expect(child_class.styles).to include(:header, :id, :name, :email)
|
120
|
+
expect(child_class.styles[:header][:bold]).to be true
|
121
|
+
expect(child_class.styles[:email][:italic]).to be true
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RailsExcelReporter do
|
4
|
+
describe 'configuration' do
|
5
|
+
it 'has a default configuration' do
|
6
|
+
expect(RailsExcelReporter.configuration).to be_a(RailsExcelReporter::Configuration)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'can be configured' do
|
10
|
+
RailsExcelReporter.configure do |config|
|
11
|
+
config.date_format = '%d/%m/%Y'
|
12
|
+
config.streaming_threshold = 500
|
13
|
+
end
|
14
|
+
|
15
|
+
expect(RailsExcelReporter.config.date_format).to eq('%d/%m/%Y')
|
16
|
+
expect(RailsExcelReporter.config.streaming_threshold).to eq(500)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'version' do
|
21
|
+
it 'has a version number' do
|
22
|
+
expect(RailsExcelReporter::VERSION).not_to be nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'rails_excel_reporter'
|
3
|
+
require 'rspec'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.expect_with :rspec do |c|
|
9
|
+
c.syntax = :expect
|
10
|
+
end
|
11
|
+
|
12
|
+
config.mock_with :rspec do |mocks|
|
13
|
+
mocks.verify_partial_doubles = true
|
14
|
+
end
|
15
|
+
|
16
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
17
|
+
config.filter_run_when_matching :focus
|
18
|
+
config.disable_monkey_patching!
|
19
|
+
config.warnings = true
|
20
|
+
|
21
|
+
config.default_formatter = 'doc' if config.files_to_run.one?
|
22
|
+
|
23
|
+
config.profile_examples = 10
|
24
|
+
config.order = :random
|
25
|
+
Kernel.srand config.seed
|
26
|
+
|
27
|
+
config.before(:each) do
|
28
|
+
RailsExcelReporter.instance_variable_set(:@configuration, nil)
|
29
|
+
end
|
30
|
+
end
|