ProMotion 0.6.5 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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