larynx 0.1.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.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +191 -0
- data/Rakefile +55 -0
- data/bin/larynx +7 -0
- data/examples/guess.rb +39 -0
- data/examples/guess_form.rb +34 -0
- data/examples/multiple_apps.rb +63 -0
- data/lib/larynx/application.rb +24 -0
- data/lib/larynx/call_handler.rb +174 -0
- data/lib/larynx/callbacks.rb +32 -0
- data/lib/larynx/command.rb +73 -0
- data/lib/larynx/commands.rb +88 -0
- data/lib/larynx/fields.rb +143 -0
- data/lib/larynx/form.rb +19 -0
- data/lib/larynx/logger.rb +8 -0
- data/lib/larynx/observable.rb +35 -0
- data/lib/larynx/prompt.rb +88 -0
- data/lib/larynx/response.rb +57 -0
- data/lib/larynx/restartable_timer.rb +26 -0
- data/lib/larynx/session.rb +20 -0
- data/lib/larynx/version.rb +3 -0
- data/lib/larynx.rb +109 -0
- data/spec/fixtures/answer.rb +125 -0
- data/spec/fixtures/channel_data.rb +147 -0
- data/spec/fixtures/dtmf.rb +52 -0
- data/spec/fixtures/execute.rb +52 -0
- data/spec/fixtures/execute_complete.rb +133 -0
- data/spec/fixtures/reply_ok.rb +6 -0
- data/spec/larynx/call_handler_spec.rb +290 -0
- data/spec/larynx/command_spec.rb +76 -0
- data/spec/larynx/eventmachince_spec.rb +14 -0
- data/spec/larynx/fields_spec.rb +194 -0
- data/spec/larynx/prompt_spec.rb +222 -0
- data/spec/larynx_spec.rb +4 -0
- data/spec/spec_helper.rb +47 -0
- metadata +96 -0
@@ -0,0 +1,290 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Larynx::CallHandler do
|
4
|
+
attr_reader :call
|
5
|
+
|
6
|
+
before do
|
7
|
+
@call = TestCallHandler.new(1)
|
8
|
+
end
|
9
|
+
|
10
|
+
context "execute" do
|
11
|
+
before do
|
12
|
+
call.queue = []
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should queue commands" do
|
16
|
+
call.execute Larynx::Command.new('dummy1')
|
17
|
+
call.execute Larynx::Command.new('dummy2')
|
18
|
+
call.queue[0].command.should == 'dummy1'
|
19
|
+
call.queue[1].command.should == 'dummy2'
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should push command on front of queue when immediate is true" do
|
23
|
+
call.execute Larynx::Command.new('dummy1')
|
24
|
+
call.execute Larynx::Command.new('dummy2'), true
|
25
|
+
call.queue[0].command.should == 'dummy2'
|
26
|
+
call.queue[1].command.should == 'dummy1'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should return first command in queue for current_command" do
|
31
|
+
call.queue = []
|
32
|
+
call.execute Larynx::Command.new('dummy')
|
33
|
+
call.current_command.command.should == 'dummy'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should send current command message on send_next_command" do
|
37
|
+
call.queue = []
|
38
|
+
call.execute Larynx::Command.new('dummy')
|
39
|
+
call.send_next_command
|
40
|
+
call.sent_data.should == 'dummy'
|
41
|
+
end
|
42
|
+
|
43
|
+
context "reply received" do
|
44
|
+
before do
|
45
|
+
call.queue = []
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should finalise current command if it is an API command" do
|
49
|
+
call.should_receive :finalize_command
|
50
|
+
call.connect
|
51
|
+
call.send_response :reply_ok
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should not finalise current command if it is an App command" do
|
55
|
+
call.should_not_receive :finalize_command
|
56
|
+
call.speak 'hello'
|
57
|
+
call.send_response :reply_ok
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "executing event received" do
|
62
|
+
before do
|
63
|
+
call.queue = []
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should run app command before callback" do
|
67
|
+
call.speak('hello world').before { @callback = true }
|
68
|
+
call.send_response :execute
|
69
|
+
@callback.should be_true
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should change state to executing" do
|
73
|
+
call.speak 'hello'
|
74
|
+
call.send_response :execute
|
75
|
+
call.state.should == :executing
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should not finalize command" do
|
79
|
+
call.should_not_receive :finalize_command
|
80
|
+
call.speak 'hello'
|
81
|
+
call.send_response :execute
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "execution complete event received" do
|
86
|
+
before do
|
87
|
+
call.queue = []
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should finalize command" do
|
91
|
+
call.should_receive :finalize_command
|
92
|
+
call.speak 'hello'
|
93
|
+
call.send_response :execute_complete
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should change state to ready" do
|
97
|
+
call.speak 'hello'
|
98
|
+
call.send_response :execute_complete
|
99
|
+
call.state.should == :ready
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context "finalizing command" do
|
104
|
+
before do
|
105
|
+
call.queue = []
|
106
|
+
call.speak('hello world').after { @callback = true }
|
107
|
+
@command = call.current_command
|
108
|
+
call.finalize_command
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should run after callback" do
|
112
|
+
@callback.should be_true
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should remove command from queue" do
|
116
|
+
call.queue.should be_empty
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should set command as last command" do
|
120
|
+
call.last_command.should == @command
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should queue connect command on init" do
|
125
|
+
call.current_command.name.should == 'connect'
|
126
|
+
end
|
127
|
+
|
128
|
+
context "on connection" do
|
129
|
+
it "should create session object" do
|
130
|
+
connect_call
|
131
|
+
call.session.should_not be_nil
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should fire global connect callback" do
|
135
|
+
Larynx.connect {|call| @callback = true }
|
136
|
+
connect_call
|
137
|
+
@callback.should be_true
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should start the call session" do
|
141
|
+
call.should_receive :start_session
|
142
|
+
connect_call
|
143
|
+
end
|
144
|
+
|
145
|
+
def connect_call
|
146
|
+
call.send_response :channel_data
|
147
|
+
end
|
148
|
+
|
149
|
+
after do
|
150
|
+
Larynx.connect {|call|}
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context "on session start" do
|
155
|
+
before do
|
156
|
+
call.queue = []
|
157
|
+
call.session = mock('session', :unique_id => '123')
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should subscribe to myevents" do
|
161
|
+
call.should_receive(:myevents)
|
162
|
+
call.start_session
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should set event lingering on after filter events" do
|
166
|
+
call.should_receive(:linger)
|
167
|
+
call.start_session
|
168
|
+
call.send_response :reply_ok
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should answer call after filter events" do
|
172
|
+
call.should_receive(:answer)
|
173
|
+
call.start_session
|
174
|
+
call.send_response :reply_ok
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context "on answer" do
|
179
|
+
before do
|
180
|
+
call.queue = []
|
181
|
+
call.session = mock('session', :unique_id => '123')
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should change state to ready" do
|
185
|
+
answer_call
|
186
|
+
call.state.should == :ready
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should fire global answer callback" do
|
190
|
+
Larynx.answer {|call| @callback = true }
|
191
|
+
answer_call
|
192
|
+
@callback.should be_true
|
193
|
+
end
|
194
|
+
|
195
|
+
def answer_call
|
196
|
+
call.start_session
|
197
|
+
call.send_response :reply_ok
|
198
|
+
call.send_response :reply_ok
|
199
|
+
call.send_response :reply_ok
|
200
|
+
call.send_response :execute_complete
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
context "DTMF event" do
|
205
|
+
it "should add DTMF digit to input" do
|
206
|
+
run_command
|
207
|
+
call.send_response :dtmf
|
208
|
+
call.input.should == ['1']
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should send break if interruptable command" do
|
212
|
+
run_command true
|
213
|
+
call.send_response :dtmf
|
214
|
+
call.sent_data.should match(/break/)
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should not send break if non-interruptable command" do
|
218
|
+
run_command false
|
219
|
+
call.send_response :dtmf
|
220
|
+
call.sent_data.should_not match(/break/)
|
221
|
+
end
|
222
|
+
|
223
|
+
it "should send next command if state is ready" do
|
224
|
+
call.state = :ready
|
225
|
+
call.should_receive(:send_next_command)
|
226
|
+
call.send_response :dtmf
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should notify observers and pass digit" do
|
230
|
+
app = mock('App')
|
231
|
+
call.add_observer app
|
232
|
+
app.should_receive(:dtmf_received).with('1')
|
233
|
+
call.send_response :dtmf
|
234
|
+
end
|
235
|
+
|
236
|
+
def run_command(interruptable=true)
|
237
|
+
call.queue = []
|
238
|
+
call.speak 'hello', :bargein => interruptable
|
239
|
+
call.send_next_command
|
240
|
+
call.send_response :execute
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
context "timer" do
|
245
|
+
it "should add EM timer with name and timeout" do
|
246
|
+
Larynx::RestartableTimer.stub!(:new)
|
247
|
+
call.timer(:test, 0.1)
|
248
|
+
call.timers[:test].should_not be_nil
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should run callback on timeout" do
|
252
|
+
em do
|
253
|
+
call.timer(:test, 0.2) { @callback = true; done }
|
254
|
+
end
|
255
|
+
@callback.should be_true
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
context "stop_timer" do
|
260
|
+
it "should run callback on timeout" do
|
261
|
+
em do
|
262
|
+
call.timer(:test, 1) { @callback = true }
|
263
|
+
EM::Timer.new(0.1) { call.stop_timer :test; done }
|
264
|
+
end
|
265
|
+
@callback.should be_true
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
context "cancel_timer" do
|
270
|
+
it "should not run callback" do
|
271
|
+
em do
|
272
|
+
call.timer(:test, 0.2) { @callback = true }
|
273
|
+
EM::Timer.new(0.1) { call.cancel_timer :test; done }
|
274
|
+
end
|
275
|
+
@callback.should_not be_true
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
context "restart_timer" do
|
280
|
+
it "should start timer again" do
|
281
|
+
start = Time.now
|
282
|
+
em do
|
283
|
+
EM::Timer.new(0.5) { call.restart_timer :test }
|
284
|
+
call.timer(:test, 1) { done }
|
285
|
+
end
|
286
|
+
(Time.now-start).should be_close(1.5, 0.2)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Larynx::Command do
|
4
|
+
|
5
|
+
it "should allow before callback" do
|
6
|
+
cmd = Larynx::Command.new('dummy').before { @callback = true }
|
7
|
+
@callback.should_not be_true
|
8
|
+
cmd.fire_callback :before
|
9
|
+
@callback.should be_true
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should allow after callback" do
|
13
|
+
cmd = Larynx::Command.new('dummy').after { @callback = true }
|
14
|
+
@callback.should_not be_true
|
15
|
+
cmd.fire_callback :after
|
16
|
+
@callback.should be_true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should add block given to new as after block" do
|
20
|
+
cmd = Larynx::Command.new('dummy') { @callback = true }
|
21
|
+
@callback.should_not be_true
|
22
|
+
cmd.fire_callback :after
|
23
|
+
@callback.should be_true
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'call command' do
|
27
|
+
before do
|
28
|
+
@cmd = Larynx::CallCommand.new('dummy', 'arg')
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return name as command and params" do
|
32
|
+
@cmd.name.should == 'dummy arg'
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should return to_s as full command message" do
|
36
|
+
@cmd.to_s.should == "dummy arg\n\n"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'api command' do
|
41
|
+
before do
|
42
|
+
@cmd = Larynx::ApiCommand.new('dummy', 'arg')
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should return name as command and params" do
|
46
|
+
@cmd.name.should == 'dummy arg'
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return to_s as full command message" do
|
50
|
+
@cmd.to_s.should == "api dummy arg\n\n"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'app command' do
|
55
|
+
before do
|
56
|
+
@cmd = Larynx::AppCommand.new('dummy', 'arg', :bargein => true)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should return name as command and params" do
|
60
|
+
@cmd.name.should == "dummy 'arg'"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should return to_s as full command message" do
|
64
|
+
@cmd.to_s.should == "sendmsg\ncall-command: execute\nexecute-app-name: dummy\nexecute-app-arg: arg\n\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should return to_s as with arg if no param" do
|
68
|
+
cmd = Larynx::AppCommand.new('dummy')
|
69
|
+
cmd.to_s.should == "sendmsg\ncall-command: execute\nexecute-app-name: dummy\n\n"
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should return true for interruptable if bargein option is true" do
|
73
|
+
@cmd.interruptable?.should be_true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Larynx::RestartableTimer do
|
4
|
+
|
5
|
+
it "should allow timer to be restarted" do
|
6
|
+
start = Time.now
|
7
|
+
em do
|
8
|
+
timer = Larynx::RestartableTimer.new(1) { done }
|
9
|
+
EM::Timer.new(0.5) { timer.restart }
|
10
|
+
end
|
11
|
+
(Time.now-start).should be_close(1.5, 0.2)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Larynx::Fields do
|
4
|
+
attr_reader :call, :app
|
5
|
+
|
6
|
+
before do
|
7
|
+
@call = TestCallHandler.new(1)
|
8
|
+
@app = Larynx::Application.new(@call)
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'module' do
|
12
|
+
include Larynx::Fields
|
13
|
+
|
14
|
+
it 'should add field class method' do
|
15
|
+
self.class.should respond_to(:field)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should add instance accessor for field name' do
|
19
|
+
self.class.field(:guess) { prompt :speak => 'hello' }
|
20
|
+
self.methods.include?(:guess)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'next_field' do
|
25
|
+
include Larynx::Fields
|
26
|
+
field(:field1) { prompt :speak => 'hello' }
|
27
|
+
field(:field2) { prompt :speak => 'hello' }
|
28
|
+
field(:field3) { prompt :speak => 'hello' }
|
29
|
+
|
30
|
+
it 'should iterate over defined fields' do
|
31
|
+
next_field.name.should == :field1
|
32
|
+
next_field.name.should == :field2
|
33
|
+
next_field.name.should == :field3
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should jump to field name if supplied' do
|
37
|
+
next_field(:field2).name.should == :field2
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'field object' do
|
42
|
+
it 'should raise exception if field has no prompt' do
|
43
|
+
lambda { field(:guess) {} }.should raise_exception(Larynx::NoPromptDefined)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should run setup callback once' do
|
47
|
+
call_me = should_be_called
|
48
|
+
fld = field(:guess) do
|
49
|
+
prompt :speak => 'first'
|
50
|
+
setup &call_me
|
51
|
+
end
|
52
|
+
fld.run app
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should return same prompt all attempts if single prompt' do
|
56
|
+
fld = field(:guess) do
|
57
|
+
prompt :speak => 'first'
|
58
|
+
end
|
59
|
+
fld.run(app)
|
60
|
+
fld.current_prompt.message.should == 'first'
|
61
|
+
fld.increment_attempts
|
62
|
+
fld.current_prompt.message.should == 'first'
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should return reprompt for subsequent prompts' do
|
66
|
+
fld = field(:guess) do
|
67
|
+
prompt :speak => 'first'
|
68
|
+
reprompt :speak => 'second'
|
69
|
+
end
|
70
|
+
fld.run(app)
|
71
|
+
fld.current_prompt.message.should == 'first'
|
72
|
+
fld.increment_attempts
|
73
|
+
fld.current_prompt.message.should == 'second'
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should return prompt for given number of repeats before subsequent prompts' do
|
77
|
+
fld = field(:guess) do
|
78
|
+
prompt :speak => 'first', :repeats => 2
|
79
|
+
reprompt :speak => 'second'
|
80
|
+
end
|
81
|
+
fld.run(app)
|
82
|
+
fld.current_prompt.message.should == 'first'
|
83
|
+
fld.increment_attempts
|
84
|
+
fld.current_prompt.message.should == 'first'
|
85
|
+
fld.increment_attempts
|
86
|
+
fld.current_prompt.message.should == 'second'
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'valid?' do
|
90
|
+
it 'should be false if input size less than minimum' do
|
91
|
+
fld = field(:guess) do
|
92
|
+
prompt :speak => 'first'
|
93
|
+
end
|
94
|
+
fld.run app
|
95
|
+
fld.current_prompt.finalise
|
96
|
+
fld.valid?.should be_false
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should run validate callback if input minimum length' do
|
100
|
+
call_me = should_be_called
|
101
|
+
fld = field(:guess, :min_length => 1) do
|
102
|
+
prompt :speak => 'first'
|
103
|
+
validate &call_me
|
104
|
+
end
|
105
|
+
fld.run app
|
106
|
+
call.input << '1'
|
107
|
+
fld.current_prompt.finalise
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'input evaluation' do
|
112
|
+
it 'should run invalid callback if length not valid' do
|
113
|
+
call_me = should_be_called
|
114
|
+
fld = field(:guess) do
|
115
|
+
prompt :speak => 'first'
|
116
|
+
invalid &call_me
|
117
|
+
end
|
118
|
+
fld.run app
|
119
|
+
fld.current_prompt.finalise
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should run invalid callback if validate callback returns false' do
|
123
|
+
call_me = should_be_called
|
124
|
+
fld = field(:guess, :min_length => 1) do
|
125
|
+
prompt :speak => 'first'
|
126
|
+
validate { false }
|
127
|
+
invalid &call_me
|
128
|
+
end
|
129
|
+
fld.run app
|
130
|
+
call.input << '1'
|
131
|
+
fld.current_prompt.finalise
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should run success callback if length valid and no validate callback' do
|
135
|
+
call_me = should_be_called
|
136
|
+
fld = field(:guess, :min_length => 1) do
|
137
|
+
prompt :speak => 'first'
|
138
|
+
success &call_me
|
139
|
+
end
|
140
|
+
fld.run app
|
141
|
+
call.input << '1'
|
142
|
+
fld.current_prompt.finalise
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'should run success callback if validate callback returns true' do
|
146
|
+
call_me = should_be_called
|
147
|
+
fld = field(:guess, :min_length => 1) do
|
148
|
+
prompt :speak => 'first'
|
149
|
+
validate { true }
|
150
|
+
success &call_me
|
151
|
+
end
|
152
|
+
fld.run app
|
153
|
+
call.input << '1'
|
154
|
+
fld.current_prompt.finalise
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should run failure callback if not valid and last attempt' do
|
158
|
+
call_me = should_be_called
|
159
|
+
fld = field(:guess, :min_length => 1, :attempts => 1) do
|
160
|
+
prompt :speak => 'first'
|
161
|
+
failure &call_me
|
162
|
+
end
|
163
|
+
fld.run app
|
164
|
+
fld.current_prompt.finalise
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should increment attempts if not valid' do
|
168
|
+
fld = field(:guess) do
|
169
|
+
prompt :speak => 'first'
|
170
|
+
reprompt :speak => 'second'
|
171
|
+
end
|
172
|
+
fld.run app
|
173
|
+
fld.current_prompt.finalise
|
174
|
+
fld.current_prompt.message.should == 'second'
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'should execute next prompt if not valid' do
|
178
|
+
fld = field(:guess) do
|
179
|
+
prompt :speak => 'first'
|
180
|
+
reprompt :speak => 'second'
|
181
|
+
end
|
182
|
+
fld.run app
|
183
|
+
fld.should_receive(:execute_prompt)
|
184
|
+
fld.current_prompt.finalise
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
def field(name, options={}, &block)
|
191
|
+
@app.class.class_eval { attr_accessor name }
|
192
|
+
Larynx::Fields::Field.new(name, options, &block)
|
193
|
+
end
|
194
|
+
end
|