Class: Rtml::Test::TmlApplication
- Inherits:
-
Object
- Object
- Rtml::Test::TmlApplication
- Includes:
- BuiltinVariables
- Defined in:
- lib/rtml/test/tml_application.rb
Overview
Loads a TML application and performs automated processing when told to do so.
Note that at any given time, the “current” screen has already been processed. For example, calling #current_screen immediately after loading a document will return the first screen that appears in the document, but that screen will have already been processed. When you call #step or #continue, the next screen will be processed, and calling #current_screen again will return the name of that screen.
Whenever user input would be required, the program halts, waiting for you to simulate that input. This class does not provide a direct interface to simulating user interaction; instead you should set the related variables if applicable, and jump directly to the desired screen.
For a proper high-level simulator that allows you to call methods like #follow_link and #swipe_card, see Rtml::Test::Simulator instead.
It is important to bear in mind that for the purposes of this class, “user input” refers to anything that can’t be calculated directly, such as EMV app selection.
Defined Under Namespace
Classes: Receipt, SimulationError, Snapshot
Constant Summary collapse
- USER_INPUT_ELEMENTS =
A list of TML elements which generally require user input. This doesn’t count elements that optionally require input such as
variant
– only those that usually require input such as card parser. %w(card form submit print)
Constants included from BuiltinVariables
BuiltinVariables::BUILTIN_VARIABLES
Instance Attribute Summary collapse
-
#receipt ⇒ Object
readonly
Returns the value of attribute receipt.
-
#screenflow ⇒ Object
readonly
Returns the value of attribute screenflow.
-
#variable_scope ⇒ Object
readonly
Returns the value of attribute variable_scope.
Instance Method Summary collapse
-
#conditions_match?(hash) ⇒ Boolean
Analyzes a hash as returned by #possible_variants and returns true if the conditions match the current application state (mostly involving the current value of TML variables).
-
#continue(options = {}) ⇒ Object
(also: #process)
Processes screens until a non-running state is achieved, and then returns that state.
-
#continue_forward(options = {}) ⇒ Object
Forces execution to continue to the next screen, even if the current screen is waiting for something to happen.
- #current_screen ⇒ Object
- #current_screen_id ⇒ Object
- #current_state ⇒ Object
- #declare_variable(name, options = {}) ⇒ Object
-
#destinations(options = {}) ⇒ Object
Returns an array containing which of the possible screens following this one meet the current constraints.
- #displaying_content? ⇒ Boolean
- #find_screen(id) ⇒ Object
-
#initialize(tml) ⇒ TmlApplication
constructor
accepts the raw TML document.
- #jump_to_screen(id) ⇒ Object
-
#jump_to_uri(path) ⇒ Object
If the path resembles a screen name that can be found within the current document, then #jump_to_screen is called.
-
#looping? ⇒ Boolean
Returns true if this application seems to be in an endless loop.
-
#next_screen_id(options = {}) ⇒ Object
Returns the first element returned by #destinations, or nil if an empty array would be returned.
-
#possible_variants ⇒ Object
Returns an array of Hashes representing all possible non-user interaction-requiring variants from this screen.
- #screens ⇒ Object
-
#step ⇒ Object
Takes a single “step” in the execution of the program.
-
#step_forward ⇒ Object
Like #step, except that user interaction is ignored as if the user had just pressed the “enter” button.
- #stop_execution! ⇒ Object
- #timeout? ⇒ Boolean
- #trigger_button_press(which) ⇒ Object
- #update_variables(hash) ⇒ Object
-
#waiting_for_input? ⇒ Boolean
Returns true if user input is expected at this time.
Methods included from BuiltinVariables
add_variable, #declare_builtin_variables!
Constructor Details
#initialize(tml) ⇒ TmlApplication
accepts the raw TML document.
32 33 34 35 36 37 38 |
# File 'lib/rtml/test/tml_application.rb', line 32 def initialize(tml) @tml = Hpricot::XML(tml) @variable_scope = Rtml::Test::VariableScope.new @screenflow = [] @receipt = Receipt.new declare_variables! end |
Instance Attribute Details
#receipt ⇒ Object (readonly)
Returns the value of attribute receipt.
29 30 31 |
# File 'lib/rtml/test/tml_application.rb', line 29 def receipt @receipt end |
#screenflow ⇒ Object (readonly)
Returns the value of attribute screenflow.
29 30 31 |
# File 'lib/rtml/test/tml_application.rb', line 29 def screenflow @screenflow end |
#variable_scope ⇒ Object (readonly)
Returns the value of attribute variable_scope.
29 30 31 |
# File 'lib/rtml/test/tml_application.rb', line 29 def variable_scope @variable_scope end |
Instance Method Details
#conditions_match?(hash) ⇒ Boolean
Analyzes a hash as returned by #possible_variants and returns true if the conditions match the current application state (mostly involving the current value of TML variables). Conditions based on hotkeys return false since this implies user interaction, and conditions based on timeouts return true since this implies user interaction never took place.
218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/rtml/test/tml_application.rb', line 218 def conditions_match?(hash) if hash.key?(:hotkey) || hash.key?(:key) false elsif hash.key?(:timeout) true elsif hash.keys == [:uri] # a <next> element should always be true, but evaluated as a last resort true else variable_scope.true_condition?(hash) end end |
#continue(options = {}) ⇒ Object Also known as: process
Processes screens until a non-running state is achieved, and then returns that state.
If :breakpoint is specified, execution will be suspended at the specified screen and :break will be returned.
If :ignore_display is specified, then execution will continue past any <display> elements that would normally cause execution to halt. Normally, this only occurs if there is a timeout on the screen displaying the content, since a timeout implies automatic progression.
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/rtml/test/tml_application.rb', line 116 def continue( = {}) = { :breakpoint => } unless .kind_of?(Hash) breakpoint = .delete(:breakpoint) breakpoint = breakpoint.to_s if breakpoint && !breakpoint.kind_of?(String) ignore_display = .delete(:ignore_display) while (state = current_state) == :running || (ignore_display && state == :display) return :break if current_screen_id == breakpoint if ignore_display && state == :display step_forward else step end end return state end |
#continue_forward(options = {}) ⇒ Object
Forces execution to continue to the next screen, even if the current screen is waiting for something to happen.
134 135 136 |
# File 'lib/rtml/test/tml_application.rb', line 134 def continue_forward( = {}) continue() unless step_forward == :display end |
#current_screen ⇒ Object
48 49 50 51 52 53 54 |
# File 'lib/rtml/test/tml_application.rb', line 48 def current_screen return @current_screen if defined?(@current_screen) raise SimulationError, "No screens in current document" if screens.empty? @current_screen = Rtml::Test::Screen.new(screens.first) process_current_screen! @current_screen end |
#current_screen_id ⇒ Object
44 45 46 |
# File 'lib/rtml/test/tml_application.rb', line 44 def current_screen_id current_screen ? current_screen['id'] : nil end |
#current_state ⇒ Object
84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/rtml/test/tml_application.rb', line 84 def current_state if current_screen.nil? :stopped elsif waiting_for_input? :waiting elsif looping? :looping elsif && !timeout? :display else :running end end |
#declare_variable(name, options = {}) ⇒ Object
245 246 247 |
# File 'lib/rtml/test/tml_application.rb', line 245 def declare_variable(name, = {}) variable_scope.declare_variable(name, ) end |
#destinations(options = {}) ⇒ Object
Returns an array containing which of the possible screens following this one meet the current constraints. If there are no screens available, or if user intervention is required at this time, then an empty array is returned.
See also Rtml::Test::Screen#choices
options may include :ignore_interaction => true/false, defaults to false
199 200 201 202 203 |
# File 'lib/rtml/test/tml_application.rb', line 199 def destinations( = {}) return @destinations if @destinations return [] if !current_screen.choices.empty? && ![:ignore_interaction] @destinations = possible_variants.select { |dest| conditions_match?(dest) } end |
#displaying_content? ⇒ Boolean
102 103 104 |
# File 'lib/rtml/test/tml_application.rb', line 102 def current_screen.display? end |
#find_screen(id) ⇒ Object
186 187 188 189 190 |
# File 'lib/rtml/test/tml_application.rb', line 186 def find_screen(id) id = id.to_s unless id.kind_of?(String) id = id[1..-1] if id[0] == ?# (screen_element = screens.select { |s| s['id'] == id }.first) ? Rtml::Test::Screen.new(screen_element) : nil end |
#jump_to_screen(id) ⇒ Object
140 141 142 143 144 145 146 147 148 |
# File 'lib/rtml/test/tml_application.rb', line 140 def jump_to_screen(id) unmemoize! id = id.to_s unless id.kind_of?(String) if id[/^tmlvar:(.*)$/] id = variable_scope[$~[1]] end @current_screen = find_screen(id) || raise(Rtml::Errors::ApplicationError, "Tried to jump to missing screen #{id.inspect}") process_current_screen! end |
#jump_to_uri(path) ⇒ Object
If the path resembles a screen name that can be found within the current document, then #jump_to_screen is called. Otherwise, :new_document is thrown with this path as the argument.
This is mostly for internal processing, such as for hyperlinks.
(Does this belong in Rtml::Test::Simulator instead? Time will tell as that class is developed.)
178 179 180 181 182 183 184 |
# File 'lib/rtml/test/tml_application.rb', line 178 def jump_to_uri(path) if find_screen(path) jump_to_screen path else throw :new_document, path end end |
#looping? ⇒ Boolean
Returns true if this application seems to be in an endless loop.
254 255 256 |
# File 'lib/rtml/test/tml_application.rb', line 254 def looping? snapshot_matches_previous? end |
#next_screen_id(options = {}) ⇒ Object
Returns the first element returned by #destinations, or nil if an empty array would be returned.
options may include :ignore_interaction => true/false, defaults to false
209 210 211 |
# File 'lib/rtml/test/tml_application.rb', line 209 def next_screen_id( = {}) (nxt = destinations().first) ? nxt[:uri] : nil end |
#possible_variants ⇒ Object
Returns an array of Hashes representing all possible non-user interaction-requiring variants from this screen. These are returned regardless of whether user interation is required at this time.
The hashes are laid out thusly:
{ :uri => "destination_uri", :key => "hotkey", :timeout => "seconds", :lo => "left_operand",
:op => "operation", :ro => "right_operand" }
Note that some of these keys may be omitted, according to the TML rules for the variant
element.
For TML next
elements, all keys except :uri are omitted.
241 242 243 |
# File 'lib/rtml/test/tml_application.rb', line 241 def possible_variants current_screen.possible_variants end |
#screens ⇒ Object
40 41 42 |
# File 'lib/rtml/test/tml_application.rb', line 40 def screens @screens ||= ((@tml / "screen") || []) end |
#step ⇒ Object
Takes a single “step” in the execution of the program. This has no effect and returns :waiting if user input is required.
A step is taken even if the #current_state is :looping, so this can be used to force execution even if #continue normally wouldn’t proceed.
Returns the ID of the current screen as a string otherwise.
72 73 74 75 |
# File 'lib/rtml/test/tml_application.rb', line 72 def step return current_state if ![:running, :looping].include?(current_state) perform_step! end |
#step_forward ⇒ Object
Like #step, except that user interaction is ignored as if the user had just pressed the “enter” button.
79 80 81 82 |
# File 'lib/rtml/test/tml_application.rb', line 79 def step_forward return current_state if ![:running, :looping, :waiting, :display].include?(current_state) perform_step!(:ignore_interaction => true) end |
#stop_execution! ⇒ Object
56 57 58 |
# File 'lib/rtml/test/tml_application.rb', line 56 def stop_execution! @current_screen = nil end |
#timeout? ⇒ Boolean
98 99 100 |
# File 'lib/rtml/test/tml_application.rb', line 98 def timeout? current_screen.timeout > 0 end |
#trigger_button_press(which) ⇒ Object
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/rtml/test/tml_application.rb', line 150 def (which) assert_screen_present which = which.to_s unless which.kind_of?(String) which.downcase! which = 'enter' if which == 'ok' if (uri = current_screen.uri_for_hotkey(which)) jump_to_uri uri elsif which == 'enter' # a special case: it makes the terminal continue forward under most conditions. if uri = current_screen.autoselect_hyperlink visit uri else continue_forward end elsif (defaults = ((@tml / "defaults") || []).first) && %w(cancel menu).include?(which) jump_to_uri defaults[which] else raise Rtml::Errors::ApplicationError, "Keypress has no effect on screen #{current_screen_id.inspect}" end end |
#update_variables(hash) ⇒ Object
249 250 251 |
# File 'lib/rtml/test/tml_application.rb', line 249 def update_variables(hash) variable_scope.update_with(hash) end |
#waiting_for_input? ⇒ Boolean
Returns true if user input is expected at this time. If true, program flow will not proceed.
61 62 63 |
# File 'lib/rtml/test/tml_application.rb', line 61 def waiting_for_input? current_screen.input? end |