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.
- data/History.txt +5 -0
- data/Manifest.txt +22 -11
- data/README.txt +2 -2
- data/Rakefile.hoe +2 -1
- data/design-notes/graphical-tests-for-rails-objects.graffle +644 -0
- data/examples/objects with notes.expected +5 -0
- data/examples/objects with notes.graffle +338 -0
- data/examples/objects with notes.rb +42 -0
- data/examples/rails-workflow-test.expected +1 -1
- data/examples/rails-workflow-test.graffle/data.plist +86 -7
- data/examples/rails-workflow-test.rb +11 -9
- data/graffle.tmproj +82 -190
- data/lib/graffle.rb +19 -2
- data/lib/graffle/point.rb +1 -2
- data/lib/graffle/stereotypes.rb +62 -16
- data/lib/graffle/version.rb +1 -1
- data/lib/graphical_tests_for_rails.rb +8 -5
- data/lib/graphical_tests_for_rails/graphic-volunteers.rb +75 -0
- data/lib/graphical_tests_for_rails/orderings.rb +90 -84
- data/lib/graphical_tests_for_rails/picture-appliers.rb +225 -0
- data/lib/graphical_tests_for_rails/text-appliers.rb +135 -0
- data/lib/graphical_tests_for_rails/volunteer-pool.rb +115 -0
- data/test/abstract-graphic-tests.rb +48 -0
- data/test/document-tests.rb +5 -5
- data/test/examples-tests.rb +42 -0
- data/test/graphical_tests_for_rails/{graphic-interpreter-tests.rb → deprecated-graphic-interpreter-tests.rb} +11 -21
- data/test/graphical_tests_for_rails/graphic-volunteer-tests.rb +218 -0
- data/test/graphical_tests_for_rails/in-workflow-order-tests.rb +1 -1
- data/test/graphical_tests_for_rails/picture-applier-tests.rb +215 -0
- data/test/graphical_tests_for_rails/{text-interpreter-tests.rb → text-applier-tests.rb} +17 -3
- data/test/graphical_tests_for_rails/util.rb +16 -0
- data/test/line-graphic-tests.rb +9 -1
- data/test/note-tests.rb +62 -0
- data/test/{graffle-file-types → sample-files}/as-a-package.graffle/data.plist +0 -0
- data/test/{graffle-file-types → sample-files}/as-a-package.graffle/image1.png +0 -0
- data/test/{graffle-file-types → sample-files}/as-a-package.graffle/image2.png +0 -0
- data/test/{graffle-file-types → sample-files}/as-a-package.graffle/image3.png +0 -0
- data/test/{graffle-file-types → sample-files}/multiple-canvases.graffle +0 -0
- data/test/{graffle-file-types → sample-files}/opening-tests.rb +9 -4
- data/test/{graffle-file-types → sample-files}/two-boxes-and-a-line.graffle +0 -0
- data/test/shaped-graphic-tests.rb +2 -3
- metadata +42 -18
- data/lib/graphical_tests_for_rails/interpreters.rb +0 -147
- 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
|
-
|
26
|
-
user_disputes("this point can be reached") { "unreachable?"}
|
25
|
+
user_is_bewildered
|
27
26
|
end
|
28
27
|
end
|
29
28
|
|
data/lib/graffle/stereotypes.rb
CHANGED
@@ -6,16 +6,16 @@
|
|
6
6
|
module Graffle
|
7
7
|
module Builders # :nodoc:
|
8
8
|
def self.raw(mod, basic_structure = {}, &block)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
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:
|
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
|
-
#
|
148
|
-
#
|
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
|
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
|
-
#
|
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
|
-
#
|
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 "
|
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
|
-
|
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
|
data/lib/graffle/version.rb
CHANGED
@@ -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
|
-
#
|
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,
|
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
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
94
|
-
|
95
|
-
@source.
|
96
|
-
|
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
|