Implement Unified Embed for CodeSandbox URLs (#15808)

* Implement embed and write specs

* fix slight regex ommision

* remove error over invalid options; add specs to cover

* account for missing options when using embed keyword

* no videos, no ns video_id 🤦🏾‍♀️
This commit is contained in:
Arit Amana 2021-12-17 12:12:54 -05:00 committed by GitHub
parent 4b93684dab
commit dee0b36981
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 72 additions and 44 deletions

View file

@ -1,15 +1,17 @@
class CodesandboxTag < LiquidTagBase
PARTIAL = "liquids/codesandbox".freeze
REGISTRY_REGEXP = %r{https?://(?:www|app\.)?(?:codesandbox\.io/embed/)(?<id>[\w-]{,60})(?:\?)?(?<options>\S+)?}
OPTIONS_REGEXP =
%r{\A(initialpath=([a-zA-Z0-9\-_/.@%])+)\Z|
\A(file=([a-zA-Z0-9\-_/.@%])+)\Z|
\A(module=([a-zA-Z0-9\-_/.@%])+)\Z|
\A(runonclick=((0|1){1}))\Z|
\Aview=(editor|split|preview)\Z}x
def initialize(_tag_name, id, _parse_context)
super
@id = parse_id(id)
@query = parse_options(id)
input = CGI.unescape_html(strip_tags(id))
@id, @query = parse_id_or_url_and_options(input)
end
def render(_context)
@ -24,39 +26,38 @@ class CodesandboxTag < LiquidTagBase
private
def parse_id(input)
def parse_id_or_url_and_options(input)
match = pattern_match_for(input, [REGISTRY_REGEXP])
return [match[:id], parse_options(match[:options]&.split("&"))] if match
id = input.split.first
raise StandardError, "CodeSandbox Error: Invalid ID" unless valid_id?(id)
id
[id, parse_options(extract_options(input))]
end
def valid_id?(id)
id =~ /\A[a-zA-Z0-9\-]{0,60}\Z/
id =~ /\A[\w-]{,60}\Z/
end
def parse_options(input)
def extract_options(input)
_, *options = input.split
options.map { |option| valid_option(option) }.reject(&:nil?)
query = options.join("&")
if query.blank?
query
else
"?#{query}"
end
options
end
# Valid options must start with 'initialpath=' or 'module=' and a string of at least 1 length
# composed of letters, numbers, dashes, underscores, forward slashes, @ signs, periods/dots,
# and % symbols. Invalid options will raise an exception
def valid_option(option)
raise StandardError, "CodeSandbox Error: Invalid options" unless (option =~ OPTIONS_REGEXP)&.zero?
def parse_options(options)
return if options.blank?
option
query = options.filter_map { |option| option if valid_option(option) }.join("&")
query.blank? ? query : "?#{query}"
end
def valid_option(option)
(option =~ OPTIONS_REGEXP)&.zero?
end
end
Liquid::Template.register_tag("codesandbox", CodesandboxTag)
UnifiedEmbed.register(CodesandboxTag, regexp: CodesandboxTag::REGISTRY_REGEXP)

View file

@ -3,6 +3,7 @@ require "rails_helper"
RSpec.describe CodesandboxTag, type: :liquid_tag do
describe "#id" do
let(:valid_id) { "22qaa1wcxr" }
let(:valid_id_with_file) { "68jkdlsaie file=/file.js" }
let(:valid_id_with_initialpath) { "68jkdlsaie initialpath=/initial/path/file.js" }
let(:valid_id_with_module) { "28qvv1wvxr module=/path/to/module.html" }
let(:valid_id_with_view) { "28qvv1wvxr view=editor" }
@ -21,23 +22,17 @@ RSpec.describe CodesandboxTag, type: :liquid_tag do
"43lkjfdauf initialpath=/initial-path/file.js module=/path/to/module.html runonclick=1 view=split"
end
let(:valid_id_with_special_characters) { "68jkfdsasa initialpath=/.@%_- module=-/%@._" }
let(:valid_id_with_valid_and_invalid_options) do
"43lkjfdauf initialpath=/initial-path/file.js fakeoption=/fakeoptionvalue runonclick=1"
end
let(:valid_id_with_only_invalid_options) do
"43lkjfdauf fakeoption1=/fakeoptionvalue fakeoption2=/fakeoptionvalue"
end
let(:bad_ids) do
[
"28qvv1wvxr initialpath=((",
"22qaa1wcxr module=",
"22qaa1wcxr runonclick=",
"22qaa1wcxr runonclick=42836",
"68jkdlsaie initialpath=uses-a-(",
"68jkfdsasa initialpath=/uses-a-semi-colon-;",
"43lkjfdauf module= initialpath=",
"89fadksjhe random=/stuff",
"initialpath=/initial/path/file.js",
"view=foobar",
"view=",
"54jfadslkj module=stuff \"onmouseover='alert(\"XSS\")'",
"42fadfdaf;",
"%&*($#@$&=",
Faker::Alphanumeric.alphanumeric(number: 70),
"%^$#@!",
]
end
@ -51,6 +46,11 @@ RSpec.describe CodesandboxTag, type: :liquid_tag do
expect(liquid.render).to include("<iframe")
end
it "accepts a valid id with file" do
liquid = generate_tag(valid_id_with_file)
expect(liquid.render).to include("<iframe")
end
it "accepts a valid id with initialpath" do
liquid = generate_tag(valid_id_with_initialpath)
expect(liquid.render).to include("<iframe")
@ -111,6 +111,20 @@ RSpec.describe CodesandboxTag, type: :liquid_tag do
expect(liquid.render).to include("<iframe")
end
it "accepts a valid id with good and bad options, and filters bad options" do
liquid = generate_tag(valid_id_with_valid_and_invalid_options)
puts liquid.render
expect(liquid.render).to include("<iframe")
expect(liquid.render).not_to include("fakeoption=/fakeoptionvalue")
end
it "accepts a valid id with bad options, and filters bad options" do
liquid = generate_tag(valid_id_with_only_invalid_options)
expect(liquid.render).to include("<iframe")
expect(liquid.render).not_to include("fakeoption1=/fakeoptionvalue")
expect(liquid.render).not_to include("fakeoption2=/fakeoptionvalue")
end
it "rejects bad ids" do
bad_ids.each do |id|
expect { generate_tag(id) }.to raise_error(StandardError)

View file

@ -11,6 +11,12 @@ RSpec.describe UnifiedEmbed do
"https://app.blogcast.host/embed/4942",
]
valid_codesandbox_url_formats = [
"https://codesandbox.io/embed/exciting-knuth-hywlv",
"https://app.codesandbox.io/embed/exciting-knuth-hywlv",
"https://app.codesandbox.io/embed/exciting-knuth-hywlv?file=/index.html&runonclick=0&view=editor",
]
valid_instagram_url_formats = [
"https://www.instagram.com/p/CXgzXWXroHK/",
"https://instagram.com/p/CXgzXWXroHK/",
@ -31,13 +37,20 @@ RSpec.describe UnifiedEmbed do
"https://youtu.be/rc5AyncB_Xw",
]
valid_blogcast_url_formats.each do |url|
it "returns BlogcastTag for a valid blogcast url" do
it "returns BlogcastTag for a valid blogcast url" do
valid_blogcast_url_formats.each do |url|
expect(described_class.find_liquid_tag_for(link: url))
.to eq(BlogcastTag)
end
end
it "returns CodesandboxTag for a valid codesandbox url" do
valid_codesandbox_url_formats.each do |url|
expect(described_class.find_liquid_tag_for(link: url))
.to eq(CodesandboxTag)
end
end
it "returns GistTag for a gist url" do
expect(described_class.find_liquid_tag_for(link: "https://gist.github.com/jeremyf/662585f5c4d22184a6ae133a71bf891a"))
.to eq(GistTag)
@ -53,8 +66,8 @@ RSpec.describe UnifiedEmbed do
.to eq(CodepenTag)
end
valid_instagram_url_formats.each do |url|
it "returns InstagramTag for a valid instagram url" do
it "returns InstagramTag for a valid instagram url" do
valid_instagram_url_formats.each do |url|
expect(described_class.find_liquid_tag_for(link: url))
.to eq(InstagramTag)
end
@ -95,15 +108,15 @@ RSpec.describe UnifiedEmbed do
.to eq(WikipediaTag)
end
valid_vimeo_url_formats.each do |url|
it "returns VimeoTag for a valid vimeo url" do
it "returns VimeoTag for a valid vimeo url" do
valid_vimeo_url_formats.each do |url|
expect(described_class.find_liquid_tag_for(link: url))
.to eq(VimeoTag)
end
end
valid_youtube_url_formats.each do |url|
it "returns YoutubeTag for a valid youtube url" do
it "returns YoutubeTag for a valid youtube url" do
valid_youtube_url_formats.each do |url|
expect(described_class.find_liquid_tag_for(link: url))
.to eq(YoutubeTag)
end