1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
require 'pp'
require_relative 'docparser'
if ARGV.empty? || ARGV == ['-h'] || ARGV == ['--help']
$stderr.puts "usage: ruby #{$PROGRAM_NAME} <dirA> <dirB>"
$stderr.puts " ruby #{$PROGRAM_NAME} src php > tests/JSPHP-suite.json"
else
dir_a, dir_b = ARGV
js = parse_any_path dir_a
php = parse_any_path dir_b
class_names = (js + php).map{|c| c[:name] }.sort.uniq
tests = []
classes = php.select{|c| class_names.include? c[:name] }
testable_classes = classes
.reject{|c| c[:abstract] } # can't test abstract classes
.reject{|c| !c[:parent] || c[:parent] == 'ElementMixin' || c[:parent] == 'Theme' } # can't test abstract
.reject{|c| %w[Element Widget Layout Theme].include? c[:name] } # no toplevel
.reject{|c| c[:name] == 'DropdownInputWidget' } # different PHP and JS implementations
# values to test for each type
expandos = {
'null' => [nil],
'number' => [0, -1, 300],
'boolean' => [true, false],
'string' => ['Foo bar', '<b>HTML?</b>'],
}
# values to test for names
sensible_values = {
'href' => ['http://example.com/'],
['TextInputWidget', 'type'] => %w[text password],
['ButtonInputWidget', 'type'] => %w[button input],
['FieldLayout', 'help'] => true, # different PHP and JS implementations
['FieldsetLayout', 'help'] => true, # different PHP and JS implementations
'type' => %w[text button],
'method' => %w[GET POST],
'action' => [],
'enctype' => true,
'target' => ['_blank'],
'accessKey' => ['k'],
'name' => true,
'autofocus' => true, # usually makes no sense in JS
'tabIndex' => [-1, 0, 100],
'icon' => ['picture'],
'indicator' => ['down'],
'flags' => %w[constructive],
'label' => expandos['string'] + ['', ' '],
# these are defined by Element and would bloat the tests
'classes' => true,
'id' => true,
'content' => true,
'text' => true,
}
find_class = lambda do |klass|
return classes.find{|c| c[:name] == klass }
end
expand_types_to_values = lambda do |types|
return types.map{|t|
as_array = true if t.sub! '[]', ''
t = 'ButtonWidget' if t == 'Widget' # Widget is not "testable", use a subclass
if expandos[t]
# Primitive. Run tests with the provided values.
vals = expandos[t]
elsif testable_classes.find{|c| c[:name] == t }
# OOUI object. Test suite will instantiate one and run the test with it.
params = find_class.call(t)[:methods][0][:params] || []
config = params.map{|config_option|
types = config_option[:type].split '|'
values = expand_types_to_values.call(types)
{ config_option[:name] => values[0] }
}
vals = [ '_placeholder_' + {
class: t,
config: config.inject({}, :merge)
}.to_json ]
else
# We don't know how to test this. The empty value will result in no
# tests being generated for this combination of config values.
vals = []
end
as_array ? vals.map{|v| [v] } : vals
}.inject(:+)
end
find_config_sources = lambda do |klass_name|
return [] unless klass_name
klass_names = [klass_name]
while klass_name
klass = find_class.call(klass_name)
break unless klass
klass_names +=
find_config_sources.call(klass[:parent]) +
klass[:mixins].map(&find_config_sources).flatten
klass_name = klass[:parent]
end
return klass_names.uniq
end
testable_classes.each do |klass|
config_sources = find_config_sources.call(klass[:name])
.map{|c| find_class.call(c)[:methods][0] }
config = config_sources.map{|c| c[:config] }.compact.inject(:+)
required_config = klass[:methods][0][:params] || []
# generate every possible configuration of configuration option sets
maxlength = [config.length, 2].min
config_combinations = (0..maxlength).map{|l| config.combination(l).to_a }.inject(:+)
# for each set, generate all possible values to use based on option's type
config_combinations = config_combinations.map{|config_comb|
config_comb += required_config
expanded = config_comb.map{|config_option|
types = config_option[:type].split '|'
sensible = sensible_values[ [ klass[:name], config_option[:name] ] ] ||
sensible_values[ config_option[:name] ]
if sensible == true
[] # the empty value will result in no tests being generated
else
values = sensible || expand_types_to_values.call(types)
values.map{|v| config_option.dup.merge(value: v) } + [nil]
end
}
expanded.length > 0 ? expanded[0].product(*expanded[1..-1]) : []
}.inject(:concat).map(&:compact).uniq
# really require the required ones
config_combinations = config_combinations.select{|config_comb|
required_config.all?{|r| config_comb.find{|c| c[:name] == r[:name] } }
}
config_combinations.each do |config_comb|
tests << {
class: klass[:name],
config: Hash[ config_comb.map{|c| [ c[:name], c[:value] ] } ]
}
end
end
tests = tests.group_by{|t| t[:class] }
puts JSON.pretty_generate tests
end
|