SlideShare a Scribd company logo
WEWLJCIO - WORKING
EFFECTIVELY WITH LEGACY
JAVASCRIPT CODE IN OPAL
FORREST CHANG
FKCHANG2000@YAHOO.COM
BACKGOUND - OPAL-HOT-RELOADER
Hot reloader for
Hot loaded code
I merged a pull request that added hot css reloading
Opal
THE NEW CODE
Worked
But in Javascript
No tests
:(
Want to extend code
Want it to be easier to maintain
REAL LIFE REFACTORING
Real life better than canned examples
Need tests
Want to be in Ruby/Opal
BY MICHAEL
FEATHERS
WORKING EFFECTIVELY WITH LEGACY CODE
Similar situation
"Legacy code" == code w/o tests
How to put tests on something w/o tests
But also a little different
Want to convert languages
Subject to the predefined browser API and a particular way of
interacting with it
THE SUBMITTED CODE
Added a new if clause to OpalHotReloader#reload() to handle
CSS hot reloading
Implemented in Javascript via Opal x-string.
ORIGINAL RELOAD() METHOD
def reload(e)
# original code
reload_request = JSON.parse(`e.data`)
if reload_request[:type] == "ruby"
puts "Reloading ruby #{reload_request[:filename]}"
eval reload_request[:source_code]
if @reload_post_callback
@reload_post_callback.call
else
puts "not reloading code"
end
end
ADDED IF CODE TO RELOAD() METHOD
# the new css hot reloading code
if reload_request[:type] == "css"
url = reload_request[:url]
puts "Reloading CSS: #{url}"
# Work outsources Javascript via x-string
%x{
var toAppend = "t_hot_reload=" + (new Date()).getTime();
var links = document.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) {
if (link.href.indexOf("?") === -1) {
link.href += "?" + toAppend;
} else {
if (link.href.indexOf("t_hot_reload") === -1) {
link.href += "&" + toAppend;
} else {
link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend)
}
}
}
}
}
end
end
OBSERVATIONS
reload() used to be small
Now 3x the size
Has an additional responsiblity
REFACTORING
Extract Method to handle new responsibilty
Extract class to hold that responsibilty (SRP)
Delegate to instance new class
New instance @css_reloader to be created in initialize method
REFACTORED RELOAD()
def reload(e)
reload_request = JSON.parse(`e.data`)
if reload_request[:type] == "ruby"
puts "Reloading ruby #{reload_request[:filename]}"
eval reload_request[:source_code]
if @reload_post_callback
@reload_post_callback.call
else
puts "not reloading code"
end
end
if reload_request[:type] == "css"
@css_reloader.reload(reload_request) # extracted method called here
end
end
THE NEW CSSRELOADER CLASS
class OpalHotReloader
class CssReloader
def reload(reload_request)
url = reload_request[:url]
%x{
var toAppend = "t_hot_reload=" + (new Date()).getTime();
var links = document.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) {
if (link.href.indexOf("?") === -1) {
link.href += "?" + toAppend;
} else {
if (link.href.indexOf("t_hot_reload") === -1) {
link.href += "&" + toAppend;
} else {
link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend)
}
}
}
}
}
end
end
end
TESTING THE SOLUTION
No automated tests yet.
Test manually
PITA
New code works!
WRITING AUTOMATED TESTS
Refactored works, we can now add automated tests
Using - minitest also available for those who don't like rspecopal-rspec
A PROBLEM
Technique to update css involves manipulating style links in the global
document object
Could
Create links in the actual DOM of the spec runner (not hard)
But don't like the non transparency of this
Don't like code that calls out directly to global document
Would be nice to be able to inject a test document
DEPENDENCY INJECTION
Desired outcome: Inject test doc for test, inject the real document for
application
Don't need standard dependency injection methods (constructor, setter,
interface)
Able to just pass document as a parameter
MORE ISSUES FOR DOCUMENT TEST DOUBLE
Limited options for hot reloading of CSS - have to do it a certain way
document interface not under my control - must match it
Stubbing Javascript objects in Opal
Opal/JS interface a problem here - Opal objects different
Opal-rspec has powerful mock/stub capability, but only Opal objects
Need to create own method
2 CONVENIENCE METHODS
Creates javascript objects directly via x-strings
create_link() to create the link DOM object that will get altered to
facillitate the css hot reloading and
fake_links_document() a convenience method which returns both a
test double for global document object, which responds to the
getElementsByTagName('link') call and a test double for the link
itself, that I will inspect to see whether it has been correctly altered.
CODE
def create_link( href)
%x|
var ss = document.createElement("link");
ss.type = "text/css";
ss.rel = "stylesheet";
ss.href = #{href};
return ss;
|
end
def fake_links_document(href)
link = create_link(href)
doc = `{ getElementsByTagName: function(name) { links = [ #{link}]; return links;}}`
{ link: link, document: doc}
end
ADD DOCUMENT TO RELOAD() SIGNATURE
# change from
def reload(reload_request)
# to
def reload(reload_request, document)
CALL WITH THE NEW SIGNATURE
# in OpalHotReloader#reload()
# instead of calling it this way
@css_reloader.reload(reload_request)
# we pass in the real browser document
@css_reloader.reload(reload_request, `document`)
MODIFY CSSRELOADER TO TAKE
DOCUMENT
class OpalHotReloader
class CssReloader
def reload(reload_request, document) # pass in the "document"
url = reload_request[:url]
%x{
var toAppend = "t_hot_reload=" + (new Date()).getTime();
// invoke it here
var links = #{document}.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) {
if (link.href.indexOf("?") === -1) {
link.href += "?" + toAppend;
} else {
if (link.href.indexOf("t_hot_reload") === -1) {
link.href += "&" + toAppend;
} else {
link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend)
}
}
}
}
}
end
end
end
TESTING SIGNATURE CHANGE
Must hand test again
Works!
REQUIRED TEST CASES
A plain stylesheet link where we add the hot reload argument for the first
time.
Updating a link that has already been updated with a hot reload argument.
Appending an additional hot reload argument parameter to a stylesheet
link that already has a parameter.
WRITING SPECS FOR THESE CASES
require 'native'
require 'opal_hot_reloader'
require 'opal_hot_reloader/css_reloader'
describe OpalHotReloader::CssReloader do
def create_link( href)
%x|
var ss = document.createElement("link");
ss.type = "text/css";
ss.rel = "stylesheet";
ss.href = #{href};
return ss;
|
end
def fake_links_document(href)
link = create_link(href)
doc = `{ getElementsByTagName: function(name) { links = [ #{link}]; return links;}}`
{ link: link, document: doc}
end
context 'Rack::Sass::Plugin' do
it 'should add t_hot_reload to a css path' do
css_path = 'stylesheets/base.css'
doubles = fake_links_document(css_path)
link = Native(doubles[:link])
expect(link[:href]).to match /#{Regexp.escape(css_path)}$/
subject.reload({ url: css_path}, doubles[:document])
expect(link[:href]).to match /#{Regexp.escape(css_path)}?t_hot_reload=d+/
end
PAGE 2
it 'should update t_hot_reload argument if there is one already' do
css_path = 'stylesheets/base.css?t_hot_reload=1111111111111'
doubles = fake_links_document(css_path)
link = Native(doubles[:link])
expect(link[:href]).to match /#{Regexp.escape(css_path)}$/
subject.reload({ url: css_path}, doubles[:document])
expect(link[:href]).to match /#{Regexp.escape('stylesheets/base.css?t_hot_reload=')
}(d)+/
expect($1).to_not eq '1111111111111'
end
it 'should append t_hot_reload if there are existing arguments' do
css_path = 'stylesheets/base.css?some-arg=1'
doubles = fake_links_document(css_path)
link = Native(doubles[:link])
expect(link[:href]).to match /#{Regexp.escape(css_path)}$/
subject.reload({ url: css_path}, doubles[:document])
expect(link[:href]).to match /#{Regexp.escape(css_path)}&t_hot_reload=(d)+/
end
end
end
SPECS PASS - SAFE TO REFACTOR
Added automated test coverage for the 3 cases
Now safe to rewrite the reload() method in Ruby/Opal
Spec provide safety net to prove we don't break the desired functionality
A trick - have reload() delegate to reload_ruby() reload_js() to
have both code side by side - handy for development and debugging
THE TRICK
require 'native'
class OpalHotReloader
class CssReloader
def reload(reload_request, document)
# currently using the Ruby version
reload_ruby(reload_request, document)
# reload_js(reload_request, document)
end
def reload_ruby(reload_request, document)
url = reload_request[:url]
puts "Reloading CSS: #{url}"
to_append = "t_hot_reload=#{Time.now.to_i}"
links = Native(`document.getElementsByTagName("link")`)
(0..links.length-1).each { |i|
link = links[i]
if link.rel == 'stylesheet' && link.href.index(url)
if link.href !~ /?/
link.href += "?#{to_append}"
else
if link.href !~ /t_hot_reload/
link.href += "&#{to_append}"
else
link.href = link.href.sub(/t_hot_reload=d{13}/, to_append)
end
end
end
}
end
PAGE 2
def reload_js(reload_request, document)
url = reload_request[:url]
%x{
var toAppend = "t_hot_reload=" + (new Date()).getTime();
var links = #{document}.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) {
if (link.href.indexOf("?") === -1) {
link.href += "?" + toAppend;
} else {
if (link.href.indexOf("t_hot_reload") === -1) {
link.href += "&" + toAppend;
} else {
link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend)
}
}
}
}
}
end
end
end
IMPLEMENTATION
Ruby version - a line by line translation
Specs passed
Can remove Javascript version
OPAL ONLY CSSRELOADER
require 'native'
class OpalHotReloader
class CssReloader
def reload(reload_request, document)
url = reload_request[:url]
puts "Reloading CSS: #{url}"
to_append = "t_hot_reload=#{Time.now.to_i}"
links = Native(`document.getElementsByTagName("link")`)
(0..links.length-1).each { |i|
link = links[i]
if link.rel == 'stylesheet' && link.href.index(url)
if link.href !~ /?/
link.href += "?#{to_append}"
else
if link.href !~ /t_hot_reload/
link.href += "&#{to_append}"
else
link.href = link.href.sub(/t_hot_reload=d{13}/, to_append)
end
end
end
}
end
end
end
ONWARD
Now have specs
Converted code to Ruby
Much easier to implment hot reloading of Rails CSS/SASS
Already did it, it was easy, used TDD for that
LINKS TO BLOG POST
Blogger - code syntax highlighted
Medium - prettier but no syntax highlighting
http://funkworks.blogspot.com/2016/06/wewljcio-working-effectively-
with.html
https://medium.com/@fkchang2000/wewljcio-working-effectively-with-
legacy-javascript-code-in-opal-4fd624693de4
FIN
Questions?

More Related Content

PDF
Apache Click
오석 한
 
PPTX
Parsing strange v3
Hal Stern
 
PDF
SQLite in Adobe AIR
Peter Elst
 
PDF
DBD::SQLite
charsbar
 
PPTX
Python Code Camp for Professionals 1/4
DEVCON
 
PDF
Nativescript angular
Christoffer Noring
 
PPT
More Asp
guest4c97670
 
PDF
Knowledge is Power: Getting out of trouble by understanding Git - Steve Smith...
Codemotion
 
Apache Click
오석 한
 
Parsing strange v3
Hal Stern
 
SQLite in Adobe AIR
Peter Elst
 
DBD::SQLite
charsbar
 
Python Code Camp for Professionals 1/4
DEVCON
 
Nativescript angular
Christoffer Noring
 
More Asp
guest4c97670
 
Knowledge is Power: Getting out of trouble by understanding Git - Steve Smith...
Codemotion
 

What's hot (20)

ODP
Introduction to Angular js
Mustafa Gamal
 
KEY
JavaScript Testing for Rubyists
Jamie Dyer
 
PDF
Going Beyond LAMP Again - Manchester WordPress User Group
Tim Nash
 
PPTX
How to perform debounce in react
BOSC Tech Labs
 
PDF
Hidden Treasures in Project Wonder
WO Community
 
PDF
Operacion Guinda 2
Red RADAR
 
PDF
Demo how to create visualforce and apex controller to update, delete custom o...
tuan vo
 
PPTX
Python and EM CLI: The Enterprise Management Super Tools
Seth Miller
 
PDF
Merrill's Journey to CI-CD and Continuous Testing by Ashish Mukherjee
Sauce Labs
 
PDF
Adapters db-104-informixstoredprocedure
prathap kumar
 
DOC
Cis407 a ilab 5 web application development devry university
lhkslkdh89009
 
PDF
4 introduction-php-mvc-cakephp-m4-controllers-slides
MasterCode.vn
 
PPTX
Introduction to windows power shell in sharepoint 2010
Binh Nguyen
 
PPTX
Build Lightweight Web Module
Morgan Cheng
 
PPT
Google Bot Herding, PageRank Sculpting and Manipulation
David Degrelle - Consultant SEO Expert
 
PDF
Knowledge is Power: Getting out of trouble by understanding Git - Steve Smith...
Codemotion
 
PDF
Me and my importers
Donny Wals
 
PDF
Hardcore URL Routing for WordPress - WordCamp Atlanta 2014
Mike Schinkel
 
PDF
Node.js: scalability tips - Azure Dev Community Vijayawada
Luciano Mammino
 
PDF
PowerShell Tips & Tricks for Exchange
Michel de Rooij
 
Introduction to Angular js
Mustafa Gamal
 
JavaScript Testing for Rubyists
Jamie Dyer
 
Going Beyond LAMP Again - Manchester WordPress User Group
Tim Nash
 
How to perform debounce in react
BOSC Tech Labs
 
Hidden Treasures in Project Wonder
WO Community
 
Operacion Guinda 2
Red RADAR
 
Demo how to create visualforce and apex controller to update, delete custom o...
tuan vo
 
Python and EM CLI: The Enterprise Management Super Tools
Seth Miller
 
Merrill's Journey to CI-CD and Continuous Testing by Ashish Mukherjee
Sauce Labs
 
Adapters db-104-informixstoredprocedure
prathap kumar
 
Cis407 a ilab 5 web application development devry university
lhkslkdh89009
 
4 introduction-php-mvc-cakephp-m4-controllers-slides
MasterCode.vn
 
Introduction to windows power shell in sharepoint 2010
Binh Nguyen
 
Build Lightweight Web Module
Morgan Cheng
 
Google Bot Herding, PageRank Sculpting and Manipulation
David Degrelle - Consultant SEO Expert
 
Knowledge is Power: Getting out of trouble by understanding Git - Steve Smith...
Codemotion
 
Me and my importers
Donny Wals
 
Hardcore URL Routing for WordPress - WordCamp Atlanta 2014
Mike Schinkel
 
Node.js: scalability tips - Azure Dev Community Vijayawada
Luciano Mammino
 
PowerShell Tips & Tricks for Exchange
Michel de Rooij
 
Ad

More from Forrest Chang (11)

PDF
Crystal is a Rubyists friend (quick anecdote)
Forrest Chang
 
PDF
Making terminal based apps w:ruby
Forrest Chang
 
PDF
Opal-hot-reloader
Forrest Chang
 
PDF
Ruby-ying Javascript: Avoiding jQuery Spaghetti
Forrest Chang
 
PDF
Rubyconf 2014 recap
Forrest Chang
 
PDF
6 reasons Jubilee could be a Rubyist's new best friend
Forrest Chang
 
PDF
Opal a new_hope
Forrest Chang
 
PDF
Opal chapter 4_a_new_hope
Forrest Chang
 
PDF
Data Intensive RIAs on Rails with very little code (Netzke)
Forrest Chang
 
PDF
Rubyconf2012 recap
Forrest Chang
 
KEY
Opal - Ruby Style!! Ruby in the browser
Forrest Chang
 
Crystal is a Rubyists friend (quick anecdote)
Forrest Chang
 
Making terminal based apps w:ruby
Forrest Chang
 
Opal-hot-reloader
Forrest Chang
 
Ruby-ying Javascript: Avoiding jQuery Spaghetti
Forrest Chang
 
Rubyconf 2014 recap
Forrest Chang
 
6 reasons Jubilee could be a Rubyist's new best friend
Forrest Chang
 
Opal a new_hope
Forrest Chang
 
Opal chapter 4_a_new_hope
Forrest Chang
 
Data Intensive RIAs on Rails with very little code (Netzke)
Forrest Chang
 
Rubyconf2012 recap
Forrest Chang
 
Opal - Ruby Style!! Ruby in the browser
Forrest Chang
 
Ad

Recently uploaded (20)

PPTX
Can You Build Dashboards Using Open Source Visualization Tool.pptx
Varsha Nayak
 
PPTX
Odoo Integration Services by Candidroot Solutions
CandidRoot Solutions Private Limited
 
PDF
Adobe Illustrator Crack Full Download (Latest Version 2025) Pre-Activated
imang66g
 
PDF
New Download FL Studio Crack Full Version [Latest 2025]
imang66g
 
PPTX
AI-Ready Handoff: Auto-Summaries & Draft Emails from MQL to Slack in One Flow
bbedford2
 
PDF
What to consider before purchasing Microsoft 365 Business Premium_PDF.pdf
Q-Advise
 
PDF
vAdobe Premiere Pro 2025 (v25.2.3.004) Crack Pre-Activated Latest
imang66g
 
PPTX
Visualising Data with Scatterplots in IBM SPSS Statistics.pptx
Version 1 Analytics
 
PDF
An Experience-Based Look at AI Lead Generation Pricing, Features & B2B Results
Thomas albart
 
PDF
Appium Automation Testing Tutorial PDF: Learn Mobile Testing in 7 Days
jamescantor38
 
PDF
New Download MiniTool Partition Wizard Crack Latest Version 2025
imang66g
 
PPTX
Web Testing.pptx528278vshbuqffqhhqiwnwuq
studylike474
 
PDF
On Software Engineers' Productivity - Beyond Misleading Metrics
Romén Rodríguez-Gil
 
PPTX
ASSIGNMENT_1[1][1][1][1][1] (1) variables.pptx
kr2589474
 
PPTX
The-Dawn-of-AI-Reshaping-Our-World.pptxx
parthbhanushali307
 
PDF
Balancing Resource Capacity and Workloads with OnePlan – Avoid Overloading Te...
OnePlan Solutions
 
PDF
Key Features to Look for in Arizona App Development Services
Net-Craft.com
 
PPT
Activate_Methodology_Summary presentatio
annapureddyn
 
PDF
49784907924775488180_LRN2959_Data_Pump_23ai.pdf
Abilash868456
 
PDF
Teaching Reproducibility and Embracing Variability: From Floating-Point Exper...
University of Rennes, INSA Rennes, Inria/IRISA, CNRS
 
Can You Build Dashboards Using Open Source Visualization Tool.pptx
Varsha Nayak
 
Odoo Integration Services by Candidroot Solutions
CandidRoot Solutions Private Limited
 
Adobe Illustrator Crack Full Download (Latest Version 2025) Pre-Activated
imang66g
 
New Download FL Studio Crack Full Version [Latest 2025]
imang66g
 
AI-Ready Handoff: Auto-Summaries & Draft Emails from MQL to Slack in One Flow
bbedford2
 
What to consider before purchasing Microsoft 365 Business Premium_PDF.pdf
Q-Advise
 
vAdobe Premiere Pro 2025 (v25.2.3.004) Crack Pre-Activated Latest
imang66g
 
Visualising Data with Scatterplots in IBM SPSS Statistics.pptx
Version 1 Analytics
 
An Experience-Based Look at AI Lead Generation Pricing, Features & B2B Results
Thomas albart
 
Appium Automation Testing Tutorial PDF: Learn Mobile Testing in 7 Days
jamescantor38
 
New Download MiniTool Partition Wizard Crack Latest Version 2025
imang66g
 
Web Testing.pptx528278vshbuqffqhhqiwnwuq
studylike474
 
On Software Engineers' Productivity - Beyond Misleading Metrics
Romén Rodríguez-Gil
 
ASSIGNMENT_1[1][1][1][1][1] (1) variables.pptx
kr2589474
 
The-Dawn-of-AI-Reshaping-Our-World.pptxx
parthbhanushali307
 
Balancing Resource Capacity and Workloads with OnePlan – Avoid Overloading Te...
OnePlan Solutions
 
Key Features to Look for in Arizona App Development Services
Net-Craft.com
 
Activate_Methodology_Summary presentatio
annapureddyn
 
49784907924775488180_LRN2959_Data_Pump_23ai.pdf
Abilash868456
 
Teaching Reproducibility and Embracing Variability: From Floating-Point Exper...
University of Rennes, INSA Rennes, Inria/IRISA, CNRS
 

Working Effectively with Legacy Javascript code in Opal

  • 1. WEWLJCIO - WORKING EFFECTIVELY WITH LEGACY JAVASCRIPT CODE IN OPAL FORREST CHANG [email protected]
  • 2. BACKGOUND - OPAL-HOT-RELOADER Hot reloader for Hot loaded code I merged a pull request that added hot css reloading Opal
  • 3. THE NEW CODE Worked But in Javascript No tests :( Want to extend code Want it to be easier to maintain
  • 4. REAL LIFE REFACTORING Real life better than canned examples Need tests Want to be in Ruby/Opal
  • 5. BY MICHAEL FEATHERS WORKING EFFECTIVELY WITH LEGACY CODE Similar situation "Legacy code" == code w/o tests How to put tests on something w/o tests But also a little different Want to convert languages Subject to the predefined browser API and a particular way of interacting with it
  • 6. THE SUBMITTED CODE Added a new if clause to OpalHotReloader#reload() to handle CSS hot reloading Implemented in Javascript via Opal x-string.
  • 7. ORIGINAL RELOAD() METHOD def reload(e) # original code reload_request = JSON.parse(`e.data`) if reload_request[:type] == "ruby" puts "Reloading ruby #{reload_request[:filename]}" eval reload_request[:source_code] if @reload_post_callback @reload_post_callback.call else puts "not reloading code" end end
  • 8. ADDED IF CODE TO RELOAD() METHOD # the new css hot reloading code if reload_request[:type] == "css" url = reload_request[:url] puts "Reloading CSS: #{url}" # Work outsources Javascript via x-string %x{ var toAppend = "t_hot_reload=" + (new Date()).getTime(); var links = document.getElementsByTagName("link"); for (var i = 0; i < links.length; i++) { var link = links[i]; if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) { if (link.href.indexOf("?") === -1) { link.href += "?" + toAppend; } else { if (link.href.indexOf("t_hot_reload") === -1) { link.href += "&" + toAppend; } else { link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend) } } } } } end end
  • 9. OBSERVATIONS reload() used to be small Now 3x the size Has an additional responsiblity
  • 10. REFACTORING Extract Method to handle new responsibilty Extract class to hold that responsibilty (SRP) Delegate to instance new class New instance @css_reloader to be created in initialize method
  • 11. REFACTORED RELOAD() def reload(e) reload_request = JSON.parse(`e.data`) if reload_request[:type] == "ruby" puts "Reloading ruby #{reload_request[:filename]}" eval reload_request[:source_code] if @reload_post_callback @reload_post_callback.call else puts "not reloading code" end end if reload_request[:type] == "css" @css_reloader.reload(reload_request) # extracted method called here end end
  • 12. THE NEW CSSRELOADER CLASS class OpalHotReloader class CssReloader def reload(reload_request) url = reload_request[:url] %x{ var toAppend = "t_hot_reload=" + (new Date()).getTime(); var links = document.getElementsByTagName("link"); for (var i = 0; i < links.length; i++) { var link = links[i]; if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) { if (link.href.indexOf("?") === -1) { link.href += "?" + toAppend; } else { if (link.href.indexOf("t_hot_reload") === -1) { link.href += "&" + toAppend; } else { link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend) } } } } } end end end
  • 13. TESTING THE SOLUTION No automated tests yet. Test manually PITA New code works!
  • 14. WRITING AUTOMATED TESTS Refactored works, we can now add automated tests Using - minitest also available for those who don't like rspecopal-rspec
  • 15. A PROBLEM Technique to update css involves manipulating style links in the global document object Could Create links in the actual DOM of the spec runner (not hard) But don't like the non transparency of this Don't like code that calls out directly to global document Would be nice to be able to inject a test document
  • 16. DEPENDENCY INJECTION Desired outcome: Inject test doc for test, inject the real document for application Don't need standard dependency injection methods (constructor, setter, interface) Able to just pass document as a parameter
  • 17. MORE ISSUES FOR DOCUMENT TEST DOUBLE Limited options for hot reloading of CSS - have to do it a certain way document interface not under my control - must match it Stubbing Javascript objects in Opal Opal/JS interface a problem here - Opal objects different Opal-rspec has powerful mock/stub capability, but only Opal objects Need to create own method
  • 18. 2 CONVENIENCE METHODS Creates javascript objects directly via x-strings create_link() to create the link DOM object that will get altered to facillitate the css hot reloading and fake_links_document() a convenience method which returns both a test double for global document object, which responds to the getElementsByTagName('link') call and a test double for the link itself, that I will inspect to see whether it has been correctly altered.
  • 19. CODE def create_link( href) %x| var ss = document.createElement("link"); ss.type = "text/css"; ss.rel = "stylesheet"; ss.href = #{href}; return ss; | end def fake_links_document(href) link = create_link(href) doc = `{ getElementsByTagName: function(name) { links = [ #{link}]; return links;}}` { link: link, document: doc} end
  • 20. ADD DOCUMENT TO RELOAD() SIGNATURE # change from def reload(reload_request) # to def reload(reload_request, document)
  • 21. CALL WITH THE NEW SIGNATURE # in OpalHotReloader#reload() # instead of calling it this way @css_reloader.reload(reload_request) # we pass in the real browser document @css_reloader.reload(reload_request, `document`)
  • 22. MODIFY CSSRELOADER TO TAKE DOCUMENT class OpalHotReloader class CssReloader def reload(reload_request, document) # pass in the "document" url = reload_request[:url] %x{ var toAppend = "t_hot_reload=" + (new Date()).getTime(); // invoke it here var links = #{document}.getElementsByTagName("link"); for (var i = 0; i < links.length; i++) { var link = links[i]; if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) { if (link.href.indexOf("?") === -1) { link.href += "?" + toAppend; } else { if (link.href.indexOf("t_hot_reload") === -1) { link.href += "&" + toAppend; } else { link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend) } } } } } end end end
  • 23. TESTING SIGNATURE CHANGE Must hand test again Works!
  • 24. REQUIRED TEST CASES A plain stylesheet link where we add the hot reload argument for the first time. Updating a link that has already been updated with a hot reload argument. Appending an additional hot reload argument parameter to a stylesheet link that already has a parameter.
  • 25. WRITING SPECS FOR THESE CASES require 'native' require 'opal_hot_reloader' require 'opal_hot_reloader/css_reloader' describe OpalHotReloader::CssReloader do def create_link( href) %x| var ss = document.createElement("link"); ss.type = "text/css"; ss.rel = "stylesheet"; ss.href = #{href}; return ss; | end def fake_links_document(href) link = create_link(href) doc = `{ getElementsByTagName: function(name) { links = [ #{link}]; return links;}}` { link: link, document: doc} end context 'Rack::Sass::Plugin' do it 'should add t_hot_reload to a css path' do css_path = 'stylesheets/base.css' doubles = fake_links_document(css_path) link = Native(doubles[:link]) expect(link[:href]).to match /#{Regexp.escape(css_path)}$/ subject.reload({ url: css_path}, doubles[:document]) expect(link[:href]).to match /#{Regexp.escape(css_path)}?t_hot_reload=d+/ end
  • 26. PAGE 2 it 'should update t_hot_reload argument if there is one already' do css_path = 'stylesheets/base.css?t_hot_reload=1111111111111' doubles = fake_links_document(css_path) link = Native(doubles[:link]) expect(link[:href]).to match /#{Regexp.escape(css_path)}$/ subject.reload({ url: css_path}, doubles[:document]) expect(link[:href]).to match /#{Regexp.escape('stylesheets/base.css?t_hot_reload=') }(d)+/ expect($1).to_not eq '1111111111111' end it 'should append t_hot_reload if there are existing arguments' do css_path = 'stylesheets/base.css?some-arg=1' doubles = fake_links_document(css_path) link = Native(doubles[:link]) expect(link[:href]).to match /#{Regexp.escape(css_path)}$/ subject.reload({ url: css_path}, doubles[:document]) expect(link[:href]).to match /#{Regexp.escape(css_path)}&t_hot_reload=(d)+/ end end end
  • 27. SPECS PASS - SAFE TO REFACTOR Added automated test coverage for the 3 cases Now safe to rewrite the reload() method in Ruby/Opal Spec provide safety net to prove we don't break the desired functionality A trick - have reload() delegate to reload_ruby() reload_js() to have both code side by side - handy for development and debugging
  • 28. THE TRICK require 'native' class OpalHotReloader class CssReloader def reload(reload_request, document) # currently using the Ruby version reload_ruby(reload_request, document) # reload_js(reload_request, document) end def reload_ruby(reload_request, document) url = reload_request[:url] puts "Reloading CSS: #{url}" to_append = "t_hot_reload=#{Time.now.to_i}" links = Native(`document.getElementsByTagName("link")`) (0..links.length-1).each { |i| link = links[i] if link.rel == 'stylesheet' && link.href.index(url) if link.href !~ /?/ link.href += "?#{to_append}" else if link.href !~ /t_hot_reload/ link.href += "&#{to_append}" else link.href = link.href.sub(/t_hot_reload=d{13}/, to_append) end end end } end
  • 29. PAGE 2 def reload_js(reload_request, document) url = reload_request[:url] %x{ var toAppend = "t_hot_reload=" + (new Date()).getTime(); var links = #{document}.getElementsByTagName("link"); for (var i = 0; i < links.length; i++) { var link = links[i]; if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) { if (link.href.indexOf("?") === -1) { link.href += "?" + toAppend; } else { if (link.href.indexOf("t_hot_reload") === -1) { link.href += "&" + toAppend; } else { link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend) } } } } } end end end
  • 30. IMPLEMENTATION Ruby version - a line by line translation Specs passed Can remove Javascript version
  • 31. OPAL ONLY CSSRELOADER require 'native' class OpalHotReloader class CssReloader def reload(reload_request, document) url = reload_request[:url] puts "Reloading CSS: #{url}" to_append = "t_hot_reload=#{Time.now.to_i}" links = Native(`document.getElementsByTagName("link")`) (0..links.length-1).each { |i| link = links[i] if link.rel == 'stylesheet' && link.href.index(url) if link.href !~ /?/ link.href += "?#{to_append}" else if link.href !~ /t_hot_reload/ link.href += "&#{to_append}" else link.href = link.href.sub(/t_hot_reload=d{13}/, to_append) end end end } end end end
  • 32. ONWARD Now have specs Converted code to Ruby Much easier to implment hot reloading of Rails CSS/SASS Already did it, it was easy, used TDD for that
  • 33. LINKS TO BLOG POST Blogger - code syntax highlighted Medium - prettier but no syntax highlighting http://funkworks.blogspot.com/2016/06/wewljcio-working-effectively- with.html https://medium.com/@fkchang2000/wewljcio-working-effectively-with- legacy-javascript-code-in-opal-4fd624693de4