ProMotion 0.6.5 → 0.7.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.
Files changed (56) hide show
  1. data/README.md +79 -502
  2. data/Rakefile +30 -0
  3. data/app/app_delegate.rb +3 -1
  4. data/lib/ProMotion.rb +1 -1
  5. data/lib/ProMotion/cocoatouch/TableViewCell.rb +5 -1
  6. data/lib/ProMotion/delegate/delegate.rb +16 -0
  7. data/lib/ProMotion/delegate/delegate_helper.rb +88 -0
  8. data/lib/ProMotion/delegate/delegate_notifications.rb +61 -0
  9. data/lib/ProMotion/helpers/console.rb +3 -3
  10. data/lib/ProMotion/helpers/logger.rb +15 -15
  11. data/lib/ProMotion/helpers/view_helper.rb +7 -5
  12. data/lib/ProMotion/push_notifications/push_notification.rb +51 -0
  13. data/lib/ProMotion/screen_helpers/screen_elements.rb +2 -2
  14. data/lib/ProMotion/screen_helpers/screen_navigation.rb +5 -1
  15. data/lib/ProMotion/screen_helpers/screen_tabs.rb +2 -2
  16. data/lib/ProMotion/screens/_compatibility/formotion_screen.rb +65 -0
  17. data/lib/ProMotion/screens/_screen_module.rb +33 -20
  18. data/lib/ProMotion/screens/_table_screen_module.rb +4 -4
  19. data/lib/ProMotion/{screen_helpers → screens}/_tables/_refreshable_table.rb +2 -2
  20. data/lib/ProMotion/{screen_helpers → screens}/_tables/_searchable_table.rb +27 -34
  21. data/lib/ProMotion/screens/_tables/_sectioned_table.rb +5 -0
  22. data/lib/ProMotion/screens/_tables/_table.rb +149 -0
  23. data/lib/ProMotion/screens/_tables/grouped_table.rb +16 -0
  24. data/lib/ProMotion/screens/_tables/plain_table.rb +17 -0
  25. data/lib/ProMotion/screens/_tables/table_data.rb +148 -0
  26. data/lib/ProMotion/screens/_tables/table_view_cell_module.rb +156 -0
  27. data/lib/ProMotion/screens/table_screen.rb +3 -2
  28. data/lib/ProMotion/version.rb +1 -1
  29. data/spec/functional/func_screen_spec.rb +66 -0
  30. data/spec/functional/func_split_screen_spec.rb +66 -0
  31. data/spec/functional/func_table_screen_spec.rb +52 -0
  32. data/spec/helpers/functional_screen.rb +15 -0
  33. data/spec/helpers/table_screen.rb +32 -8
  34. data/spec/helpers/table_screen_refreshable.rb +1 -1
  35. data/spec/helpers/table_screen_searchable.rb +1 -1
  36. data/spec/helpers/test_delegate.rb +2 -0
  37. data/spec/unit/delegate_spec.rb +38 -0
  38. data/spec/{ios_version_spec.rb → unit/ios_version_spec.rb} +0 -0
  39. data/spec/{logger_spec.rb → unit/logger_spec.rb} +0 -0
  40. data/spec/{main_spec.rb → unit/main_spec.rb} +0 -0
  41. data/spec/{screen_helpers_spec.rb → unit/screen_helpers_spec.rb} +14 -6
  42. data/spec/{screen_module_spec.rb → unit/screen_module_spec.rb} +0 -0
  43. data/spec/{screen_spec.rb → unit/screen_spec.rb} +21 -3
  44. data/spec/{split_screen_in_tab_bar_spec.rb → unit/split_screen_in_tab_bar_spec.rb} +0 -0
  45. data/spec/{split_screen_open_screen_spec.rb → unit/split_screen_open_screen_spec.rb} +0 -0
  46. data/spec/{split_screen_spec.rb → unit/split_screen_spec.rb} +0 -0
  47. data/spec/unit/tables/table_module_spec.rb +108 -0
  48. data/spec/unit/tables/table_screen_spec.rb +92 -0
  49. data/spec/unit/tables/table_view_cell_spec.rb +106 -0
  50. data/spec/{view_helper_spec.rb → unit/view_helper_spec.rb} +0 -0
  51. metadata +50 -29
  52. data/lib/ProMotion/delegate.rb +0 -63
  53. data/lib/ProMotion/screen_helpers/_tables/_sectioned_table.rb +0 -270
  54. data/lib/ProMotion/screen_helpers/_tables/grouped_table.rb +0 -14
  55. data/lib/ProMotion/screen_helpers/_tables/plain_table.rb +0 -15
  56. data/spec/table_screen_spec.rb +0 -72
@@ -0,0 +1,52 @@
1
+ describe "ProMotion::TestTableScreen functionality" do
2
+ tests PM::TestTableScreen
3
+
4
+ # Override controller to properly instantiate
5
+ def controller
6
+ rotate_device to: :portrait, button: :bottom
7
+ @controller ||= TestTableScreen.new(nav_bar: true)
8
+ @controller.on_load
9
+ @controller.main_controller
10
+ end
11
+
12
+ after do
13
+ @controller = nil
14
+ end
15
+
16
+ it "should have a navigation bar" do
17
+ @controller.navigationController.should.be.kind_of(UINavigationController)
18
+ end
19
+
20
+ it "should increment the tap counter on tap" do
21
+ tap("Increment")
22
+ @controller.tap_counter.should == 3
23
+ end
24
+
25
+ it "should add a new table cell on tap" do
26
+ tap("Add New Row")
27
+ view("Dynamically Added").class.should == UILabel
28
+ end
29
+
30
+ it "should do nothing when no action specified" do
31
+ tap("Just another blank row")
32
+ @controller.should == @controller
33
+ end
34
+
35
+ it "should increment the tap counter by one on tap" do
36
+ tap("Increment One")
37
+ @controller.tap_counter.should == 1
38
+ end
39
+
40
+ it "should delete the specified row from the table view on tap" do
41
+ @controller.tableView(@controller.tableView, numberOfRowsInSection:0).should == 6
42
+ tap("Delete the row below")
43
+ @controller.tableView(@controller.tableView, numberOfRowsInSection:0).should == 5
44
+ end
45
+
46
+ it "should delete the specified row from the table view on tap with an animation" do
47
+ @controller.tableView(@controller.tableView, numberOfRowsInSection:0).should == 6
48
+ tap("Delete the row below with an animation")
49
+ @controller.tableView(@controller.tableView, numberOfRowsInSection:0).should == 5
50
+ end
51
+
52
+ end
@@ -0,0 +1,15 @@
1
+ class FunctionalScreen < PM::Screen
2
+ attr_accessor :button_was_triggered
3
+
4
+ title "Functional"
5
+
6
+ def will_appear
7
+ self.button_was_triggered = false
8
+ add UILabel.alloc.initWithFrame([[ 10, 10 ], [ 300, 40 ]]),
9
+ text: "Label Here"
10
+ end
11
+
12
+ def triggered_button
13
+ self.button_was_triggered = true
14
+ end
15
+ end
@@ -1,15 +1,22 @@
1
- class TableScreen < ProMotion::SectionedTableScreen
1
+ class TestTableScreen < ProMotion::SectionedTableScreen
2
+
3
+ def promotion_table_data
4
+ @promotion_table_data
5
+ end
2
6
 
3
7
  def on_load
4
8
  @tap_counter ||= 0
5
9
  end
6
10
 
7
11
  def table_data
8
- [{
12
+ @data ||= [{
9
13
  title: "Your Account",
10
14
  cells: [
11
15
  { title: "Increment", action: :increment_counter_by, arguments: { number: 3 } },
12
- { title: "Add New Row", action: :add_tableview_row, accessibilityLabel: "Add New Row" },
16
+ { title: "Add New Row", action: :add_tableview_row },
17
+ { title: "Delete the row below", action: :delete_cell, arguments: {section: 0, row:3 } },
18
+ { title: "Just another blank row" },
19
+ { title: "Delete the row below with an animation", action: :delete_cell, arguments: {animated: true, section: 0, row:5 } },
13
20
  { title: "Just another blank row" }
14
21
  ]
15
22
  }, {
@@ -18,6 +25,14 @@ class TableScreen < ProMotion::SectionedTableScreen
18
25
  { title: "Increment One", action: :increment_counter },
19
26
  { title: "Feedback", remote_image: { url: "http://placekitten.com/100/100", placeholder: "some-local-image", size: 50, radius: 15 } }
20
27
  ]
28
+ }, {
29
+ title: "Image Tests",
30
+ cells: [
31
+ { title: "Image Test 1", image: {image: UIImage.imageNamed("list.png"), radius: 10} },
32
+ { title: "Image Test 2", image: {image: "list.png"} },
33
+ { title: "Image Test 3", image: UIImage.imageNamed("list.png") },
34
+ { title: "Image Test 4", image: "list.png" },
35
+ ]
21
36
  }]
22
37
  end
23
38
 
@@ -25,24 +40,33 @@ class TableScreen < ProMotion::SectionedTableScreen
25
40
  args[:id]
26
41
  end
27
42
 
28
- def add_tableview_row
43
+ def add_tableview_row(args={})
29
44
  @data[0][:cells] << {
30
45
  title: "Dynamically Added"
31
46
  }
32
47
  update_table_data
33
48
  end
34
49
 
35
- def increment_counter
50
+ def delete_cell(args={})
51
+ if args[:animated]
52
+ delete_row(NSIndexPath.indexPathForRow(args[:row], inSection:args[:section]))
53
+ else
54
+ @data[args[:section]][:cells].delete_at args[:row]
55
+ update_table_data
56
+ end
57
+ end
58
+
59
+ def increment_counter(args={})
36
60
  @tap_counter += 1
37
61
  end
38
62
 
39
- def increment_counter_by(args)
63
+ def increment_counter_by(args={})
40
64
  @tap_counter = @tap_counter + args[:number]
41
65
  end
42
-
66
+
43
67
  def tap_counter
44
68
  @tap_counter
45
69
  end
46
70
 
47
71
 
48
- end
72
+ end
@@ -1,4 +1,4 @@
1
- class TableScreenRefreshable < TableScreen
1
+ class TableScreenRefreshable < TestTableScreen
2
2
  attr_accessor :on_refresh_called
3
3
 
4
4
  refreshable
@@ -1,4 +1,4 @@
1
- class TableScreenSearchable < TableScreen
1
+ class TableScreenSearchable < TestTableScreen
2
2
 
3
3
  searchable
4
4
 
@@ -1,4 +1,6 @@
1
1
  class TestDelegate < ProMotion::Delegate
2
+ status_bar false
3
+
2
4
  def on_load(app, options)
3
5
  end
4
6
  end
@@ -0,0 +1,38 @@
1
+ describe "PM::Delegate" do
2
+
3
+ before { @subject = TestDelegate.new }
4
+
5
+ it 'should call on_load on launch' do
6
+ @subject.mock!(:on_load) do |app, options|
7
+ options[:jamon].should.be.true
8
+ app.should.be.kind_of(UIApplication)
9
+ end
10
+
11
+ @subject.application(UIApplication.sharedApplication, didFinishLaunchingWithOptions:{jamon: true})
12
+ end
13
+
14
+ it "should handle push notifications" do
15
+
16
+ @subject.mock!(:on_push_notification) do |notification|
17
+ notification.should.be.kind_of(PM::PushNotification)
18
+ notification.alert.should == "Eating Bacon"
19
+ notification.badge.should == 42
20
+ notification.sound.should == "jamon"
21
+ @subject.aps_notification.should == notification
22
+ end
23
+
24
+ launch_options = { UIApplicationLaunchOptionsRemoteNotificationKey => PM::PushNotification.fake_notification(alert: "Eating Bacon", badge: 42, sound: "jamon").notification }
25
+ @subject.application(nil, didFinishLaunchingWithOptions:launch_options )
26
+
27
+ end
28
+
29
+ it "should set home_screen when opening a new screen" do
30
+
31
+ @subject.application(UIApplication.sharedApplication, didFinishLaunchingWithOptions: nil)
32
+ @subject.open BasicScreen.new(nav_bar: true)
33
+ @subject.home_screen.should.be.kind_of BasicScreen
34
+ @subject.window.rootViewController.should.be.kind_of UINavigationController
35
+
36
+ end
37
+
38
+ end
File without changes
File without changes
@@ -116,9 +116,9 @@ describe "screen helpers" do
116
116
 
117
117
  new_screen.parent_screen.should == @screen
118
118
  new_screen.title.should == 'Some Title'
119
- new_screen.is_modal?.should == true
119
+ new_screen.modal?.should == true
120
120
  new_screen.hidesBottomBarWhenPushed.should == true
121
- new_screen.has_nav_bar?.should == true
121
+ new_screen.nav_bar?.should == true
122
122
  end
123
123
 
124
124
  it "should present the #main_controller when showing a modal screen" do
@@ -141,7 +141,7 @@ describe "screen helpers" do
141
141
 
142
142
  it "should open a root screen if :close_all is provided" do
143
143
  @screen.mock!(:open_root_screen) { |screen| screen.should.be.instance_of BasicScreen }
144
- @screen.open_screen BasicScreen, close_all: true
144
+ @screen.open BasicScreen, close_all: true
145
145
  end
146
146
 
147
147
  it "should present a modal screen if :modal is provided" do
@@ -149,7 +149,15 @@ describe "screen helpers" do
149
149
  screen.should.be.instance_of BasicScreen
150
150
  animated.should == true
151
151
  end
152
- @screen.open_screen BasicScreen, modal: true
152
+ @screen.open BasicScreen, modal: true
153
+ end
154
+
155
+ it "should present a modal screen if open_modal is used" do
156
+ @screen.mock!(:present_modal_view_controller) do |screen, animated|
157
+ screen.should.be.instance_of BasicScreen
158
+ animated.should == true
159
+ end
160
+ @screen.open_modal BasicScreen
153
161
  end
154
162
 
155
163
  it "should open screen in tab bar if :in_tab is provided" do
@@ -158,12 +166,12 @@ describe "screen helpers" do
158
166
  screen.should.be.instance_of BasicScreen
159
167
  tab_name.should == 'my_tab'
160
168
  end
161
- @screen.open_screen BasicScreen, in_tab: 'my_tab'
169
+ @screen.open BasicScreen, in_tab: 'my_tab'
162
170
  end
163
171
 
164
172
  it "should pop onto navigation controller if current screen is on nav stack already" do
165
173
  @screen.mock!(:push_view_controller) { |vc| vc.should.be.instance_of BasicScreen }
166
- @screen.open_screen BasicScreen
174
+ @screen.open BasicScreen
167
175
  end
168
176
 
169
177
  it "should open the provided view controller as root view if no other conditions are met" do
@@ -31,8 +31,8 @@ describe "screen properties" do
31
31
  HomeScreen.debug_mode.should == true
32
32
  end
33
33
 
34
- it "#is_modal? should be true" do
35
- @screen.is_modal?.should == true
34
+ it "#modal? should be true" do
35
+ @screen.modal?.should == true
36
36
  end
37
37
 
38
38
  it "should know it is the first screen" do
@@ -115,7 +115,7 @@ describe "screen properties" do
115
115
  describe "navigation controller behavior" do
116
116
 
117
117
  it "should have a nav bar" do
118
- @screen.has_nav_bar?.should == true
118
+ @screen.nav_bar?.should == true
119
119
  end
120
120
 
121
121
  it "#main_controller should return a navigation controller" do
@@ -183,3 +183,21 @@ describe "screen properties" do
183
183
  end
184
184
 
185
185
  end
186
+
187
+ describe "screen with toolbar" do
188
+
189
+ it "showing" do
190
+ # Simulate AppDelegate setup of main screen
191
+ screen = HomeScreen.new modal: true, nav_bar: true, toolbar: true
192
+ screen.on_load
193
+ screen.navigationController.toolbarHidden?.should == false
194
+ end
195
+
196
+ it "hidden" do
197
+ # Simulate AppDelegate setup of main screen
198
+ screen = HomeScreen.new modal: true, nav_bar: true, toolbar: false
199
+ screen.on_load
200
+ screen.navigationController.toolbarHidden?.should == true
201
+ end
202
+
203
+ end
@@ -0,0 +1,108 @@
1
+ describe "PM::Table module" do
2
+
3
+ def cell_factory(args={})
4
+ { title: "Basic", action: :basic_cell_tapped, arguments: { id: 1 } }.merge!(args)
5
+ end
6
+
7
+ def custom_cell
8
+ cell_factory({
9
+ title: "Crazy Full Featured Cell",
10
+ subtitle: "This is way too huge..see note",
11
+ arguments: { data: [ "lots", "of", "data" ] },
12
+ action: :tapped_cell_1,
13
+ height: 50, # manually changes the cell's height
14
+ cell_style: UITableViewCellStyleSubtitle,
15
+ cell_identifier: "Cell",
16
+ cell_class: PM::TableViewCell,
17
+ masks_to_bounds: true,
18
+ background_color: UIColor.whiteColor,
19
+ selection_style: UITableViewCellSelectionStyleGray,
20
+ cell_class_attributes: {
21
+ # any Obj-C attributes to set on the cell
22
+ backgroundColor: UIColor.whiteColor
23
+ },
24
+ accessory: :switch, # currently only :switch is supported
25
+ accessory_view: @some_accessory_view,
26
+ accessory_type: UITableViewCellAccessoryCheckmark,
27
+ accessory_checked: true, # whether it's "checked" or not
28
+ image: { image: UIImage.imageNamed("something"), radius: 15 },
29
+ remote_image: { # remote image, requires SDWebImage CocoaPod
30
+ url: "http://placekitten.com/200/300", placeholder: "some-local-image",
31
+ size: 50, radius: 15
32
+ },
33
+ subviews: [ @some_view, @some_other_view ] # arbitrary views added to the cell
34
+ })
35
+ end
36
+
37
+ before do
38
+ @subject = TestTableScreen.new
39
+ @subject.mock! :table_data do
40
+ [{
41
+ title: "Table cell group 1", cells: [ ]
42
+ },{
43
+ title: "Table cell group 2", cells: [ cell_factory ]
44
+ },{
45
+ title: "Table cell group 3", cells: [ cell_factory(title: "3-1"), cell_factory(title: "3-2") ]
46
+ },{
47
+ title: "Table cell group 4", cells: [ custom_cell, cell_factory(title: "4-2"), cell_factory(title: "4-3"), cell_factory(title: "4-4") ]
48
+ }]
49
+ end
50
+
51
+ @subject.on_load
52
+
53
+ @ip = NSIndexPath.indexPathForRow(1, inSection: 2) # Cell 3-2
54
+ @custom_ip = NSIndexPath.indexPathForRow(0, inSection: 3) # Cell "Crazy Full Featured Cell"
55
+
56
+ @subject.update_table_data
57
+ end
58
+
59
+ it "should have the right number of sections" do
60
+ @subject.numberOfSectionsInTableView(@subject.table_view).should == 4
61
+ end
62
+
63
+ it "should set the section titles" do
64
+ @subject.tableView(@subject.table_view, titleForHeaderInSection:0).should == "Table cell group 1"
65
+ @subject.tableView(@subject.table_view, titleForHeaderInSection:1).should == "Table cell group 2"
66
+ @subject.tableView(@subject.table_view, titleForHeaderInSection:2).should == "Table cell group 3"
67
+ @subject.tableView(@subject.table_view, titleForHeaderInSection:3).should == "Table cell group 4"
68
+ end
69
+
70
+ it "should create the right number of cells" do
71
+ @subject.tableView(@subject.table_view, numberOfRowsInSection:0).should == 0
72
+ @subject.tableView(@subject.table_view, numberOfRowsInSection:1).should == 1
73
+ @subject.tableView(@subject.table_view, numberOfRowsInSection:2).should == 2
74
+ @subject.tableView(@subject.table_view, numberOfRowsInSection:3).should == 4
75
+ end
76
+
77
+ it "should create the jumplist" do
78
+ @subject.mock! :table_data_index, do
79
+ Array("A".."Z")
80
+ end
81
+
82
+ @subject.sectionIndexTitlesForTableView(@subject.table_view).should == Array("A".."Z")
83
+ end
84
+
85
+ it "should return the proper cell" do
86
+ cell = @subject.tableView(@subject.table_view, cellForRowAtIndexPath: @ip)
87
+ cell.should.be.kind_of(UITableViewCell)
88
+ cell.textLabel.text.should == "3-2"
89
+ end
90
+
91
+ it "should return the table's cell height if none is given" do
92
+ @subject.tableView(@subject.table_view, heightForRowAtIndexPath:@ip).should == 44.0 # Built-in default
93
+ end
94
+
95
+ it "should allow setting a custom cell height" do
96
+ @subject.tableView(@subject.table_view, heightForRowAtIndexPath:@custom_ip).should.be > 0.0
97
+ @subject.tableView(@subject.table_view, heightForRowAtIndexPath:@custom_ip).should == custom_cell[:height].to_f
98
+ end
99
+
100
+ it "should trigger the right action on select and pass in the right arguments" do
101
+ @subject.mock! :tapped_cell_1 do |args|
102
+ args[:data].should == [ "lots", "of", "data" ]
103
+ end
104
+
105
+ @subject.tableView(@subject.table_view, didSelectRowAtIndexPath:@custom_ip)
106
+ end
107
+
108
+ end
@@ -0,0 +1,92 @@
1
+ describe "table screens" do
2
+
3
+ describe "basic functionality" do
4
+
5
+ before do
6
+ @screen = TestTableScreen.new
7
+ @screen.on_load
8
+ end
9
+
10
+ it "should display some sections" do
11
+ @screen.promotion_table_data.sections.should.be.kind_of(Array)
12
+ end
13
+
14
+ it "should have proper cell numbers" do
15
+ @screen.tableView(@screen.tableView, numberOfRowsInSection:0).should == 6
16
+ @screen.tableView(@screen.tableView, numberOfRowsInSection:1).should == 2
17
+ @screen.tableView(@screen.tableView, numberOfRowsInSection:2).should == 4
18
+ end
19
+
20
+ it "should return a UITableViewCell" do
21
+ index_path = NSIndexPath.indexPathForRow(1, inSection: 1)
22
+
23
+ @screen.tableView(@screen.tableView, cellForRowAtIndexPath: index_path).should.be.kind_of UITableViewCell
24
+ end
25
+
26
+ it "should have a placeholder image in the last cell" do
27
+ index_path = NSIndexPath.indexPathForRow(1, inSection: 1)
28
+
29
+ @screen.tableView(@screen.tableView, cellForRowAtIndexPath: index_path).imageView.should.be.kind_of UIImageView
30
+ end
31
+
32
+ it "should display all images properly no matter how they were initialized" do
33
+ section = @screen.promotion_table_data.sections.count - 1 # All the cells we want to test are in the last section
34
+
35
+ @screen.tableView(@screen.tableView, numberOfRowsInSection:section).times do |i|
36
+ index_path = NSIndexPath.indexPathForRow(i, inSection: section)
37
+
38
+ @screen.tableView(@screen.tableView, cellForRowAtIndexPath: index_path).imageView.should.be.kind_of UIImageView
39
+
40
+ # Test the corner radius on the first cell.
41
+ if i == 0
42
+ @screen.tableView(@screen.tableView, cellForRowAtIndexPath: index_path).imageView.layer.cornerRadius.to_f.should == 10.0
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ describe "search functionality" do
50
+
51
+ before do
52
+ @screen = TableScreenSearchable.new
53
+ @screen.on_load
54
+ end
55
+
56
+ it "should be searchable" do
57
+ @screen.class.get_searchable.should == true
58
+ end
59
+
60
+ it "should create a search header" do
61
+ @screen.table_view.tableHeaderView.should.be.kind_of UISearchBar
62
+ end
63
+
64
+ end
65
+
66
+ describe "refresh functionality" do
67
+
68
+ # Note this test only works if on iOS 6+ or when using CKRefreshControl.
69
+
70
+ before do
71
+ @screen = TableScreenRefreshable.new
72
+ @screen.on_load
73
+ end
74
+
75
+ it "should be refreshable" do
76
+ @screen.class.get_refreshable.should == true
77
+ end
78
+
79
+ it "should create a refresh object" do
80
+ @screen.instance_variable_get("@refresh_control").should.be.kind_of UIRefreshControl
81
+ end
82
+
83
+ it "should respond to start_refreshing and end_refreshing" do
84
+ @screen.respond_to?(:start_refreshing).should == true
85
+ @screen.respond_to?(:end_refreshing).should == true
86
+ end
87
+
88
+ # Animations cause the refresh object to fail when tested. Test manually.
89
+
90
+ end
91
+
92
+ end