journey 1.0.0.rc1

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 (51) hide show
  1. data/.autotest +8 -0
  2. data/.gemtest +0 -0
  3. data/CHANGELOG.rdoc +6 -0
  4. data/Gemfile +11 -0
  5. data/Manifest.txt +50 -0
  6. data/README.rdoc +48 -0
  7. data/Rakefile +31 -0
  8. data/journey.gemspec +42 -0
  9. data/lib/journey.rb +5 -0
  10. data/lib/journey/backwards.rb +5 -0
  11. data/lib/journey/core-ext/hash.rb +11 -0
  12. data/lib/journey/formatter.rb +129 -0
  13. data/lib/journey/gtg/builder.rb +159 -0
  14. data/lib/journey/gtg/simulator.rb +44 -0
  15. data/lib/journey/gtg/transition_table.rb +152 -0
  16. data/lib/journey/nfa/builder.rb +74 -0
  17. data/lib/journey/nfa/dot.rb +34 -0
  18. data/lib/journey/nfa/simulator.rb +45 -0
  19. data/lib/journey/nfa/transition_table.rb +164 -0
  20. data/lib/journey/nodes/node.rb +104 -0
  21. data/lib/journey/parser.rb +204 -0
  22. data/lib/journey/parser.y +47 -0
  23. data/lib/journey/parser_extras.rb +21 -0
  24. data/lib/journey/path/pattern.rb +190 -0
  25. data/lib/journey/route.rb +92 -0
  26. data/lib/journey/router.rb +138 -0
  27. data/lib/journey/router/strexp.rb +22 -0
  28. data/lib/journey/router/utils.rb +57 -0
  29. data/lib/journey/routes.rb +74 -0
  30. data/lib/journey/scanner.rb +58 -0
  31. data/lib/journey/visitors.rb +186 -0
  32. data/lib/journey/visualizer/d3.min.js +2 -0
  33. data/lib/journey/visualizer/fsm.css +34 -0
  34. data/lib/journey/visualizer/fsm.js +134 -0
  35. data/lib/journey/visualizer/index.html.erb +50 -0
  36. data/lib/journey/visualizer/reset.css +48 -0
  37. data/test/gtg/test_builder.rb +77 -0
  38. data/test/gtg/test_transition_table.rb +113 -0
  39. data/test/helper.rb +4 -0
  40. data/test/nfa/test_simulator.rb +96 -0
  41. data/test/nfa/test_transition_table.rb +70 -0
  42. data/test/nodes/test_symbol.rb +15 -0
  43. data/test/path/test_pattern.rb +260 -0
  44. data/test/route/definition/test_parser.rb +108 -0
  45. data/test/route/definition/test_scanner.rb +52 -0
  46. data/test/router/test_strexp.rb +30 -0
  47. data/test/router/test_utils.rb +19 -0
  48. data/test/test_route.rb +95 -0
  49. data/test/test_router.rb +464 -0
  50. data/test/test_routes.rb +51 -0
  51. metadata +168 -0
@@ -0,0 +1,108 @@
1
+ require 'helper'
2
+
3
+ module Journey
4
+ module Definition
5
+ class TestParser < MiniTest::Unit::TestCase
6
+ def setup
7
+ @parser = Parser.new
8
+ end
9
+
10
+ def test_slash
11
+ assert_equal :SLASH, @parser.parse('/').type
12
+ assert_round_trip '/'
13
+ end
14
+
15
+ def test_segment
16
+ assert_round_trip '/foo'
17
+ end
18
+
19
+ def test_segments
20
+ assert_round_trip '/foo/bar'
21
+ end
22
+
23
+ def test_segment_symbol
24
+ assert_round_trip '/foo/:id'
25
+ end
26
+
27
+ def test_symbol
28
+ assert_round_trip '/:foo'
29
+ end
30
+
31
+ def test_group
32
+ assert_round_trip '(/:foo)'
33
+ end
34
+
35
+ def test_groups
36
+ assert_round_trip '(/:foo)(/:bar)'
37
+ end
38
+
39
+ def test_nested_groups
40
+ assert_round_trip '(/:foo(/:bar))'
41
+ end
42
+
43
+ def test_dot_symbol
44
+ assert_round_trip('.:format')
45
+ end
46
+
47
+ def test_dot_literal
48
+ assert_round_trip('.xml')
49
+ end
50
+
51
+ def test_segment_dot
52
+ assert_round_trip('/foo.:bar')
53
+ end
54
+
55
+ def test_segment_group_dot
56
+ assert_round_trip('/foo(.:bar)')
57
+ end
58
+
59
+ def test_segment_group
60
+ assert_round_trip('/foo(/:action)')
61
+ end
62
+
63
+ def test_segment_groups
64
+ assert_round_trip('/foo(/:action)(/:bar)')
65
+ end
66
+
67
+ def test_segment_nested_groups
68
+ assert_round_trip('/foo(/:action(/:bar))')
69
+ end
70
+
71
+ def test_group_followed_by_path
72
+ assert_round_trip('/foo(/:action)/:bar')
73
+ end
74
+
75
+ def test_star
76
+ assert_round_trip('*foo')
77
+ assert_round_trip('/*foo')
78
+ assert_round_trip('/bar/*foo')
79
+ assert_round_trip('/bar/(*foo)')
80
+ end
81
+
82
+ def test_or
83
+ assert_round_trip('a|b')
84
+ assert_round_trip('a|b|c')
85
+ assert_round_trip('(a|b)|c')
86
+ assert_round_trip('a|(b|c)')
87
+ assert_round_trip('*a|(b|c)')
88
+ assert_round_trip('*a|:b|c')
89
+ end
90
+
91
+ def test_arbitrary
92
+ assert_round_trip('/bar/*foo#')
93
+ end
94
+
95
+ def test_literal_dot_paren
96
+ assert_round_trip "/sprockets.js(.:format)"
97
+ end
98
+
99
+ def test_groups_with_dot
100
+ assert_round_trip "/(:locale)(.:format)"
101
+ end
102
+
103
+ def assert_round_trip str
104
+ assert_equal str, @parser.parse(str).to_s
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,52 @@
1
+ require 'helper'
2
+
3
+ module Journey
4
+ module Definition
5
+ class TestScanner < MiniTest::Unit::TestCase
6
+ def setup
7
+ @scanner = Scanner.new
8
+ end
9
+
10
+ # /page/:id(/:action)(.:format)
11
+ def test_tokens
12
+ [
13
+ ['/', [[:SLASH, '/']]],
14
+ ['*omg', [[:STAR, '*'], [:LITERAL, 'omg']]],
15
+ ['/page', [[:SLASH, '/'], [:LITERAL, 'page']]],
16
+ ['/:page', [[:SLASH, '/'], [:SYMBOL, ':page']]],
17
+ ['/(:page)', [
18
+ [:SLASH, '/'],
19
+ [:LPAREN, '('],
20
+ [:SYMBOL, ':page'],
21
+ [:RPAREN, ')'],
22
+ ]],
23
+ ['(/:action)', [
24
+ [:LPAREN, '('],
25
+ [:SLASH, '/'],
26
+ [:SYMBOL, ':action'],
27
+ [:RPAREN, ')'],
28
+ ]],
29
+ ['(())', [[:LPAREN, '('],
30
+ [:LPAREN, '('], [:RPAREN, ')'], [:RPAREN, ')']]],
31
+ ['(.:format)', [
32
+ [:LPAREN, '('],
33
+ [:DOT, '.'],
34
+ [:SYMBOL, ':format'],
35
+ [:RPAREN, ')'],
36
+ ]],
37
+ ].each do |str, expected|
38
+ @scanner.scan_setup str
39
+ assert_tokens expected, @scanner
40
+ end
41
+ end
42
+
43
+ def assert_tokens tokens, scanner
44
+ toks = []
45
+ while tok = scanner.next_token
46
+ toks << tok
47
+ end
48
+ assert_equal tokens, toks
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,30 @@
1
+ require 'helper'
2
+
3
+ module Journey
4
+ class Router
5
+ class TestStrexp < MiniTest::Unit::TestCase
6
+ def test_many_names
7
+ exp = Strexp.new(
8
+ "/:controller(/:action(/:id(.:format)))",
9
+ {:controller=>/.+?/},
10
+ ["/", ".", "?"],
11
+ true)
12
+
13
+ assert_equal ["controller", "action", "id", "format"], exp.names
14
+ end
15
+
16
+ def test_names
17
+ {
18
+ "/bar(.:format)" => %w{ format },
19
+ ":format" => %w{ format },
20
+ ":format-" => %w{ format },
21
+ ":format0" => %w{ format0 },
22
+ ":format1,:format2" => %w{ format1 format2 },
23
+ }.each do |string, expected|
24
+ exp = Strexp.new(string, {}, ["/", ".", "?"])
25
+ assert_equal expected, exp.names
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ require 'helper'
2
+
3
+ module Journey
4
+ class Router
5
+ class TestUtils < MiniTest::Unit::TestCase
6
+ def test_path_escape
7
+ assert_equal "a/b%20c+d", Utils.escape_path("a/b c+d")
8
+ end
9
+
10
+ def test_fragment_escape
11
+ assert_equal "a/b%20c+d?e", Utils.escape_fragment("a/b c+d?e")
12
+ end
13
+
14
+ def test_uri_unescape
15
+ assert_equal "a/b c+d", Utils.unescape_uri("a%2Fb%20c+d")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,95 @@
1
+ require 'helper'
2
+
3
+ module Journey
4
+ class TestRoute < MiniTest::Unit::TestCase
5
+ def test_initialize
6
+ app = Object.new
7
+ path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
8
+ defaults = Object.new
9
+ route = Route.new("name", app, path, {}, defaults)
10
+
11
+ assert_equal app, route.app
12
+ assert_equal path, route.path
13
+ assert_equal defaults, route.defaults
14
+ end
15
+
16
+ def test_route_adds_itself_as_memo
17
+ app = Object.new
18
+ path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
19
+ defaults = Object.new
20
+ route = Route.new("name", app, path, {}, defaults)
21
+
22
+ route.ast.grep(Nodes::Terminal).each do |node|
23
+ assert_equal route, node.memo
24
+ end
25
+ end
26
+
27
+ def test_ip_address
28
+ path = Path::Pattern.new '/messages/:id(.:format)'
29
+ route = Route.new("name", nil, path, {:ip => '192.168.1.1'},
30
+ { :controller => 'foo', :action => 'bar' })
31
+ assert_equal '192.168.1.1', route.ip
32
+ end
33
+
34
+ def test_default_ip
35
+ path = Path::Pattern.new '/messages/:id(.:format)'
36
+ route = Route.new("name", nil, path, {},
37
+ { :controller => 'foo', :action => 'bar' })
38
+ assert_equal(//, route.ip)
39
+ end
40
+
41
+ def test_format_empty
42
+ path = Path::Pattern.new '/messages/:id(.:format)'
43
+ route = Route.new("name", nil, path, {},
44
+ { :controller => 'foo', :action => 'bar' })
45
+
46
+ assert_equal '/messages', route.format({})
47
+ end
48
+
49
+ def test_format_with_star
50
+ path = Path::Pattern.new '/:controller/*extra'
51
+ route = Route.new("name", nil, path, {},
52
+ { :controller => 'foo', :action => 'bar' })
53
+ assert_equal '/foo/himom', route.format({
54
+ :controller => 'foo',
55
+ :extra => 'himom',
56
+ })
57
+ end
58
+
59
+ def test_connects_all_match
60
+ path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
61
+ route = Route.new("name", nil, path, {:action => 'bar'}, { :controller => 'foo' })
62
+
63
+ assert_equal '/foo/bar/10', route.format({
64
+ :controller => 'foo',
65
+ :action => 'bar',
66
+ :id => 10
67
+ })
68
+ end
69
+
70
+ def test_extras_are_not_included_if_optional
71
+ path = Path::Pattern.new '/page/:id(/:action)'
72
+ route = Route.new("name", nil, path, { }, { :action => 'show' })
73
+
74
+ assert_equal '/page/10', route.format({ :id => 10 })
75
+ end
76
+
77
+ def test_score
78
+ path = Path::Pattern.new "/page/:id(/:action)(.:format)"
79
+ specific = Route.new "name", nil, path, {}, {:controller=>"pages", :action=>"show"}
80
+
81
+ path = Path::Pattern.new "/:controller(/:action(/:id))(.:format)"
82
+ generic = Route.new "name", nil, path, {}
83
+
84
+ knowledge = {:id=>20, :controller=>"pages", :action=>"show"}
85
+
86
+ routes = [specific, generic]
87
+
88
+ refute_equal specific.score(knowledge), generic.score(knowledge)
89
+
90
+ found = routes.sort_by { |r| r.score(knowledge) }.last
91
+
92
+ assert_equal specific, found
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,464 @@
1
+ require 'helper'
2
+
3
+ module Journey
4
+ class TestRouter < MiniTest::Unit::TestCase
5
+ attr_reader :routes
6
+
7
+ def setup
8
+ @routes = Routes.new
9
+ @router = Router.new(@routes, {})
10
+ @formatter = Formatter.new(@routes)
11
+ end
12
+
13
+ def test_request_class_reader
14
+ klass = Object.new
15
+ router = Router.new(routes, :request_class => klass)
16
+ assert_equal klass, router.request_class
17
+ end
18
+
19
+ class FakeRequestFeeler < Struct.new(:env, :called)
20
+ def new env
21
+ self.env = env
22
+ self
23
+ end
24
+
25
+ def hello
26
+ self.called = true
27
+ 'world'
28
+ end
29
+ end
30
+
31
+ def test_dashes
32
+ klass = FakeRequestFeeler.new nil
33
+ router = Router.new(routes, {})
34
+
35
+ exp = Router::Strexp.new '/foo-bar-baz', {}, ['/.?']
36
+ path = Path::Pattern.new exp
37
+
38
+ routes.add_route nil, path, {}, {:id => nil}, {}
39
+
40
+ env = rails_env 'PATH_INFO' => '/foo-bar-baz'
41
+ called = false
42
+ router.recognize(env) do |r, _, params|
43
+ called = true
44
+ end
45
+ assert called
46
+ end
47
+
48
+ def test_request_class_and_requirements_success
49
+ klass = FakeRequestFeeler.new nil
50
+ router = Router.new(routes, {:request_class => klass })
51
+
52
+ requirements = { :hello => /world/ }
53
+
54
+ exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?']
55
+ path = Path::Pattern.new exp
56
+
57
+ routes.add_route nil, path, requirements, {:id => nil}, {}
58
+
59
+ env = rails_env 'PATH_INFO' => '/foo/10'
60
+ router.recognize(env) do |r, _, params|
61
+ assert_equal({:id => '10'}, params)
62
+ end
63
+
64
+ assert klass.called, 'hello should have been called'
65
+ assert_equal env.env, klass.env
66
+ end
67
+
68
+ def test_request_class_and_requirements_fail
69
+ klass = FakeRequestFeeler.new nil
70
+ router = Router.new(routes, {:request_class => klass })
71
+
72
+ requirements = { :hello => /mom/ }
73
+
74
+ exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?']
75
+ path = Path::Pattern.new exp
76
+
77
+ router.routes.add_route nil, path, requirements, {:id => nil}, {}
78
+
79
+ env = rails_env 'PATH_INFO' => '/foo/10'
80
+ router.recognize(env) do |r, _, params|
81
+ flunk 'route should not be found'
82
+ end
83
+
84
+ assert klass.called, 'hello should have been called'
85
+ assert_equal env.env, klass.env
86
+ end
87
+
88
+ def test_required_parts_verified_are_anchored
89
+ add_routes @router, [
90
+ Router::Strexp.new("/foo/:id", { :id => /\d/ }, ['/', '.', '?'], false)
91
+ ]
92
+
93
+ assert_raises(Router::RoutingError) do
94
+ @formatter.generate(:path_info, nil, { :id => '10' }, { })
95
+ end
96
+ end
97
+
98
+ def test_required_parts_are_verified_when_building
99
+ add_routes @router, [
100
+ Router::Strexp.new("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
101
+ ]
102
+
103
+ path, _ = @formatter.generate(:path_info, nil, { :id => '10' }, { })
104
+ assert_equal '/foo/10', path
105
+
106
+ assert_raises(Router::RoutingError) do
107
+ @formatter.generate(:path_info, nil, { :id => 'aa' }, { })
108
+ end
109
+ end
110
+
111
+ def test_only_required_parts_are_verified
112
+ add_routes @router, [
113
+ Router::Strexp.new("/foo(/:id)", {:id => /\d/}, ['/', '.', '?'], false)
114
+ ]
115
+
116
+ path, _ = @formatter.generate(:path_info, nil, { :id => '10' }, { })
117
+ assert_equal '/foo/10', path
118
+
119
+ path, _ = @formatter.generate(:path_info, nil, { }, { })
120
+ assert_equal '/foo', path
121
+
122
+ path, _ = @formatter.generate(:path_info, nil, { :id => 'aa' }, { })
123
+ assert_equal '/foo/aa', path
124
+ end
125
+
126
+ def test_X_Cascade
127
+ add_routes @router, [ "/messages(.:format)" ]
128
+ resp = @router.call({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' })
129
+ assert_equal ['Not Found'], resp.last
130
+ assert_equal 'pass', resp[1]['X-Cascade']
131
+ assert_equal 404, resp.first
132
+ end
133
+
134
+ def test_defaults_merge_correctly
135
+ path = Path::Pattern.new '/foo(/:id)'
136
+ @router.routes.add_route nil, path, {}, {:id => nil}, {}
137
+
138
+ env = rails_env 'PATH_INFO' => '/foo/10'
139
+ @router.recognize(env) do |r, _, params|
140
+ assert_equal({:id => '10'}, params)
141
+ end
142
+
143
+ env = rails_env 'PATH_INFO' => '/foo'
144
+ @router.recognize(env) do |r, _, params|
145
+ assert_equal({:id => nil}, params)
146
+ end
147
+ end
148
+
149
+ def test_recognize_with_unbound_regexp
150
+ add_routes @router, [
151
+ Router::Strexp.new("/foo", { }, ['/', '.', '?'], false)
152
+ ]
153
+
154
+ env = rails_env 'PATH_INFO' => '/foo/bar'
155
+
156
+ @router.recognize(env) { |*_| }
157
+
158
+ assert_equal '/foo', env.env['SCRIPT_NAME']
159
+ assert_equal '/bar', env.env['PATH_INFO']
160
+ end
161
+
162
+ def test_bound_regexp_keeps_path_info
163
+ add_routes @router, [
164
+ Router::Strexp.new("/foo", { }, ['/', '.', '?'], true)
165
+ ]
166
+
167
+ env = rails_env 'PATH_INFO' => '/foo'
168
+
169
+ before = env.env['SCRIPT_NAME']
170
+
171
+ @router.recognize(env) { |*_| }
172
+
173
+ assert_equal before, env.env['SCRIPT_NAME']
174
+ assert_equal '/foo', env.env['PATH_INFO']
175
+ end
176
+
177
+ def test_path_not_found
178
+ add_routes @router, [
179
+ "/messages(.:format)",
180
+ "/messages/new(.:format)",
181
+ "/messages/:id/edit(.:format)",
182
+ "/messages/:id(.:format)"
183
+ ]
184
+ env = rails_env 'PATH_INFO' => '/messages/1.1.1'
185
+ yielded = false
186
+
187
+ @router.recognize(env) do |*whatever|
188
+ yielded = false
189
+ end
190
+ refute yielded
191
+ end
192
+
193
+ def test_required_part_in_recall
194
+ add_routes @router, [ "/messages/:a/:b" ]
195
+
196
+ path, _ = @formatter.generate(:path_info, nil, { :a => 'a' }, { :b => 'b' })
197
+ assert_equal "/messages/a/b", path
198
+ end
199
+
200
+ def test_splat_in_recall
201
+ add_routes @router, [ "/*path" ]
202
+
203
+ path, _ = @formatter.generate(:path_info, nil, { }, { :path => 'b' })
204
+ assert_equal "/b", path
205
+ end
206
+
207
+ def test_recall_should_be_used_when_scoring
208
+ add_routes @router, [
209
+ "/messages/:action(/:id(.:format))",
210
+ "/messages/:id(.:format)"
211
+ ]
212
+
213
+ path, _ = @formatter.generate(:path_info, nil, { :id => 10 }, { :action => 'index' })
214
+ assert_equal "/messages/index/10", path
215
+ end
216
+
217
+ def add_routes router, paths
218
+ paths.each do |path|
219
+ path = Path::Pattern.new path
220
+ router.routes.add_route nil, path, {}, {}, {}
221
+ end
222
+ end
223
+
224
+ def test_nil_path_parts_are_ignored
225
+ path = Path::Pattern.new "/:controller(/:action(.:format))"
226
+ @router.routes.add_route nil, path, {}, {}, {}
227
+
228
+ params = { :controller => "tasks", :format => nil }
229
+ extras = { :action => 'lol' }
230
+
231
+ path, _ = @formatter.generate(:path_info, nil, params, extras)
232
+ assert_equal '/tasks', path
233
+ end
234
+
235
+ def test_generate_slash
236
+ path = Path::Pattern.new '/'
237
+ @router.routes.add_route nil, path, {}, {}, {}
238
+
239
+ params = [ [:controller, "tasks"],
240
+ [:action, "show"] ]
241
+
242
+ path, _ = @formatter.generate(:path_info, nil, Hash[params], {})
243
+ assert_equal '/', path
244
+ end
245
+
246
+ def test_generate_calls_param_proc
247
+ path = Path::Pattern.new '/:controller(/:action)'
248
+ @router.routes.add_route nil, path, {}, {}, {}
249
+
250
+ parameterized = []
251
+ params = [ [:controller, "tasks"],
252
+ [:action, "show"] ]
253
+
254
+ @formatter.generate(
255
+ :path_info,
256
+ nil,
257
+ Hash[params],
258
+ {},
259
+ lambda { |k,v| parameterized << [k,v]; v })
260
+
261
+ assert_equal params.map(&:to_s).sort, parameterized.map(&:to_s).sort
262
+ end
263
+
264
+ def test_generate_id
265
+ path = Path::Pattern.new '/:controller(/:action)'
266
+ @router.routes.add_route nil, path, {}, {}, {}
267
+
268
+ path, params = @formatter.generate(
269
+ :path_info, nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {})
270
+ assert_equal '/tasks/show', path
271
+ assert_equal({:id => 1}, params)
272
+ end
273
+
274
+ def test_generate_escapes
275
+ path = Path::Pattern.new '/:controller(/:action)'
276
+ @router.routes.add_route nil, path, {}, {}, {}
277
+
278
+ path, _ = @formatter.generate(:path_info,
279
+ nil, { :controller => "tasks",
280
+ :action => "a/b c+d",
281
+ }, {})
282
+ assert_equal '/tasks/a/b%20c+d', path
283
+ end
284
+
285
+ def test_generate_extra_params
286
+ path = Path::Pattern.new '/:controller(/:action)'
287
+ @router.routes.add_route nil, path, {}, {}, {}
288
+
289
+ path, params = @formatter.generate(:path_info,
290
+ nil, { :id => 1,
291
+ :controller => "tasks",
292
+ :action => "show",
293
+ :relative_url_root => nil
294
+ }, {})
295
+ assert_equal '/tasks/show', path
296
+ assert_equal({:id => 1, :relative_url_root => nil}, params)
297
+ end
298
+
299
+ def test_generate_uses_recall_if_needed
300
+ path = Path::Pattern.new '/:controller(/:action(/:id))'
301
+ @router.routes.add_route nil, path, {}, {}, {}
302
+
303
+ path, params = @formatter.generate(:path_info,
304
+ nil,
305
+ {:controller =>"tasks", :id => 10},
306
+ {:action =>"index"})
307
+ assert_equal '/tasks/index/10', path
308
+ assert_equal({}, params)
309
+ end
310
+
311
+ def test_generate_with_name
312
+ path = Path::Pattern.new '/:controller(/:action)'
313
+ @router.routes.add_route nil, path, {}, {}, {}
314
+
315
+ path, params = @formatter.generate(:path_info,
316
+ "tasks",
317
+ {:controller=>"tasks"},
318
+ {:controller=>"tasks", :action=>"index"})
319
+ assert_equal '/tasks', path
320
+ assert_equal({}, params)
321
+ end
322
+
323
+ {
324
+ '/content' => { :controller => 'content' },
325
+ '/content/list' => { :controller => 'content', :action => 'list' },
326
+ '/content/show/10' => { :controller => 'content', :action => 'show', :id => "10" },
327
+ }.each do |request_path, expected|
328
+ define_method("test_recognize_#{expected.keys.map(&:to_s).join('_')}") do
329
+ path = Path::Pattern.new "/:controller(/:action(/:id))"
330
+ app = Object.new
331
+ route = @router.routes.add_route(app, path, {}, {}, {})
332
+
333
+ env = rails_env 'PATH_INFO' => request_path
334
+ called = false
335
+
336
+ @router.recognize(env) do |r, _, params|
337
+ assert_equal route, r
338
+ assert_equal(expected, params)
339
+ called = true
340
+ end
341
+
342
+ assert called
343
+ end
344
+ end
345
+
346
+ {
347
+ :segment => ['/a%2Fb%20c+d/splat', { :segment => 'a/b c+d', :splat => 'splat' }],
348
+ :splat => ['/segment/a/b%20c+d', { :segment => 'segment', :splat => 'a/b c+d' }]
349
+ }.each do |name, (request_path, expected)|
350
+ define_method("test_recognize_#{name}") do
351
+ path = Path::Pattern.new '/:segment/*splat'
352
+ app = Object.new
353
+ route = @router.routes.add_route(app, path, {}, {}, {})
354
+
355
+ env = rails_env 'PATH_INFO' => request_path
356
+ called = false
357
+
358
+ @router.recognize(env) do |r, _, params|
359
+ assert_equal route, r
360
+ assert_equal(expected, params)
361
+ called = true
362
+ end
363
+
364
+ assert called
365
+ end
366
+ end
367
+
368
+ def test_namespaced_controller
369
+ strexp = Router::Strexp.new(
370
+ "/:controller(/:action(/:id))",
371
+ { :controller => /.+?/ },
372
+ ["/", ".", "?"]
373
+ )
374
+ path = Path::Pattern.new strexp
375
+ app = Object.new
376
+ route = @router.routes.add_route(app, path, {}, {}, {})
377
+
378
+ env = rails_env 'PATH_INFO' => '/admin/users/show/10'
379
+ called = false
380
+ expected = {
381
+ :controller => 'admin/users',
382
+ :action => 'show',
383
+ :id => '10'
384
+ }
385
+
386
+ @router.recognize(env) do |r, _, params|
387
+ assert_equal route, r
388
+ assert_equal(expected, params)
389
+ called = true
390
+ end
391
+ assert called
392
+ end
393
+
394
+ def test_recognize_literal
395
+ path = Path::Pattern.new "/books(/:action(.:format))"
396
+ app = Object.new
397
+ route = @router.routes.add_route(app, path, {}, {:controller => 'books'})
398
+
399
+ env = rails_env 'PATH_INFO' => '/books/list.rss'
400
+ expected = { :controller => 'books', :action => 'list', :format => 'rss' }
401
+ called = false
402
+ @router.recognize(env) do |r, _, params|
403
+ assert_equal route, r
404
+ assert_equal(expected, params)
405
+ called = true
406
+ end
407
+
408
+ assert called
409
+ end
410
+
411
+ def test_recognize_cares_about_verbs
412
+ path = Path::Pattern.new "/books(/:action(.:format))"
413
+ app = Object.new
414
+ conditions = {
415
+ :request_method => 'GET'
416
+ }
417
+ @router.routes.add_route(app, path, conditions, {})
418
+
419
+ conditions = conditions.dup
420
+ conditions[:request_method] = 'POST'
421
+
422
+ post = @router.routes.add_route(app, path, conditions, {})
423
+
424
+ env = rails_env 'PATH_INFO' => '/books/list.rss',
425
+ "REQUEST_METHOD" => "POST"
426
+
427
+ called = false
428
+ @router.recognize(env) do |r, _, params|
429
+ assert_equal post, r
430
+ called = true
431
+ end
432
+
433
+ assert called
434
+ end
435
+
436
+ private
437
+
438
+ RailsEnv = Struct.new(:env)
439
+
440
+ def rails_env env
441
+ RailsEnv.new rack_env env
442
+ end
443
+
444
+ def rack_env env
445
+ {
446
+ "rack.version" => [1, 1],
447
+ "rack.input" => StringIO.new,
448
+ "rack.errors" => StringIO.new,
449
+ "rack.multithread" => true,
450
+ "rack.multiprocess" => true,
451
+ "rack.run_once" => false,
452
+ "REQUEST_METHOD" => "GET",
453
+ "SERVER_NAME" => "example.org",
454
+ "SERVER_PORT" => "80",
455
+ "QUERY_STRING" => "",
456
+ "PATH_INFO" => "/content",
457
+ "rack.url_scheme" => "http",
458
+ "HTTPS" => "off",
459
+ "SCRIPT_NAME" => "",
460
+ "CONTENT_LENGTH" => "0"
461
+ }.merge env
462
+ end
463
+ end
464
+ end