# NOTE: # Changes to this file should be kept in sync with # https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/frontend/chronic_duration_spec.js. require 'spec_helper' describe ChronicDuration do describe ".parse" do @exemplars = { '1:20' => 60 + 20, '1:20.51' => 60 + 20.51, '4:01:01' => 4 * 3600 + 60 + 1, '3 mins 4 sec' => 3 * 60 + 4, '3 Mins 4 Sec' => 3 * 60 + 4, 'three mins four sec' => 3 * 60 + 4, '2 hrs 20 min' => 2 * 3600 + 20 * 60, '2h20min' => 2 * 3600 + 20 * 60, '6 mos 1 day' => 6 * 30 * 24 * 3600 + 24 * 3600, '1 year 6 mos 1 day' => 1 * 31557600 + 6 * 30 * 24 * 3600 + 24 * 3600, '2.5 hrs' => 2.5 * 3600, '47 yrs 6 mos and 4.5d' => 47 * 31557600 + 6 * 30 * 24 * 3600 + 4.5 * 24 * 3600, 'two hours and twenty minutes' => 2 * 3600 + 20 * 60, 'four hours and forty minutes' => 4 * 3600 + 40 * 60, 'four hours, and fourty minutes' => 4 * 3600 + 40 * 60, '3 weeks and, 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2, '3 weeks, plus 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2, '3 weeks with 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2, '1 month' => 3600 * 24 * 30, '2 months' => 3600 * 24 * 30 * 2, '18 months' => 3600 * 24 * 30 * 18, '1 year 6 months' => (3600 * 24 * (365.25 + 6 * 30)).to_i, 'day' => 3600 * 24, 'minute 30s' => 90 } context "when string can't be parsed" do it "returns nil" do expect(ChronicDuration.parse('gobblygoo')).to be_nil end it "cannot parse zero" do expect(ChronicDuration.parse('0')).to be_nil end context "when @@raise_exceptions set to true" do it "raises with ChronicDuration::DurationParseError" do ChronicDuration.raise_exceptions = true expect { ChronicDuration.parse('23 gobblygoos') }.to raise_error(ChronicDuration::DurationParseError) ChronicDuration.raise_exceptions = false end end end it "should return zero if the string parses as zero and the keep_zero option is true" do expect(ChronicDuration.parse('0', :keep_zero => true)).to eq(0) end it "should return a float if seconds are in decimals" do expect(ChronicDuration.parse('12 mins 3.141 seconds')).to be_a(Float) end it "should return an integer unless the seconds are in decimals" do expect(ChronicDuration.parse('12 mins 3 seconds')).to be_a(Integer) end it "should be able to parse minutes by default" do expect(ChronicDuration.parse('5', :default_unit => "minutes")).to eq(300) end @exemplars.each do |k, v| it "parses a duration like #{k}" do expect(ChronicDuration.parse(k)).to eq(v) end end context 'with :hours_per_day and :days_per_month params' do it 'uses provided :hours_per_day' do expect(ChronicDuration.parse('1d', hours_per_day: 24)).to eq(24 * 60 * 60) expect(ChronicDuration.parse('1d', hours_per_day: 8)).to eq(8 * 60 * 60) end it 'uses provided :days_per_month' do expect(ChronicDuration.parse('1mo', days_per_month: 30)).to eq(30 * 24 * 60 * 60) expect(ChronicDuration.parse('1mo', days_per_month: 20)).to eq(20 * 24 * 60 * 60) expect(ChronicDuration.parse('1w', days_per_month: 30)).to eq(7 * 24 * 60 * 60) expect(ChronicDuration.parse('1w', days_per_month: 20)).to eq(5 * 24 * 60 * 60) end it 'uses provided both :hours_per_day and :days_per_month' do expect(ChronicDuration.parse('1mo', days_per_month: 30, hours_per_day: 24)).to eq(30 * 24 * 60 * 60) expect(ChronicDuration.parse('1mo', days_per_month: 20, hours_per_day: 8)).to eq(20 * 8 * 60 * 60) expect(ChronicDuration.parse('1w', days_per_month: 30, hours_per_day: 24)).to eq(7 * 24 * 60 * 60) expect(ChronicDuration.parse('1w', days_per_month: 20, hours_per_day: 8)).to eq(5 * 8 * 60 * 60) end end end describe '.output' do @exemplars = { (60 + 20) => { :micro => '1m20s', :short => '1m 20s', :default => '1 min 20 secs', :long => '1 minute 20 seconds', :chrono => '1:20' }, (60 + 20.51) => { :micro => '1m20.51s', :short => '1m 20.51s', :default => '1 min 20.51 secs', :long => '1 minute 20.51 seconds', :chrono => '1:20.51' }, (60 + 20.51928) => { :micro => '1m20.51928s', :short => '1m 20.51928s', :default => '1 min 20.51928 secs', :long => '1 minute 20.51928 seconds', :chrono => '1:20.51928' }, (4 * 3600 + 60 + 1) => { :micro => '4h1m1s', :short => '4h 1m 1s', :default => '4 hrs 1 min 1 sec', :long => '4 hours 1 minute 1 second', :chrono => '4:01:01' }, (2 * 3600 + 20 * 60) => { :micro => '2h20m', :short => '2h 20m', :default => '2 hrs 20 mins', :long => '2 hours 20 minutes', :chrono => '2:20' }, (2 * 3600 + 20 * 60) => { :micro => '2h20m', :short => '2h 20m', :default => '2 hrs 20 mins', :long => '2 hours 20 minutes', :chrono => '2:20:00' }, (6 * 30 * 24 * 3600 + 24 * 3600) => { :micro => '6mo1d', :short => '6mo 1d', :default => '6 mos 1 day', :long => '6 months 1 day', :chrono => '6:01:00:00:00' # Yuck. FIXME }, (365.25 * 24 * 3600 + 24 * 3600 ).to_i => { :micro => '1y1d', :short => '1y 1d', :default => '1 yr 1 day', :long => '1 year 1 day', :chrono => '1:00:01:00:00:00' }, (3 * 365.25 * 24 * 3600 + 24 * 3600 ).to_i => { :micro => '3y1d', :short => '3y 1d', :default => '3 yrs 1 day', :long => '3 years 1 day', :chrono => '3:00:01:00:00:00' }, (3600 * 24 * 30 * 18) => { :micro => '18mo', :short => '18mo', :default => '18 mos', :long => '18 months', :chrono => '18:00:00:00:00' } } @exemplars.each do |k, v| v.each do |key, val| it "properly outputs a duration of #{k} seconds as #{val} using the #{key.to_s} format option" do expect(ChronicDuration.output(k, :format => key)).to eq(val) end end end @keep_zero_exemplars = { (true) => { :micro => '0s', :short => '0s', :default => '0 secs', :long => '0 seconds', :chrono => '0' }, (false) => { :micro => nil, :short => nil, :default => nil, :long => nil, :chrono => '0' }, } @keep_zero_exemplars.each do |k, v| v.each do |key, val| it "should properly output a duration of 0 seconds as #{val.nil? ? "nil" : val} using the #{key.to_s} format option, if the keep_zero option is #{k.to_s}" do expect(ChronicDuration.output(0, :format => key, :keep_zero => k)).to eq(val) end end end it "returns weeks when needed" do expect(ChronicDuration.output(45*24*60*60, :weeks => true)).to match(/.*wk.*/) end it "returns hours and minutes only when :hours_only option specified" do expect(ChronicDuration.output(395*24*60*60 + 15*60, :limit_to_hours => true)).to eq('9480 hrs 15 mins') end context 'with :hours_per_day and :days_per_month params' do it 'uses provided :hours_per_day' do expect(ChronicDuration.output(24 * 60 * 60, hours_per_day: 24)).to eq('1 day') expect(ChronicDuration.output(24 * 60 * 60, hours_per_day: 8)).to eq('3 days') end it 'uses provided :days_per_month' do expect(ChronicDuration.output(7 * 24 * 60 * 60, weeks: true, days_per_month: 30)).to eq('1 wk') expect(ChronicDuration.output(7 * 24 * 60 * 60, weeks: true, days_per_month: 20)).to eq('1 wk 2 days') end it 'uses provided both :hours_per_day and :days_per_month' do expect(ChronicDuration.output(7 * 24 * 60 * 60, weeks: true, days_per_month: 30, hours_per_day: 24)).to eq('1 wk') expect(ChronicDuration.output(5 * 8 * 60 * 60, weeks: true, days_per_month: 20, hours_per_day: 8)).to eq('1 wk') end it 'uses provided params alonside with :weeks when converting to months' do expect(ChronicDuration.output(30 * 24 * 60 * 60, days_per_month: 30, hours_per_day: 24)).to eq('1 mo') expect(ChronicDuration.output(30 * 24 * 60 * 60, days_per_month: 30, hours_per_day: 24, weeks: true)).to eq('1 mo 2 days') expect(ChronicDuration.output(20 * 8 * 60 * 60, days_per_month: 20, hours_per_day: 8)).to eq('1 mo') expect(ChronicDuration.output(20 * 8 * 60 * 60, days_per_month: 20, hours_per_day: 8, weeks: true)).to eq('1 mo') end end it "returns the specified number of units if provided" do expect(ChronicDuration.output(4 * 3600 + 60 + 1, units: 2)).to eq('4 hrs 1 min') expect(ChronicDuration.output(6 * 30 * 24 * 3600 + 24 * 3600 + 3600 + 60 + 1, units: 3, format: :long)).to eq('6 months 1 day 1 hour') end context "when the format is not specified" do it "uses the default format" do expect(ChronicDuration.output(2 * 3600 + 20 * 60)).to eq('2 hrs 20 mins') end end @exemplars.each do |seconds, format_spec| format_spec.each do |format, _| it "outputs a duration for #{seconds} that parses back to the same thing when using the #{format} format" do expect(ChronicDuration.parse( ChronicDuration.output(seconds, format: format, use_complete_matcher: true) )).to eq(seconds) end end end it "uses user-specified joiner if provided" do expect(ChronicDuration.output(2 * 3600 + 20 * 60, joiner: ', ')).to eq('2 hrs, 20 mins') end end describe ".filter_by_type" do it "receives a chrono-formatted time like 3:14 and return a human time like 3 minutes 14 seconds" do expect(ChronicDuration.instance_eval("filter_by_type('3:14')")).to eq('3 minutes 14 seconds') end it "receives chrono-formatted time like 12:10:14 and return a human time like 12 hours 10 minutes 14 seconds" do expect(ChronicDuration.instance_eval("filter_by_type('12:10:14')")).to eq('12 hours 10 minutes 14 seconds') end it "returns the input if it's not a chrono-formatted time" do expect(ChronicDuration.instance_eval("filter_by_type('4 hours')")).to eq('4 hours') end end describe ".cleanup" do it "cleans up extraneous words" do expect(ChronicDuration.instance_eval("cleanup('4 days and 11 hours')")).to eq('4 days 11 hours') end it "cleans up extraneous spaces" do expect(ChronicDuration.instance_eval("cleanup(' 4 days and 11 hours')")).to eq('4 days 11 hours') end it "inserts spaces where there aren't any" do expect(ChronicDuration.instance_eval("cleanup('4m11.5s')")).to eq('4 minutes 11.5 seconds') end end describe "work week" do before(:all) do ChronicDuration.hours_per_day = 8 ChronicDuration.days_per_month = 20 end after(:all) do ChronicDuration.hours_per_day = 24 ChronicDuration.days_per_month = 30 end it "should parse knowing the work week" do week = ChronicDuration.parse('5d') expect(ChronicDuration.parse('40h')).to eq(week) expect(ChronicDuration.parse('1w')).to eq(week) end end end