mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-30 18:47:51 +00:00
feat: move embedding config to a yaml file (#11611)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
132
config/markdown_embeds.yml
Normal file
132
config/markdown_embeds.yml
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# Markdown Embed Configuration
|
||||||
|
#
|
||||||
|
# This file defines patterns and templates for converting URLs into embedded content
|
||||||
|
# in markdown rendering. Each embed type has:
|
||||||
|
# - regex: Pattern with named capture groups (?<name>...)
|
||||||
|
# - template: HTML template with %{capture_group_name} placeholders
|
||||||
|
#
|
||||||
|
# To add a new embed type:
|
||||||
|
# 1. Add a new top-level key
|
||||||
|
# 2. Define the regex pattern with named capture groups: (?<name>pattern)
|
||||||
|
# 3. Create an HTML template using %{name} placeholders matching the capture groups
|
||||||
|
|
||||||
|
youtube:
|
||||||
|
regex: 'https?://(?:www\.)?(?:youtube\.com/watch\?v=|youtu\.be/)(?<video_id>[^&/]+)'
|
||||||
|
template: |
|
||||||
|
<div style="position: relative; padding-bottom: 62.5%; height: 0;">
|
||||||
|
<iframe
|
||||||
|
src="https://www.youtube-nocookie.com/embed/%{video_id}"
|
||||||
|
frameborder="0"
|
||||||
|
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
loom:
|
||||||
|
regex: 'https?://(?:www\.)?loom\.com/share/(?<video_id>[^&/]+)'
|
||||||
|
template: |
|
||||||
|
<div style="position: relative; padding-bottom: 62.5%; height: 0;">
|
||||||
|
<iframe
|
||||||
|
src="https://www.loom.com/embed/%{video_id}"
|
||||||
|
frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen
|
||||||
|
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
vimeo:
|
||||||
|
regex: 'https?://(?:www\.)?vimeo\.com/(?<video_id>\d+)'
|
||||||
|
template: |
|
||||||
|
<div style="position: relative; padding-bottom: 62.5%; height: 0;">
|
||||||
|
<iframe
|
||||||
|
src="https://player.vimeo.com/video/%{video_id}?dnt=true"
|
||||||
|
frameborder="0"
|
||||||
|
allow="autoplay; fullscreen; picture-in-picture"
|
||||||
|
allowfullscreen
|
||||||
|
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
mp4:
|
||||||
|
regex: '(?<link_url>https?://(?:www\.)?.+\.mp4)'
|
||||||
|
template: |
|
||||||
|
<video width="640" height="360" controls>
|
||||||
|
<source src="%{link_url}" type="video/mp4">
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
arcade:
|
||||||
|
regex: 'https?://(?:www\.)?app\.arcade\.software/share/(?<video_id>[^&/]+)'
|
||||||
|
template: |
|
||||||
|
<div style="position: relative; padding-bottom: 62.5%; height: 0;">
|
||||||
|
<iframe
|
||||||
|
src="https://app.arcade.software/embed/%{video_id}"
|
||||||
|
frameborder="0"
|
||||||
|
webkitallowfullscreen
|
||||||
|
mozallowfullscreen
|
||||||
|
allowfullscreen
|
||||||
|
allow="fullscreen"
|
||||||
|
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
wistia:
|
||||||
|
regex: 'https?://(?:www\.)?[^/]+\.wistia\.com/medias/(?<video_id>[^&/]+)'
|
||||||
|
template: |
|
||||||
|
<div style="position: relative; padding-bottom: 56.25%; height: 0;">
|
||||||
|
<script src="https://fast.wistia.com/player.js" async></script>
|
||||||
|
<script src="https://fast.wistia.com/embed/%{video_id}.js" async type="module"></script>
|
||||||
|
<style>
|
||||||
|
wistia-player[media-id='%{video_id}']:not(:defined) {
|
||||||
|
background: center / contain no-repeat url('https://fast.wistia.com/embed/medias/%{video_id}/swatch');
|
||||||
|
display: block;
|
||||||
|
filter: blur(5px);
|
||||||
|
padding-top:56.25%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<wistia-player
|
||||||
|
media-id="%{video_id}"
|
||||||
|
aspect="1.7777777777777777"
|
||||||
|
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">
|
||||||
|
</wistia-player>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
bunny:
|
||||||
|
regex: 'https?://iframe\.mediadelivery\.net/play/(?<library_id>\d+)/(?<video_id>[^&/?]+)'
|
||||||
|
template: |
|
||||||
|
<div style="position: relative; padding-top: 56.25%;">
|
||||||
|
<iframe
|
||||||
|
src="https://iframe.mediadelivery.net/embed/%{library_id}/%{video_id}?autoplay=false&loop=false&muted=false&preload=true&responsive=true"
|
||||||
|
title="Bunny video player"
|
||||||
|
loading="lazy"
|
||||||
|
style="border: 0; position: absolute; top: 0; height: 100%; width: 100%;"
|
||||||
|
allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
codepen:
|
||||||
|
regex: 'https?://(?:www\.)?codepen\.io/(?<user>[^/]+)/pen/(?<pen_id>[^/?]+)'
|
||||||
|
template: |
|
||||||
|
<div style="height: 400px; box-sizing: border-box; display: flex; align-items: center; justify-content: center;">
|
||||||
|
<iframe
|
||||||
|
height="400"
|
||||||
|
style="width: 100%;"
|
||||||
|
scrolling="no"
|
||||||
|
title="CodePen Embed"
|
||||||
|
src="https://codepen.io/%{user}/embed/%{pen_id}?default-tab=result"
|
||||||
|
frameborder="no"
|
||||||
|
loading="lazy"
|
||||||
|
allowtransparency="true"
|
||||||
|
allowfullscreen="true">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
github_gist:
|
||||||
|
regex: 'https?://gist\.github\.com/(?<username>[^/]+)/(?<gist_id>[a-f0-9]+)'
|
||||||
|
template: |
|
||||||
|
<script src="https://gist.github.com/%{username}/%{gist_id}.js"></script>
|
||||||
|
<noscript>
|
||||||
|
<div style="border: 1px solid #d1d9e0; border-radius: 6px; padding: 16px; margin: 16px 0; background: #f6f8fa;">
|
||||||
|
<a href="https://gist.github.com/%{username}/%{gist_id}" target="_blank" style="color: #0969da; text-decoration: none;">
|
||||||
|
View this gist on GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</noscript>
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
class CustomMarkdownRenderer < CommonMarker::HtmlRenderer
|
class CustomMarkdownRenderer < CommonMarker::HtmlRenderer
|
||||||
# TODO: let move this regex from here to a config file where we can update this list much more easily
|
CONFIG_PATH = Rails.root.join('config/markdown_embeds.yml')
|
||||||
# the config file will also have the matching embed template as well.
|
|
||||||
YOUTUBE_REGEX = %r{https?://(?:www\.)?(?:youtube\.com/watch\?v=|youtu\.be/)([^&/]+)}
|
def self.config
|
||||||
LOOM_REGEX = %r{https?://(?:www\.)?loom\.com/share/([^&/]+)}
|
@config ||= YAML.load_file(CONFIG_PATH)
|
||||||
VIMEO_REGEX = %r{https?://(?:www\.)?vimeo\.com/(\d+)}
|
end
|
||||||
MP4_REGEX = %r{https?://(?:www\.)?.+\.(mp4)}
|
|
||||||
ARCADE_REGEX = %r{https?://(?:www\.)?app\.arcade\.software/share/([^&/]+)}
|
def self.embed_regexes
|
||||||
WISTIA_REGEX = %r{https?://(?:www\.)?([^/]+)\.wistia\.com/medias/([^&/]+)}
|
@embed_regexes ||= config.transform_values { |embed_config| Regexp.new(embed_config['regex']) }
|
||||||
BUNNY_REGEX = %r{https?://iframe\.mediadelivery\.net/play/(\d+)/([^&/?]+)}
|
end
|
||||||
|
|
||||||
def text(node)
|
def text(node)
|
||||||
content = node.string_content
|
content = node.string_content
|
||||||
@@ -23,7 +23,7 @@ class CustomMarkdownRenderer < CommonMarker::HtmlRenderer
|
|||||||
def link(node)
|
def link(node)
|
||||||
return if surrounded_by_empty_lines?(node) && render_embedded_content(node)
|
return if surrounded_by_empty_lines?(node) && render_embedded_content(node)
|
||||||
|
|
||||||
# If it's not YouTube or Vimeo link, render normally
|
# If it's not a supported embed link, render normally
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -47,25 +47,35 @@ class CustomMarkdownRenderer < CommonMarker::HtmlRenderer
|
|||||||
|
|
||||||
def render_embedded_content(node)
|
def render_embedded_content(node)
|
||||||
link_url = node.url
|
link_url = node.url
|
||||||
embedding_methods = {
|
embed_html = find_matching_embed(link_url)
|
||||||
YOUTUBE_REGEX => :make_youtube_embed,
|
|
||||||
VIMEO_REGEX => :make_vimeo_embed,
|
|
||||||
MP4_REGEX => :make_video_embed,
|
|
||||||
LOOM_REGEX => :make_loom_embed,
|
|
||||||
ARCADE_REGEX => :make_arcade_embed,
|
|
||||||
WISTIA_REGEX => :make_wistia_embed,
|
|
||||||
BUNNY_REGEX => :make_bunny_embed
|
|
||||||
}
|
|
||||||
|
|
||||||
embedding_methods.each do |regex, method|
|
return false unless embed_html
|
||||||
|
|
||||||
|
out(embed_html)
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_matching_embed(link_url)
|
||||||
|
self.class.embed_regexes.each do |embed_key, regex|
|
||||||
match = link_url.match(regex)
|
match = link_url.match(regex)
|
||||||
if match
|
next unless match
|
||||||
out(send(method, match))
|
|
||||||
return true
|
return render_embed_from_match(embed_key, match)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
false
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_embed_from_match(embed_key, match_data)
|
||||||
|
embed_config = self.class.config[embed_key]
|
||||||
|
return nil unless embed_config
|
||||||
|
|
||||||
|
template = embed_config['template']
|
||||||
|
# Use Ruby's built-in named captures with gsub to handle CSS % values
|
||||||
|
match_data.named_captures.each do |var_name, value|
|
||||||
|
template = template.gsub("%{#{var_name}}", value)
|
||||||
|
end
|
||||||
|
template
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_sup(content)
|
def parse_sup(content)
|
||||||
@@ -77,39 +87,4 @@ class CustomMarkdownRenderer < CommonMarker::HtmlRenderer
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_youtube_embed(youtube_match)
|
|
||||||
video_id = youtube_match[1]
|
|
||||||
EmbedRenderer.youtube(video_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_loom_embed(loom_match)
|
|
||||||
video_id = loom_match[1]
|
|
||||||
EmbedRenderer.loom(video_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_vimeo_embed(vimeo_match)
|
|
||||||
video_id = vimeo_match[1]
|
|
||||||
EmbedRenderer.vimeo(video_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_video_embed(link_url)
|
|
||||||
EmbedRenderer.video(link_url)
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_wistia_embed(wistia_match)
|
|
||||||
video_id = wistia_match[2]
|
|
||||||
EmbedRenderer.wistia(video_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_arcade_embed(arcade_match)
|
|
||||||
video_id = arcade_match[1]
|
|
||||||
EmbedRenderer.arcade(video_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_bunny_embed(bunny_match)
|
|
||||||
library_id = bunny_match[1]
|
|
||||||
video_id = bunny_match[2]
|
|
||||||
EmbedRenderer.bunny(library_id, video_id)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
module EmbedRenderer
|
|
||||||
def self.youtube(video_id)
|
|
||||||
%(
|
|
||||||
<div style="position: relative; padding-bottom: 62.5%; height: 0;">
|
|
||||||
<iframe
|
|
||||||
src="https://www.youtube-nocookie.com/embed/#{video_id}"
|
|
||||||
frameborder="0"
|
|
||||||
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
|
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
||||||
allowfullscreen></iframe>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.loom(video_id)
|
|
||||||
%(
|
|
||||||
<div style="position: relative; padding-bottom: 62.5%; height: 0;">
|
|
||||||
<iframe
|
|
||||||
src="https://www.loom.com/embed/#{video_id}"
|
|
||||||
frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen
|
|
||||||
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></iframe>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.vimeo(video_id)
|
|
||||||
%(
|
|
||||||
<div style="position: relative; padding-bottom: 62.5%; height: 0;">
|
|
||||||
<iframe
|
|
||||||
src="https://player.vimeo.com/video/#{video_id}?dnt=true"
|
|
||||||
frameborder="0"
|
|
||||||
allow="autoplay; fullscreen; picture-in-picture"
|
|
||||||
allowfullscreen
|
|
||||||
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></iframe>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.video(link_url)
|
|
||||||
%(
|
|
||||||
<video width="640" height="360" controls>
|
|
||||||
<source src="#{link_url}" type="video/mp4">
|
|
||||||
Your browser does not support the video tag.
|
|
||||||
</video>
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Generates an HTML embed for a Wistia video.
|
|
||||||
# @param wistia_match [MatchData] A match object from the WISTIA_REGEX regex, where wistia_match[2] contains the video ID.
|
|
||||||
def self.wistia(video_id)
|
|
||||||
%(
|
|
||||||
<div style="position: relative; padding-bottom: 56.25%; height: 0;">
|
|
||||||
<script src="https://fast.wistia.com/player.js" async></script>
|
|
||||||
<script src="https://fast.wistia.com/embed/#{video_id}.js" async type="module"></script>
|
|
||||||
<style>
|
|
||||||
wistia-player[media-id='#{video_id}']:not(:defined) {
|
|
||||||
background: center / contain no-repeat url('https://fast.wistia.com/embed/medias/#{video_id}/swatch');
|
|
||||||
display: block;
|
|
||||||
filter: blur(5px);
|
|
||||||
padding-top:56.25%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<wistia-player
|
|
||||||
media-id="#{video_id}"
|
|
||||||
aspect="1.7777777777777777"
|
|
||||||
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">
|
|
||||||
</wistia-player>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.arcade(video_id)
|
|
||||||
%(
|
|
||||||
<div style="position: relative; padding-bottom: 62.5%; height: 0;">
|
|
||||||
<iframe
|
|
||||||
src="https://app.arcade.software/embed/#{video_id}"
|
|
||||||
frameborder="0"
|
|
||||||
webkitallowfullscreen
|
|
||||||
mozallowfullscreen
|
|
||||||
allowfullscreen
|
|
||||||
allow="fullscreen"
|
|
||||||
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.bunny(library_id, video_id)
|
|
||||||
%(
|
|
||||||
<div style="position: relative; padding-top: 56.25%;">
|
|
||||||
<iframe
|
|
||||||
src="https://iframe.mediadelivery.net/embed/#{library_id}/#{video_id}?autoplay=false&loop=false&muted=false&preload=true&responsive=true"
|
|
||||||
title="Bunny video player"
|
|
||||||
loading="lazy"
|
|
||||||
style="border: 0; position: absolute; top: 0; height: 100%; width: 100%;"
|
|
||||||
allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"
|
|
||||||
allowfullscreen>
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
103
spec/config/markdown_embeds_spec.rb
Normal file
103
spec/config/markdown_embeds_spec.rb
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
# rubocop:disable RSpec/DescribeClass
|
||||||
|
describe 'Markdown Embeds Configuration' do
|
||||||
|
# rubocop:enable RSpec/DescribeClass
|
||||||
|
let(:config) { YAML.load_file(Rails.root.join('config/markdown_embeds.yml')) }
|
||||||
|
|
||||||
|
describe 'YAML structure' do
|
||||||
|
it 'loads valid YAML' do
|
||||||
|
expect(config).to be_a(Hash)
|
||||||
|
expect(config).not_to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has required keys for each embed type' do
|
||||||
|
config.each do |embed_type, embed_config|
|
||||||
|
expect(embed_config).to have_key('regex'), "#{embed_type} missing regex"
|
||||||
|
expect(embed_config).to have_key('template'), "#{embed_type} missing template"
|
||||||
|
expect(embed_config['regex']).to be_a(String), "#{embed_type} regex should be string"
|
||||||
|
expect(embed_config['template']).to be_a(String), "#{embed_type} template should be string"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'contains expected embed types' do
|
||||||
|
expected_types = %w[youtube loom vimeo mp4 arcade wistia bunny codepen github_gist]
|
||||||
|
expect(config.keys).to match_array(expected_types)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'regex patterns and named capture groups' do
|
||||||
|
let(:test_cases) do
|
||||||
|
{
|
||||||
|
'youtube' => [
|
||||||
|
{ url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', expected: { 'video_id' => 'dQw4w9WgXcQ' } },
|
||||||
|
{ url: 'https://youtu.be/dQw4w9WgXcQ', expected: { 'video_id' => 'dQw4w9WgXcQ' } },
|
||||||
|
{ url: 'https://youtube.com/watch?v=abc123XYZ', expected: { 'video_id' => 'abc123XYZ' } }
|
||||||
|
],
|
||||||
|
'loom' => [
|
||||||
|
{ url: 'https://www.loom.com/share/abc123def456', expected: { 'video_id' => 'abc123def456' } },
|
||||||
|
{ url: 'https://loom.com/share/xyz789', expected: { 'video_id' => 'xyz789' } }
|
||||||
|
],
|
||||||
|
'vimeo' => [
|
||||||
|
{ url: 'https://vimeo.com/123456789', expected: { 'video_id' => '123456789' } },
|
||||||
|
{ url: 'https://www.vimeo.com/987654321', expected: { 'video_id' => '987654321' } }
|
||||||
|
],
|
||||||
|
'mp4' => [
|
||||||
|
{ url: 'https://example.com/video.mp4', expected: { 'link_url' => 'https://example.com/video.mp4' } },
|
||||||
|
{ url: 'https://www.test.com/path/to/movie.mp4', expected: { 'link_url' => 'https://www.test.com/path/to/movie.mp4' } }
|
||||||
|
],
|
||||||
|
'arcade' => [
|
||||||
|
{ url: 'https://app.arcade.software/share/arcade123', expected: { 'video_id' => 'arcade123' } },
|
||||||
|
{ url: 'https://www.app.arcade.software/share/demo456', expected: { 'video_id' => 'demo456' } }
|
||||||
|
],
|
||||||
|
'wistia' => [
|
||||||
|
{ url: 'https://chatwoot.wistia.com/medias/kjwjeq6f9i', expected: { 'video_id' => 'kjwjeq6f9i' } },
|
||||||
|
{ url: 'https://www.company.wistia.com/medias/abc123def', expected: { 'video_id' => 'abc123def' } }
|
||||||
|
],
|
||||||
|
'bunny' => [
|
||||||
|
{ url: 'https://iframe.mediadelivery.net/play/431789/1f105841-cad9-46fe-a70e-b7623c60797c',
|
||||||
|
expected: { 'library_id' => '431789', 'video_id' => '1f105841-cad9-46fe-a70e-b7623c60797c' } },
|
||||||
|
{ url: 'https://iframe.mediadelivery.net/play/12345/abcdef-ghijkl', expected: { 'library_id' => '12345', 'video_id' => 'abcdef-ghijkl' } }
|
||||||
|
],
|
||||||
|
'codepen' => [
|
||||||
|
{ url: 'https://codepen.io/username/pen/abcdef', expected: { 'user' => 'username', 'pen_id' => 'abcdef' } },
|
||||||
|
{ url: 'https://www.codepen.io/testuser/pen/xyz123', expected: { 'user' => 'testuser', 'pen_id' => 'xyz123' } }
|
||||||
|
],
|
||||||
|
'github_gist' => [
|
||||||
|
{ url: 'https://gist.github.com/username/1234567890abcdef1234567890abcdef',
|
||||||
|
expected: { 'username' => 'username', 'gist_id' => '1234567890abcdef1234567890abcdef' } },
|
||||||
|
{ url: 'https://gist.github.com/testuser/fedcba0987654321fedcba0987654321', expected: { 'username' => 'testuser', 'gist_id' => 'fedcba0987654321fedcba0987654321' } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'correctly captures named groups for all embed types' do
|
||||||
|
test_cases.each do |embed_type, cases|
|
||||||
|
regex = Regexp.new(config[embed_type]['regex'])
|
||||||
|
|
||||||
|
cases.each do |test_case|
|
||||||
|
match = regex.match(test_case[:url])
|
||||||
|
expect(match).not_to be_nil, "#{embed_type} regex failed to match URL: #{test_case[:url]}"
|
||||||
|
expect(match.named_captures).to eq(test_case[:expected]),
|
||||||
|
"#{embed_type} captured groups don't match expected for URL: #{test_case[:url]}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'validates that template variables match capture group names' do
|
||||||
|
config.each do |embed_type, embed_config|
|
||||||
|
regex = Regexp.new(embed_config['regex'])
|
||||||
|
template = embed_config['template']
|
||||||
|
|
||||||
|
# Extract template variables like %{video_id}
|
||||||
|
template_vars = template.scan(/%\{(\w+)\}/).flatten.uniq
|
||||||
|
|
||||||
|
# Get named capture groups from regex
|
||||||
|
capture_names = regex.names
|
||||||
|
|
||||||
|
expect(capture_names).to match_array(template_vars),
|
||||||
|
"#{embed_type}: Template variables #{template_vars} don't match capture groups #{capture_names}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user