2019-04-30 08:27:42 +08:00
# frozen_string_literal: true
2015-09-25 06:20:59 +08:00
require " stringio "
2014-01-01 03:37:43 +08:00
2022-07-28 10:27:38 +08:00
RSpec . describe TopicEmbed do
2014-12-31 22:55:03 +08:00
it { is_expected . to belong_to :topic }
it { is_expected . to belong_to :post }
it { is_expected . to validate_presence_of :embed_url }
2014-01-01 03:37:43 +08:00
2022-07-27 18:21:10 +08:00
describe " .import " do
2019-05-07 11:12:20 +08:00
fab! ( :user ) { Fabricate ( :user ) }
2014-01-01 03:37:43 +08:00
let ( :title ) { " How to turn a fish from good to evil in 30 seconds " }
let ( :url ) { " http://eviltrout.com/123 " }
2021-11-17 13:39:49 +08:00
let ( :contents ) do
" <p>hello world new post <a href='/hello'>hello</a> <img src='images/wat.jpg'></p> "
2023-01-09 19:18:21 +08:00
end
2019-05-07 11:12:20 +08:00
fab! ( :embeddable_host ) { Fabricate ( :embeddable_host ) }
2021-08-18 05:17:07 +08:00
fab! ( :category ) { Fabricate ( :category ) }
2021-09-14 04:01:59 +08:00
fab! ( :tag ) { Fabricate ( :tag ) }
2014-01-01 03:37:43 +08:00
it " returns nil when the URL is malformed " do
2014-12-31 22:55:03 +08:00
expect ( TopicEmbed . import ( user , " invalid url " , title , contents ) ) . to eq ( nil )
expect ( TopicEmbed . count ) . to eq ( 0 )
2014-01-01 03:37:43 +08:00
end
2022-07-28 00:14:14 +08:00
context " when creating a post " do
2014-01-01 03:37:43 +08:00
let! ( :post ) { TopicEmbed . import ( user , url , title , contents ) }
2018-08-24 09:41:54 +08:00
let ( :topic_embed ) { TopicEmbed . find_by ( post : post ) }
2014-01-01 03:37:43 +08:00
it " works as expected with a new URL " do
2014-12-31 22:55:03 +08:00
expect ( post ) . to be_present
2014-01-01 03:37:43 +08:00
# It uses raw_html rendering
2014-12-31 22:55:03 +08:00
expect ( post . cook_method ) . to eq ( Post . cook_methods [ :raw_html ] )
expect ( post . cooked ) . to eq ( post . raw )
2014-01-01 03:37:43 +08:00
# It converts relative URLs to absolute
2015-09-25 06:20:59 +08:00
expect ( post . cooked ) . to have_tag ( " a " , with : { href : " http://eviltrout.com/hello " } )
expect ( post . cooked ) . to have_tag ( " img " , with : { src : " http://eviltrout.com/images/wat.jpg " } )
2014-01-01 03:37:43 +08:00
2021-11-17 13:39:49 +08:00
# It converts relative URLs to absolute when expanded
stub_request ( :get , url ) . to_return ( status : 200 , body : contents )
expect ( TopicEmbed . expanded_for ( post ) ) . to have_tag (
" img " ,
with : {
src : " http://eviltrout.com/images/wat.jpg " ,
} ,
)
2014-12-31 22:55:03 +08:00
expect ( post . topic . has_topic_embed? ) . to eq ( true )
expect ( TopicEmbed . where ( topic_id : post . topic_id ) ) . to be_present
2015-08-19 05:15:46 +08:00
expect ( post . topic . category ) . to eq ( embeddable_host . category )
2020-10-02 03:40:13 +08:00
expect ( post . topic ) . not_to be_visible
2014-01-01 03:37:43 +08:00
end
2018-08-21 18:19:03 +08:00
it " Supports updating the post content " do
2018-08-24 09:41:54 +08:00
expect do
2020-05-05 11:46:57 +08:00
TopicEmbed . import ( user , url , " New title received " , " <p>muhahaha new contents!</p> " )
2018-08-24 09:41:54 +08:00
end . to change { topic_embed . reload . content_sha1 }
2020-04-21 02:27:43 +08:00
expect ( topic_embed . topic . title ) . to eq ( " New title received " )
2018-04-19 03:22:43 +08:00
2018-08-24 09:41:54 +08:00
expect ( topic_embed . post . cooked ) . to match ( / new contents / )
2018-08-21 18:19:03 +08:00
end
it " Supports updating the post author " do
new_user = Fabricate ( :user )
2018-08-24 09:41:54 +08:00
TopicEmbed . import ( new_user , url , title , contents )
2018-08-21 18:19:03 +08:00
2018-08-24 09:41:54 +08:00
topic_embed . reload
expect ( topic_embed . post . user ) . to eq ( new_user )
expect ( topic_embed . post . topic . user ) . to eq ( new_user )
2014-01-01 03:37:43 +08:00
end
2014-03-27 11:24:57 +08:00
it " Should leave uppercase Feed Entry URL untouched in content " do
cased_url = " http://eviltrout.com/ABCD "
post = TopicEmbed . import ( user , cased_url , title , " some random content " )
2014-12-31 22:55:03 +08:00
expect ( post . cooked ) . to match ( / #{ cased_url } / )
2014-03-27 11:24:57 +08:00
end
it " Should leave lowercase Feed Entry URL untouched in content " do
cased_url = " http://eviltrout.com/abcd "
post = TopicEmbed . import ( user , cased_url , title , " some random content " )
2014-12-31 22:55:03 +08:00
expect ( post . cooked ) . to match ( / #{ cased_url } / )
2014-03-27 11:24:57 +08:00
end
2020-04-14 03:17:02 +08:00
it " will make the topic unlisted if `embed_unlisted` is set until someone replies " do
2020-04-21 03:11:59 +08:00
Jobs . run_immediately!
2020-04-14 03:17:02 +08:00
SiteSetting . embed_unlisted = true
imported_post =
TopicEmbed . import ( user , " http://eviltrout.com/abcd " , title , " some random content " )
expect ( imported_post . topic ) . not_to be_visible
pc =
PostCreator . new (
Fabricate ( :user ) ,
raw : " this is a reply that will make the topic visible " ,
topic_id : imported_post . topic_id ,
reply_to_post_number : 1 ,
)
pc . create
expect ( imported_post . topic . reload ) . to be_visible
end
2020-10-02 03:40:13 +08:00
it " won't be invisible if `embed_unlisted` is set to false " do
Jobs . run_immediately!
SiteSetting . embed_unlisted = false
imported_post =
TopicEmbed . import ( user , " http://eviltrout.com/abcd " , title , " some random content " )
expect ( imported_post . topic ) . to be_visible
end
2021-08-18 05:17:07 +08:00
it " creates the topic in the category passed as a parameter " do
Jobs . run_immediately!
imported_post =
TopicEmbed . import (
user ,
" http://eviltrout.com/abcd " ,
title ,
" some random content " ,
category_id : category . id ,
)
expect ( imported_post . topic . category ) . not_to eq ( embeddable_host . category )
expect ( imported_post . topic . category ) . to eq ( category )
end
2021-09-02 02:46:39 +08:00
2021-09-14 04:01:59 +08:00
it " creates the topic with the tag passed as a parameter " do
Jobs . run_immediately!
SiteSetting . tagging_enabled = true
imported_post =
TopicEmbed . import (
user ,
" http://eviltrout.com/abcd " ,
title ,
" some random content " ,
tags : [ tag . name ] ,
)
expect ( imported_post . topic . tags ) . to include ( tag )
end
2021-09-02 02:46:39 +08:00
it " respects overriding the cook_method when asked " do
Jobs . run_immediately!
SiteSetting . embed_support_markdown = false
stub_request ( :get , " https://www.youtube.com/watch?v=K56soYl0U1w " ) . to_return (
status : 200 ,
body : " " ,
headers : {
2023-01-09 19:18:21 +08:00
} ,
2021-09-02 02:46:39 +08:00
)
stub_request ( :get , " https://www.youtube.com/embed/K56soYl0U1w " ) . to_return (
status : 200 ,
body : " " ,
headers : {
2023-01-09 19:18:21 +08:00
} ,
2021-09-02 02:46:39 +08:00
)
imported_post =
TopicEmbed . import (
user ,
" http://eviltrout.com/abcd " ,
title ,
" https://www.youtube.com/watch?v=K56soYl0U1w " ,
cook_method : Post . cook_methods [ :regular ] ,
)
expect ( imported_post . cooked ) . to match ( / onebox|iframe / )
end
2014-01-01 03:37:43 +08:00
end
2022-07-28 00:14:14 +08:00
context " when post creation supports markdown rendering " do
2018-03-11 10:26:47 +08:00
before { SiteSetting . embed_support_markdown = true }
it " works as expected " do
post = TopicEmbed . import ( user , url , title , " some random content " )
expect ( post ) . to be_present
# It uses regular rendering
expect ( post . cook_method ) . to eq ( Post . cook_methods [ :regular ] )
end
end
2019-07-25 21:21:01 +08:00
describe " embedded content truncation " do
MAX_LENGTH_BEFORE_TRUNCATION = 100
let ( :long_content ) { " <p> #{ " a " * MAX_LENGTH_BEFORE_TRUNCATION } </p> \n <p>more</p> " }
it " truncates the imported post when truncation is enabled " do
SiteSetting . embed_truncate = true
post = TopicEmbed . import ( user , url , title , long_content )
expect ( post . raw ) . not_to include ( long_content )
end
it " keeps everything in the imported post when truncation is disabled " do
SiteSetting . embed_truncate = false
post = TopicEmbed . import ( user , url , title , long_content )
expect ( post . raw ) . to include ( long_content )
end
2019-08-07 10:45:55 +08:00
it " looks at first div when there is no paragraph " do
no_para = " <div><h>testing it</h></div> "
SiteSetting . embed_truncate = true
post = TopicEmbed . import ( user , url , title , no_para )
expect ( post . raw ) . to include ( " testing it " )
end
2019-07-25 21:21:01 +08:00
end
2014-01-01 03:37:43 +08:00
end
2022-07-27 18:21:10 +08:00
describe " .topic_id_for_embed " do
2018-01-05 02:13:17 +08:00
it " returns correct topic id irrespective of url protocol " do
topic_embed = Fabricate ( :topic_embed , embed_url : " http://example.com/post/248 " )
expect ( TopicEmbed . topic_id_for_embed ( " http://exAMPle.com/post/248 " ) ) . to eq (
topic_embed . topic_id ,
)
expect ( TopicEmbed . topic_id_for_embed ( " https://example.com/post/248/ " ) ) . to eq (
topic_embed . topic_id ,
2023-01-09 19:18:21 +08:00
)
2018-01-05 02:13:17 +08:00
expect ( TopicEmbed . topic_id_for_embed ( " http://example.com/post/248/2 " ) ) . to eq ( nil )
expect ( TopicEmbed . topic_id_for_embed ( " http://examples.com/post/248 " ) ) . to eq ( nil )
expect ( TopicEmbed . topic_id_for_embed ( " http://example.com/post/24 " ) ) . to eq ( nil )
expect ( TopicEmbed . topic_id_for_embed ( " http://example.com/post " ) ) . to eq ( nil )
end
2018-02-14 07:28:16 +08:00
it " finds the topic id when the embed_url contains a query string " do
topic_embed = Fabricate ( :topic_embed , embed_url : " http://example.com/post/248?key=foo " )
expect ( TopicEmbed . topic_id_for_embed ( " http://example.com/post/248?key=foo " ) ) . to eq (
topic_embed . topic_id ,
)
end
2018-01-05 02:13:17 +08:00
end
2015-09-25 06:20:59 +08:00
describe " .find_remote " do
2021-10-01 23:48:21 +08:00
fab! ( :embeddable_host ) { Fabricate ( :embeddable_host ) }
2016-08-23 00:43:02 +08:00
2022-07-27 18:21:10 +08:00
describe " .title_scrub " do
2016-08-23 00:43:02 +08:00
let ( :url ) { " http://eviltrout.com/123 " }
let ( :contents ) do
" <title>Through the Looking Glass - Classic Books</title><body>some content here</body> "
end
2021-10-27 16:39:28 +08:00
before { stub_request ( :get , url ) . to_return ( status : 200 , body : contents ) }
2023-01-09 19:18:21 +08:00
2016-08-23 00:43:02 +08:00
it " doesn't scrub the title by default " do
2016-08-31 00:01:04 +08:00
response = TopicEmbed . find_remote ( url )
expect ( response . title ) . to eq ( " Through the Looking Glass - Classic Books " )
2016-08-23 00:43:02 +08:00
end
it " scrubs the title when the option is enabled " do
SiteSetting . embed_title_scrubber = " - Classic Books$ "
2016-08-31 00:01:04 +08:00
response = TopicEmbed . find_remote ( url )
expect ( response . title ) . to eq ( " Through the Looking Glass " )
2016-08-23 00:43:02 +08:00
end
end
2022-07-28 00:14:14 +08:00
context 'with post with allowed classes "foo" and "emoji"' do
2019-05-07 11:12:20 +08:00
fab! ( :user ) { Fabricate ( :user ) }
2015-09-25 06:20:59 +08:00
let ( :url ) { " http://eviltrout.com/123 " }
let ( :contents ) do
" my normal size emoji <p class='foo'>Hi</p> <img class='emoji other foo' src='/images/smiley.jpg'> "
2023-01-09 19:18:21 +08:00
end
2015-09-25 06:20:59 +08:00
2017-07-07 14:09:14 +08:00
before do
2020-07-27 08:23:54 +08:00
SiteSetting . allowed_embed_classnames = " emoji, foo "
2021-10-27 16:39:28 +08:00
stub_request ( :get , url ) . to_return ( status : 200 , body : contents )
2021-10-01 23:48:21 +08:00
@response = TopicEmbed . find_remote ( url )
2016-08-31 00:01:04 +08:00
end
it " has no author tag " do
2021-10-01 23:48:21 +08:00
expect ( @response . author ) . to be_blank
2015-09-25 06:20:59 +08:00
end
it " img node has emoji class " do
2021-10-01 23:48:21 +08:00
expect ( @response . body ) . to have_tag ( " img " , with : { class : " emoji " } )
2015-09-25 06:20:59 +08:00
end
it " img node has foo class " do
2021-10-01 23:48:21 +08:00
expect ( @response . body ) . to have_tag ( " img " , with : { class : " foo " } )
2015-09-25 06:20:59 +08:00
end
it " p node has foo class " do
2021-10-01 23:48:21 +08:00
expect ( @response . body ) . to have_tag ( " p " , with : { class : " foo " } )
2015-09-25 06:20:59 +08:00
end
it " nodes removes classes other than emoji " do
2021-10-01 23:48:21 +08:00
expect ( @response . body ) . to have_tag ( " img " , without : { class : " other " } )
2016-08-31 00:01:04 +08:00
end
end
2022-07-28 00:14:14 +08:00
context " with post with author metadata " do
2019-05-07 11:12:20 +08:00
fab! ( :user ) { Fabricate ( :user , username : " eviltrout " ) }
2016-08-31 00:01:04 +08:00
let ( :url ) { " http://eviltrout.com/321 " }
let ( :contents ) do
'<html><head><meta name="author" content="eviltrout"></head><body>rich and morty</body></html>'
2015-09-25 06:20:59 +08:00
end
2021-10-27 16:39:28 +08:00
before ( :each ) { stub_request ( :get , url ) . to_return ( status : 200 , body : contents ) }
2023-01-09 19:18:21 +08:00
2016-08-31 00:01:04 +08:00
it " has no author tag " do
2021-10-01 23:48:21 +08:00
response = TopicEmbed . find_remote ( url )
2016-08-31 00:01:04 +08:00
expect ( response . author ) . to eq ( user )
end
2015-09-25 06:20:59 +08:00
end
2022-07-28 00:14:14 +08:00
context " with post with no allowed classes " do
2019-05-07 11:12:20 +08:00
fab! ( :user ) { Fabricate ( :user ) }
2015-09-25 06:20:59 +08:00
let ( :url ) { " http://eviltrout.com/123 " }
let ( :contents ) do
" my normal size emoji <p class='foo'>Hi</p> <img class='emoji other foo' src='/images/smiley.jpg'> "
2023-01-09 19:18:21 +08:00
end
2015-09-25 06:20:59 +08:00
before ( :each ) do
2020-07-27 08:23:54 +08:00
SiteSetting . allowed_embed_classnames = " "
2021-10-27 16:39:28 +08:00
stub_request ( :get , url ) . to_return ( status : 200 , body : contents )
2021-10-01 23:48:21 +08:00
@response = TopicEmbed . find_remote ( url )
2015-09-25 06:20:59 +08:00
end
it 'img node doesn\'t have emoji class' do
2021-10-01 23:48:21 +08:00
expect ( @response . body ) . to have_tag ( " img " , without : { class : " emoji " } )
2015-09-25 06:20:59 +08:00
end
it 'img node doesn\'t have foo class' do
2021-10-01 23:48:21 +08:00
expect ( @response . body ) . to have_tag ( " img " , without : { class : " foo " } )
2015-09-25 06:20:59 +08:00
end
it 'p node doesn\'t foo class' do
2021-10-01 23:48:21 +08:00
expect ( @response . body ) . to have_tag ( " p " , without : { class : " foo " } )
2015-09-25 06:20:59 +08:00
end
it 'img node doesn\'t have other class' do
2021-10-01 23:48:21 +08:00
expect ( @response . body ) . to have_tag ( " img " , without : { class : " other " } )
2015-09-25 06:20:59 +08:00
end
2017-03-08 00:21:26 +08:00
end
2022-07-28 00:14:14 +08:00
context " with non-ascii URL " do
2017-03-08 00:21:26 +08:00
let ( :url ) { " http://eviltrout.com/test/ماهی " }
let ( :contents ) { " <title>سلام</title><body>این یک پاراگراف آزمون است.</body> " }
2015-09-25 06:20:59 +08:00
2021-10-27 16:39:28 +08:00
before { stub_request ( :get , url ) . to_return ( status : 200 , body : contents ) }
2017-03-08 00:21:26 +08:00
it " doesn't throw an error " do
response = TopicEmbed . find_remote ( url )
expect ( response . title ) . to eq ( " سلام " )
end
2015-09-25 06:20:59 +08:00
end
2022-07-28 00:14:14 +08:00
context " with encoded URL " do
2017-09-22 23:36:44 +08:00
let ( :url ) { " http://example.com/hello%20world " }
let ( :contents ) { " <title>Hello World!</title><body></body> " }
2021-10-27 16:39:28 +08:00
before { stub_request ( :get , url ) . to_return ( status : 200 , body : contents ) }
2017-09-22 23:36:44 +08:00
it " doesn't throw an error " do
response = TopicEmbed . find_remote ( url )
expect ( response . title ) . to eq ( " Hello World! " )
end
end
2022-07-28 00:14:14 +08:00
context " with non-http URL " do
2020-05-23 12:56:13 +08:00
it " throws an error " do
2020-05-28 22:59:20 +08:00
url = " /test.txt "
expect ( TopicEmbed . find_remote ( url ) ) . to be_nil
2020-05-23 12:56:13 +08:00
end
end
2022-07-28 00:14:14 +08:00
context " with emails " do
2017-09-22 20:26:06 +08:00
let ( :url ) { " http://example.com/foo " }
let ( :contents ) do
'<p><a href="mailto:foo%40example.com">URL encoded @ symbol</a></p><p><a href="mailto:bar@example.com">normal mailto link</a></p>'
end
2021-10-27 16:39:28 +08:00
before { stub_request ( :get , url ) . to_return ( status : 200 , body : contents ) }
2023-01-09 19:18:21 +08:00
2017-09-22 20:26:06 +08:00
it " handles mailto links " do
response = TopicEmbed . find_remote ( url )
2021-10-01 23:48:21 +08:00
2021-11-17 13:39:49 +08:00
expect ( response . body ) . to have_tag ( " a " , with : { href : " mailto:foo@example.com " } )
2017-09-22 20:26:06 +08:00
expect ( response . body ) . to have_tag ( " a " , with : { href : " mailto:bar@example.com " } )
end
end
2021-04-30 17:10:19 +08:00
2022-07-28 00:14:14 +08:00
context " with malformed href " do
2021-04-30 17:10:19 +08:00
let ( :url ) { " http://example.com/foo " }
let ( :contents ) { '<p><a href="(http://foo.bar)">Baz</a></p>' }
2021-10-27 16:39:28 +08:00
before { stub_request ( :get , url ) . to_return ( status : 200 , body : contents ) }
2021-04-30 17:10:19 +08:00
it " doesn’ t raise an exception " do
expect { TopicEmbed . find_remote ( url ) } . not_to raise_error
end
end
2021-10-01 23:48:21 +08:00
2022-07-28 00:14:14 +08:00
context " with canonical links " do
2021-10-01 23:48:21 +08:00
let ( :url ) { " http://eviltrout.com/123?asd " }
let ( :canonical_url ) { " http://eviltrout.com/123 " }
let ( :content ) { " <head><link rel= \" canonical \" href= \" #{ canonical_url } \" ></head> " }
let ( :canonical_content ) { " <title>Canonical</title><body></body> " }
before do
2021-10-27 16:39:28 +08:00
stub_request ( :get , url ) . to_return ( status : 200 , body : content )
2021-10-01 23:48:21 +08:00
stub_request ( :head , canonical_url )
2021-10-27 16:39:28 +08:00
stub_request ( :get , canonical_url ) . to_return ( status : 200 , body : canonical_content )
2021-10-01 23:48:21 +08:00
end
it " a " do
response = TopicEmbed . find_remote ( url )
expect ( response . title ) . to eq ( " Canonical " )
end
end
2015-09-25 06:20:59 +08:00
end
2020-02-07 23:54:24 +08:00
describe " .absolutize_urls " do
2020-12-04 06:16:01 +08:00
it " handles badly formed URIs " do
2022-01-21 10:03:49 +08:00
invalid_url = " http://source.com/ # double # anchor "
contents = " hello world new post <a href='/hello'>hello</a> "
2020-02-07 23:54:24 +08:00
raw = TopicEmbed . absolutize_urls ( invalid_url , contents )
2020-12-04 06:16:01 +08:00
expect ( raw ) . to eq ( " hello world new post <a href= \" http://source.com/hello \" >hello</a> " )
2020-02-07 23:54:24 +08:00
end
2022-01-21 10:03:49 +08:00
it " handles malformed links " do
url = " https://somesource.com "
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
contents = << ~ HTML
2022-01-21 10:03:49 +08:00
hello world new post < a href = " mailto:somemail@somewhere.org> " > hello < / a>
some image < img src = " https:/><invalidimagesrc/ " >
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
HTML
2022-01-21 10:03:49 +08:00
raw = TopicEmbed . absolutize_urls ( url , contents )
expect ( raw ) . to eq ( contents )
end
2020-02-07 23:54:24 +08:00
end
2022-08-03 02:49:28 +08:00
describe " .imported_from_html " do
after { I18n . reload! }
it " uses the default site locale for the 'imported_from' footer " do
TranslationOverride . upsert! (
" en " ,
" embed.imported_from " ,
" English translation of embed.imported_from with %{link} " ,
)
TranslationOverride . upsert! (
" de " ,
" embed.imported_from " ,
" German translation of embed.imported_from with %{link} " ,
)
I18n . locale = :en
expected_html = TopicEmbed . imported_from_html ( " some_url " )
I18n . locale = :de
expect ( TopicEmbed . imported_from_html ( " some_url " ) ) . to eq ( expected_html )
end
2023-04-18 15:05:29 +08:00
it " normalize_encodes the url " do
html =
TopicEmbed . imported_from_html (
'http://www.discourse.org/%23<%2Fa><img%20src%3Dx%20onerror%3Dalert("document.domain")%3B>' ,
)
expected_html =
" \n <hr> \n <small>This is a companion discussion topic for the original entry at <a href='http://www.discourse.org/%23%3C/a%3E%3Cimg%20src=x%20onerror=alert(%22document.domain%22);%3E'>http://www.discourse.org/%23%3C/a%3E%3Cimg%20src=x%20onerror=alert(%22document.domain%22);%3E</a></small> \n "
expect ( html ) . to eq ( expected_html )
end
2022-08-03 02:49:28 +08:00
end
2014-01-01 03:37:43 +08:00
end