graffle 0.1.8 → 0.1.9

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