Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce StimulusReflex::HTML::Document, featuring Nokogiri::HTML5 #601

Merged
4 changes: 3 additions & 1 deletion lib/stimulus_reflex.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
require "stimulus_reflex/concern_enhancer"
require "stimulus_reflex/configuration"
require "stimulus_reflex/callbacks"
require "stimulus_reflex/fragment"
require "stimulus_reflex/html/part"
require "stimulus_reflex/html/fragment"
require "stimulus_reflex/html/document"
require "stimulus_reflex/request_parameters"
require "stimulus_reflex/reflex"
require "stimulus_reflex/reflex_data"
Expand Down
3 changes: 0 additions & 3 deletions lib/stimulus_reflex/broadcasters/broadcaster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ class Broadcaster
attr_reader :reflex, :cable_ready, :logger, :operations
delegate :permanent_attribute_name, :payload, to: :reflex

DEFAULT_HTML_WITHOUT_FORMAT = Nokogiri::XML::Node::SaveOptions::DEFAULT_HTML &
~Nokogiri::XML::Node::SaveOptions::FORMAT

def initialize(reflex)
@reflex = reflex
@logger = Rails.logger if defined?(Rails.logger)
Expand Down
4 changes: 2 additions & 2 deletions lib/stimulus_reflex/broadcasters/page_broadcaster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ module StimulusReflex
class PageBroadcaster < Broadcaster
def broadcast(selectors, data)
reflex.controller.process reflex.params[:action]
fragment = StimulusReflex::Fragment.new(reflex.controller.response.body)
fragment = StimulusReflex::HTML::Document.new(reflex.controller.response.body)

return if fragment.empty?

selectors = selectors.select { |s| fragment.match(s).present? }
selectors.each do |selector|
operations << [selector, StimulusReflex.config.morph_operation]
html = fragment.match(selector).to_html
html = fragment.match(selector).inner_html
cable_ready.send StimulusReflex.config.morph_operation, {
selector: selector,
html: html,
Expand Down
6 changes: 3 additions & 3 deletions lib/stimulus_reflex/broadcasters/selector_broadcaster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ def broadcast(_, data = {})
selectors, html = morph
updates = create_update_collection(selectors, html)
updates.each do |update|
fragment = StimulusReflex::Fragment.new(update.html)
fragment = StimulusReflex::HTML::Fragment.new(update.html)
match = fragment.match(update.selector)
if match.present?
operations << [update.selector, StimulusReflex.config.morph_operation]
cable_ready.send StimulusReflex.config.morph_operation, {
selector: update.selector,
html: match.to_html,
html: match.inner_html,
payload: payload,
children_only: true,
permanent_attribute_name: permanent_attribute_name,
Expand All @@ -23,7 +23,7 @@ def broadcast(_, data = {})
operations << [update.selector, StimulusReflex.config.replace_operation]
cable_ready.send StimulusReflex.config.replace_operation, {
selector: update.selector,
html: fragment.to_html,
html: fragment.inner_html,
payload: payload,
stimulus_reflex: data.merge(morph: to_sym)
}
Expand Down
30 changes: 0 additions & 30 deletions lib/stimulus_reflex/fragment.rb

This file was deleted.

9 changes: 9 additions & 0 deletions lib/stimulus_reflex/html/document.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module StimulusReflex
module HTML
class Document < Part
def nokogiri_parser(html)
Nokogiri::HTML5::Document.parse(html)
end
end
end
end
11 changes: 11 additions & 0 deletions lib/stimulus_reflex/html/fragment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module StimulusReflex
module HTML
class Fragment < Part
def nokogiri_parser(html)
Nokogiri::HTML.fragment(html)
marcoroth marked this conversation as resolved.
Show resolved Hide resolved
end
end
end
end
58 changes: 58 additions & 0 deletions lib/stimulus_reflex/html/part.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

module StimulusReflex
module HTML
class Part
DEFAULT_HTML_WITHOUT_FORMAT = Nokogiri::XML::Node::SaveOptions::DEFAULT_HTML & ~Nokogiri::XML::Node::SaveOptions::FORMAT

delegate :element, to: :@fragment

def to_html
@fragment.to_html(save_with: DEFAULT_HTML_WITHOUT_FORMAT)
end

def outer_html
@fragment.to_html(save_with: DEFAULT_HTML_WITHOUT_FORMAT)
end

def inner_html
@fragment.inner_html(save_with: DEFAULT_HTML_WITHOUT_FORMAT)
end

def nokogiri_parser(html)
raise "not implemented"
end

def initialize(html)
@fragment = nokogiri_parser(html.to_s)
@matches = {
"body" => Match.new(@fragment.at_css("body"))
}
end

def empty?
@fragment.content.empty?
end

def match(selector)
@matches[selector] ||= Match.new(@fragment.at_css(selector))
end

Match = Struct.new(:element) do
delegate :present?, to: :element

def outer_html
element&.to_html(save_with: DEFAULT_HTML_WITHOUT_FORMAT)
end

def to_html
element&.to_html(save_with: DEFAULT_HTML_WITHOUT_FORMAT)
end

def inner_html
element&.inner_html(save_with: DEFAULT_HTML_WITHOUT_FORMAT)
end
end
end
end
end
128 changes: 128 additions & 0 deletions test/html/document_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# frozen_string_literal: true

require_relative "../test_helper"

class StimulusReflex::HTML::DocumentTest < ActiveSupport::TestCase
test "should handle nil" do
document = StimulusReflex::HTML::Document.new(nil)

assert_equal "<html><head></head><body></body></html>", document.to_html
assert_equal "<html><head></head><body></body></html>", document.outer_html
assert_equal "<html><head></head><body></body></html>", document.inner_html
end

test "should handle empty string" do
document = StimulusReflex::HTML::Document.new("")

assert_equal "<html><head></head><body></body></html>", document.to_html
assert_equal "<html><head></head><body></body></html>", document.outer_html
assert_equal "<html><head></head><body></body></html>", document.inner_html
end

test "should accept fragement and build whole document of HTML" do
raw_html = <<-HTML
<div id="container">
<h1 id="title">Home#index</h1>
</div>
HTML

document = StimulusReflex::HTML::Document.new(raw_html)

inner_title = "Home#index"
outer_title = "<h1 id=\"title\">Home#index</h1>"
inner_container = outer_title
outer_container = "<div id=\"container\"> #{inner_container} </div>"
inner_body = outer_container
outer_body = "<body>#{inner_body} </body>"
whole_document = "<html><head></head>#{outer_body}</html>"

assert_equal outer_body, document.match("body").to_html.squish
assert_equal outer_container, document.match("#container").to_html.squish
assert_equal outer_title, document.match("#title").to_html.squish

assert_equal whole_document, document.to_html.squish
assert_equal whole_document, document.inner_html.squish
assert_equal whole_document, document.outer_html.squish

assert_equal inner_body, document.match("body").inner_html.squish
assert_equal outer_body, document.match("body").outer_html.squish

assert_equal inner_container, document.match("#container").inner_html.squish
assert_equal outer_container, document.match("#container").outer_html.squish

assert_equal outer_title, document.match("#title").outer_html.squish
assert_equal inner_title, document.match("#title").inner_html.squish
end

test "should extract body of a document" do
raw_html = <<-HTML
<body id="body">
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>
</body>
HTML

document = StimulusReflex::HTML::Document.new(raw_html)

inner_body = "<h1>Home#index</h1> <p>Find me in app/views/home/index.html.erb</p>"
outer_body = "<body id=\"body\"> #{inner_body} </body>"
whole_document = "<html><head></head>#{outer_body}</html>"

assert_equal whole_document, document.to_html.squish
assert_equal whole_document, document.outer_html.squish
assert_equal whole_document, document.inner_html.squish

assert_equal outer_body, document.match("body").outer_html.squish
assert_equal outer_body, document.match("#body").outer_html.squish

assert_equal inner_body, document.match("#body").inner_html.squish
assert_equal inner_body, document.match("body").inner_html.squish

assert_equal outer_body, document.match("body").to_html.squish
assert_equal outer_body, document.match("#body").to_html.squish
end

test "should extract whole HTML document" do
raw_body = <<-BODY
<body id="body">
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>
</body>
BODY

raw_html = <<-HTML
<!DOCTYPE html>
<html>
<head>
<title>StimulusReflex Test</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="token" />
<link rel="stylesheet" href="/assets/application.css" data-turbo-track="reload" />
<script src="/assets/application.js" data-turbo-track="reload" defer="defer"></script>
</head>

#{raw_body}
</html>
HTML

inner_body = "<h1>Home#index</h1> <p>Find me in app/views/home/index.html.erb</p>"
outer_body = "<body id=\"body\"> #{inner_body} </body>"
expected_html = "<!DOCTYPE html><html><head> <title>StimulusReflex Test</title> <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"> <meta name=\"csrf-param\" content=\"authenticity_token\"> <meta name=\"csrf-token\" content=\"token\"> <link rel=\"stylesheet\" href=\"/assets/application.css\" data-turbo-track=\"reload\"> <script src=\"/assets/application.js\" data-turbo-track=\"reload\" defer=\"defer\"></script> </head> #{outer_body}</html>"

document = StimulusReflex::HTML::Document.new(raw_html)

assert_equal expected_html, document.to_html.squish
assert_equal expected_html, document.outer_html.squish
assert_equal expected_html, document.inner_html.squish

assert_equal outer_body, document.match("body").outer_html.squish
assert_equal outer_body, document.match("#body").outer_html.squish

assert_equal inner_body, document.match("body").inner_html.squish
assert_equal inner_body, document.match("#body").inner_html.squish

assert_equal outer_body, document.match("body").to_html.squish
assert_equal outer_body, document.match("#body").to_html.squish
end
end
Loading