graffle 0.1.8 → 0.1.9

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 (44) hide show
  1. data/History.txt +5 -0
  2. data/Manifest.txt +22 -11
  3. data/README.txt +2 -2
  4. data/Rakefile.hoe +2 -1
  5. data/design-notes/graphical-tests-for-rails-objects.graffle +644 -0
  6. data/examples/objects with notes.expected +5 -0
  7. data/examples/objects with notes.graffle +338 -0
  8. data/examples/objects with notes.rb +42 -0
  9. data/examples/rails-workflow-test.expected +1 -1
  10. data/examples/rails-workflow-test.graffle/data.plist +86 -7
  11. data/examples/rails-workflow-test.rb +11 -9
  12. data/graffle.tmproj +82 -190
  13. data/lib/graffle.rb +19 -2
  14. data/lib/graffle/point.rb +1 -2
  15. data/lib/graffle/stereotypes.rb +62 -16
  16. data/lib/graffle/version.rb +1 -1
  17. data/lib/graphical_tests_for_rails.rb +8 -5
  18. data/lib/graphical_tests_for_rails/graphic-volunteers.rb +75 -0
  19. data/lib/graphical_tests_for_rails/orderings.rb +90 -84
  20. data/lib/graphical_tests_for_rails/picture-appliers.rb +225 -0
  21. data/lib/graphical_tests_for_rails/text-appliers.rb +135 -0
  22. data/lib/graphical_tests_for_rails/volunteer-pool.rb +115 -0
  23. data/test/abstract-graphic-tests.rb +48 -0
  24. data/test/document-tests.rb +5 -5
  25. data/test/examples-tests.rb +42 -0
  26. data/test/graphical_tests_for_rails/{graphic-interpreter-tests.rb → deprecated-graphic-interpreter-tests.rb} +11 -21
  27. data/test/graphical_tests_for_rails/graphic-volunteer-tests.rb +218 -0
  28. data/test/graphical_tests_for_rails/in-workflow-order-tests.rb +1 -1
  29. data/test/graphical_tests_for_rails/picture-applier-tests.rb +215 -0
  30. data/test/graphical_tests_for_rails/{text-interpreter-tests.rb → text-applier-tests.rb} +17 -3
  31. data/test/graphical_tests_for_rails/util.rb +16 -0
  32. data/test/line-graphic-tests.rb +9 -1
  33. data/test/note-tests.rb +62 -0
  34. data/test/{graffle-file-types → sample-files}/as-a-package.graffle/data.plist +0 -0
  35. data/test/{graffle-file-types → sample-files}/as-a-package.graffle/image1.png +0 -0
  36. data/test/{graffle-file-types → sample-files}/as-a-package.graffle/image2.png +0 -0
  37. data/test/{graffle-file-types → sample-files}/as-a-package.graffle/image3.png +0 -0
  38. data/test/{graffle-file-types → sample-files}/multiple-canvases.graffle +0 -0
  39. data/test/{graffle-file-types → sample-files}/opening-tests.rb +9 -4
  40. data/test/{graffle-file-types → sample-files}/two-boxes-and-a-line.graffle +0 -0
  41. data/test/shaped-graphic-tests.rb +2 -3
  42. metadata +42 -18
  43. data/lib/graphical_tests_for_rails/interpreters.rb +0 -147
  44. data/test/tests-of-examples/workflow-slowtests.rb +0 -19
data/lib/graffle.rb CHANGED
@@ -19,17 +19,34 @@ require 'graffle/point'
19
19
  module Graffle
20
20
 
21
21
  # Parse the given data and stereotype the result. Returns an object
22
- # stereotyped as Graffle::Document.
22
+ # stereotyped as Graffle::Document. Deprecated in favor of
23
+ # Parse.parse_file and Parse.parse_xml.
24
+
23
25
  def self.parse(filename_or_xml)
26
+ puts "Graffle.parse is deprecated in favor of Graffle.parse_file or Graffle.parse_xml."
24
27
  if File.directory?(filename_or_xml)
25
28
  filename_or_xml = File.join(filename_or_xml, "data.plist")
26
29
  end
27
-
30
+
28
31
  prog1(Plist.parse_xml(filename_or_xml)) do | doc |
29
32
  Document.takes_on(doc)
30
33
  end
31
34
  end
32
35
 
36
+ def self.parse_file(filename)
37
+ if File.directory?(filename)
38
+ filename = File.join(filename, "data.plist")
39
+ end
40
+
41
+ parse_xml(File.read(filename))
42
+ end
43
+
44
+ def self.parse_xml(content)
45
+ prog1(Plist.parse_xml(content)) do | doc |
46
+ Document.takes_on(doc)
47
+ end
48
+ end
49
+
33
50
  def self.stereotype(o) # :nodoc:
34
51
  return true if ShapedGraphic.takes_on(o)
35
52
  return true if LineGraphic.takes_on(o)
data/lib/graffle/point.rb CHANGED
@@ -22,8 +22,7 @@ module Graffle
22
22
  elsif args[0] =~ /\{(.*),\s(.*)\}/
23
23
  initialize(Float($1), Float($2))
24
24
  else
25
- # TODO: this should be user_is_bewildered(msg = "how could this point be reached?")
26
- user_disputes("this point can be reached") { "unreachable?"}
25
+ user_is_bewildered
27
26
  end
28
27
  end
29
28
 
@@ -6,16 +6,16 @@
6
6
  module Graffle
7
7
  module Builders # :nodoc:
8
8
  def self.raw(mod, basic_structure = {}, &block)
9
- g = basic_structure.dup
10
- g.behave_like(mod)
11
- g.instance_eval(&block) if block
12
- g
9
+ prog1(basic_structure.dup) do | g |
10
+ g.behave_like(mod)
11
+ g.instance_eval(&block) if block
12
+ end
13
13
  end
14
14
 
15
15
  def self.classed(mod, basic_structure = {}, &block)
16
- g = raw(mod, basic_structure, &block)
17
- g['Class'] = mod.basename
18
- g
16
+ prog1(raw(mod, basic_structure, &block)) do | g |
17
+ g['Class'] = mod.basename
18
+ end
19
19
  end
20
20
 
21
21
  def abstract_graphic(&block)
@@ -57,21 +57,28 @@ module Graffle
57
57
  }
58
58
  end
59
59
 
60
+ def annotation(string)
61
+ prog1(string) { | s | s.behave_like(Graffle::Note) }
62
+ end
63
+
60
64
  end
61
65
 
62
66
 
63
- # TODO: rename AbstractGraphic Nestable
67
+ # TODO: Can groups have notes of their own? Putting notes behavior
68
+ # in AbstractGraphic assumes they can.
64
69
 
65
70
  # Behavior that's common to all visible objects, be they lines,
66
71
  # rectangles, text objects, etc.
67
72
  module AbstractGraphic
68
73
  include Comparable
69
-
74
+ include Builders
75
+
70
76
  # TODO: I'm not wild about the way this both checks if something's
71
77
  # possible and also does it. Separate in caller?
72
78
  def self.takes_on(o, mod) # :nodoc:
73
79
  if graffle_class_matches?(o, mod)
74
80
  o.behave_like(mod)
81
+ o.notes.behave_like(Note) if o.has_notes?
75
82
  return true
76
83
  end
77
84
  end
@@ -103,6 +110,28 @@ module Graffle
103
110
  # A Point. Must be defined in the whatever includes this module.
104
111
  def origin; includer_responsibility; end
105
112
 
113
+ # OmniGraffle Pro objects can have _notes_ attached to them.
114
+ # A note is just an RTF string. If there is no note, the return
115
+ # value is a null object that responds to as_lines with an empty
116
+ # array.
117
+ def notes
118
+ if has_key?('Notes')
119
+ self['Notes']
120
+ else
121
+ Text::Null.new
122
+ end
123
+ end
124
+ alias_method :note, :notes
125
+
126
+ def with_notes(string) # :nodoc:
127
+ self['Notes'] = annotation(string)
128
+ end
129
+ alias_method :with_note, :with_notes
130
+
131
+ # Does this object contain a note? (Notes are an OmniGraffle
132
+ # Pro feature)
133
+ def has_note?; self.has_key?('Notes'); end
134
+ alias_method :has_notes?, :has_note?
106
135
 
107
136
 
108
137
  end
@@ -144,8 +173,8 @@ module Graffle
144
173
 
145
174
  # If this is a graffle document with an implicit single
146
175
  # sheet, make it explicit.
147
- # TODO: Check that OmniGraffle non-pro can read such a doc. Probably
148
- # more needs to go along with the GraphicsList.
176
+ # MISSING: This destroys the structure of the file, so it
177
+ # should not now be written.
149
178
  def make_sure_has_sheets # :nodoc:
150
179
  splice_in_single_sheet unless self.has_key?('Sheets')
151
180
  self
@@ -237,7 +266,7 @@ module Graffle
237
266
  end
238
267
  alias_method :contents, :content
239
268
 
240
- def with_text(string) # :nodoc:
269
+ def with_content(string) # :nodoc:
241
270
  self['Text'] = text(string)
242
271
  end
243
272
  end
@@ -274,6 +303,11 @@ module Graffle
274
303
  end
275
304
  end
276
305
 
306
+ # true if the line has a label. false otherwise.
307
+ def has_label?
308
+ not (not label) # Here is a gesture in honor of Scheme.
309
+ end
310
+
277
311
  # Almost certainly, what you care about is the Text of the label, which
278
312
  # you could get like this:
279
313
  # line.label.content
@@ -323,7 +357,7 @@ module Graffle
323
357
  container.find_by_id(endpoint['ID'])
324
358
  end
325
359
 
326
- # TODO: note this is no good for updating, since fields other than
360
+ # MISSING: note this is no good for updating, since fields other than
327
361
  # ID may be destroyed.
328
362
  def _link__bemhack(type, thing)
329
363
  id = thing.respond_to?(:graffle_id) ? thing.graffle_id : thing
@@ -335,7 +369,7 @@ module Graffle
335
369
  # Behavior common to Sheet and Group, both of which contain an array
336
370
  # of AbstractGraphic.
337
371
  # :stopdoc:
338
- # TODO: perhaps some cleverness with Enumerator would be in order?
372
+ # MISSING: perhaps some cleverness with Enumerator would be in order?
339
373
  # :startdoc:
340
374
  module Container
341
375
 
@@ -368,7 +402,8 @@ module Graffle
368
402
  if Graffle.stereotype(child)
369
403
  child.container = parent
370
404
  else
371
- puts "TODO: can not yet stereotype child with id #{child['ID'].inspect} and class #{child['Class'].inspect}."
405
+ puts "Can not yet stereotype child with id #{child['ID'].inspect} and class #{child['Class'].inspect}."
406
+ puts "Most likely there's been no call for it yet. So call for it."
372
407
  pp child.keys
373
408
  end
374
409
  end
@@ -460,11 +495,22 @@ module Graffle
460
495
  StyledTextReader.new(self.as_rtf).as_lines
461
496
  end
462
497
 
463
- class Null # Null object for text
498
+ # Null object for text
499
+ class Null # :nodoc:
464
500
  def as_lines; []; end
465
501
  end
466
502
 
467
503
  end
468
504
 
505
+ # Access to the RTF in an object's note.
506
+ module Note
507
+ include Text
508
+ # The RTF itself.
509
+ def as_rtf
510
+ self
511
+ end
512
+
513
+ end
514
+
469
515
 
470
516
  end
@@ -4,5 +4,5 @@
4
4
  # Copyright (c) 2007. All rights reserved.
5
5
 
6
6
  module Graffle
7
- Version = '0.1.8'
7
+ Version = '0.1.9'
8
8
  end
@@ -4,22 +4,25 @@
4
4
  # Copyright (c) 2007. All rights reserved.
5
5
 
6
6
 
7
-
8
7
  require 'graffle'
9
- require 'graphical_tests_for_rails/interpreters'
10
8
  require 'graphical_tests_for_rails/orderings'
9
+ require 'graphical_tests_for_rails/text-appliers'
10
+ require 'graphical_tests_for_rails/picture-appliers'
11
+ require 'graphical_tests_for_rails/graphic-volunteers'
12
+ require 'graphical_tests_for_rails/volunteer-pool'
11
13
 
12
14
  # Classes within this module help you build up Rails integration tests
13
15
  # from OmniGraffle files. The important classes are these:
14
16
  #
15
17
  # * GraphicInterpreter: These objects are 'programmed' with
16
- # AbstractMessageMakers who know how to convert lines of text
18
+ # AbstractTextApplier objects who know how to convert lines of text
17
19
  # into messages and arguments to send to a _target_.
18
20
  # * GraphicOrderer: These objects take a list of graphics in no
19
21
  # particular order and put it in the right order for a particular
20
- # style of test. For example, a WorkflowOrder represents an
22
+ # style of test. For example, an InWorkflowOrder represents an
21
23
  # order created by tracing through shapes connected by lines.
22
24
 
23
25
  module GraphicalTestsForRails
24
-
26
+ include GraphicArrayOrderings
27
+ include TextAppliers
25
28
  end
@@ -0,0 +1,75 @@
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
@@ -5,97 +5,103 @@
5
5
 
6
6
  require 'graffle'
7
7
 
8
+
8
9
  module GraphicalTestsForRails
9
-
10
- # A graphic order takes a Sheet in its constructor. The graphics
11
- # method returns an array of AbstractGraphic objects that represents
12
- # an ordered sequence of parts of a test.
13
- #
14
- # This abstract class only exists so there's a place to hang this comment.
15
- class GraphicOrderer
16
- def graphics; subclass_responsibility; end
17
- end
18
-
19
- # (The following is much more obvious if you look at a sample document.)
20
- # TODO: make an examples directory.
21
- #
22
- # The sheet contains one or more _rivulets_ of ShapedGraphic objects connected
23
- # with LineGraphic objects. All together, they're supposed to represent
24
- # a user workflow. (The gap between one rivulet and the next typically
25
- # means that the user has gone away from the app, then returned.)
26
- #
27
- # graphics produces a concatenated array of the
28
- # rivulets (both the ShapedGraphic and LineGraphic objects).
29
- #
30
- # Suppose there's more than one rivulet on the Sheet. graphics produces all
31
- # the elements of the first, followed by all the elements of the second,
32
- # etc. The first rivulet is the one _headed_ by the highest object on the
33
- # page. After all of its elements have been placed in the graphics array,
34
- # the next rivulet is headed by the highest remaining element on the Sheet.
35
- #
36
- # Within a single rivulet, location is irrelevant. All that matters is
37
- # where lines start and end.
38
- #
39
- # Notice that the head of the second rivulet may be higher than some
40
- # element of the first.
41
- #
42
- # A rivulet may have a single element.
43
-
44
- class InWorkflowOrder < GraphicOrderer
45
- include Graffle
46
-
47
- def initialize(sheet)
48
- @sheet = sheet
10
+
11
+ # Just a wrapper module so that classes are grouped nicely in rdoc.
12
+ # You don't have to include this - that happens if you require
13
+ # graphical_tests_for_rails.rb.
14
+ module GraphicArrayOrderings
15
+ # A graphic order takes a Graffle::Sheet in its constructor. The graphics
16
+ # method returns an array of Graffle::AbstractGraphic objects that represents
17
+ # an ordered sequence of parts of a test.
18
+ #
19
+ # This abstract class only exists so there's a place to hang this comment.
20
+ class GraphicOrderer
21
+ def graphics; subclass_responsibility; end
49
22
  end
50
-
51
- def graphics()
52
- @retval = []
53
- @source = @sheet.graphics_without_labels.sort_by {|g| g.origin}
54
- def @source.delete_this_id(graffle_id)
55
- delete_if { |g| g.graffle_id == graffle_id }
56
- end
57
-
58
- while (not @source.empty?)
59
- something = @source.shift
60
- @retval << something
61
- trace_from_something(something)
23
+
24
+ # (The following is much more obvious if you look at a sample document.)
25
+ #
26
+ # The sheet contains one or more _rivulets_ of Graffle::ShapedGraphic objects connected
27
+ # with Graffle::LineGraphic objects. All together, they're supposed to represent
28
+ # a user workflow. (The gap between one rivulet and the next typically
29
+ # means that the user has gone away from the app, then returned.)
30
+ #
31
+ # graphics produces a concatenated array of the
32
+ # rivulets (both the Graffle::ShapedGraphic and Graffle::LineGraphic objects).
33
+ #
34
+ # Suppose there's more than one rivulet on the Graffle::Sheet. graphics produces all
35
+ # the elements of the first, followed by all the elements of the second,
36
+ # etc. The first rivulet is the one _headed_ by the highest object on the
37
+ # page. After all of its elements have been placed in the graphics array,
38
+ # the next rivulet is headed by the highest remaining element on the Graffle::Sheet.
39
+ #
40
+ # Within a single rivulet, location is irrelevant. All that matters is
41
+ # where lines start and end.
42
+ #
43
+ # Notice that the head of the second rivulet may be higher than some
44
+ # element of the first.
45
+ #
46
+ # A rivulet may have a single element.
47
+
48
+ class InWorkflowOrder < GraphicOrderer
49
+ include Graffle
50
+
51
+ def initialize(sheet)
52
+ @sheet = sheet
62
53
  end
63
- @retval
64
- end
65
-
66
- private
67
-
68
- def trace_from_something(something)
69
- if something.behaves_like?(LineGraphic)
70
- trace_from_line(something)
71
- elsif something.behaves_like?(ShapedGraphic)
72
- trace_from_shaped(something)
73
- else
74
- raise "Not supposed to do this with groups yet"
54
+
55
+ def graphics()
56
+ @retval = []
57
+ @source = @sheet.graphics_without_labels.sort_by {|g| g.origin}
58
+ def @source.delete_this_id(graffle_id)
59
+ delete_if { |g| g.graffle_id == graffle_id }
60
+ end
61
+
62
+ while (not @source.empty?)
63
+ something = @source.shift
64
+ @retval << something
65
+ trace_from_something(something)
66
+ end
67
+ @retval
75
68
  end
76
- end
77
-
78
- def trace_from_line(line)
79
- # Strictly, a line could connect to another line. How is that
80
- # to be interpreted as a workflow, though?
81
- shaped = line.to
82
- if shaped
83
- @retval << shaped
84
- @source.delete_this_id(shaped.graffle_id)
85
- trace_from_shaped(shaped)
69
+
70
+ private
71
+
72
+ def trace_from_something(something)
73
+ if something.behaves_like?(LineGraphic)
74
+ trace_from_line(something)
75
+ elsif something.behaves_like?(ShapedGraphic)
76
+ trace_from_shaped(something)
77
+ else
78
+ raise "Not supposed to do this with groups yet"
79
+ end
86
80
  end
87
- end
88
-
89
- def trace_from_shaped(shaped)
90
- line = @source.find do |g|
91
- g.behaves_like?(LineGraphic) && g.from == shaped
81
+
82
+ def trace_from_line(line)
83
+ # Strictly, a line could connect to another line. How is that
84
+ # to be interpreted as a workflow, though?
85
+ shaped = line.to
86
+ if shaped
87
+ @retval << shaped
88
+ @source.delete_this_id(shaped.graffle_id)
89
+ trace_from_shaped(shaped)
90
+ end
92
91
  end
93
- if line
94
- @retval << line
95
- @source.delete_this_id(line.graffle_id)
96
- trace_from_line(line)
92
+
93
+ def trace_from_shaped(shaped)
94
+ line = @source.find do |g|
95
+ g.behaves_like?(LineGraphic) && g.from == shaped
96
+ end
97
+ if line
98
+ @retval << line
99
+ @source.delete_this_id(line.graffle_id)
100
+ trace_from_line(line)
101
+ end
97
102
  end
103
+
98
104
  end
99
-
100
105
  end
106
+
101
107
  end