graffle 0.1.9 → 0.2.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 (42) hide show
  1. data/History.txt +9 -0
  2. data/Manifest.txt +15 -8
  3. data/Rakefile.hoe +1 -2
  4. data/bin/interpret-intentions.rb +51 -0
  5. data/examples/objects with notes.rb +1 -0
  6. data/examples/old-style-rails-workflow-test.expected +11 -0
  7. data/examples/old-style-rails-workflow-test.rb +43 -0
  8. data/examples/rails-workflow-test.rb +7 -6
  9. data/examples/sheet with key.expected +5 -0
  10. data/examples/sheet with key.graffle +0 -0
  11. data/examples/sheet with key.rb +38 -0
  12. data/graffle.tmproj +242 -75
  13. data/lib/graffle.rb +11 -2
  14. data/lib/graffle/nodoc/hacks.rb +1 -0
  15. data/lib/graffle/point.rb +1 -0
  16. data/lib/graffle/stereotypes.rb +34 -3
  17. data/lib/graffle/version.rb +3 -1
  18. data/lib/graphical_tests_for_rails.rb +11 -9
  19. data/lib/graphical_tests_for_rails/jumbled-string-canonicalizer.rb +117 -0
  20. data/lib/graphical_tests_for_rails/orderings.rb +1 -2
  21. data/lib/graphical_tests_for_rails/picture-applier.rb +257 -0
  22. data/lib/graphical_tests_for_rails/stereotype-extensions.rb +111 -0
  23. data/lib/graphical_tests_for_rails/text-appliers.rb +10 -84
  24. data/lib/graphical_tests_for_rails/user-intention.rb +250 -0
  25. data/test/abstract-graphic-tests.rb +10 -0
  26. data/test/container-tests.rb +29 -0
  27. data/test/graphical_tests_for_rails/jumbled-string-canonicalizer-tests.rb +174 -0
  28. data/test/graphical_tests_for_rails/new-style-picture-applier-tests.rb +286 -0
  29. data/test/graphical_tests_for_rails/old-style-picture-applier-tests.rb +129 -0
  30. data/test/graphical_tests_for_rails/user-intention-tests.rb +389 -0
  31. data/test/graphical_tests_for_rails/util.rb +9 -0
  32. data/test/sheet-tests.rb +21 -0
  33. data/test/text-tests.rb +6 -0
  34. metadata +25 -26
  35. data/design-notes/graphical-tests-for-rails-objects.graffle +0 -644
  36. data/lib/graphical_tests_for_rails/graphic-volunteers.rb +0 -75
  37. data/lib/graphical_tests_for_rails/picture-appliers.rb +0 -225
  38. data/lib/graphical_tests_for_rails/volunteer-pool.rb +0 -115
  39. data/test/graphical_tests_for_rails/deprecated-graphic-interpreter-tests.rb +0 -121
  40. data/test/graphical_tests_for_rails/graphic-volunteer-tests.rb +0 -218
  41. data/test/graphical_tests_for_rails/picture-applier-tests.rb +0 -215
  42. data/test/graphical_tests_for_rails/text-applier-tests.rb +0 -111
@@ -1,75 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # Created by Brian Marick on 2007-07-20.
4
- # Copyright (c) 2007. All rights reserved.
5
-
6
- require 'enumerator'
7
- require 'test/unit/assertionfailederror'
8
- require 'graphical_tests_for_rails/text-appliers'
9
-
10
- module GraphicalTestsForRails
11
-
12
- class AbstractGraphicVolunteer # :nodoc:
13
-
14
- attr_accessor :text_applier
15
-
16
- def initialize(name)
17
- @name = name
18
- @matcher = @lines_extractor = @text_applier = nil
19
- end
20
-
21
- def do_nothing_defaults
22
- @matcher = proc { | object | true }
23
- @lines_extractor = proc { |object| [] }
24
- @text_applier = TextAppliers::TextApplierThatDoesNothing.new
25
- end
26
-
27
- def likes?(object);
28
- @matcher.call(object)
29
- end
30
-
31
- def extract_lines(object)
32
- @lines_extractor.call(object)
33
- end
34
-
35
- def apply(graphic, target)
36
- @text_applier.apply(extract_lines(graphic), target)
37
- end
38
-
39
- end
40
-
41
- class EagerButUselessVolunteer < AbstractGraphicVolunteer # :nodoc:
42
- def initialize
43
- super('do-nothing graphic applier')
44
- do_nothing_defaults
45
- end
46
- end
47
-
48
-
49
- # A GraphicVolunteer knows how to match and apply one particular
50
- # kind of graphic (such as 'line label content'). These are normally
51
- # invisible to users. See design-notes/graphical-tests-for-rails-objects.graffle
52
- # for a picture of class collaborations.
53
- class GraphicVolunteer < AbstractGraphicVolunteer
54
- attr_writer :next
55
-
56
- def initialize(name);
57
- super(name)
58
- do_nothing_defaults
59
- @next = EagerButUselessVolunteer.new
60
- end
61
-
62
- def handles(&block); @matcher = block; self; end
63
- def and_extracts(&block); @lines_extractor = block; self; end
64
-
65
- def likes?(object)
66
- if super(object)
67
- next_object = @lines_extractor.call(object)
68
- @next.likes?(next_object)
69
- end
70
- end
71
- end
72
-
73
-
74
-
75
- end
@@ -1,225 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # Created by Brian Marick on 2007-07-21.
4
- # Copyright (c) 2007. All rights reserved.
5
-
6
- require 'enumerator'
7
- require 'test/unit/assertionfailederror'
8
- require 'fluid'
9
- require 'graphical_tests_for_rails/text-appliers'
10
- require 'graphical_tests_for_rails/volunteer-pool'
11
-
12
-
13
- module GraphicalTestsForRails
14
-
15
- # A PictureApplier takes an array of graphics and arranges for
16
- # messages derived from them to be sent to a _target_. PictureAppliers
17
- # are "programmed" with other objects that handle the details of
18
- # transforming graphics and the text they contain into messages.
19
- #
20
- # The
21
- # graphics are processed in order. GraphicOrderer objects should be
22
- # used to arrange the array before it's given to PictureApplier.
23
- class PictureApplier
24
- include Graffle
25
- include GraphicalTestsForRails::TextAppliers
26
-
27
- # The block tells the PictureApplier what to do by sending given,
28
- # matching, use, and skip messages. That looks like this:
29
- #
30
- # PictureApplier.new {
31
- # given('note').matching('ignore').skip
32
- # given('shaped graphic content').use(ArgsFromQuotedText.new)
33
- # }
34
- #
35
- # Each declaration sees if it can match a graphical object. If so,
36
- # the last action is performed. Declarations are processed in
37
- # order, so one match can be given precedence. Consider this:
38
- #
39
- # PictureApplier.new {
40
- # given('note').matching('ignore').skip
41
- # given('shaped graphic content').use(ArgsFromQuotedText.new)
42
- # }
43
- #
44
- # This means that shaped graphic objects irrelevant to the test
45
- # can be ignored by attaching the appropriate annotation.
46
-
47
- def initialize(&block)
48
- @universe = GraphicVolunteerPool.new
49
- @mobilized = []
50
- instance_eval(&block) if block
51
- @mobilized << EagerButUselessVolunteer.new
52
- end
53
-
54
- # Claim that the PictureApplier matches graphics matching
55
- # the _form_description_, a string. Here are the form descriptions
56
- # currently supported:
57
- #
58
- # * 'shaped graphic content': A shaped graphic is a rectangle, oval,
59
- # embedded picture, etc.: anything that is neither a line nor a
60
- # group. The content is the text you type in when you double-click
61
- # on the object. When given the form description, the PictureApplier
62
- # will match any shaped graphic that contains text.
63
- # * 'line label content': This matches any line that has a label.
64
- # (Note: lines can have more than one label. That's not handled.)
65
- # * 'note': Matches any object that contains an annotation. (Annotations are
66
- # only available in OmniGraffle Pro. They're arbitrary text
67
- # associated with a line or shaped graphic. You can see the
68
- # annotations if you hover over the object.)
69
- #
70
- # The _form_description_ describes the first of two matching steps.
71
- # If it matches, it extracts the appropriate text (content or
72
- # annotation) and hands it to the next step, described by given.
73
- #
74
- # This method returns self so that other methods can be chained onto it.
75
- def given(form_description)
76
- @mobilized << @universe.mobilize_form_watcher(form_description)
77
- self
78
- end
79
-
80
- # Claim that the PictureApplier matches particular text extracted from
81
- # a graphical object. These _text_descriptions_ are currently supported:
82
- #
83
- # * 'text...': match if the text ends in ellipses (ignoring whitespace).
84
- # * 'ignore': match if the text ends in the word "ignore" (ignoring whitespace).
85
- #
86
- # This method returns self so that other methods can be chained onto it.
87
- def matching(text_description)
88
- @mobilized.last.next = @universe.mobilize_content_watcher(text_description)
89
- self
90
- end
91
-
92
- # When a PictureApplier matches an object, the extracted text is
93
- # passed to the _text_applier_ to be turned into messages.
94
- def use(text_applier)
95
- @mobilized.last.text_applier = text_applier
96
- end
97
-
98
- # After a PictureApplier matches a graphical object, it's ignored.
99
- # This is used to prevent text associated with the object from being
100
- # turned into messages.
101
- def skip
102
- use(TextApplierThatDoesNothing.new)
103
- end
104
-
105
- # After the PictureApplier has been programmed, this method
106
- # applies it to the _graphics_, resulting in methods that are sent
107
- # to the _target_.
108
- #
109
- # If there's a problem, a log of what was sent to the _target_ is
110
- # included in the exception message. The _log_ can be started out with
111
- # the third argument, in which case new entries are appended. This can
112
- # be useful when a single test is made up of several graphics lists.
113
- def apply(graphics, target, log=[])
114
- while_logging_for_test_errors(log) do
115
- graphics.each { |g| find_volunteer_for(g).apply(g, target) }
116
- end
117
- end
118
-
119
- # Given a _graphic_ find a GraphicVolunteer that can apply it.
120
- # (Probably not for external use.)
121
- def find_volunteer_for(graphic)
122
- @mobilized.find { | m | m.likes?(graphic) }
123
- end
124
-
125
- private
126
-
127
- def while_logging_for_test_errors(log)
128
- Fluid.let(:log, log) do
129
- begin
130
- yield
131
- rescue Test::Unit::AssertionFailedError, StandardError, ScriptError => e
132
- new_message = %Q{#{e.message} after\n#{Fluid.log.join("\n")}}
133
- new_e = e.exception(new_message)
134
- new_e.set_backtrace(e.backtrace)
135
- raise new_e
136
- end
137
- end
138
- end
139
- end
140
-
141
- # This class is deprecated. See PictureApplier.
142
- #
143
- # A GraphicInterpreter takes a list of _graphics_, some
144
- # instructions about how to interpret them, and a _target_.
145
- # It interprets the instructions to create messages that it then
146
- # sends to the target.
147
- #
148
- class GraphicInterpreter
149
-
150
- # The _graphics_ are an array of OmniGraffle Graffle::AbstractGraphic objects. The
151
- # _instructions_ map a string to an object that must respond to
152
- # message_and_args_from. (See AbstractTextApplier#message_and_args_from
153
- # for more.)
154
- #
155
- # Here are the available instructions (keys in _instructions_):
156
- #
157
- # * ['line labels' => ...]: each line within the (single)
158
- # label on the line is to be interpreted as a message-send by an
159
- # instance of the given class.
160
- # * ['shaped graphic text' => ...]: each line of content
161
- # within the graphic is to be interpreted by the given class. The
162
- # content is the text you type in an object after double-clicking
163
- # on it.
164
- # * ['text...' => ...]: a line ending in ellipses is treated this way,
165
- # no matter what other instructions say.
166
- #
167
- def initialize(graphics, instructions = {})
168
- puts "Warning: #{self.class} is deprecated. Use PictureApplier instead."
169
- @graphics = graphics
170
- @line_label_strategy =
171
- instructions['line labels']
172
- @shaped_graphic_text_strategy =
173
- instructions['shaped graphic text']
174
-
175
- @ellipses_override = instructions['text...']
176
- end
177
-
178
- # Apply the _graphics_, according to the _instructions_, against
179
- # the _target_.
180
- def run_against(target)
181
- @target = target
182
- @log = []
183
- @graphics.each do |g|
184
- case g.stereotype.basename
185
- when "ShapedGraphic"
186
- process_commands(@shaped_graphic_text_strategy,
187
- g.content.as_lines)
188
- when "LineGraphic"
189
- process_commands(@line_label_strategy,
190
- g.label_lines)
191
- end
192
- end
193
- end
194
-
195
- private
196
-
197
- def process_commands(text_applier, command_lines)
198
- return if text_applier.nil?
199
- command_lines.each do |line|
200
- info = if /\.\.\.\s*$/ =~ line && @ellipses_override
201
- @ellipses_override.message_and_args_from(line)
202
- else
203
- text_applier.message_and_args_from(line)
204
- end
205
- begin
206
- @log << readable(*info)
207
- @target.send(*info)
208
- rescue Test::Unit::AssertionFailedError, StandardError, ScriptError => e
209
- new_message = %Q{#{e.message} after\n#{@log.join("\n")}}
210
- new_e = e.exception(new_message)
211
- new_e.set_backtrace(e.backtrace)
212
- raise new_e
213
- end
214
- end
215
- end
216
-
217
- def readable(message, *args)
218
- inspected = args.collect { |a| a.inspect }
219
- "+ #{message}(#{inspected.join(', ')})"
220
- end
221
-
222
- end
223
-
224
- end
225
-
@@ -1,115 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # Created by Brian Marick on 2007-07-21.
4
- # Copyright (c) 2007. All rights reserved.
5
-
6
- require 'graffle'
7
- require 'graphical_tests_for_rails/graphic-volunteers'
8
-
9
- # TODO: Turn these all into one class.
10
- module GraphicalTestsForRails
11
-
12
- # A GraphicVolunteerPool describes the ways a PictureApplier
13
- # can by programmed with PictureApplier#given statements.
14
- # You only care about this if you want to add new ways to use
15
- # Graffle pictures in tests.
16
-
17
- class GraphicVolunteerPool
18
- def initialize
19
- @graphic_maker = FormFocusedTemplate.new
20
- @text_maker = ContentFocusedTemplate.new
21
- end
22
-
23
- def mobilize_form_watcher(description)
24
- @graphic_maker.make_applier(description)
25
- end
26
-
27
- def mobilize_content_watcher(description)
28
- @text_maker.make_applier(description)
29
- end
30
-
31
- end
32
-
33
- # There are two distinct kinds of Templates only so they
34
- # can produce different error messages.
35
- class EmptyTemplate # :nodoc:
36
- include Graffle
37
-
38
- def initialize
39
- @templates = {}
40
- end
41
-
42
- def proclaim(name)
43
- prog1(GraphicVolunteer.new(name)) { | t | @templates[name] = t }
44
- end
45
-
46
- def make_applier(name)
47
- t = @templates[name]
48
- user_claims(t) {
49
- "#{name} must be one of #{@templates.keys.inspect}"
50
- }
51
- t.dup
52
- end
53
- end
54
-
55
- class FormFocusedTemplate < EmptyTemplate # :nodoc:
56
- def initialize
57
- super
58
-
59
- proclaim('shaped graphic content').handles { | graphic |
60
- shaped_graphic_with_content?(graphic)
61
- }.and_extracts { | graphic |
62
- graphic.content.as_lines
63
- }
64
-
65
- proclaim('line label content').handles { | graphic |
66
- line_graphic_with_label_content?(graphic)
67
- }.and_extracts { | graphic |
68
- graphic.label_lines
69
- }
70
-
71
- proclaim('note').handles { | graphic |
72
- graphic_with_note?(graphic)
73
- }.and_extracts { | graphic |
74
- graphic_note(graphic)
75
- }
76
- end
77
-
78
- private
79
- def labeled_line?(graphic)
80
- return false unless graphic.behaves_like?(LineGraphic)
81
- graphic.has_label?
82
- end
83
- def shaped_graphic_with_content?(graphic)
84
- graphic.behaves_like?(ShapedGraphic) && graphic.has_content?
85
- end
86
-
87
- def line_graphic_with_label_content?(graphic)
88
- labeled_line?(graphic) && graphic.label.has_content?
89
- end
90
-
91
- def graphic_with_note?(graphic)
92
- return true if graphic.behaves_like?(AbstractGraphic) && graphic.has_note?
93
- labeled_line?(graphic) && graphic.label.has_note?
94
- end
95
-
96
- def graphic_note(graphic)
97
- return graphic.note.as_lines if graphic.has_note?
98
- return graphic.label.note.as_lines
99
- end
100
- end
101
-
102
- class ContentFocusedTemplate < EmptyTemplate # :nodoc:
103
- def initialize
104
- super
105
-
106
- proclaim('text...').handles { | lines |
107
- lines.join.strip =~ /\.\.+$/
108
- }
109
-
110
- proclaim('ignore').handles do | lines |
111
- lines.any? { |l| l.strip.ends_with?("ignore") }
112
- end
113
- end
114
- end
115
- end
@@ -1,121 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # Created by Brian Marick on 2007-07-10.
4
- # Copyright (c) 2007. All rights reserved.
5
-
6
- require 'test/unit'
7
- require 's4t-utils'
8
- include S4tUtils
9
-
10
- require "../set-standalone-test-paths.rb" unless $started_from_rakefile
11
-
12
- require 'extensions/string'
13
- require 'graphical_tests_for_rails'
14
- require 'test/graphical_tests_for_rails/util'
15
-
16
-
17
- class GraphicInterpreterTest < Test::Unit::TestCase
18
- include Graffle::Builders
19
- include GraphicalTestsForRails
20
-
21
- def two_graphics
22
- s = sheet {
23
- with line_label {
24
- for_line 1
25
- with_content 'produce "argument"'
26
- }
27
- with line_graphic {
28
- graffle_id_is 1
29
- }
30
- with shaped_graphic {
31
- graffle_id_is 2
32
- with_content "SIGNUP"
33
- }
34
- }
35
-
36
- [s.find_by_id(1), s.find_by_id(2)]
37
- end
38
-
39
- def assert_commands_produced(expected, interpreter)
40
- recorder = CommandRecorder.new
41
- interpreter.run_against(recorder)
42
- assert_equal(expected, recorder.record)
43
- end
44
-
45
- def test_interpreter_applies_strategies
46
- interpreter = GraphicInterpreter.new(two_graphics,
47
- 'line labels' => ArgsFromQuotedText.new,
48
- 'shaped graphic text' => TextAsNameOfPage.new('assert_on_page'))
49
- assert_commands_produced(['produce("argument")', 'assert_on_page("signup")'],
50
- interpreter)
51
- end
52
-
53
-
54
- def test_interpreter_can_omit_strategies
55
- interpreter = GraphicInterpreter.new(two_graphics)
56
- assert_commands_produced([], interpreter)
57
- end
58
-
59
- def assert_that_fails(page_name)
60
- assert_equal('something that does not match', page_name)
61
- end
62
-
63
- def test_interpreter_annotates_test_failures_with_log
64
- interpreter = GraphicInterpreter.new(two_graphics,
65
- 'shaped graphic text' => TextAsNameOfPage.new('assert_that_fails'))
66
- interpreter.run_against(self)
67
- flunk("Should be unreached.")
68
- rescue Test::Unit::AssertionFailedError => e
69
- assert_match(/\+ assert_that_fails\("signup"\)/, e.message)
70
- end
71
-
72
- def test_interpreter_annotates_test_errors_with_log
73
- interpreter = GraphicInterpreter.new(two_graphics,
74
- 'line labels' => ArgsFromQuotedText.new) # will generate nonexistent method
75
- interpreter.run_against(self)
76
- flunk("Should be unreached.")
77
- rescue Exception => e
78
- assert_match(/\+ produce\("argument"\)/, e.message)
79
- end
80
-
81
- def four_graphics_including_ellipses
82
- s = sheet {
83
- with line_label {
84
- for_line 1
85
- with_content 'label'
86
- }
87
- with line_graphic {
88
- graffle_id_is 1
89
- }
90
- with shaped_graphic {
91
- graffle_id_is 2
92
- with_content "signup "
93
- }
94
- with line_label {
95
- for_line 11
96
- with_content 'waiting on a "friend"... '
97
- }
98
- with line_graphic {
99
- graffle_id_is 11
100
- }
101
- with shaped_graphic {
102
- graffle_id_is 22
103
- with_content "time passes..."
104
- }
105
- }
106
-
107
- [1, 2, 11, 22].collect { |id| s.find_by_id(id) }
108
- end
109
-
110
- def test_ellipses_strategy_overrides_others
111
- interpreter = GraphicInterpreter.new(four_graphics_including_ellipses,
112
- 'line labels' => TextAsNameOfPage.new("line_here"),
113
- 'shaped graphic text' => TextAsNameOfPage.new('shape_here'),
114
- 'text...' => ArgsFromQuotedText.new)
115
- assert_commands_produced(['line_here("label")', 'shape_here("signup")',
116
- 'waiting_on_a("friend")', 'time_passes()'],
117
- interpreter)
118
- end
119
-
120
-
121
- end