graffle 0.1.9 → 0.2.0

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