diff options
50 files changed, 1307 insertions, 167 deletions
diff --git a/INSTALL.md b/INSTALL.md index 4e052a0db..1d5badb79 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -9,7 +9,7 @@ and install that version. Example with [rvm](https://rvm.io/): - rvm install 2.3.0 + rvm install 2.3.1 Add the bundler gem @@ -92,6 +92,43 @@ To create Referential with some data (Route, JourneyPattern, VehicleJourney, etc If PG complains about illegal type `hstore` in your tests that is probably because the shared extension is not installed, here is what to do: +#### Check installation + +* Run tests + + bundle exec rake spec + bundle exec rake teaspoon + +* Start local server + + bundle exec rails server + + +#### Synchronize With STIF + +If you have access to STIF CodifLigne and Reflex : + +* Launch Sidekiq + + bundle exec sidekiq + +* Execute the Synchronization Tasks + + bundle exec rake codifligne:sync + bundle exec rake reflex:sync + +**N.B.** These are asynchronious tasks, you can observe the launched jobs in your [Sidekiq Console](http://localhost:3000/sidekiq) + +#### Data in various Apartments (Referentials) + +To create `Referential` objects with some data (`Route`, `JourneyPattern`, `VehicleJourney`, etc) : + + bundle exec rake referential:create + +# Troubleshooting + +If Postgres complains about illegal type `hstore` in your tests that is probably because the shared extension is not installed, here is what to do: + bundle exec rake db:test:purge Thanks to `lib/tasks/extensions.rake`. diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js b/app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js index 54d62f999..0ed961f44 100644 --- a/app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js +++ b/app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js @@ -84,6 +84,10 @@ const actions = { resetValidation: (target) => { $(target).parent().removeClass('has-error').children('.help-block').remove() }, + humanOID : (oid) => { + var a = oid.split(':') + return a[a.length - 1] + }, validateFields : (fields) => { const test = [] diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPattern.js b/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPattern.js index d9f6d5550..14ddf2b99 100644 --- a/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPattern.js +++ b/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPattern.js @@ -14,7 +14,7 @@ class JourneyPattern extends Component{ let vjURL = routeURL + '/vehicle_journeys?jp=' + jpOid return ( - <a data-no-turbolink="true" href={vjURL}>Horaires des courses</a> + <a data-turbolinks="false" href={vjURL}>Horaires des courses</a> ) } @@ -62,7 +62,7 @@ class JourneyPattern extends Component{ )} <div className='th'> - <div className='strong mb-xs'>{this.props.value.object_id ? this.props.value.object_id : '-'}</div> + <div className='strong mb-xs'>{this.props.value.object_id ? actions.humanOID(this.props.value.object_id) : '-'}</div> <div>{this.props.value.registration_number}</div> <div>{actions.getChecked(this.props.value.stop_points).length} arrêt(s)</div> diff --git a/app/assets/javascripts/es6_browserified/time_tables/actions/index.js b/app/assets/javascripts/es6_browserified/time_tables/actions/index.js index cef9bc75d..3f15b7f01 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/actions/index.js +++ b/app/assets/javascripts/es6_browserified/time_tables/actions/index.js @@ -45,7 +45,7 @@ const actions = { pagination, nextPage : true }), - changePage : (dispatch, pagination, val) => ({ + changePage : (dispatch, val) => ({ type: 'CHANGE_PAGE', dispatch, page: val @@ -104,16 +104,14 @@ const actions = { timeTablePeriods, metas }), - includeDateInPeriod: (index, day, dayTypes) => ({ + includeDateInPeriod: (index, dayTypes) => ({ type: 'INCLUDE_DATE_IN_PERIOD', index, - day, dayTypes }), - excludeDateFromPeriod: (index, day, dayTypes) => ({ + excludeDateFromPeriod: (index, dayTypes) => ({ type: 'EXCLUDE_DATE_FROM_PERIOD', index, - day, dayTypes }), openConfirmModal : (callback) => ({ @@ -140,6 +138,10 @@ const actions = { return (D + ' ' + M + ' ' + Y) }, + getLocaleDate(strDate) { + let date = new Date(strDate) + return date.toLocaleDateString() + }, updateSynthesis: (state, daytypes) => { let periods = state.time_table_periods diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/ExceptionsInDay.js b/app/assets/javascripts/es6_browserified/time_tables/components/ExceptionsInDay.js index 13615a6ef..e90099283 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/ExceptionsInDay.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/ExceptionsInDay.js @@ -21,7 +21,7 @@ class ExceptionsInDay extends Component { data-actiontype='remove' onClick={(e) => { $(e.currentTarget).toggleClass('active') - this.props.onExcludeDateFromPeriod(this.props.index, this.props.value.current_month[this.props.index], this.props.metas.day_types) + this.props.onExcludeDateFromPeriod(this.props.index, this.props.metas.day_types) }} > <span className='fa fa-times'></span> @@ -37,7 +37,7 @@ class ExceptionsInDay extends Component { data-actiontype='add' onClick={(e) => { $(e.currentTarget).toggleClass('active') - this.props.onIncludeDateInPeriod(this.props.index, this.props.value.current_month[this.props.index], this.props.metas.day_types) + this.props.onIncludeDateInPeriod(this.props.index, this.props.metas.day_types) }} > <span className='fa fa-plus'></span> diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/Navigate.js b/app/assets/javascripts/es6_browserified/time_tables/components/Navigate.js index c43cd025a..74ca36ea6 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/Navigate.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/Navigate.js @@ -39,7 +39,7 @@ let Navigate = ({ dispatch, metas, timetable, pagination, status, filters}) => { value={month} onClick={e => { e.preventDefault() - dispatch(actions.checkConfirmModal(e, actions.changePage(dispatch, pagination, e.currentTarget.value), pagination.stateChanged, dispatch)) + dispatch(actions.checkConfirmModal(e, actions.changePage(dispatch, e.currentTarget.value), pagination.stateChanged, dispatch)) }} > {actions.monthName(month) + ' ' + new Date(month).getFullYear()} diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodManager.js b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodManager.js index de3f31ee0..cf4cbfb32 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodManager.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodManager.js @@ -8,14 +8,33 @@ class PeriodManager extends Component { super(props) } + toEndPeriod(curr, end) { + let diff + + let startCurrM = curr.split('-')[1] + let endPeriodM = end.split('-')[1] + + let lastDayInM = new Date(curr.split('-')[2], startCurrM + 1, 0) + lastDayInM = lastDayInM.toJSON().substr(0, 10).split('-')[2] + + if(startCurrM === endPeriodM) { + diff = (end.split('-')[2] - curr.split('-')[2]) + } else { + diff = (lastDayInM - curr.split('-')[2]) + } + + return diff + } + render() { return ( <div className='period_manager' id={this.props.value.id} + data-toendperiod={this.toEndPeriod(this.props.currentDate.toJSON().substr(0, 10), this.props.value.period_end)} > <p className='strong'> - {actions.getHumanDate(this.props.value.period_start, 3).substr(0, 7) + ' > ' + actions.getHumanDate(this.props.value.period_end, 3)} + {actions.getLocaleDate(this.props.value.period_start) + ' > ' + actions.getLocaleDate(this.props.value.period_end)} </p> <div className='dropdown'> @@ -58,6 +77,7 @@ class PeriodManager extends Component { PeriodManager.propTypes = { value: PropTypes.object.isRequired, + currentDate: PropTypes.object.isRequired, onDeletePeriod: PropTypes.func.isRequired, onOpenEditPeriodForm: PropTypes.func.isRequired } diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodsInDay.js b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodsInDay.js index 93a8fe433..ca44d3a07 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodsInDay.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodsInDay.js @@ -50,9 +50,10 @@ class PeriodsInDay extends Component { key={i} index={i} value={p} + metas={this.props.metas} + currentDate={this.props.currentDate} onDeletePeriod={this.props.onDeletePeriod} onOpenEditPeriodForm={this.props.onOpenEditPeriodForm} - metas={this.props.metas} /> ) } else { diff --git a/app/assets/javascripts/es6_browserified/time_tables/containers/Timetable.js b/app/assets/javascripts/es6_browserified/time_tables/containers/Timetable.js index 2a17d3dea..c6b5fcc6b 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/containers/Timetable.js +++ b/app/assets/javascripts/es6_browserified/time_tables/containers/Timetable.js @@ -15,11 +15,11 @@ const mapDispatchToProps = (dispatch) => { onDeletePeriod: (index, dayTypes) =>{ dispatch(actions.deletePeriod(index, dayTypes)) }, - onExcludeDateFromPeriod: (index, day, dayTypes) => { - dispatch(actions.excludeDateFromPeriod(index, day, dayTypes)) + onExcludeDateFromPeriod: (index, dayTypes) => { + dispatch(actions.excludeDateFromPeriod(index, dayTypes)) }, - onIncludeDateInPeriod: (index, day, dayTypes) => { - dispatch(actions.includeDateInPeriod(index, day, dayTypes)) + onIncludeDateInPeriod: (index, dayTypes) => { + dispatch(actions.includeDateInPeriod(index, dayTypes)) }, onOpenEditPeriodForm: (period, index) => { dispatch(actions.openEditPeriodForm(period, index)) diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js index ea03694bd..0e6f5ed12 100644 --- a/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js +++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js @@ -248,6 +248,10 @@ const actions = { type: 'RECEIVE_TOTAL_COUNT', total }), + humanOID : (oid) => { + var a = oid.split(':') + return a[a.length - 1] + }, fetchVehicleJourneys : (dispatch, currentPage, nextPage, queryString) => { if(currentPage == undefined){ currentPage = 1 diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourney.js index d795d76e3..6f338f747 100644 --- a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourney.js +++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourney.js @@ -1,6 +1,7 @@ var React = require('react') var Component = require('react').Component var PropTypes = require('react').PropTypes +var actions = require('../actions') class VehicleJourney extends Component { constructor(props) { @@ -48,8 +49,8 @@ class VehicleJourney extends Component { return ( <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.errors ? ' has-error': '')}> <div className='th'> - <div className='strong mb-xs'>{this.props.value.objectid ? this.props.value.objectid : '-'}</div> - <div>{this.props.value.journey_pattern.objectid}</div> + <div className='strong mb-xs'>{this.props.value.objectid ? actions.humanOID(this.props.value.objectid) : '-'}</div> + <div>{actions.humanOID(this.props.value.journey_pattern.objectid)}</div> {this.props.value.time_tables.map((tt, i)=> <div key={i}>{this.timeTableURL(tt)}</div> )} diff --git a/app/assets/stylesheets/modules/_jp_collection.sass b/app/assets/stylesheets/modules/_jp_collection.sass index d1f864e5c..c109fc71a 100644 --- a/app/assets/stylesheets/modules/_jp_collection.sass +++ b/app/assets/stylesheets/modules/_jp_collection.sass @@ -82,3 +82,19 @@ .t2e-head > .td:last-child > div > span &:after bottom: 50% + + .t2e-head > .td:nth-child(2) > div, + .t2e-head > .td:last-child > div + > span:before + content: '•' + color: $blue + text-align: center + font-size: 28px + letter-spacing: 0 + text-indent: -0.01em + line-height: 12px + width: 15px + height: 15px + left: -23px + top: 50% + margin-top: -8px diff --git a/app/assets/stylesheets/modules/_routes_stopoints.sass b/app/assets/stylesheets/modules/_routes_stopoints.sass index 735e91df7..88e662849 100644 --- a/app/assets/stylesheets/modules/_routes_stopoints.sass +++ b/app/assets/stylesheets/modules/_routes_stopoints.sass @@ -12,7 +12,7 @@ height: 100% > div:first-child position: relative - padding-left: 20px /* intial value is 10 */ + padding-left: 25px /* intial value is 10 */ overflow: hidden span @@ -70,3 +70,19 @@ .nested-head + .nested-fields > .wrapper > div:first-child span:after top: 5px + + .nested-head + .nested-fields > .wrapper > div:first-child, + .nested-fields:last-child > .wrapper:last-child > div:first-child + span:before + content: '•' + color: $blue + text-align: center + font-size: 28px + letter-spacing: 0 + text-indent: -0.01em + line-height: 12px + width: 15px + height: 15px + left: -23px + top: 50% + margin-top: -8px diff --git a/app/assets/stylesheets/modules/_timetables.sass b/app/assets/stylesheets/modules/_timetables.sass index 03dba3e23..84f1af043 100644 --- a/app/assets/stylesheets/modules/_timetables.sass +++ b/app/assets/stylesheets/modules/_timetables.sass @@ -125,21 +125,35 @@ display: block height: auto word-wrap: normal - white-space: nowrap + white-space: normal position: absolute - left: 8px + left: 0 top: 50% transform: translateY(-50%) z-index: 5 + padding: 0 8px + + @for $i from 0 through 31 + &[data-toendperiod='#{$i}'] + width: 40px * ($i + 1) > * display: inline-block vertical-align: middle margin: 0 + max-width: calc(100% - 30px) &.dropdown margin-left: 5px + &[data-toendperiod='0'], &[data-toendperiod='1'], &[data-toendperiod='2'] + max-width: none + > * + display: none + + &.dropdown + display: inline-block + .btn.dropdown-toggle color: $blue background-color: rgba(#fff, 0) diff --git a/app/assets/stylesheets/modules/_vj_collection.sass b/app/assets/stylesheets/modules/_vj_collection.sass index 50ad1cd54..8ff983310 100644 --- a/app/assets/stylesheets/modules/_vj_collection.sass +++ b/app/assets/stylesheets/modules/_vj_collection.sass @@ -79,6 +79,27 @@ &:after bottom: -6px + .t2e-head > .td:last-child > div > span + &:after + bottom: 50% + + .table-2entries .t2e-head > .td:nth-child(2) > div, + .table-2entries .t2e-head > .td:last-child > div, + .table-2entries.no_result .t2e-head > .td:last-child > div + > span:before + content: '•' + color: $blue + text-align: center + font-size: 28px + letter-spacing: 0 + text-indent: -0.01em + line-height: 12px + width: 15px + height: 15px + left: -23px + top: 50% + margin-top: -8px + // Errors .table-2entries .t2e-item-list .t2e-item @@ -92,7 +113,6 @@ left: 0 right: 0 bottom: 0 - z-index: 5 border: 2px solid $red > .th diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f2c9b4c6f..42b7c2a25 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -12,7 +12,10 @@ class ApplicationController < ActionController::Base helper LanguageEngine::Engine.helpers def set_locale - I18n.locale = session[:language] || I18n.default_locale + # I18n.locale = session[:language] || I18n.default_locale + # For testing different locales w/o restarting the server + I18n.locale = (params['lang'] || session[:language] || I18n.default_locale).to_sym + logger.info "locale set to #{I18n.locale.inspect}" end def pundit_user diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb index d18e165d2..5370d9cbb 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -45,14 +45,13 @@ class CalendarsController < BreadcrumbController end def ransack_contains_date - # 3 parts to date object, in order to use in ransackable_scopes + date =[] if params[:q] && !params[:q]['contains_date(1i)'].empty? - date =[] ['contains_date(1i)', 'contains_date(2i)', 'contains_date(3i)'].each do |key| - date << params[:q][key] + date << params[:q][key].to_i params[:q].delete(key) end - params[:q]['contains_date'] = Date.parse(date.join('-')) + params[:q]['contains_date'] = Date.new(*date) end end diff --git a/app/controllers/routes_controller.rb b/app/controllers/routes_controller.rb index a1aadf883..73febc4b9 100644 --- a/app/controllers/routes_controller.rb +++ b/app/controllers/routes_controller.rb @@ -46,12 +46,6 @@ class RoutesController < ChouetteController end end - # overwrite inherited resources to use delete instead of destroy - # foreign keys will propagate deletion) - def destroy_resource(object) - object.delete - end - def destroy destroy! do |success, failure| success.html { redirect_to referential_line_path(@referential,@line) } diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb index c084b592a..316652ca2 100644 --- a/app/controllers/vehicle_journeys_controller.rb +++ b/app/controllers/vehicle_journeys_controller.rb @@ -77,14 +77,14 @@ class VehicleJourneysController < ChouetteController protected def collection - scope = route.vehicle_journeys.joins(:journey_pattern).joins('LEFT JOIN "vehicle_journey_at_stops" ON "vehicle_journey_at_stops"."vehicle_journey_id" = "vehicle_journeys"."id"') + scope = route.vehicle_journeys.with_stops @q = scope.search filtered_ransack_params grouping = ransack_periode_filter @q.build_grouping(grouping) if grouping @ppage = 20 - @vehicle_journeys = @q.result(distinct: true).paginate(:page => params[:page], :per_page => @ppage) + @vehicle_journeys = @q.result.paginate(:page => params[:page], :per_page => @ppage) @footnotes = route.line.footnotes.to_json @matrix = resource_class.matrix(@vehicle_journeys) @vehicle_journeys diff --git a/app/models/calendar.rb b/app/models/calendar.rb index cd945a67f..91a17e853 100644 --- a/app/models/calendar.rb +++ b/app/models/calendar.rb @@ -118,9 +118,18 @@ class Calendar < ActiveRecord::Base end end + def flatten_date_array attributes, key + date_int = %w(1 2 3).map {|e| attributes["#{key}(#{e}i)"].to_i } + Date.new(*date_int) + end + def periods_attributes=(attributes = {}) @periods = [] attributes.each do |index, period_attribute| + # Convert date_select to date + ['begin', 'end'].map do |attr| + period_attribute[attr] = flatten_date_array(period_attribute, attr) + end period = Period.new(period_attribute.merge(id: index)) @periods << period unless period.marked_for_destruction? end @@ -223,6 +232,7 @@ class Calendar < ActiveRecord::Base def date_values_attributes=(attributes = {}) @date_values = [] attributes.each do |index, date_value_attribute| + date_value_attribute['value'] = flatten_date_array(date_value_attribute, 'value') date_value = DateValue.new(date_value_attribute.merge(id: index)) @date_values << date_value unless date_value.marked_for_destruction? end diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb index 429189ff5..9f6344055 100644 --- a/app/models/chouette/route.rb +++ b/app/models/chouette/route.rb @@ -105,7 +105,7 @@ class Chouette::Route < Chouette::TridentActiveRecord end def time_tables - self.vehicle_journeys.joins(:time_tables).map(&:"time_tables").flatten.uniq + vehicle_journeys.joins(:time_tables).map(&:"time_tables").flatten.uniq end def sorted_vehicle_journeys(journey_category_model) diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb index 798fa81b4..37f609163 100644 --- a/app/models/chouette/time_table.rb +++ b/app/models/chouette/time_table.rb @@ -33,6 +33,31 @@ class Chouette::TimeTable < Chouette::TridentActiveRecord validates_associated :dates validates_associated :periods + def continuous_dates + chunk = {} + group = nil + self.dates.where(in_out: true).each_with_index do |date, index| + group ||= index + group = (date.date == dates[index - 1].date + 1.day) ? group : group + 1 + chunk[group] ||= [] + chunk[group] << date + end + chunk.values + end + + def convert_continuous_dates_to_periods + chunks = self.continuous_dates + # Remove less than 3 continuous day chunk + chunks.delete_if {|chunk| chunk.count < 3} + + transaction do + chunks.each do |chunk| + self.periods.create!(period_start: chunk.first.date, period_end: chunk.last.date) + chunk.map(&:destroy) + end + end + end + def state_update state update_attributes(self.class.state_permited_attributes(state)) self.tag_list = state['tags'].collect{|t| t['name']}.join(', ') diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb index 4d7d596d8..297e462f0 100644 --- a/app/models/chouette/vehicle_journey.rb +++ b/app/models/chouette/vehicle_journey.rb @@ -209,5 +209,18 @@ module Chouette end end + def self.with_stops + self + .joins(:journey_pattern) + .joins(' + LEFT JOIN "vehicle_journey_at_stops" + ON "vehicle_journey_at_stops"."vehicle_journey_id" = + "vehicle_journeys"."id" + AND "vehicle_journey_at_stops"."stop_point_id" = + "journey_patterns"."departure_stop_point_id" + ') + .order("vehicle_journey_at_stops.departure_time") + end + end end diff --git a/app/models/route_observer.rb b/app/models/route_observer.rb index 71578c6da..1848bbc85 100644 --- a/app/models/route_observer.rb +++ b/app/models/route_observer.rb @@ -14,8 +14,8 @@ class RouteObserver < ActiveRecord::Observer end end - def after_destroy(route) + def before_destroy(route) Rails.logger.debug "after_destroy(#{route.inspect})" - line.routes.where(opposite_route: route).update_all(opposite_route: nil) + route.line.routes.where(opposite_route: route).update_all(opposite_route_id: nil) end end diff --git a/app/models/time_table_combination.rb b/app/models/time_table_combination.rb index 783ef53ef..519c02f2b 100644 --- a/app/models/time_table_combination.rb +++ b/app/models/time_table_combination.rb @@ -1,33 +1,33 @@ class TimeTableCombination - include ActiveModel::Validations - include ActiveModel::Conversion + include ActiveModel::Validations + include ActiveModel::Conversion extend ActiveModel::Naming - - attr_accessor :source_id, :combined_id, :operation - - validates_presence_of :source_id, :combined_id, :operation + + attr_accessor :source_id, :combined_id, :operation + + validates_presence_of :source_id, :combined_id, :operation validates_inclusion_of :operation, :in => %w( union intersection disjunction), :allow_nil => true - + def clean self.source_id = nil self.combined_id = nil self.operation = nil self.errors.clear - end - + end + def self.operations %w( union intersection disjunction) end - def initialize(attributes = {}) - attributes.each do |name, value| - send("#{name}=", value) - end - end - - def persisted? - false - end + def initialize(attributes = {}) + attributes.each do |name, value| + send("#{name}=", value) + end + end + + def persisted? + false + end def combine source = Chouette::TimeTable.find( source_id) @@ -38,10 +38,10 @@ class TimeTableCombination source.intersect! combined elsif operation == "disjunction" source.disjoin! combined - else + else raise "unknown operation" end source end - + end diff --git a/app/views/line_referentials/show.html.slim b/app/views/line_referentials/show.html.slim index 5c0df1a71..95c2c02b0 100644 --- a/app/views/line_referentials/show.html.slim +++ b/app/views/line_referentials/show.html.slim @@ -27,9 +27,9 @@ table.table thead tr - th Synchronisé - th Statut - th Message + th = t('.synchronized') + th = t('.status') + th = t('.message') tbody - @line_referential.line_referential_syncs.each_with_index do |sync, i| @@ -39,8 +39,9 @@ - sync.line_referential_sync_messages.last.tap do |log| - if log.criticity = log.criticity tr - td = l(log.created_at, format: :short) - td + td style='width: 150px' + = l(log.created_at, format: :short_with_time) + td.text-center .fa.fa-circle class="text-#{criticity_class(log.criticity)}" td - data = log.message_attributs.symbolize_keys! diff --git a/app/views/referentials/show.html.slim b/app/views/referentials/show.html.slim index 45fffe6a1..befa851ab 100644 --- a/app/views/referentials/show.html.slim +++ b/app/views/referentials/show.html.slim @@ -50,8 +50,8 @@ { 'ID Codif' => Proc.new { |n| n.objectid.local_id }, :number => 'number', :name => 'name', - :deactivated => Proc.new{|n| n.deactivated? ? t('false') : t('true')}, - :transport_mode => 'transport_mode', + :deactivated => Proc.new{ |n| n.deactivated? ? t('false') : t('true') }, + :transport_mode => Proc.new{ |n| n.transport_mode ? t("enumerize.line.transport_mode.#{n.transport_mode}") : '' }, 'networks.name' => Proc.new { |n| n.try(:network).try(:name) }, 'companies.name' => Proc.new { |n| n.try(:company).try(:name) } }, [:show], diff --git a/app/views/stop_area_referentials/show.html.slim b/app/views/stop_area_referentials/show.html.slim index 24428eea4..56ecbf6da 100644 --- a/app/views/stop_area_referentials/show.html.slim +++ b/app/views/stop_area_referentials/show.html.slim @@ -32,8 +32,9 @@ - sync.stop_area_referential_sync_messages.last.tap do |log| - if log.criticity = log.criticity tr - td = l(log.created_at, format: :short) - td + td style='width:150px' + = l(log.created_at, format: :short_with_time) + td.text-center .fa.fa-circle class="text-#{criticity_class(log.criticity)}" td - data = log.message_attributs.symbolize_keys! diff --git a/app/views/time_tables/index.html.slim b/app/views/time_tables/index.html.slim index 01b65653c..c17f96c85 100644 --- a/app/views/time_tables/index.html.slim +++ b/app/views/time_tables/index.html.slim @@ -15,7 +15,7 @@ .row .col-lg-12 = table_builder @time_tables, - { :color => Proc.new{|tt| tt.color ? content_tag(:span, '', class: 'fa fa-circle', style: "color:#{tt.color}") : '-' }, :comment => 'comment', + { 'OiD' => Proc.new { |n| n.objectid.local_id }, :color => Proc.new{|tt| tt.color ? content_tag(:span, '', class: 'fa fa-circle', style: "color:#{tt.color}") : '-' }, :comment => 'comment', "Période englobante" => Proc.new{ |tt| tt.bounding_dates.empty? ? '-' : t('bounding_dates', debut: l(tt.bounding_dates.min), end: l(tt.bounding_dates.max)) }, "Nombre de courses associées" => Proc.new{ |tt| tt.vehicle_journeys.count }, "Journées d'application" => Proc.new{ |tt| (%w(monday tuesday wednesday thursday friday saturday sunday).collect{|d| tt.send(d) ? t("calendars.days.#{d}") : '' }).reject{|a| a.empty?}.join(', ').html_safe }, diff --git a/config/application.rb b/config/application.rb index 8606a3156..6c8de781d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -25,7 +25,7 @@ module ChouetteIhm # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] - config.i18n.default_locale = :fr + config.i18n.default_locale = ENV.fetch('RAILS_LOCALE', 'fr').to_sym # Configure Browserify to use babelify to compile ES6 config.browserify_rails.commandline_options = "-t [ babelify --presets [ react es2015 ] ]" diff --git a/config/locales/en.yml b/config/locales/en.yml index 5ed0b9ec5..3f6a68f8d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -8,10 +8,12 @@ en: hour: "%Hh%M" minute: "%M min" short: "%Y/%m/%d" + short_with_time: "%Y/%m/%d at %Hh%M" date: formats: short: "%Y/%m/%d" + short_with_time: "%Y/%m/%d at %Hh%M" last_update: 'Last update on<br>%{time}' last_sync: 'Last sync on %{time}' diff --git a/config/locales/fr.yml b/config/locales/fr.yml index db8a3608d..49d1c5bb7 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -8,10 +8,12 @@ fr: hour: "%Hh%M" minute: "%M min" short: "%d/%m/%Y" + short_with_time: "%d/%m/%Y à %Hh%M" date: formats: short: "%d/%m/%Y" + short_with_time: "%d/%m/%Y à %Hh%M" last_update: 'Dernière mise à jour<br>le %{time}' last_sync: 'Dernière mise à jour le %{time}' diff --git a/config/locales/line_referentials.en.yml b/config/locales/line_referentials.en.yml index 7d84a24f8..78083912d 100644 --- a/config/locales/line_referentials.en.yml +++ b/config/locales/line_referentials.en.yml @@ -8,6 +8,9 @@ en: title: "Edit %{line_referential} referential" show: title: "iLICO synchronization" + synchronized: Synchronized + status: Status + message: Message activerecord: models: line_referential: diff --git a/config/locales/line_referentials.fr.yml b/config/locales/line_referentials.fr.yml index fe8496d99..c8dfbd640 100644 --- a/config/locales/line_referentials.fr.yml +++ b/config/locales/line_referentials.fr.yml @@ -8,6 +8,9 @@ fr: title: "Editer le référentiel %{line_referential}" show: title: 'Synchronisation iLICO' + synchronized: Synchronisé + status: Statut + message: Message activerecord: models: line_referential: diff --git a/db/schema.rb b/db/schema.rb index 17df3c34f..a45181687 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -283,7 +283,7 @@ ActiveRecord::Schema.define(version: 20170502130327) do t.datetime "started_at" t.datetime "ended_at" t.string "token_download" - t.string "type", limit: 255 + t.string "type" end add_index "imports", ["referential_id"], name: "index_imports_on_referential_id", using: :btree @@ -601,7 +601,7 @@ ActiveRecord::Schema.define(version: 20170502130327) do create_table "stop_areas", id: :bigserial, force: :cascade do |t| t.integer "parent_id", limit: 8 - t.string "objectid", null: false + t.string "objectid", null: false t.integer "object_version", limit: 8 t.string "creator_id" t.string "name" @@ -610,8 +610,8 @@ ActiveRecord::Schema.define(version: 20170502130327) do t.string "registration_number" t.string "nearest_topic_name" t.integer "fare_code" - t.decimal "longitude", precision: 19, scale: 16 - t.decimal "latitude", precision: 19, scale: 16 + t.decimal "longitude", precision: 19, scale: 16 + t.decimal "latitude", precision: 19, scale: 16 t.string "long_lat_type" t.string "country_code" t.string "street_name" @@ -629,7 +629,7 @@ ActiveRecord::Schema.define(version: 20170502130327) do t.datetime "deleted_at" t.datetime "created_at" t.datetime "updated_at" - t.string "stif_type", limit: 255 + t.string "stif_type" end add_index "stop_areas", ["name"], name: "index_stop_areas_on_name", using: :btree @@ -696,18 +696,18 @@ ActiveRecord::Schema.define(version: 20170502130327) do add_index "time_table_periods", ["time_table_id"], name: "index_time_table_periods_on_time_table_id", using: :btree create_table "time_tables", id: :bigserial, force: :cascade do |t| - t.string "objectid", null: false - t.integer "object_version", limit: 8, default: 1 + t.string "objectid", null: false + t.integer "object_version", limit: 8, default: 1 t.string "creator_id" t.string "version" t.string "comment" - t.integer "int_day_types", default: 0 + t.integer "int_day_types", default: 0 t.date "start_date" t.date "end_date" t.integer "calendar_id", limit: 8 t.datetime "created_at" t.datetime "updated_at" - t.string "color", limit: 255 + t.string "color" t.integer "created_from_id" end diff --git a/lib/stif/codif_line_synchronization.rb b/lib/stif/codif_line_synchronization.rb index feb313f45..2bb4d8778 100644 --- a/lib/stif/codif_line_synchronization.rb +++ b/lib/stif/codif_line_synchronization.rb @@ -11,18 +11,18 @@ module Stif def processed_counts { - imported: self.imported_count, - updated: self.updated_count, - deleted: self.deleted_count + imported: imported_count, + updated: updated_count, + deleted: deleted_count } end def increment_counts prop_name, value - self.send("#{prop_name}=", self.send(prop_name) + value) + send("#{prop_name}=", self.send(prop_name) + value) end def synchronize - self.reset_counts + reset_counts start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) # Fetch Codifline data client = Codifligne::API.new diff --git a/lib/stif/reflex_synchronization.rb b/lib/stif/reflex_synchronization.rb index 6d8497bce..675486265 100644 --- a/lib/stif/reflex_synchronization.rb +++ b/lib/stif/reflex_synchronization.rb @@ -35,7 +35,7 @@ module Stif end def synchronize - self.reset_counts + reset_counts ['getOR', 'getOP'].each do |method| start = Time.now results = Reflex::API.new().process(method) diff --git a/spec/factories/chouette_routes.rb b/spec/factories/chouette_routes.rb index e872d24f5..c1a9423c5 100644 --- a/spec/factories/chouette_routes.rb +++ b/spec/factories/chouette_routes.rb @@ -20,7 +20,18 @@ FactoryGirl.define do create_list(:stop_point, evaluator.stop_points_count, route: route) end + factory :route_with_journey_patterns do + transient do + journey_patterns_count 2 + end + + after(:create) do |route, evaluator| + create_list(:journey_pattern, evaluator.journey_patterns_count, route: route) + end + + end end + end end diff --git a/spec/factories/chouette_vehicle_journey.rb b/spec/factories/chouette_vehicle_journey.rb index b7c5e37d5..9ba660800 100644 --- a/spec/factories/chouette_vehicle_journey.rb +++ b/spec/factories/chouette_vehicle_journey.rb @@ -3,29 +3,31 @@ FactoryGirl.define do factory :vehicle_journey_common, :class => Chouette::VehicleJourney do sequence(:objectid) { |n| "test:VehicleJourney:#{n}" } - factory :vehicle_journey do + factory :vehicle_journey_empty do association :journey_pattern, :factory => :journey_pattern after(:build) do |vehicle_journey| vehicle_journey.route = vehicle_journey.journey_pattern.route end - after(:create) do |vehicle_journey| - vehicle_journey.journey_pattern.stop_points.each_with_index do |stop_point, index| - vehicle_journey.vehicle_journey_at_stops << create(:vehicle_journey_at_stop, - :vehicle_journey => vehicle_journey, - :stop_point => stop_point, - :arrival_time => '2000-01-01 01:00:00 UTC', - :departure_time => '2000-01-01 03:00:00 UTC') + factory :vehicle_journey do + after(:create) do |vehicle_journey| + vehicle_journey.journey_pattern.stop_points.each_with_index do |stop_point, index| + vehicle_journey.vehicle_journey_at_stops << create(:vehicle_journey_at_stop, + :vehicle_journey => vehicle_journey, + :stop_point => stop_point, + :arrival_time => '2000-01-01 01:00:00 UTC', + :departure_time => '2000-01-01 03:00:00 UTC') + end end - end - factory :vehicle_journey_odd do - association :journey_pattern, :factory => :journey_pattern_odd - end + factory :vehicle_journey_odd do + association :journey_pattern, :factory => :journey_pattern_odd + end - factory :vehicle_journey_even do - association :journey_pattern, :factory => :journey_pattern_even + factory :vehicle_journey_even do + association :journey_pattern, :factory => :journey_pattern_even + end end end end diff --git a/spec/javascripts/time_table/actions_spec.js b/spec/javascripts/time_table/actions_spec.js index eac2f86bb..1ab5635a0 100644 --- a/spec/javascripts/time_table/actions_spec.js +++ b/spec/javascripts/time_table/actions_spec.js @@ -1,5 +1,16 @@ var actions = require('es6_browserified/time_tables/actions') - +const dispatch = function(){} +const dayTypes = [true, true, true, true, true, true, true] +const day = { + date : "2017-05-01", + day : "lundi", + excluded_date : false, + in_periods : true, + include_date : false, + mday : 1, + wday : 1, + wnumber : "18" +} describe('actions', () => { it('should create an action to update dayTypes', () => { const expectedAction = { @@ -48,4 +59,146 @@ describe('actions', () => { } expect(actions.unselect2Tags(selectedItem)).toEqual(expectedAction) }) + + it('should create an action to go to previous page', () => { + let pagination = { + currentPage: '2017-01-01', + periode_range: [], + stateChanged: false + } + const expectedAction = { + type: 'GO_TO_PREVIOUS_PAGE', + dispatch, + pagination, + nextPage: false + } + expect(actions.goToPreviousPage(dispatch, pagination)).toEqual(expectedAction) + }) + + it('should create an action to go to next page', () => { + let pagination = { + currentPage: '2017-01-01', + periode_range: [], + stateChanged: false + } + const expectedAction = { + type: 'GO_TO_NEXT_PAGE', + dispatch, + pagination, + nextPage: true + } + expect(actions.goToNextPage(dispatch, pagination)).toEqual(expectedAction) + }) + + it('should create an action to change page', () => { + let page = '2017-05-04' + const expectedAction = { + type: 'CHANGE_PAGE', + dispatch, + page: page + } + expect(actions.changePage(dispatch, page)).toEqual(expectedAction) + }) + + it('should create an action to delete period', () => { + let index = 1 + const expectedAction = { + type: 'DELETE_PERIOD', + index, + dayTypes + } + expect(actions.deletePeriod(index, dayTypes)).toEqual(expectedAction) + }) + + it('should create an action to open add period form', () => { + const expectedAction = { + type: 'OPEN_ADD_PERIOD_FORM', + } + expect(actions.openAddPeriodForm()).toEqual(expectedAction) + }) + + it('should create an action to open edit period form', () => { + let period = { + id : 1, + period_end : "2017-03-05", + period_start : "2017-02-23" + } + let index = 1 + const expectedAction = { + type: 'OPEN_EDIT_PERIOD_FORM', + period, + index + } + expect(actions.openEditPeriodForm(period, index)).toEqual(expectedAction) + }) + + it('should create an action to close period form', () => { + const expectedAction = { + type: 'CLOSE_PERIOD_FORM', + } + expect(actions.closePeriodForm()).toEqual(expectedAction) + }) + + it('should create an action to update period form', () => { + let val = "11" + let group = "start" + let selectType = "day" + const expectedAction = { + type: 'UPDATE_PERIOD_FORM', + val, + group, + selectType + } + expect(actions.updatePeriodForm(val, group, selectType)).toEqual(expectedAction) + }) + + it('should create an action to validate period form', () => { + let modalProps = {} + let timeTablePeriods = [] + let metas = {} + const expectedAction = { + type: 'VALIDATE_PERIOD_FORM', + modalProps, + timeTablePeriods, + metas + } + expect(actions.validatePeriodForm(modalProps, timeTablePeriods, metas)).toEqual(expectedAction) + }) + + it('should create an action to include date in period', () => { + let index = 1 + const expectedAction = { + type: 'INCLUDE_DATE_IN_PERIOD', + index, + dayTypes + } + expect(actions.includeDateInPeriod(index, dayTypes)).toEqual(expectedAction) + }) + + it('should create an action to exclude date from period', () => { + let index = 1 + const expectedAction = { + type: 'EXCLUDE_DATE_FROM_PERIOD', + index, + dayTypes + } + expect(actions.excludeDateFromPeriod(index, dayTypes)).toEqual(expectedAction) + }) + + it('should create an action to open confirm modal', () => { + let callback = function(){} + const expectedAction = { + type: 'OPEN_CONFIRM_MODAL', + callback + } + expect(actions.openConfirmModal(callback)).toEqual(expectedAction) + }) + + it('should create an action to close modal', () => { + const expectedAction = { + type: 'CLOSE_MODAL', + } + expect(actions.closeModal()).toEqual(expectedAction) + }) + }) diff --git a/spec/javascripts/time_table/reducers/metas_spec.js b/spec/javascripts/time_table/reducers/metas_spec.js index adc6a9d05..61e3048db 100644 --- a/spec/javascripts/time_table/reducers/metas_spec.js +++ b/spec/javascripts/time_table/reducers/metas_spec.js @@ -2,7 +2,7 @@ var metasReducer = require('es6_browserified/time_tables/reducers/metas') let state = {} -describe('status reducer', () => { +describe('metas reducer', () => { beforeEach(() => { let tag = { id: 0, diff --git a/spec/javascripts/time_table/reducers/modal_spec.js b/spec/javascripts/time_table/reducers/modal_spec.js new file mode 100644 index 000000000..4246027b8 --- /dev/null +++ b/spec/javascripts/time_table/reducers/modal_spec.js @@ -0,0 +1,249 @@ +var modalReducer = require('es6_browserified/time_tables/reducers/modal') + +let state = {} + +describe('modal reducer', () => { + beforeEach(() => { + state = { + confirmModal: {}, + modalProps: { + active: false, + begin: { + day: '01', + month: '01', + year: String(new Date().getFullYear()) + }, + end: { + day: '01', + month: '01', + year: String(new Date().getFullYear()) + }, + index: false, + error: '' + }, + type: "" + } + }) + + it('should return the initial state', () => { + expect( + modalReducer(undefined, {}) + ).toEqual({}) + }) + + it('should handle OPEN_CONFIRM_MODAL', () => { + let callback = function(){} + expect( + modalReducer(state, { + type: 'OPEN_CONFIRM_MODAL', + callback + }) + ).toEqual(Object.assign({}, state, {type: "confirm", confirmModal: { callback: callback }})) + }) + + it('should handle CLOSE_PERIOD_FORM', () => { + let newModalProps = Object.assign({}, state.modalProps, {active: false}) + expect( + modalReducer(state, { + type: 'CLOSE_PERIOD_FORM' + }) + ).toEqual(Object.assign({}, state, {modalProps: newModalProps})) + }) + + it('should handle OPEN_EDIT_PERIOD_FORM', () => { + let period = { + id : 1, + period_end : "2017-03-05", + period_start : "2017-02-23" + } + let period_start = period.period_start.split('-') + let period_end = period.period_end.split('-') + + let index = 1 + + let newModalProps = { + active: true, + begin: { + day: period_start[2], + month: period_start[1], + year: period_start[0] + }, + end: { + day: period_end[2], + month: period_end[1], + year: period_end[0] + }, + index: index, + error: '' + } + expect( + modalReducer(state, { + type: 'OPEN_EDIT_PERIOD_FORM', + period, + index + }) + ).toEqual(Object.assign({}, state, {modalProps: newModalProps})) + }) + + it('should handle OPEN_ADD_PERIOD_FORM', () => { + let emptyDate = { + day: '01', + month: '01', + year: String(new Date().getFullYear()) + } + let newModalProps = Object.assign({}, state.modalProps, { + active: true, + begin: emptyDate, + end: emptyDate, + index: false, + error: "" + }) + + expect( + modalReducer(state, { + type: 'OPEN_ADD_PERIOD_FORM' + }) + ).toEqual(Object.assign({}, state, {modalProps: newModalProps})) + }) + + it('should handle UPDATE_PERIOD_FORM', () => { + let val = "11" + let group = "begin" + let selectType = "day" + + let newModalProps = { + active: false, + begin: { + day: val, + month: '01', + year: String(new Date().getFullYear()) + }, + end: { + day: '01', + month: '01', + year: String(new Date().getFullYear()) + }, + index: false, + error: '' + } + + expect( + modalReducer(state, { + type: 'UPDATE_PERIOD_FORM', + val, + group, + selectType + }) + ).toEqual(Object.assign({}, state, {modalProps: newModalProps})) + }) + + it('should handle VALIDATE_PERIOD_FORM and throw error if period starts after the end', () => { + let modProps = { + active: false, + begin: { + day: '13', + month: '01', + year: String(new Date().getFullYear()) + }, + end: { + day: '01', + month: '01', + year: String(new Date().getFullYear()) + }, + index: false, + error: '' + } + let newModalProps = { + active: false, + begin: { + day: '01', + month: '01', + year: String(new Date().getFullYear()) + }, + end: { + day: '01', + month: '01', + year: String(new Date().getFullYear()) + }, + index: false, + error: 'La date de départ doit être antérieure à la date de fin' + } + + let ttperiods = [] + + expect( + modalReducer(state, { + type: 'VALIDATE_PERIOD_FORM', + modalProps : modProps, + timeTablePeriods: ttperiods + }) + ).toEqual(Object.assign({}, state, {modalProps: newModalProps})) + }) + + it('should handle VALIDATE_PERIOD_FORM and throw error if periods overlap', () => { + let state2 = { + confirmModal: {}, + modalProps: { + active: false, + begin: { + day: '03', + month: '05', + year: '2017' + }, + end: { + day: '09', + month: '05', + year: '2017' + }, + index: false, + error: '' + }, + type: '' + } + let modProps2 = { + active: false, + begin: { + day: '03', + month: '05', + year: '2017' + }, + end: { + day: '09', + month: '05', + year: '2017' + }, + index: false, + error: '' + } + let ttperiods2 = [ + {id: 261, period_start: '2017-02-23', period_end: '2017-03-05'}, + {id: 262, period_start: '2017-03-15', period_end: '2017-03-25'}, + {id: 264, period_start: '2017-04-24', period_end: '2017-05-04'}, + {id: 265, period_start: '2017-05-14', period_end: '2017-05-24'} + ] + + let newModalProps2 = { + active: true, + begin: { + day: '03', + month: '05', + year: '2017' + }, + end: { + day: '09', + month: '05', + year: '2017' + }, + index: false, + error: "Les périodes ne peuvent pas se chevaucher" + } + + expect( + modalReducer(state2, { + type: 'VALIDATE_PERIOD_FORM', + modalProps : modProps2, + timeTablePeriods: ttperiods2 + }) + ).toEqual(Object.assign({}, state2, {modalProps: newModalProps2})) + }) +}) diff --git a/spec/javascripts/time_table/reducers/pagination_spec.js b/spec/javascripts/time_table/reducers/pagination_spec.js new file mode 100644 index 000000000..740ded3ac --- /dev/null +++ b/spec/javascripts/time_table/reducers/pagination_spec.js @@ -0,0 +1,128 @@ +var paginationReducer = require('es6_browserified/time_tables/reducers/pagination') + +const dispatch = function(){} + +let pagination = { + currentPage: "1982-02-15", + periode_range: ["1982-02-01", "1982-02-02", "1982-02-03"], + stateChanged: false +} + +let state = {} + +describe('pagination reducer', () => { + beforeEach(() => { + state = { + currentPage: "", + periode_range: [], + stateChanged: false + } + }) + + it('should return the initial state', () => { + expect( + paginationReducer(undefined, {}) + ).toEqual({}) + }) + + it('should handle RECEIVE_TIME_TABLES', () => { + let json = [{ + current_periode_range: "1982-02-15", + periode_range: ["1982-02-01", "1982-02-02", "1982-02-03"] + }] + expect( + paginationReducer(state, { + type: 'RECEIVE_TIME_TABLES', + json + }) + ).toEqual(Object.assign({}, state, {currentPage: json.current_periode_range, periode_range: json.periode_range})) + }) + + it('should handle GO_TO_PREVIOUS_PAGE', () => { + let nextPage = nextPage ? 1 : -1 + let newPage = pagination.periode_range[pagination.periode_range.indexOf(pagination.currentPage) + nextPage] + + expect( + paginationReducer(state, { + type: 'GO_TO_PREVIOUS_PAGE', + dispatch, + pagination, + nextPage: false + }) + ).toEqual(Object.assign({}, state, {currentPage : newPage, stateChanged: false})) + }) + it('should handle GO_TO_NEXT_PAGE', () => { + let nextPage = nextPage ? 1 : -1 + let newPage = pagination.periode_range[pagination.periode_range.indexOf(pagination.currentPage) + nextPage] + + expect( + paginationReducer(state, { + type: 'GO_TO_NEXT_PAGE', + dispatch, + pagination, + nextPage: false + }) + ).toEqual(Object.assign({}, state, {currentPage : newPage, stateChanged: false})) + }) + + it('should handle CHANGE_PAGE', () => { + let page = "1982-02-15" + expect( + paginationReducer(state, { + type: 'CHANGE_PAGE', + dispatch, + page + }) + ).toEqual(Object.assign({}, state, {currentPage : page, stateChanged: false})) + }) + + it('should handle INCLUDE_DATE_IN_PERIOD', () => { + expect( + paginationReducer(state, { + type: 'INCLUDE_DATE_IN_PERIOD' + }) + ).toEqual(Object.assign({}, state, {stateChanged: true})) + }) + it('should handle EXCLUDE_DATE_FROM_PERIOD', () => { + expect( + paginationReducer(state, { + type: 'EXCLUDE_DATE_FROM_PERIOD' + }) + ).toEqual(Object.assign({}, state, {stateChanged: true})) + }) + it('should handle DELETE_PERIOD', () => { + expect( + paginationReducer(state, { + type: 'DELETE_PERIOD' + }) + ).toEqual(Object.assign({}, state, {stateChanged: true})) + }) + it('should handle VALIDATE_PERIOD_FORM', () => { + expect( + paginationReducer(state, { + type: 'VALIDATE_PERIOD_FORM' + }) + ).toEqual(Object.assign({}, state, {stateChanged: true})) + }) + it('should handle UPDATE_COMMENT', () => { + expect( + paginationReducer(state, { + type: 'UPDATE_COMMENT' + }) + ).toEqual(Object.assign({}, state, {stateChanged: true})) + }) + it('should handle UPDATE_COLOR', () => { + expect( + paginationReducer(state, { + type: 'UPDATE_COLOR' + }) + ).toEqual(Object.assign({}, state, {stateChanged: true})) + }) + it('should handle UPDATE_DAY_TYPES', () => { + expect( + paginationReducer(state, { + type: 'UPDATE_DAY_TYPES' + }) + ).toEqual(Object.assign({}, state, {stateChanged: true})) + }) +}) diff --git a/spec/javascripts/time_table/reducers/status_spec.js b/spec/javascripts/time_table/reducers/status_spec.js new file mode 100644 index 000000000..f000324cc --- /dev/null +++ b/spec/javascripts/time_table/reducers/status_spec.js @@ -0,0 +1,50 @@ +var statusReducer = require('es6_browserified/time_tables/reducers/status') + +let state = {} + +describe('status reducer', () => { + beforeEach(() => { + state = { + actionType: "edit", + fetchSuccess: true, + isFetching: false + } + }) + + it('should return the initial state', () => { + expect( + statusReducer(undefined, {}) + ).toEqual({}) + }) + + it('should handle UNAVAILABLE_SERVER', () => { + expect( + statusReducer(state, { + type: 'UNAVAILABLE_SERVER' + }) + ).toEqual(Object.assign({}, state, {fetchSuccess: false})) + }) + + it('should handle FETCH_API', () => { + expect( + statusReducer(state, { + type: 'FETCH_API' + }) + ).toEqual(Object.assign({}, state, {isFetching: true})) + }) + + it('should handle RECEIVE_TIME_TABLES', () => { + expect( + statusReducer(state, { + type: 'RECEIVE_TIME_TABLES' + }) + ).toEqual(Object.assign({}, state, {fetchSuccess: true, isFetching: false})) + }) + it('should handle RECEIVE_MONTH', () => { + expect( + statusReducer(state, { + type: 'RECEIVE_MONTH' + }) + ).toEqual(Object.assign({}, state, {fetchSuccess: true, isFetching: false})) + }) +}) diff --git a/spec/javascripts/time_table/reducers/timetable_spec.js b/spec/javascripts/time_table/reducers/timetable_spec.js new file mode 100644 index 000000000..0b418a52e --- /dev/null +++ b/spec/javascripts/time_table/reducers/timetable_spec.js @@ -0,0 +1,184 @@ +require('whatwg-fetch') +var timetableReducer = require('es6_browserified/time_tables/reducers/timetable') + +let state = {} +const dispatch = function(){} +let arrDayTypes = [true, true, true, true, true, true, true] +let strDayTypes = 'LuMaMeJeVeSaDi' +let time_table_periods = [{"id":261,"period_start":"2017-02-23","period_end":"2017-03-05"},{"id":262,"period_start":"2017-03-15","period_end":"2017-03-25"},{"id":263,"period_start":"2017-04-04","period_end":"2017-04-14"},{"id":264,"period_start":"2017-04-24","period_end":"2017-05-04"},{"id":265,"period_start":"2017-05-14","period_end":"2017-05-24"}] +let current_periode_range = "2017-05-01" +let periode_range = ["2014-05-01","2014-06-01","2014-07-01","2014-08-01","2014-09-01","2014-10-01","2014-11-01","2014-12-01","2015-01-01","2015-02-01","2015-03-01","2015-04-01","2015-05-01","2015-06-01","2015-07-01","2015-08-01","2015-09-01","2015-10-01","2015-11-01","2015-12-01","2016-01-01","2016-02-01","2016-03-01","2016-04-01","2016-05-01","2016-06-01","2016-07-01","2016-08-01","2016-09-01","2016-10-01","2016-11-01","2016-12-01","2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01","2018-08-01","2018-09-01","2018-10-01","2018-11-01","2018-12-01","2019-01-01","2019-02-01","2019-03-01","2019-04-01","2019-05-01","2019-06-01","2019-07-01","2019-08-01","2019-09-01","2019-10-01","2019-11-01","2019-12-01","2020-01-01","2020-02-01","2020-03-01","2020-04-01","2020-05-01"] +let current_month = [{"day":"lundi","date":"2017-05-01","wday":1,"wnumber":"18","mday":1,"include_date":false,"excluded_date":false},{"day":"mardi","date":"2017-05-02","wday":2,"wnumber":"18","mday":2,"include_date":false,"excluded_date":false},{"day":"mercredi","date":"2017-05-03","wday":3,"wnumber":"18","mday":3,"include_date":false,"excluded_date":false},{"day":"jeudi","date":"2017-05-04","wday":4,"wnumber":"18","mday":4,"include_date":false,"excluded_date":false},{"day":"vendredi","date":"2017-05-05","wday":5,"wnumber":"18","mday":5,"include_date":false,"excluded_date":false},{"day":"samedi","date":"2017-05-06","wday":6,"wnumber":"18","mday":6,"include_date":false,"excluded_date":false},{"day":"dimanche","date":"2017-05-07","wday":0,"wnumber":"18","mday":7,"include_date":false,"excluded_date":false},{"day":"lundi","date":"2017-05-08","wday":1,"wnumber":"19","mday":8,"include_date":false,"excluded_date":false},{"day":"mardi","date":"2017-05-09","wday":2,"wnumber":"19","mday":9,"include_date":false,"excluded_date":false},{"day":"mercredi","date":"2017-05-10","wday":3,"wnumber":"19","mday":10,"include_date":false,"excluded_date":false},{"day":"jeudi","date":"2017-05-11","wday":4,"wnumber":"19","mday":11,"include_date":false,"excluded_date":false},{"day":"vendredi","date":"2017-05-12","wday":5,"wnumber":"19","mday":12,"include_date":false,"excluded_date":false},{"day":"samedi","date":"2017-05-13","wday":6,"wnumber":"19","mday":13,"include_date":false,"excluded_date":false},{"day":"dimanche","date":"2017-05-14","wday":0,"wnumber":"19","mday":14,"include_date":false,"excluded_date":false},{"day":"lundi","date":"2017-05-15","wday":1,"wnumber":"20","mday":15,"include_date":false,"excluded_date":false},{"day":"mardi","date":"2017-05-16","wday":2,"wnumber":"20","mday":16,"include_date":false,"excluded_date":false},{"day":"mercredi","date":"2017-05-17","wday":3,"wnumber":"20","mday":17,"include_date":false,"excluded_date":false},{"day":"jeudi","date":"2017-05-18","wday":4,"wnumber":"20","mday":18,"include_date":false,"excluded_date":false},{"day":"vendredi","date":"2017-05-19","wday":5,"wnumber":"20","mday":19,"include_date":false,"excluded_date":false},{"day":"samedi","date":"2017-05-20","wday":6,"wnumber":"20","mday":20,"include_date":false,"excluded_date":false},{"day":"dimanche","date":"2017-05-21","wday":0,"wnumber":"20","mday":21,"include_date":false,"excluded_date":false},{"day":"lundi","date":"2017-05-22","wday":1,"wnumber":"21","mday":22,"include_date":false,"excluded_date":false},{"day":"mardi","date":"2017-05-23","wday":2,"wnumber":"21","mday":23,"include_date":false,"excluded_date":false},{"day":"mercredi","date":"2017-05-24","wday":3,"wnumber":"21","mday":24,"include_date":false,"excluded_date":false},{"day":"jeudi","date":"2017-05-25","wday":4,"wnumber":"21","mday":25,"include_date":false,"excluded_date":false},{"day":"vendredi","date":"2017-05-26","wday":5,"wnumber":"21","mday":26,"include_date":false,"excluded_date":false},{"day":"samedi","date":"2017-05-27","wday":6,"wnumber":"21","mday":27,"include_date":false,"excluded_date":false},{"day":"dimanche","date":"2017-05-28","wday":0,"wnumber":"21","mday":28,"include_date":false,"excluded_date":false},{"day":"lundi","date":"2017-05-29","wday":1,"wnumber":"22","mday":29,"include_date":false,"excluded_date":false},{"day":"mardi","date":"2017-05-30","wday":2,"wnumber":"22","mday":30,"include_date":false,"excluded_date":false},{"day":"mercredi","date":"2017-05-31","wday":3,"wnumber":"22","mday":31,"include_date":false,"excluded_date":false}] + +let newCurrentMonth = [{"day":"lundi","date":"2017-05-01","wday":1,"wnumber":"18","mday":1,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mardi","date":"2017-05-02","wday":2,"wnumber":"18","mday":2,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mercredi","date":"2017-05-03","wday":3,"wnumber":"18","mday":3,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"jeudi","date":"2017-05-04","wday":4,"wnumber":"18","mday":4,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"vendredi","date":"2017-05-05","wday":5,"wnumber":"18","mday":5,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"samedi","date":"2017-05-06","wday":6,"wnumber":"18","mday":6,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"dimanche","date":"2017-05-07","wday":0,"wnumber":"18","mday":7,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"lundi","date":"2017-05-08","wday":1,"wnumber":"19","mday":8,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"mardi","date":"2017-05-09","wday":2,"wnumber":"19","mday":9,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"mercredi","date":"2017-05-10","wday":3,"wnumber":"19","mday":10,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"jeudi","date":"2017-05-11","wday":4,"wnumber":"19","mday":11,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"vendredi","date":"2017-05-12","wday":5,"wnumber":"19","mday":12,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"samedi","date":"2017-05-13","wday":6,"wnumber":"19","mday":13,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"dimanche","date":"2017-05-14","wday":0,"wnumber":"19","mday":14,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"lundi","date":"2017-05-15","wday":1,"wnumber":"20","mday":15,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mardi","date":"2017-05-16","wday":2,"wnumber":"20","mday":16,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mercredi","date":"2017-05-17","wday":3,"wnumber":"20","mday":17,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"jeudi","date":"2017-05-18","wday":4,"wnumber":"20","mday":18,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"vendredi","date":"2017-05-19","wday":5,"wnumber":"20","mday":19,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"samedi","date":"2017-05-20","wday":6,"wnumber":"20","mday":20,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"dimanche","date":"2017-05-21","wday":0,"wnumber":"20","mday":21,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"lundi","date":"2017-05-22","wday":1,"wnumber":"21","mday":22,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mardi","date":"2017-05-23","wday":2,"wnumber":"21","mday":23,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mercredi","date":"2017-05-24","wday":3,"wnumber":"21","mday":24,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"jeudi","date":"2017-05-25","wday":4,"wnumber":"21","mday":25,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"vendredi","date":"2017-05-26","wday":5,"wnumber":"21","mday":26,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"samedi","date":"2017-05-27","wday":6,"wnumber":"21","mday":27,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"dimanche","date":"2017-05-28","wday":0,"wnumber":"21","mday":28,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"lundi","date":"2017-05-29","wday":1,"wnumber":"22","mday":29,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"mardi","date":"2017-05-30","wday":2,"wnumber":"22","mday":30,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"mercredi","date":"2017-05-31","wday":3,"wnumber":"22","mday":31,"include_date":false,"excluded_date":false,"in_periods":false}] + +let json = { + current_month: current_month, + current_periode_range: current_periode_range, + periode_range: periode_range, + time_table_periods: time_table_periods, + day_types: strDayTypes +} + +describe('timetable reducer with empty state', () => { + beforeEach(() => { + state = { + current_month: [], + current_periode_range: "", + periode_range: [], + time_table_periods: [] + } + }) + + it('should return the initial state', () => { + expect( + timetableReducer(undefined, {}) + ).toEqual({}) + }) + + it('should handle RECEIVE_TIME_TABLES', () => { + let newState = { + current_month: newCurrentMonth, + current_periode_range: current_periode_range, + periode_range: periode_range, + time_table_periods: time_table_periods, + } + expect( + timetableReducer(state, { + type: 'RECEIVE_TIME_TABLES', + json + }) + ).toEqual(newState) + }) +}) + +describe('timetable reducer with filled state', () => { + beforeEach(() => { + state = { + current_month: newCurrentMonth, + current_periode_range: current_periode_range, + periode_range: periode_range, + time_table_periods: time_table_periods, + } + }) + + it('should handle RECEIVE_MONTH', () => { + expect( + timetableReducer(state, { + type: 'RECEIVE_MONTH', + json: { + days: current_month, + day_types: strDayTypes + } + }) + ).toEqual(state) + }) + + + it('should handle GO_TO_PREVIOUS_PAGE', () => { + let pagination = { + periode_range: periode_range, + currentPage: current_periode_range + } + expect( + timetableReducer(state, { + type: 'GO_TO_PREVIOUS_PAGE', + dispatch, + pagination, + nextPage: false + }) + ).toEqual(Object.assign({}, state, {current_periode_range: '2017-04-01'})) + }) + + it('should handle GO_TO_NEXT_PAGE', () => { + let pagination = { + periode_range: periode_range, + currentPage: current_periode_range + } + expect( + timetableReducer(state, { + type: 'GO_TO_NEXT_PAGE', + dispatch, + pagination, + nextPage: true + }) + ).toEqual(Object.assign({}, state, {current_periode_range: '2017-06-01'})) + }) + + it('should handle CHANGE_PAGE', () => { + const actions = { + fetchTimeTables: function(){} + } + let newPage = '2017-05-01' + expect( + timetableReducer(state, { + type: 'CHANGE_PAGE', + dispatch, + page: newPage + }) + ).toEqual(Object.assign({}, state, {current_periode_range: newPage})) + }) + + it('should handle DELETE_PERIOD', () => { + state.time_table_periods[0].deleted = true + expect( + timetableReducer(state, { + type: 'DELETE_PERIOD', + index: 0, + dayTypes: arrDayTypes + }) + ).toEqual(state) + }) + + it('should handle INCLUDE_DATE_IN_PERIOD', () => { + state.current_month[4].include_date = true + expect( + timetableReducer(state, { + type: 'INCLUDE_DATE_IN_PERIOD', + index: 4, + dayTypes: arrDayTypes + }) + ).toEqual(state) + }) + + it('should handle EXCLUDE_DATE_FROM_PERIOD', () => { + state.current_month[0].excluded_date = true + expect( + timetableReducer(state, { + type: 'EXCLUDE_DATE_FROM_PERIOD', + index: 0, + dayTypes: arrDayTypes + }) + ).toEqual(state) + }) + + it('should handle VALIDATE_PERIOD_FORM', () => { + state.current_month[13].in_periods = false + state.time_table_periods[4].period_start = '2017-05-15' + let modalProps = { + active: false, + begin: { + day: '15', + month: '05', + year: '2017' + }, + end: { + day: '24', + month: '05', + year: '2017' + }, + error: '', + index: 4 + } + expect( + timetableReducer(state, { + type: 'VALIDATE_PERIOD_FORM', + modalProps: modalProps, + timeTablePeriods: state.time_table_periods, + metas: { + day_types: arrDayTypes + } + }) + ).toEqual(state) + }) +}) diff --git a/spec/models/chouette/route/route_base_spec.rb b/spec/models/chouette/route/route_base_spec.rb new file mode 100644 index 000000000..08f201022 --- /dev/null +++ b/spec/models/chouette/route/route_base_spec.rb @@ -0,0 +1,69 @@ +RSpec.describe Chouette::Route, :type => :model do + + subject { create(:route) } + + describe '#objectid' do + subject { super().objectid } + it { is_expected.to be_kind_of(Chouette::ObjectId) } + end + + it { is_expected.to enumerize(:direction).in(:straight_forward, :backward, :clockwise, :counter_clockwise, :north, :north_west, :west, :south_west, :south, :south_east, :east, :north_east) } + it { is_expected.to enumerize(:wayback).in(:straight_forward, :backward) } + + #it { is_expected.to validate_presence_of :name } + it { is_expected.to validate_presence_of :line } + it { is_expected.to validate_uniqueness_of :objectid } + #it { is_expected.to validate_presence_of :wayback_code } + #it { is_expected.to validate_presence_of :direction_code } + it { is_expected.to validate_inclusion_of(:direction).in_array(%i(straight_forward backward clockwise counter_clockwise north north_west west south_west south south_east east north_east)) } + it { is_expected.to validate_inclusion_of(:wayback).in_array(%i(straight_forward backward)) } + + context "reordering methods" do + let(:bad_stop_point_ids){subject.stop_points.map { |sp| sp.id + 1}} + let(:ident){subject.stop_points.map(&:id)} + let(:first_last_swap){ [ident.last] + ident[1..-2] + [ident.first]} + + describe "#reorder!" do + context "invalid stop_point_ids" do + let(:new_stop_point_ids) { bad_stop_point_ids} + it { expect(subject.reorder!( new_stop_point_ids)).to be_falsey} + end + + context "swaped last and first stop_point_ids" do + let!(:new_stop_point_ids) { first_last_swap} + let!(:old_stop_point_ids) { subject.stop_points.map(&:id) } + let!(:old_stop_area_ids) { subject.stop_areas.map(&:id) } + + it "should keep stop_point_ids order unchanged" do + expect(subject.reorder!( new_stop_point_ids)).to be_truthy + expect(subject.stop_points.map(&:id)).to eq( old_stop_point_ids) + end + # This test is no longer relevant, as reordering is done with Reactux + # it "should have changed stop_area_ids order" do + # expect(subject.reorder!( new_stop_point_ids)).to be_truthy + # subject.reload + # expect(subject.stop_areas.map(&:id)).to eq( [old_stop_area_ids.last] + old_stop_area_ids[1..-2] + [old_stop_area_ids.first]) + # end + end + end + + describe "#stop_point_permutation?" do + context "invalid stop_point_ids" do + let( :new_stop_point_ids ) { bad_stop_point_ids} + it { is_expected.not_to be_stop_point_permutation( new_stop_point_ids)} + end + context "unchanged stop_point_ids" do + let(:new_stop_point_ids) { ident} + it { is_expected.to be_stop_point_permutation( new_stop_point_ids)} + end + context "swaped last and first stop_point_ids" do + let(:new_stop_point_ids) { first_last_swap} + it { is_expected.to be_stop_point_permutation( new_stop_point_ids)} + end + end + end + +end + + + diff --git a/spec/models/chouette/route/route_destroy_spec.rb b/spec/models/chouette/route/route_destroy_spec.rb new file mode 100644 index 000000000..930ce521a --- /dev/null +++ b/spec/models/chouette/route/route_destroy_spec.rb @@ -0,0 +1,45 @@ +RSpec.describe Chouette::Route, :type => :model do + + subject { create( :route_with_journey_patterns ) } + + + context "delete a route" do + let( :vehicle_journey ){ create :vehicle_journey } + + it "deletes the associated journey_patterns" do + expected_delta = subject.journey_patterns.count + expect( expected_delta > 0 ).to eq(true) + expect{ subject.destroy }.to change{Chouette::JourneyPattern.count}.by -expected_delta + end + + it "deletes the associated stop_points" do + expected_delta = subject.stop_points.count + expect( expected_delta > 0 ).to eq(true) + expect{ subject.destroy }.to change{Chouette::StopPoint.count}.by -expected_delta + end + + it "does not delete the associated stop_areas" do + count = subject.stop_points.count + expect( count > 0 ).to eq(true) + expect{ subject.destroy }.not_to change{Chouette::StopArea.count} + end + + it "deletes the associated vehicle_journeys" do + vehicle_journey + expect{ vehicle_journey.route.destroy}.to change{Chouette::VehicleJourney.count}.by -1 + end + + it "does not delete the corresponding time_tables" do + tt = create :time_table + tt.vehicle_journeys << vehicle_journey + tables = vehicle_journey.route.time_tables + expect( tables ).not_to be_empty + expect{ vehicle_journey.route.destroy }.not_to change{Chouette::TimeTable.count} + end + + it "does not delete the associated line" do + expect( subject.line ).not_to be_nil + expect{ subject.destroy }.not_to change{Chouette::Line.count} + end + end +end diff --git a/spec/models/chouette/route_spec.rb b/spec/models/chouette/route/route_stop_points_spec.rb index 6138f28b9..03c53b4cf 100644 --- a/spec/models/chouette/route_spec.rb +++ b/spec/models/chouette/route/route_stop_points_spec.rb @@ -1,72 +1,10 @@ -require 'spec_helper' +RSpec.describe Chouette::Route, :type => :model do -describe Chouette::Route, :type => :model do subject { create(:route) } - describe '#objectid' do - subject { super().objectid } - it { is_expected.to be_kind_of(Chouette::ObjectId) } - end - - it { is_expected.to enumerize(:direction).in(:straight_forward, :backward, :clockwise, :counter_clockwise, :north, :north_west, :west, :south_west, :south, :south_east, :east, :north_east) } - it { is_expected.to enumerize(:wayback).in(:straight_forward, :backward) } - - #it { is_expected.to validate_presence_of :name } - it { is_expected.to validate_presence_of :line } - it { is_expected.to validate_uniqueness_of :objectid } - #it { is_expected.to validate_presence_of :wayback_code } - #it { is_expected.to validate_presence_of :direction_code } - it { is_expected.to validate_inclusion_of(:direction).in_array(%i(straight_forward backward clockwise counter_clockwise north north_west west south_west south south_east east north_east)) } - it { is_expected.to validate_inclusion_of(:wayback).in_array(%i(straight_forward backward)) } - - context "reordering methods" do - let( :bad_stop_point_ids){subject.stop_points.map { |sp| sp.id + 1}} - let( :ident){subject.stop_points.map(&:id)} - let( :first_last_swap){ [ident.last] + ident[1..-2] + [ident.first]} - - describe "#reorder!" do - context "invalid stop_point_ids" do - let( :new_stop_point_ids) { bad_stop_point_ids} - it { expect(subject.reorder!( new_stop_point_ids)).to be_falsey} - end - - context "swaped last and first stop_point_ids" do - let!( :new_stop_point_ids) { first_last_swap} - let!( :old_stop_point_ids) { subject.stop_points.map(&:id) } - let!( :old_stop_area_ids) { subject.stop_areas.map(&:id) } - - it "should keep stop_point_ids order unchanged" do - expect(subject.reorder!( new_stop_point_ids)).to be_truthy - expect(subject.stop_points.map(&:id)).to eq( old_stop_point_ids) - end - # This test is no longer relevant, as reordering is done with Reactux - # it "should have changed stop_area_ids order" do - # expect(subject.reorder!( new_stop_point_ids)).to be_truthy - # subject.reload - # expect(subject.stop_areas.map(&:id)).to eq( [old_stop_area_ids.last] + old_stop_area_ids[1..-2] + [old_stop_area_ids.first]) - # end - end - end - - describe "#stop_point_permutation?" do - context "invalid stop_point_ids" do - let( :new_stop_point_ids) { bad_stop_point_ids} - it { is_expected.not_to be_stop_point_permutation( new_stop_point_ids)} - end - context "unchanged stop_point_ids" do - let( :new_stop_point_ids) { ident} - it { is_expected.to be_stop_point_permutation( new_stop_point_ids)} - end - context "swaped last and first stop_point_ids" do - let( :new_stop_point_ids) { first_last_swap} - it { is_expected.to be_stop_point_permutation( new_stop_point_ids)} - end - end - end - describe "#stop_points_attributes=" do - let( :journey_pattern) { create( :journey_pattern, :route => subject )} - let( :vehicle_journey) { create( :vehicle_journey, :journey_pattern => journey_pattern)} + let(:journey_pattern) { create( :journey_pattern, :route => subject )} + let(:vehicle_journey) { create( :vehicle_journey, :journey_pattern => journey_pattern)} def subject_stop_points_attributes {}.tap do |hash| subject.stop_points.each_with_index { |sp,index| hash[ index.to_s ] = sp.attributes } @@ -171,3 +109,4 @@ describe Chouette::Route, :type => :model do end end end + diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb index 3d3a948bc..b22183ab6 100644 --- a/spec/models/chouette/vehicle_journey_spec.rb +++ b/spec/models/chouette/vehicle_journey_spec.rb @@ -221,6 +221,95 @@ describe Chouette::VehicleJourney, :type => :model do end end + describe ".with_stops" do + def initialize_stop_times(vehicle_journey, &block) + vehicle_journey + .vehicle_journey_at_stops + .each_with_index do |at_stop, index| + at_stop.update( + departure_time: at_stop.departure_time + block.call(index), + arrival_time: at_stop.arrival_time + block.call(index) + ) + end + end + + it "selects vehicle journeys including stops in order or earliest departure time" do + # Create later vehicle journey to give it a later id, such that it should + # appear last if the order in the query isn't right. + journey_late = create(:vehicle_journey) + journey_early = create( + :vehicle_journey, + route: journey_late.route, + journey_pattern: journey_late.journey_pattern + ) + + initialize_stop_times(journey_early) do |index| + (index + 5).minutes + end + initialize_stop_times(journey_late) do |index| + (index + 65).minutes + end + + expected_journey_order = [journey_early, journey_late] + + expect( + journey_late + .route + .vehicle_journeys + .with_stops + .to_a + ).to eq(expected_journey_order) + end + + it "orders journeys with nil times at the end" do + journey_nil = create(:vehicle_journey_empty) + journey = create( + :vehicle_journey, + route: journey_nil.route, + journey_pattern: journey_nil.journey_pattern + ) + + expected_journey_order = [journey, journey_nil] + + expect( + journey + .route + .vehicle_journeys + .with_stops + .to_a + ).to eq(expected_journey_order) + end + + it "journeys that skip the first stop(s) get ordered by the time of the \ + first stop that they make" do + journey_missing_stop = create(:vehicle_journey) + journey_early = create( + :vehicle_journey, + route: journey_missing_stop.route, + journey_pattern: journey_missing_stop.journey_pattern + ) + + initialize_stop_times(journey_early) do |index| + (index + 5).minutes + end + initialize_stop_times(journey_missing_stop) do |index| + (index + 65).minutes + end + + journey_missing_stop.vehicle_journey_at_stops.first.destroy + + expected_journey_order = [journey_early, journey_missing_stop] + + expect( + journey_missing_stop + .route + .vehicle_journeys + .with_stops + .to_a + ).to eq(expected_journey_order) + end + end + subject { create(:vehicle_journey_odd) } describe "in_relation_to_a_journey_pattern methods" do let!(:route) { create(:route)} diff --git a/spec/models/time_table_combination_spec.rb b/spec/models/time_table_combination_spec.rb index 46d5f8504..4c99a14fa 100644 --- a/spec/models/time_table_combination_spec.rb +++ b/spec/models/time_table_combination_spec.rb @@ -4,7 +4,37 @@ describe TimeTableCombination, :type => :model do let!(:source){ create(:time_table)} let!(:combined){create(:time_table)} subject {build(:time_table_combination)} - + + describe '#continuous_dates' do + it 'should group continuous dates' do + dates = source.dates.where(in_out: true) + expect(source.continuous_dates[0].count).to eq(dates.count) + + # 6 more continuous date, 1 isolated date + (10..15).each do |n| + source.dates.create(date: Date.today + n.day, in_out: true) + end + source.dates.create(date: Date.today + 1.year, in_out: true) + expect(source.reload.continuous_dates[1].count).to eq(6) + expect(source.reload.continuous_dates[2].count).to eq(1) + end + end + + describe '#convert_continuous_dates_to_periods' do + it 'should convert continuous dates to periods' do + (10..12).each do |n| + source.dates.create(date: Date.today + n.day, in_out: true) + end + source.dates.create(date: Date.today + 1.year, in_out: true) + + expect { + source.reload.convert_continuous_dates_to_periods + }.to change {source.periods.count}.by(2) + + expect(source.reload.dates.where(in_out: true).count).to eq(1) + end + end + describe "#combine" do context "when operation is union" do before(:each) do |
