mir_extensions 0.2.0

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 (70) hide show
  1. data/.bundle/config +2 -0
  2. data/.document +5 -0
  3. data/.gitignore +21 -0
  4. data/.rspec +1 -0
  5. data/Gemfile +41 -0
  6. data/Gemfile.lock +116 -0
  7. data/LICENSE +20 -0
  8. data/README +256 -0
  9. data/README.rdoc +17 -0
  10. data/Rakefile +50 -0
  11. data/VERSION +1 -0
  12. data/app/controllers/application_controller.rb +9 -0
  13. data/app/helpers/application_helper.rb +2 -0
  14. data/app/models/primary.rb +11 -0
  15. data/app/models/secondary.rb +7 -0
  16. data/app/views/layouts/application.html.erb +14 -0
  17. data/autotest/discover.rb +2 -0
  18. data/config.ru +4 -0
  19. data/config/application.rb +47 -0
  20. data/config/boot.rb +13 -0
  21. data/config/database.yml +17 -0
  22. data/config/environment.rb +5 -0
  23. data/config/environments/development.rb +22 -0
  24. data/config/environments/production.rb +49 -0
  25. data/config/environments/test.rb +35 -0
  26. data/config/initializers/backtrace_silencers.rb +7 -0
  27. data/config/initializers/inflections.rb +10 -0
  28. data/config/initializers/mime_types.rb +5 -0
  29. data/config/initializers/secret_token.rb +7 -0
  30. data/config/initializers/session_store.rb +8 -0
  31. data/config/locales/en.yml +5 -0
  32. data/config/routes.rb +58 -0
  33. data/db/development.sqlite3 +1 -0
  34. data/db/mir_ext_development.sqlite3 +0 -0
  35. data/db/mir_ext_test.sqlite3 +0 -0
  36. data/db/schema.rb +26 -0
  37. data/db/seeds.rb +7 -0
  38. data/db/test.sqlite3 +0 -0
  39. data/doc/README_FOR_APP +2 -0
  40. data/lib/core_ext/controller_extensions.rb +37 -0
  41. data/lib/core_ext/core_ext.rb +344 -0
  42. data/lib/core_ext/helper_extensions.rb +383 -0
  43. data/lib/mir_extensions.rb +37 -0
  44. data/lib/tasks/.gitkeep +0 -0
  45. data/log/development.log +151 -0
  46. data/log/production.log +0 -0
  47. data/log/server.log +0 -0
  48. data/log/test.log +27 -0
  49. data/mir_extensions.gemspec +119 -0
  50. data/public/404.html +26 -0
  51. data/public/422.html +26 -0
  52. data/public/500.html +26 -0
  53. data/public/favicon.ico +0 -0
  54. data/public/images/rails.png +0 -0
  55. data/public/index.html +262 -0
  56. data/public/javascripts/application.js +2 -0
  57. data/public/javascripts/controls.js +965 -0
  58. data/public/javascripts/dragdrop.js +974 -0
  59. data/public/javascripts/effects.js +1123 -0
  60. data/public/javascripts/prototype.js +6001 -0
  61. data/public/javascripts/rails.js +175 -0
  62. data/public/robots.txt +5 -0
  63. data/public/stylesheets/.gitkeep +0 -0
  64. data/script/rails +6 -0
  65. data/spec/controllers/application_controller_spec.rb +41 -0
  66. data/spec/helpers/application_helper_spec.rb +40 -0
  67. data/spec/mir_extensions_spec.rb +269 -0
  68. data/spec/spec_helper.rb +27 -0
  69. data/vendor/plugins/.gitkeep +0 -0
  70. metadata +170 -0
@@ -0,0 +1,175 @@
1
+ (function() {
2
+ // Technique from Juriy Zaytsev
3
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
4
+ function isEventSupported(eventName) {
5
+ var el = document.createElement('div');
6
+ eventName = 'on' + eventName;
7
+ var isSupported = (eventName in el);
8
+ if (!isSupported) {
9
+ el.setAttribute(eventName, 'return;');
10
+ isSupported = typeof el[eventName] == 'function';
11
+ }
12
+ el = null;
13
+ return isSupported;
14
+ }
15
+
16
+ function isForm(element) {
17
+ return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
18
+ }
19
+
20
+ function isInput(element) {
21
+ if (Object.isElement(element)) {
22
+ var name = element.nodeName.toUpperCase()
23
+ return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
24
+ }
25
+ else return false
26
+ }
27
+
28
+ var submitBubbles = isEventSupported('submit'),
29
+ changeBubbles = isEventSupported('change')
30
+
31
+ if (!submitBubbles || !changeBubbles) {
32
+ // augment the Event.Handler class to observe custom events when needed
33
+ Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
34
+ function(init, element, eventName, selector, callback) {
35
+ init(element, eventName, selector, callback)
36
+ // is the handler being attached to an element that doesn't support this event?
37
+ if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
38
+ (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
39
+ // "submit" => "emulated:submit"
40
+ this.eventName = 'emulated:' + this.eventName
41
+ }
42
+ }
43
+ )
44
+ }
45
+
46
+ if (!submitBubbles) {
47
+ // discover forms on the page by observing focus events which always bubble
48
+ document.on('focusin', 'form', function(focusEvent, form) {
49
+ // special handler for the real "submit" event (one-time operation)
50
+ if (!form.retrieve('emulated:submit')) {
51
+ form.on('submit', function(submitEvent) {
52
+ var emulated = form.fire('emulated:submit', submitEvent, true)
53
+ // if custom event received preventDefault, cancel the real one too
54
+ if (emulated.returnValue === false) submitEvent.preventDefault()
55
+ })
56
+ form.store('emulated:submit', true)
57
+ }
58
+ })
59
+ }
60
+
61
+ if (!changeBubbles) {
62
+ // discover form inputs on the page
63
+ document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
64
+ // special handler for real "change" events
65
+ if (!input.retrieve('emulated:change')) {
66
+ input.on('change', function(changeEvent) {
67
+ input.fire('emulated:change', changeEvent, true)
68
+ })
69
+ input.store('emulated:change', true)
70
+ }
71
+ })
72
+ }
73
+
74
+ function handleRemote(element) {
75
+ var method, url, params;
76
+
77
+ var event = element.fire("ajax:before");
78
+ if (event.stopped) return false;
79
+
80
+ if (element.tagName.toLowerCase() === 'form') {
81
+ method = element.readAttribute('method') || 'post';
82
+ url = element.readAttribute('action');
83
+ params = element.serialize();
84
+ } else {
85
+ method = element.readAttribute('data-method') || 'get';
86
+ url = element.readAttribute('href');
87
+ params = {};
88
+ }
89
+
90
+ new Ajax.Request(url, {
91
+ method: method,
92
+ parameters: params,
93
+ evalScripts: true,
94
+
95
+ onComplete: function(request) { element.fire("ajax:complete", request); },
96
+ onSuccess: function(request) { element.fire("ajax:success", request); },
97
+ onFailure: function(request) { element.fire("ajax:failure", request); }
98
+ });
99
+
100
+ element.fire("ajax:after");
101
+ }
102
+
103
+ function handleMethod(element) {
104
+ var method = element.readAttribute('data-method'),
105
+ url = element.readAttribute('href'),
106
+ csrf_param = $$('meta[name=csrf-param]')[0],
107
+ csrf_token = $$('meta[name=csrf-token]')[0];
108
+
109
+ var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
110
+ element.parentNode.insert(form);
111
+
112
+ if (method !== 'post') {
113
+ var field = new Element('input', { type: 'hidden', name: '_method', value: method });
114
+ form.insert(field);
115
+ }
116
+
117
+ if (csrf_param) {
118
+ var param = csrf_param.readAttribute('content'),
119
+ token = csrf_token.readAttribute('content'),
120
+ field = new Element('input', { type: 'hidden', name: param, value: token });
121
+ form.insert(field);
122
+ }
123
+
124
+ form.submit();
125
+ }
126
+
127
+
128
+ document.on("click", "*[data-confirm]", function(event, element) {
129
+ var message = element.readAttribute('data-confirm');
130
+ if (!confirm(message)) event.stop();
131
+ });
132
+
133
+ document.on("click", "a[data-remote]", function(event, element) {
134
+ if (event.stopped) return;
135
+ handleRemote(element);
136
+ event.stop();
137
+ });
138
+
139
+ document.on("click", "a[data-method]", function(event, element) {
140
+ if (event.stopped) return;
141
+ handleMethod(element);
142
+ event.stop();
143
+ });
144
+
145
+ document.on("submit", function(event) {
146
+ var element = event.findElement(),
147
+ message = element.readAttribute('data-confirm');
148
+ if (message && !confirm(message)) {
149
+ event.stop();
150
+ return false;
151
+ }
152
+
153
+ var inputs = element.select("input[type=submit][data-disable-with]");
154
+ inputs.each(function(input) {
155
+ input.disabled = true;
156
+ input.writeAttribute('data-original-value', input.value);
157
+ input.value = input.readAttribute('data-disable-with');
158
+ });
159
+
160
+ var element = event.findElement("form[data-remote]");
161
+ if (element) {
162
+ handleRemote(element);
163
+ event.stop();
164
+ }
165
+ });
166
+
167
+ document.on("ajax:after", "form", function(event, element) {
168
+ var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
169
+ inputs.each(function(input) {
170
+ input.value = input.readAttribute('data-original-value');
171
+ input.removeAttribute('data-original-value');
172
+ input.disabled = false;
173
+ });
174
+ });
175
+ })();
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-Agent: *
5
+ # Disallow: /
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,41 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ApplicationController do
4
+
5
+ it 'detects its IP address' do
6
+ ApplicationController.local_ip.should =~ /^192\.168\.[0-9]+\.[0-9]+$|^10\.[0-9]+\.[0-9]+\.[0-9]+$|^127.0.0.1$/
7
+ end
8
+
9
+ before :all do
10
+ @controller = ApplicationController.new
11
+ end
12
+
13
+ it 'sanitizes the sort-by param' do
14
+ @controller.stubs(:params).returns(:by => 'SELECT * FROM accounts')
15
+ @controller.sanitize_by_param.should == 'id'
16
+
17
+ @controller.stubs(:params).returns(:by => 'name')
18
+ @controller.sanitize_by_param(['name']).should == 'name'
19
+ end
20
+
21
+ it 'sanitizes the sort-direction param' do
22
+ @controller.stubs(:params).returns(:dir => 'DELETE FROM accounts')
23
+ @controller.sanitize_dir_param.should == 'ASC'
24
+
25
+ @controller.stubs(:params).returns(:dir => 'ASC')
26
+ @controller.sanitize_dir_param.should == 'ASC'
27
+
28
+ @controller.stubs(:params).returns(:dir => 'DESC')
29
+ @controller.sanitize_dir_param.should == 'DESC'
30
+ end
31
+
32
+ it 'sanitizes params' do
33
+ @controller.sanitize_params( 2, [1, 2, 3], 1).should == 2
34
+ @controller.sanitize_params( 0, [1, 2, 3], 1).should == 1
35
+ @controller.sanitize_params( nil, [1, 2, 3], 1).should == 1
36
+ @controller.sanitize_params( 0, nil, 1).should == 1
37
+ lambda{ @controller.sanitize_params( 0, [1, 2, 3], nil) }.should raise_error(ArgumentError)
38
+ @controller.sanitize_params( nil, nil, 1).should == 1
39
+ end
40
+
41
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ApplicationHelper do
4
+
5
+ it 'detects its action' do
6
+ _controller = Object.new
7
+ _controller.stubs(:action_name).returns('index')
8
+ controller.stubs(:controller).returns(_controller)
9
+ controller.action?('index').should be_true
10
+ controller.action?('destroy').should be_false
11
+ controller.action?(/index|show/).should be_true
12
+ end
13
+
14
+ it 'formats arrays as HTML lines' do
15
+ controller.array_to_lines([:a, :b, :c]).should == 'a<br />b<br />c'
16
+ end
17
+
18
+ it 'returns a check-mark div' do
19
+ controller.checkmark.should == '<div class="checkmark"></div>'
20
+ end
21
+
22
+ it 'detects its action' do
23
+ _controller = Object.new
24
+ _controller.stubs(:controller_name).returns('home')
25
+ controller.stubs(:controller).returns(_controller)
26
+ controller.controller?('home').should be_true
27
+ controller.controller?('primary').should be_false
28
+ controller.controller?(/home|primary/).should be_true
29
+ end
30
+
31
+ it 'returns an options string with the default select prompt' do
32
+ controller.options_for_array([['1', 'Option 1'], ['2', 'Option 2'], ['3', 'Option 3']]).should == "#{controller.select_prompt_option}<option value=\"1\" >Option 1</option><option value=\"2\" >Option 2</option><option value=\"3\" >Option 3</option>"
33
+ end
34
+
35
+ it 'returns an options string with the default select prompt and a default value' do
36
+ controller.options_for_array([['1', 'Option 1'], ['2', 'Option 2'], ['3', 'Option 3']], '2').should == "#{controller.select_prompt_option}<option value=\"1\" >Option 1</option><option value=\"2\" selected=\"1\">Option 2</option><option value=\"3\" >Option 3</option>"
37
+ end
38
+
39
+
40
+ end
@@ -0,0 +1,269 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "MirExtensions" do
4
+
5
+ describe 'class methods' do
6
+
7
+ describe 'state codes' do
8
+
9
+ it 'returns the state code for a given state' do
10
+ MirExtensions.state_code_for('Virginia').should == 'VA'
11
+ end
12
+
13
+ it 'returns the state for a given state code' do
14
+ MirExtensions.state_name_for('VA').should == 'Virginia'
15
+ end
16
+
17
+ it 'handles failures gracefully' do
18
+ MirExtensions.state_name_for('XX').should be_nil
19
+ end
20
+
21
+ end
22
+
23
+ it 'returns the month name for a month number' do
24
+ MirExtensions.month_name_for(0).should == 'JAN'
25
+ end
26
+
27
+ it 'returns a canonical URL' do
28
+ MirExtensions.canonical_url('cnn.com').should == 'cnn.com/'
29
+ end
30
+
31
+ it 'normalizes slugs' do
32
+ MirExtensions.normalize_slug('!@#$%^&*()').should == ''
33
+ MirExtensions.normalize_slug('abcdefghijklmnopqrstuvwxyz').should == 'abcdefghijklmnopqrstuvwxyz'
34
+ MirExtensions.normalize_slug('ABCDEFGHIJKLMNOPQRSTUVWXYZ').should == 'abcdefghijklmnopqrstuvwxyz'
35
+ MirExtensions.normalize_slug('0123456789').should == '0123456789'
36
+ MirExtensions.normalize_slug('mir-utility').should == 'mir-utility'
37
+ MirExtensions.normalize_slug('mir--utility').should == 'mir-utility'
38
+ MirExtensions.normalize_slug('mir-utility').should == 'mir-utility'
39
+ MirExtensions.normalize_slug('mir-utility/index//').should == 'mir-utility-index'
40
+ end
41
+
42
+ end
43
+
44
+ describe 'string extensions' do
45
+
46
+ it 'formats phone numbers' do
47
+ '3125555252'.to_phone.should == '312-555-5252'
48
+ end
49
+
50
+ it 'formats phone numbers, accepting options' do
51
+ '3125555252'.to_phone(:area_code => true).should == '(312) 555-5252'
52
+ end
53
+
54
+ end
55
+
56
+ it 'formats phone numbers' do
57
+ ''.number_to_phone('5168675309').should == '516-867-5309'
58
+ end
59
+
60
+ it 'formats phone numbers with separate area codes' do
61
+ '5168675309'.formatted_phone.should == '(516) 867-5309'
62
+ end
63
+
64
+ it 'handles invalid phone numbers gracefully' do
65
+ 'Alphabet Soup'.formatted_phone.should == 'Alphabet Soup'
66
+ 'Transylvania 6-5000'.formatted_phone.should == 'Transylvania 6-5000'
67
+ '123-45-6789'.formatted_phone.should == '123-45-6789'
68
+ end
69
+
70
+ it 'formats zip codes' do
71
+ '205000003'.formatted_zip.should == '20500-0003'
72
+ end
73
+
74
+ it 'calculates arithmetic means' do
75
+ _a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
76
+ _a.mean.should == (_a.sum.to_f/_a.size.to_f).to_f
77
+ end
78
+
79
+ it 'aliases count to size' do
80
+ _a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
81
+ _a.count.should == _a.size
82
+ end
83
+
84
+ it 'converts seconds to hours:minutes:seconds' do
85
+ 86400.to_hrs_mins_secs.should == '24:00:00'
86
+ end
87
+
88
+ it 'rounds to the nearest tenth' do
89
+ Math::PI.to_nearest_tenth.should == 3.1
90
+ end
91
+
92
+ it 'converts active records to an array of name-value pairs suitable for select tags' do
93
+ Primary.stubs(:all).returns([Primary.new(:name => 'Admin'), Primary.new(:name => 'User')])
94
+ Primary.to_option_values.should == [ ['Admin', nil], ['User', nil] ]
95
+ end
96
+
97
+ it 'strips the values of specified attributes' do
98
+ p = Primary.new(:name => ' foo bar ')
99
+ p.strip(:name)
100
+ p.name.should == 'foo bar'
101
+ end
102
+
103
+ it 'converts arrays to a histogram hash' do
104
+ [:r, :r, :o, :y, :g, :b, :i, :v, :v, :o].to_histogram.should == {:o=>2, :g=>1, :v=>2, :r=>2, :i=>1, :y=>1, :b=>1}
105
+ end
106
+
107
+ it 'converts a hash to HTTP parameters' do
108
+ _return = {
109
+ :string => 1,
110
+ :array => [2, 3],
111
+ :hash => {
112
+ :a => 4,
113
+ :b => {
114
+ :c => 5
115
+ }
116
+ }
117
+ }.to_params
118
+
119
+ _return.include?('string=1').should be_true
120
+ _return.include?('array[0]=2&array[1]=3').should be_true
121
+ _return.include?('hash[a]=4').should be_true
122
+ _return.include?('hash[b][c]=5').should be_true
123
+ end
124
+
125
+ it 'converts a hash to SQL conditions' do
126
+ _hash = {
127
+ :first_name => 'Quentin',
128
+ :last_name => 'Tarantino'
129
+ }
130
+ _hash.to_sql.should =~ /first_name = 'Quentin'/
131
+ _hash.to_sql.should =~ /last_name = 'Tarantino'/
132
+ _hash.to_sql.should =~ / AND /
133
+ end
134
+
135
+ it 'initializes SOAP headers' do
136
+ _control = {
137
+ :tag => '',
138
+ :value => ''
139
+ }
140
+ _header = Header.new( _control[:tag], _control[:value] )
141
+ _header.on_simple_outbound.should == _control[:value]
142
+ end
143
+
144
+ it 'capitalizes words without omitting characters like titleize' do
145
+ 'vice-president of the united states of america'.capitalize_words.should == 'Vice-President Of The United States Of America'
146
+ end
147
+
148
+ it 'expands address abbreviations' do
149
+ _control = {
150
+ '1600 Pennsylvania Av' => '1600 Pennsylvania Avenue',
151
+ '1600 Pennsylvania Av.' => '1600 Pennsylvania Avenue',
152
+ '1600 Pennsylvania Ave' => '1600 Pennsylvania Avenue',
153
+ '1600 Pennsylvania Ave.' => '1600 Pennsylvania Avenue',
154
+ '77 Sunset Bl' => '77 Sunset Boulevard',
155
+ '77 Sunset Bl.' => '77 Sunset Boulevard',
156
+ '77 Sunset Bld' => '77 Sunset Boulevard',
157
+ '77 Sunset Bld.' => '77 Sunset Boulevard',
158
+ '77 Sunset Blv' => '77 Sunset Boulevard',
159
+ '77 Sunset Blv.' => '77 Sunset Boulevard',
160
+ '77 Sunset Blvd' => '77 Sunset Boulevard',
161
+ '77 Sunset Blvd.' => '77 Sunset Boulevard',
162
+ '10 Columbus Cr' => '10 Columbus Circle',
163
+ '10 Columbus Cr.' => '10 Columbus Circle',
164
+ '10 Lincoln Ctr Plz' => '10 Lincoln Center Plaza',
165
+ '10 Lincoln Ctr. Plz.' => '10 Lincoln Center Plaza',
166
+ '157 King Arthur Ct' => '157 King Arthur Court',
167
+ '157 King Arthur Ct.' => '157 King Arthur Court',
168
+ '157 King Arthur Crt' => '157 King Arthur Court',
169
+ '157 King Arthur Crt.' => '157 King Arthur Court',
170
+ '680 N Lake Shore Dr' => '680 North Lake Shore Drive',
171
+ '680 N. Lake Shore Dr.' => '680 North Lake Shore Drive',
172
+ '8900 Van Wyck Expy' => '8900 Van Wyck Expressway',
173
+ '8900 Van Wyck Expy.' => '8900 Van Wyck Expressway',
174
+ '8900 Van Wyck Expw' => '8900 Van Wyck Expressway',
175
+ '8900 Van Wyck Expw.' => '8900 Van Wyck Expressway',
176
+ '8900 Van Wyck Expressw' => '8900 Van Wyck Expressway',
177
+ '8900 Van Wyck Expressw.' => '8900 Van Wyck Expressway',
178
+ '837 E Magical Frwy' => '837 East Magical Freeway',
179
+ '837 E. Magical Frwy.' => '837 East Magical Freeway',
180
+ '750 W Sunrise Hwy' => '750 West Sunrise Highway',
181
+ '750 W. Sunrise Hwy.' => '750 West Sunrise Highway',
182
+ '9264 Penny Ln' => '9264 Penny Lane',
183
+ '9264 Penny Ln.' => '9264 Penny Lane',
184
+ '10099 Ridge Gate Pky Ste 200' => '10099 Ridge Gate Parkway Suite 200',
185
+ '10099 Ridge Gate Pky. Ste. 200' => '10099 Ridge Gate Parkway Suite 200',
186
+ '10099 Ridge Gate Pkw Suite 200' => '10099 Ridge Gate Parkway Suite 200',
187
+ '10099 Ridge Gate Pkw. Suite 200' => '10099 Ridge Gate Parkway Suite 200',
188
+ '10099 Ridge Gate Pkwy Suite 200' => '10099 Ridge Gate Parkway Suite 200',
189
+ '10099 Ridge Gate Pkwy. Suite 200' => '10099 Ridge Gate Parkway Suite 200',
190
+ '10099 Ridge Gate Prkwy Suite 200' => '10099 Ridge Gate Parkway Suite 200',
191
+ '10099 Ridge Gate Prkwy. Suite 200' => '10099 Ridge Gate Parkway Suite 200',
192
+ "5137 Zebulon's Pk" => "5137 Zebulon's Pike",
193
+ "5137 Zebulon's Pk." => "5137 Zebulon's Pike",
194
+ "King's Plz" => "King's Plaza",
195
+ "King's Plz." => "King's Plaza",
196
+ '4616 Melrose Pl' => '4616 Melrose Place',
197
+ '4616 Melrose Pl.' => '4616 Melrose Place',
198
+ '93812 S Hightower Rd' => '93812 South Hightower Road',
199
+ '93812 S. Hightower Rd.' => '93812 South Hightower Road',
200
+ '249 NE Rural Rt' => '249 Northeast Rural Route',
201
+ '249 N.E. Rural Rt.' => '249 Northeast Rural Route',
202
+ '249 NE Rural Rte' => '249 Northeast Rural Route',
203
+ '249 N.E. Rural Rte.' => '249 Northeast Rural Route',
204
+ '1 SW Main St' => '1 Southwest Main Street',
205
+ '1 S.W. Main St.' => '1 Southwest Main Street',
206
+ '1935 SE Trpk' => '1935 Southeast Turnpike',
207
+ '1935 S.E. Trpk.' => '1935 Southeast Turnpike',
208
+ '369 NW Army Tr' => '369 Northwest Army Trail',
209
+ '369 N.W. Army Tr.' => '369 Northwest Army Trail'
210
+ }
211
+ _control.keys.each do |_key|
212
+ _key.expand_address_abbreviations.should == _control[_key]
213
+ end
214
+ end
215
+
216
+ it 'converts 24-hour time' do
217
+ '18:20'.to_12_hour_time == '6:20 PM'
218
+ end
219
+
220
+ it 'adds the HTTP-protocol prefix' do
221
+ 'www.seologic.com'.add_http_prefix.should == 'http://www.seologic.com'
222
+ 'ftp.seologic.com'.add_http_prefix.should == 'http://ftp.seologic.com'
223
+ 'ftp://ftp.seologic.com'.add_http_prefix.should == 'ftp://ftp.seologic.com'
224
+ 'http://'.add_http_prefix.should == 'http://'
225
+ end
226
+
227
+ it 'detects HTTP URLs' do
228
+ 'http://www.seologic.com/'.valid_http_url?.should be_true
229
+ 'https://www.seologic.com/'.valid_http_url?.should be_true
230
+ 'www.seologic.com'.valid_http_url?.should be_false
231
+ end
232
+
233
+ it 'detects trailing slashes' do
234
+ 'www.seologic.com'.has_http?.should be_false
235
+ 'www.seologic.com/'.has_trailing_slash?.should be_true
236
+ 'www.seologic.com/index'.has_trailing_slash?.should be_false
237
+ 'www.seologic.com/users/'.has_trailing_slash?.should be_true
238
+ end
239
+
240
+ it 'detects page URLs' do
241
+ 'www.seologic.com'.is_page?.should be_false
242
+ 'www.seologic.com/index'.is_page?.should be_false
243
+ 'www.seologic.com/index.cgi'.is_page?.should be_false
244
+ 'www.seologic.com/index.htm'.is_page?.should be_true
245
+ 'www.seologic.com/index.html'.is_page?.should be_true
246
+ end
247
+
248
+ it 'returns the host of a valid URI string' do
249
+ 'www.seologic.com'.to_host.should be_nil
250
+ 'www.seologic.com/webmaster-tools/link-popularity-check.php'.to_host.should be_nil
251
+ 'http://www.seologic.com'.to_host.should == 'www.seologic.com'
252
+ 'http://www.seologic.com/webmaster-tools/link-popularity-check.php'.to_host.should == 'www.seologic.com'
253
+ end
254
+
255
+ it 'converts a URI string to a URI object' do
256
+ 'www.seologic.com'.to_uri.is_a?(URI).should be_true
257
+ 'http://www.seologic.com'.to_uri.is_a?(URI::HTTP).should be_true
258
+ 'SEO Logic'.to_uri.should be_nil
259
+ end
260
+
261
+ it 'detects a valid HTTP URL' do
262
+ 'www.seologic.com'.valid_http_url?.should be_false
263
+ 'http://'.valid_http_url?.should be_false
264
+ 'http://www.seologic.com'.valid_http_url?.should be_true
265
+ 'http://http://www.seologic.com'.valid_http_url?.should be_false
266
+ 'SEO Logic'.valid_http_url?.should be_false
267
+ end
268
+
269
+ end