cellular 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3fdd4bd856508028b2de1f016627e9a107be0dd4
4
- data.tar.gz: a50db77596d53823b496506604356532e9519096
3
+ metadata.gz: a97fa1c32bf2c1aea85c36bd15bb489b9aaa20d9
4
+ data.tar.gz: a3190fd32b63a7cf284228d0b37481429ce30da4
5
5
  SHA512:
6
- metadata.gz: 1670df114c490aae52f7ad804656c4e3d538929f07fa7cbda5121466a4cfe637b3dcec5d39bb5c80e0a575e691842b8734bbe925011195b31a14d97dd4b746b8
7
- data.tar.gz: 9403f6ab944a9e0bba7df7b26a0e18c334f8a9bf16635c1cf4835573070aa1161c54814adc84e385fc1bac203097a9afddc0c8a83c36e4ee5e28afcdc0373bbe
6
+ metadata.gz: 359c6728df2bd17dafc5be5d0f04704ab7a394084ee3e0ace41f112ef48733c9883cd4ad08654a83c03d83a15d06b1fa43e215afb61d3fe628cf8f07118f310c
7
+ data.tar.gz: 610ac72f15d403b628dd60cdd70ffcb301dea9297402ec6c0d6c00ec0edfae128bfd962837c14993bffcf1dc95a0c1e6d6295772ae57c32682603f0c174e541c
data/.travis.yml CHANGED
@@ -2,4 +2,6 @@ language: ruby
2
2
  rvm:
3
3
  - 2.0.0
4
4
  - 2.1.0
5
+ - 2.2.2
6
+ - 2.3.0
5
7
  script: bundle exec rake spec
data/README.md CHANGED
@@ -36,7 +36,7 @@ Or install it yourself as:
36
36
  Cellular.configure do |config|
37
37
  config.username = 'username'
38
38
  config.password = 'password'
39
- config.backend = Cellular::Backends::Sendega
39
+ config.backend = Cellular::Backends::Sendega
40
40
  config.sender = 'Default custom sender'
41
41
  config.country_code = 'NO'
42
42
  end
@@ -48,8 +48,9 @@ interface to interact with queue backends. Read appropriate documentation to set
48
48
 
49
49
  ### Available Backends
50
50
 
51
- * [CoolSMS](http://coolsms.com/)
52
- * [Sendega](http://sendega.com/)
51
+ * [Cellular::Backends::CoolSMS](http://coolsms.com/)
52
+ * [Cellular::Backends::Sendega](http://sendega.com/)
53
+ * [Cellular::Backends::Twilio](http://twilio.com/)
53
54
  * Log (logs to `$stdout`)
54
55
  * Test (adds messages to `Cellular.deliveries`)
55
56
 
@@ -60,8 +61,8 @@ The options supported may differ between backends.
60
61
 
61
62
  ```ruby
62
63
  sms = Cellular::SMS.new(
63
- recipient: '47xxxxxxxx',
64
- sender: 'Custom sender',
64
+ recipient: '+47xxxxxxxx', # Valid international format
65
+ sender: '+370xxxxxxxx',
65
66
  message: 'This is an SMS message',
66
67
  price: 0,
67
68
  country_code: 'NO' # defaults to Cellular.config.country_code
@@ -73,8 +74,8 @@ For use with multiple recipients in one request use:
73
74
 
74
75
  ```ruby
75
76
  sms = Cellular::SMS.new(
76
- recipients: ['47xxxxxxx1','47xxxxxxx2','47xxxxxxx3'],
77
- sender: 'Custom sender',
77
+ recipients: ['+47xxxxxxx1','+47xxxxxxx2','+47xxxxxxx3'],
78
+ sender: '+370xxxxxxxx',
78
79
  message: 'This is an SMS message',
79
80
  price: 0,
80
81
  country_code: 'NO' # defaults to Cellular.config.country_code
@@ -92,8 +93,8 @@ and are concerned that it might time out or something. To use it, just call
92
93
 
93
94
  ```ruby
94
95
  sms = Cellular::SMS.new(
95
- recipient: '47xxxxxxxx',
96
- sender: 'Custom sender',
96
+ recipient: '+47xxxxxxxx',
97
+ sender: '+47xxxxxxxx',
97
98
  message: 'This is an SMS message'
98
99
  )
99
100
 
@@ -113,21 +114,30 @@ Just call `deliver_async(wait_until: timestamp)` or `deliver_async(wait: time)`
113
114
 
114
115
  ```ruby
115
116
  sms = Cellular::SMS.new(
116
- recipient: '47xxxxxxxx',
117
- sender: 'Custom sender',
117
+ recipient: '+47xxxxxxxx',
118
+ sender: '+47xxxxxxxx',
118
119
  message: 'This is an SMS message'
119
120
  )
120
121
 
121
122
  sms.deliver_async(wait_until: Date.tomorrow.noon)
122
123
  ```
123
124
 
125
+ ## Troubleshooting
126
+
127
+ If you are using Twilio as a backend, please make sure you add or (port)[https://www.twilio.com/help/faq/porting] a phone number to your account so, that you can use that as a sender option. You won't be able to send messages from any phone number unless you port it to Twilio.
128
+
129
+ Also, make sure phone numbers are in valid international format:
130
+ [`+47xxxxxx`, `+370xxxxx`]
131
+
124
132
  ## Contributing
125
133
 
126
134
  1. Fork it
127
- 2. Create your feature branch (`git checkout -b my-new-feature`)
128
- 3. Commit your changes (`git commit -am 'Add some feature'`)
129
- 4. Push to the branch (`git push origin my-new-feature`)
130
- 5. Create pull request
135
+ 2. Create your feature branch (`git checkout -b feature/my-new-feature`)
136
+ 3. Write your code and necessary tests
137
+ 4. Run your tests (`bundle exec rspec`)
138
+ 5. Commit your changes (`git commit -am 'Add some feature'`)
139
+ 6. Push to the branch (`git push origin feature/my-new-feature`)
140
+ 7. Create pull request and be awesome!
131
141
 
132
142
 
133
143
  ## Credits
data/cellular.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |gem|
9
9
  gem.name = 'cellular'
10
10
  gem.version = Cellular::VERSION
11
11
  gem.authors = ['Sindre Moen', 'Tim Kurvers']
12
- gem.email = ['johannes@hyper.no']
12
+ gem.email = ['ruby@hyper.no']
13
13
  gem.description = %q{Sending and receiving SMSs through pluggable backends}
14
14
  gem.summary = %q{Sending and receiving SMSs through pluggable backends}
15
15
  gem.homepage = ''
@@ -25,8 +25,6 @@ Gem::Specification.new do |gem|
25
25
 
26
26
  gem.add_development_dependency 'pry', '~> 0.10'
27
27
 
28
- gem.add_development_dependency 'guard', '~> 2.6'
29
- gem.add_development_dependency 'guard-rspec', '~> 4.3'
30
28
  gem.add_development_dependency 'rake', '~> 10.3'
31
29
  gem.add_development_dependency 'webmock', '~> 1.19'
32
30
 
@@ -3,6 +3,7 @@ module Cellular
3
3
  autoload :Backend, 'cellular/backends/backend'
4
4
  autoload :CoolSMS, 'cellular/backends/cool_sms'
5
5
  autoload :Sendega, 'cellular/backends/sendega'
6
+ autoload :Twilio, 'cellular/backends/twilio'
6
7
  autoload :Log, 'cellular/backends/log'
7
8
  autoload :Test, 'cellular/backends/test'
8
9
  end
@@ -0,0 +1,77 @@
1
+ require 'httparty'
2
+
3
+ module Cellular
4
+ module Backends
5
+ class Twilio < Backend
6
+ # Documentation: https://www.twilio.com/docs/api/rest
7
+ API_VERSION = '2010-04-01'
8
+ BASE_URL = 'https://api.twilio.com/'
9
+ API_URL = BASE_URL + API_VERSION
10
+
11
+ HTTP_HEADERS = {
12
+ 'Accept' => 'application/json',
13
+ 'Accept-Charset' => 'utf-8',
14
+ 'User-Agent' => "cellular/#{Cellular::VERSION}" \
15
+ " (#{RUBY_ENGINE}/#{RUBY_PLATFORM}" \
16
+ " #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL})"
17
+ }
18
+
19
+ def self.deliver(options = {})
20
+ request_queue = {}
21
+ recipients_batch(options).each_with_index do |recipient, index|
22
+ options[:batch] = recipient
23
+ request = HTTParty.post(
24
+ sms_url,
25
+ body: payload(options),
26
+ basic_auth: twilio_config,
27
+ headers: HTTP_HEADERS
28
+ )
29
+
30
+ request_queue[index] = {
31
+ recipient: options[:batch],
32
+ response: parse_response(request)
33
+ }
34
+ end
35
+
36
+ # return first response for now
37
+ request_queue[0][:response]
38
+ end
39
+
40
+ def self.parse_response(response)
41
+ [
42
+ response.code,
43
+ response.message
44
+ ]
45
+ end
46
+
47
+ def self.sms_url
48
+ "#{API_URL}/Accounts/#{twilio_config[:username]}/Messages"
49
+ end
50
+
51
+ def self.twilio_config
52
+ {
53
+ username: Cellular.config.username,
54
+ password: Cellular.config.password
55
+ }
56
+ end
57
+
58
+ def self.payload(options)
59
+ {
60
+ From: options[:sender],
61
+ To: options[:batch],
62
+ Body: options[:message],
63
+ MaxPrice: options[:price] || 0.50
64
+ }
65
+ end
66
+
67
+ def self.recipients_batch(options)
68
+ if options[:recipients].blank?
69
+ [options[:recipient]]
70
+ else
71
+ options[:recipients]
72
+ end
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -4,7 +4,7 @@ module Cellular
4
4
  class SMS
5
5
 
6
6
  attr_accessor :recipient, :sender, :message, :price, :country_code
7
- attr_accessor :recipients
7
+ attr_accessor :recipients, :delivery_status, :delivery_message
8
8
  def initialize(options = {})
9
9
  @backend = Cellular.config.backend
10
10
 
@@ -1,3 +1,3 @@
1
1
  module Cellular
2
- VERSION = '2.0.0'
2
+ VERSION = '2.1.0'
3
3
  end
@@ -0,0 +1,148 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cellular::Backends::Twilio do
4
+
5
+ let(:recipient) { '+15005550004' }
6
+ let(:sender) { '+15005550006' }
7
+ let(:message) { 'This is an SMS message' }
8
+ let(:price) { 0.001 }
9
+
10
+ let(:options) {
11
+ {
12
+ recipient: recipient,
13
+ sender: sender,
14
+ message: message,
15
+ price: price
16
+ }
17
+ }
18
+
19
+ let(:auth) {
20
+ {
21
+ username: 'account_sid',
22
+ password: 'auth_token'
23
+ }
24
+ }
25
+
26
+ let(:payload) {
27
+ {
28
+ From: sender,
29
+ To: recipient,
30
+ Body: message,
31
+ MaxPrice: price
32
+ }
33
+ }
34
+
35
+ before do
36
+ Cellular.config.username = 'account_sid'
37
+ Cellular.config.password = 'auth_token'
38
+ Cellular.config.backend = Cellular::Backends::Twilio
39
+ end
40
+
41
+ describe '::deliver' do
42
+ before do
43
+ stub_request(:post, "https://account_sid:auth_token@api.twilio.com/2010-04-01/Accounts/account_sid/Messages").
44
+ to_return(:status => [201, "CREATED"], :body => fixture('backends/twilio/success.json'), :headers => {'Content-Type' => 'application/json'})
45
+ end
46
+
47
+ it 'does uses HTTParty to deliver an SMS' do
48
+ expect(HTTParty).to receive(:post).with(described_class::sms_url, body:
49
+ payload, headers: described_class::HTTP_HEADERS, basic_auth: described_class::twilio_config).and_call_original
50
+
51
+ described_class.deliver(options)
52
+ end
53
+
54
+ context 'when successful' do
55
+ it 'does return a status code and message' do
56
+ expect(described_class.deliver(options)).to eq([
57
+ 201,
58
+ 'CREATED'
59
+ ])
60
+ end
61
+ end
62
+
63
+ context 'when not successful' do
64
+ before do
65
+ stub_request(:post, "https://account_sid:auth_token@api.twilio.com/2010-04-01/Accounts/account_sid/Messages").
66
+ to_return(:status => [400, "BAD REQUEST"], :body => fixture('backends/twilio/failure.json'), :headers => {'Content-Type' => 'application/json'})
67
+ end
68
+
69
+ it 'does return a status code and message' do
70
+ expect(described_class.deliver(options)).to eq([
71
+ 400,
72
+ 'BAD REQUEST'
73
+ ])
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ describe '::twilio_config' do
80
+ it 'does return the config for twilio' do
81
+ expect(described_class.twilio_config).to eq(
82
+ {
83
+ username: 'account_sid',
84
+ password: 'auth_token'
85
+ })
86
+ end
87
+ end
88
+
89
+ describe '::sms_url' do
90
+ it 'does return the full sms gateway url' do
91
+ expect(described_class.sms_url).to eq(
92
+ "https://api.twilio.com/2010-04-01/Accounts/account_sid/Messages"
93
+ )
94
+ end
95
+ end
96
+
97
+ describe '::payload' do
98
+ it 'does return the request payload' do
99
+ options[:batch] = recipient
100
+ expect(described_class.payload(options)).to eq(payload)
101
+ end
102
+ end
103
+
104
+ describe '::parse_response' do
105
+ before do
106
+ subject { Object.new }
107
+ end
108
+
109
+ context 'when not successful' do
110
+ it 'does return the formatted success response' do
111
+ subject.stub(:code).and_return(201)
112
+ subject.stub(:message).and_return('CREATED')
113
+
114
+ expect(described_class.parse_response(subject)).to eq(
115
+ [
116
+ 201,
117
+ 'CREATED'
118
+ ]
119
+ )
120
+ end
121
+ end
122
+
123
+ context 'when not successful' do
124
+ it 'does return the formatted failed response' do
125
+ subject.stub(:code).and_return(400)
126
+ subject.stub(:message).and_return('BAD REQUEST')
127
+
128
+ expect(described_class.parse_response(subject)).to eq(
129
+ [
130
+ 400,
131
+ 'BAD REQUEST'
132
+ ]
133
+ )
134
+ end
135
+ end
136
+ end
137
+
138
+ describe '::recipients_batch' do
139
+ it 'does wrap recipient option into a array' do
140
+ expect(described_class.recipients_batch({recipient: recipient}))
141
+ .to eq([recipient])
142
+ end
143
+ it 'does return recipients option as it is' do
144
+ expect(described_class.recipients_batch({recipients: [recipient,recipient]}))
145
+ .to eq([recipient,recipient])
146
+ end
147
+ end
148
+ end
@@ -4,21 +4,22 @@ describe Cellular::Jobs::AsyncMessenger do
4
4
  let(:sms_stub) { double "SMS", deliver: true }
5
5
  let(:sms_options) { { "recipient" => "12345678", "text" => "Foo" } }
6
6
 
7
+ before do
8
+ allow(Cellular::SMS).to receive(:new).and_return sms_stub
9
+ end
10
+
7
11
  it 'creates a new SMS object' do
8
12
  symbolized_sms_options = { recipient: "12345678", text: "Foo" }
9
13
 
10
- expect(Cellular::SMS).to receive(:new)
11
- .with(symbolized_sms_options)
12
- .and_return sms_stub
13
-
14
14
  subject.perform sms_options
15
+
16
+ expect(Cellular::SMS).to have_received(:new)
17
+ .with(symbolized_sms_options)
15
18
  end
16
19
 
17
20
  it "delivers the SMS" do
18
- allow(Cellular::SMS).to receive(:new).and_return sms_stub
19
-
20
- expect(sms_stub).to receive :deliver
21
-
22
21
  subject.perform sms_options
22
+
23
+ expect(sms_stub).to have_received :deliver
23
24
  end
24
25
  end
@@ -0,0 +1,10 @@
1
+ {
2
+ "TwilioResponse": {
3
+ "RestException": {
4
+ "Code": "21211",
5
+ "Message": "The 'To' number +4475703596201 is not a valid phone number.",
6
+ "MoreInfo": "https://www.twilio.com/docs/errors/21211",
7
+ "Status": "400"
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "TwilioResponse": {
3
+ "Message": {
4
+ "Sid": "SM3576e58e202041bf8be28e4ef52cbcbc",
5
+ "DateCreated": "Mon, 25 Apr 2016 05:18:07 +0000",
6
+ "DateUpdated": "Mon, 25 Apr 2016 05:18:07 +0000",
7
+ "DateSent": "",
8
+ "AccountSid": "AC800d5bd49542346c71674b49851a1bbf",
9
+ "To": "+447570359620",
10
+ "From": "+15005550006",
11
+ "MessagingServiceSid": "",
12
+ "Body": "This is an SMS message",
13
+ "Status": "queued",
14
+ "NumSegments": "1",
15
+ "NumMedia": "0",
16
+ "Direction": "outbound-api",
17
+ "ApiVersion": "2010-04-01",
18
+ "Price": "",
19
+ "PriceUnit": "USD",
20
+ "ErrorCode": "",
21
+ "ErrorMessage": "",
22
+ "Uri": "/2010-04-01/Accounts/AC800d5bd49542346c71674b49851a1bbf/Messages/SM3576e58e202041bf8be28e4ef52cbcbc",
23
+ "SubresourceUris": {
24
+ "Media": "/2010-04-01/Accounts/AC800d5bd49542346c71674b49851a1bbf/Messages/SM3576e58e202041bf8be28e4ef52cbcbc/Media"
25
+ }
26
+ }
27
+ }
28
+ }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cellular
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sindre Moen
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-03-26 00:00:00.000000000 Z
12
+ date: 2016-04-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: httparty
@@ -67,34 +67,6 @@ dependencies:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0.10'
70
- - !ruby/object:Gem::Dependency
71
- name: guard
72
- requirement: !ruby/object:Gem::Requirement
73
- requirements:
74
- - - "~>"
75
- - !ruby/object:Gem::Version
76
- version: '2.6'
77
- type: :development
78
- prerelease: false
79
- version_requirements: !ruby/object:Gem::Requirement
80
- requirements:
81
- - - "~>"
82
- - !ruby/object:Gem::Version
83
- version: '2.6'
84
- - !ruby/object:Gem::Dependency
85
- name: guard-rspec
86
- requirement: !ruby/object:Gem::Requirement
87
- requirements:
88
- - - "~>"
89
- - !ruby/object:Gem::Version
90
- version: '4.3'
91
- type: :development
92
- prerelease: false
93
- version_requirements: !ruby/object:Gem::Requirement
94
- requirements:
95
- - - "~>"
96
- - !ruby/object:Gem::Version
97
- version: '4.3'
98
70
  - !ruby/object:Gem::Dependency
99
71
  name: rake
100
72
  requirement: !ruby/object:Gem::Requirement
@@ -167,7 +139,7 @@ dependencies:
167
139
  version: '0.9'
168
140
  description: Sending and receiving SMSs through pluggable backends
169
141
  email:
170
- - johannes@hyper.no
142
+ - ruby@hyper.no
171
143
  executables: []
172
144
  extensions: []
173
145
  extra_rdoc_files: []
@@ -176,7 +148,6 @@ files:
176
148
  - ".travis.yml"
177
149
  - CHANGELOG.md
178
150
  - Gemfile
179
- - Guardfile
180
151
  - LICENSE.md
181
152
  - README.md
182
153
  - Rakefile
@@ -188,6 +159,7 @@ files:
188
159
  - lib/cellular/backends/log.rb
189
160
  - lib/cellular/backends/sendega.rb
190
161
  - lib/cellular/backends/test.rb
162
+ - lib/cellular/backends/twilio.rb
191
163
  - lib/cellular/configuration.rb
192
164
  - lib/cellular/jobs.rb
193
165
  - lib/cellular/jobs/async_messenger.rb
@@ -199,6 +171,7 @@ files:
199
171
  - spec/cellular/backends/log_with_rails_spec.rb
200
172
  - spec/cellular/backends/sendega_spec.rb
201
173
  - spec/cellular/backends/test_spec.rb
174
+ - spec/cellular/backends/twilio_spec.rb
202
175
  - spec/cellular/configuration_spec.rb
203
176
  - spec/cellular/jobs/async_messenger_spec.rb
204
177
  - spec/cellular/logger_spec.rb
@@ -209,6 +182,8 @@ files:
209
182
  - spec/fixtures/backends/sendega/failure.xml
210
183
  - spec/fixtures/backends/sendega/service.wsdl
211
184
  - spec/fixtures/backends/sendega/success.xml
185
+ - spec/fixtures/backends/twilio/failure.json
186
+ - spec/fixtures/backends/twilio/success.json
212
187
  - spec/spec_helper.rb
213
188
  - spec/support/fixture.rb
214
189
  homepage: ''
@@ -230,7 +205,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
230
205
  version: '0'
231
206
  requirements: []
232
207
  rubyforge_project:
233
- rubygems_version: 2.2.2
208
+ rubygems_version: 2.5.1
234
209
  signing_key:
235
210
  specification_version: 4
236
211
  summary: Sending and receiving SMSs through pluggable backends
@@ -240,6 +215,7 @@ test_files:
240
215
  - spec/cellular/backends/log_with_rails_spec.rb
241
216
  - spec/cellular/backends/sendega_spec.rb
242
217
  - spec/cellular/backends/test_spec.rb
218
+ - spec/cellular/backends/twilio_spec.rb
243
219
  - spec/cellular/configuration_spec.rb
244
220
  - spec/cellular/jobs/async_messenger_spec.rb
245
221
  - spec/cellular/logger_spec.rb
@@ -250,5 +226,7 @@ test_files:
250
226
  - spec/fixtures/backends/sendega/failure.xml
251
227
  - spec/fixtures/backends/sendega/service.wsdl
252
228
  - spec/fixtures/backends/sendega/success.xml
229
+ - spec/fixtures/backends/twilio/failure.json
230
+ - spec/fixtures/backends/twilio/success.json
253
231
  - spec/spec_helper.rb
254
232
  - spec/support/fixture.rb
data/Guardfile DELETED
@@ -1,23 +0,0 @@
1
- # A sample Guardfile
2
- # More info at https://github.com/guard/guard#readme
3
-
4
- guard :rspec, cmd: 'bundle exec rspec', notification: false do
5
- watch(%r{^spec/.+_spec\.rb$})
6
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
- watch('spec/spec_helper.rb') { "spec" }
8
-
9
- # Rails example
10
- watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
11
- watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
12
- watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
13
- watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
14
- watch('config/routes.rb') { "spec/routing" }
15
- watch('app/controllers/application_controller.rb') { "spec/controllers" }
16
-
17
- # Capybara features specs
18
- watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
19
-
20
- # Turnip features and steps
21
- watch(%r{^spec/acceptance/(.+)\.feature$})
22
- watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
23
- end