/shoulda_macros/sortable_table.rb

https://github.com/getaroom/sortable_table · Ruby · 199 lines · 162 code · 33 blank · 4 comment · 17 complexity · 1a166cdd803c43ec4e7ffc8b4c14fcbf MD5 · raw file

  1. module SortableTable
  2. module Shoulda
  3. class Attribute
  4. def initialize(attr)
  5. @attr = attr
  6. end
  7. def complex?
  8. @attr.is_a?(Hash)
  9. end
  10. def name
  11. complex? ? @attr.keys.first : @attr
  12. end
  13. def values
  14. @attr.values.flatten
  15. end
  16. def to_s
  17. name.to_s
  18. end
  19. end
  20. def should_sort_by(attribute, options = {}, &block)
  21. collection = get_collection_name(options[:collection])
  22. model_under_test = get_model_under_test(attribute, options[:model_name])
  23. block = block || default_sorting_block(model_under_test, attribute)
  24. action = options[:action] || default_sorting_action
  25. attribute = Attribute.new(attribute)
  26. %w(ascending descending).each do |direction|
  27. should "sort by #{attribute.to_s} #{direction}" do
  28. # sanity checks
  29. assert_attribute_defined(attribute, model_under_test)
  30. assert_db_records_exist_for(model_under_test)
  31. # exercise
  32. action.bind(self).call(attribute.name.to_s, direction)
  33. # sanity check
  34. assert_collection_can_be_tested_for_sorting(collection, attribute, &block)
  35. # verification
  36. assert_collection_is_sorted(collection, direction, &block)
  37. end
  38. end
  39. end
  40. def should_sort_by_attributes(*attributes, &block)
  41. attributes.each do |attr|
  42. should_sort_by attr, :action => block
  43. end
  44. end
  45. def should_display_sortable_table_header_for(*valid_sorts)
  46. valid_sorts.each do |attr|
  47. should "have a link to sort by #{attr}" do
  48. assert_select 'a[href*=?]', "sort=#{attr}", true,
  49. "link not found to sort by #{attr}. Try adding this to the view: " <<
  50. "<%= sortable_table_header :name => '#{attr}', :sort => '#{attr}' %>"
  51. end
  52. end
  53. should "not link to any invalid sorting options" do
  54. assert_select 'a[href*=?]', 'sort=' do |elements|
  55. sortings = elements.collect {|element|
  56. element.attributes['href'].match(/sort=([^&]*)/)[1]
  57. }
  58. sortings.each {|sorting|
  59. assert !valid_sorts.include?(sorting),
  60. "link found for sortion option which is not in valid list: #{sorting}."
  61. }
  62. end
  63. end
  64. end
  65. def should_display_highlighted_sortable_table_header_for column, order = "ascending"
  66. should "have the #{order} class on the #{column} table header" do
  67. assert_select('th[class=?]', order) do |tr|
  68. assert_select 'a[href*=?]', "sort=#{column}"
  69. end
  70. end
  71. end
  72. protected
  73. def get_collection_name(override)
  74. collection = get_collection_name_from_test_name
  75. (override || collection).to_sym
  76. end
  77. def get_collection_name_from_test_name
  78. collection = self.name.underscore.gsub(/_controller_test/, '')
  79. collection = remove_namespacing(collection)
  80. end
  81. def get_model_under_test(attribute, override)
  82. model_name = override || get_model_under_test_from_test_name
  83. model_name.classify.constantize
  84. end
  85. def get_model_under_test_from_test_name
  86. model_name_from_test_name = self.name.gsub(/ControllerTest/, '')
  87. remove_namespacing(model_name_from_test_name)
  88. end
  89. def remove_namespacing(string)
  90. while string.include?('/')
  91. string.slice!(0..string.rindex('/'))
  92. end
  93. while string.include?('::')
  94. string.slice!(0..string.rindex('::')+1)
  95. end
  96. string
  97. end
  98. def default_sorting_block(model_under_test, attribute)
  99. block = handle_boolean_attribute(model_under_test, attribute)
  100. block ||= lambda { |model_instance| model_instance.send(attribute) }
  101. end
  102. def handle_boolean_attribute(model_under_test, attribute)
  103. if attribute_is_boolean?(model_under_test, attribute)
  104. lambda { |model_instance| model_instance.send(attribute).to_s }
  105. end
  106. end
  107. def attribute_is_boolean?(model_under_test, attribute)
  108. db_column = model_under_test.columns.select { |each| each.name == attribute.to_s }.first
  109. db_column && db_column.type == :boolean
  110. end
  111. def default_sorting_action
  112. lambda do |sort, direction|
  113. get :index, :sort => sort, :order => direction
  114. end
  115. end
  116. end
  117. end
  118. module SortableTable
  119. module ShouldaHelpers
  120. def assert_db_records_exist_for(model_under_test)
  121. assert model_under_test.count >= 2,
  122. "need at least 2 #{model_under_test} records in the db to test sorting"
  123. end
  124. def assert_collection_can_be_tested_for_sorting(collection, attribute, &block)
  125. assert_not_nil assigns(collection),
  126. "assigns(:#{collection}) is nil"
  127. assert assigns(collection).size >= 2,
  128. "cannot test sorting without at least 2 sortable objects. " <<
  129. "assigns(:#{collection}) is #{assigns(collection).inspect}"
  130. values = assigns(collection).collect(&block).uniq
  131. assert values.size >= 2,
  132. "need at least 2 distinct #{attribute.name} values to test sorting\n" <<
  133. "found values: #{values.inspect}"
  134. end
  135. def assert_collection_is_sorted(collection, direction, &block)
  136. expected = assigns(collection).sort_by(&block)
  137. expected = expected.reverse if direction == 'descending'
  138. assert expected.collect(&block) == assigns(collection).collect(&block),
  139. "expected - #{expected.collect(&block).inspect}," <<
  140. " but was - #{assigns(collection).collect(&block).inspect}"
  141. end
  142. def assert_attribute_defined(attribute, model_under_test)
  143. if attribute.complex?
  144. attribute.values.each do |attr|
  145. if attr.include?('.')
  146. model, attr = attr.split('.')
  147. model = model.classify.constantize
  148. assert_db_column_exists(attr, model)
  149. else
  150. assert_db_column_exists(attr, model_under_test)
  151. end
  152. end
  153. else
  154. assert_db_column_exists(attribute, model_under_test)
  155. end
  156. end
  157. def assert_db_column_exists(attribute, model)
  158. column = model.
  159. columns.
  160. detect {|column| column.name == attribute.to_s }
  161. assert_not_nil column, "No such column: #{model}##{attribute}"
  162. end
  163. end
  164. end
  165. Test::Unit::TestCase.extend(SortableTable::Shoulda)
  166. Test::Unit::TestCase.send(:include, SortableTable::ShouldaHelpers)