github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/hugolib/embedded_shortcodes_test.go (about) 1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package hugolib 15 16 import ( 17 "context" 18 "encoding/json" 19 "fmt" 20 "html/template" 21 "path/filepath" 22 "strings" 23 "testing" 24 25 "github.com/spf13/cast" 26 27 "github.com/gohugoio/hugo/deps" 28 29 qt "github.com/frankban/quicktest" 30 ) 31 32 const ( 33 testBaseURL = "http://foo/bar" 34 ) 35 36 func TestShortcodeCrossrefs(t *testing.T) { 37 t.Parallel() 38 39 for _, relative := range []bool{true, false} { 40 doTestShortcodeCrossrefs(t, relative) 41 } 42 } 43 44 func doTestShortcodeCrossrefs(t *testing.T, relative bool) { 45 var ( 46 cfg, fs = newTestCfg() 47 c = qt.New(t) 48 ) 49 50 cfg.Set("baseURL", testBaseURL) 51 52 var refShortcode string 53 var expectedBase string 54 55 if relative { 56 refShortcode = "relref" 57 expectedBase = "/bar" 58 } else { 59 refShortcode = "ref" 60 expectedBase = testBaseURL 61 } 62 63 path := filepath.FromSlash("blog/post.md") 64 in := fmt.Sprintf(`{{< %s "%s" >}}`, refShortcode, path) 65 66 writeSource(t, fs, "content/"+path, simplePageWithURL+": "+in) 67 68 expected := fmt.Sprintf(`%s/simple/url/`, expectedBase) 69 70 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 71 72 c.Assert(len(s.RegularPages()), qt.Equals, 1) 73 74 content, err := s.RegularPages()[0].Content(context.Background()) 75 c.Assert(err, qt.IsNil) 76 output := cast.ToString(content) 77 78 if !strings.Contains(output, expected) { 79 t.Errorf("Got\n%q\nExpected\n%q", output, expected) 80 } 81 } 82 83 func TestShortcodeHighlight(t *testing.T) { 84 t.Parallel() 85 86 for _, this := range []struct { 87 in, expected string 88 }{ 89 { 90 `{{< highlight java >}} 91 void do(); 92 {{< /highlight >}}`, 93 `(?s)<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java"`, 94 }, 95 { 96 `{{< highlight java "style=friendly" >}} 97 void do(); 98 {{< /highlight >}}`, 99 `(?s)<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java">`, 100 }, 101 } { 102 103 var ( 104 cfg, fs = newTestCfg() 105 th = newTestHelper(cfg, fs, t) 106 ) 107 108 cfg.Set("markup.highlight.style", "bw") 109 cfg.Set("markup.highlight.noClasses", true) 110 111 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 112 title: Shorty 113 --- 114 %s`, this.in)) 115 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) 116 117 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 118 119 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 120 121 } 122 } 123 124 func TestShortcodeFigure(t *testing.T) { 125 t.Parallel() 126 127 for _, this := range []struct { 128 in, expected string 129 }{ 130 { 131 `{{< figure src="/img/hugo-logo.png" >}}`, 132 "(?s)<figure>.*?<img src=\"/img/hugo-logo.png\"/>.*?</figure>", 133 }, 134 { 135 // set alt 136 `{{< figure src="/img/hugo-logo.png" alt="Hugo logo" >}}`, 137 "(?s)<figure>.*?<img src=\"/img/hugo-logo.png\".+?alt=\"Hugo logo\"/>.*?</figure>", 138 }, 139 // set title 140 { 141 `{{< figure src="/img/hugo-logo.png" title="Hugo logo" >}}`, 142 "(?s)<figure>.*?<img src=\"/img/hugo-logo.png\"/>.*?<figcaption>.*?<h4>Hugo logo</h4>.*?</figcaption>.*?</figure>", 143 }, 144 // set attr and attrlink 145 { 146 `{{< figure src="/img/hugo-logo.png" attr="Hugo logo" attrlink="/img/hugo-logo.png" >}}`, 147 "(?s)<figure>.*?<img src=\"/img/hugo-logo.png\"/>.*?<figcaption>.*?<p>.*?<a href=\"/img/hugo-logo.png\">.*?Hugo logo.*?</a>.*?</p>.*?</figcaption>.*?</figure>", 148 }, 149 } { 150 151 var ( 152 cfg, fs = newTestCfg() 153 th = newTestHelper(cfg, fs, t) 154 ) 155 156 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 157 title: Shorty 158 --- 159 %s`, this.in)) 160 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) 161 162 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 163 164 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 165 166 } 167 } 168 169 func TestShortcodeYoutube(t *testing.T) { 170 t.Parallel() 171 172 for _, this := range []struct { 173 in, expected string 174 }{ 175 { 176 `{{< youtube w7Ft2ymGmfc >}}`, 177 "(?s)\n<div style=\".*?\">.*?<iframe src=\"https://www.youtube.com/embed/w7Ft2ymGmfc\" style=\".*?\" allowfullscreen title=\"YouTube Video\">.*?</iframe>.*?</div>\n", 178 }, 179 // set class 180 { 181 `{{< youtube w7Ft2ymGmfc video>}}`, 182 "(?s)\n<div class=\"video\">.*?<iframe src=\"https://www.youtube.com/embed/w7Ft2ymGmfc\" allowfullscreen title=\"YouTube Video\">.*?</iframe>.*?</div>\n", 183 }, 184 // set class and autoplay (using named params) 185 { 186 `{{< youtube id="w7Ft2ymGmfc" class="video" autoplay="true" >}}`, 187 "(?s)\n<div class=\"video\">.*?<iframe src=\"https://www.youtube.com/embed/w7Ft2ymGmfc\\?autoplay=1\".*?allowfullscreen title=\"YouTube Video\">.*?</iframe>.*?</div>", 188 }, 189 // set custom title for accessibility) 190 { 191 `{{< youtube id="w7Ft2ymGmfc" title="A New Hugo Site in Under Two Minutes" >}}`, 192 "(?s)\n<div style=\".*?\">.*?<iframe src=\"https://www.youtube.com/embed/w7Ft2ymGmfc\" style=\".*?\" allowfullscreen title=\"A New Hugo Site in Under Two Minutes\">.*?</iframe>.*?</div>", 193 }, 194 } { 195 var ( 196 cfg, fs = newTestCfg() 197 th = newTestHelper(cfg, fs, t) 198 ) 199 200 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 201 title: Shorty 202 --- 203 %s`, this.in)) 204 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) 205 206 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 207 208 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 209 } 210 } 211 212 func TestShortcodeVimeo(t *testing.T) { 213 t.Parallel() 214 215 for _, this := range []struct { 216 in, expected string 217 }{ 218 { 219 `{{< vimeo 146022717 >}}`, 220 "(?s)\n<div style=\".*?\">.*?<iframe src=\"https://player.vimeo.com/video/146022717\" style=\".*?\" title=\"vimeo video\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>\n", 221 }, 222 // set class 223 { 224 `{{< vimeo 146022717 video >}}`, 225 "(?s)\n<div class=\"video\">.*?<iframe src=\"https://player.vimeo.com/video/146022717\" title=\"vimeo video\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>\n", 226 }, 227 // set vimeo title 228 { 229 `{{< vimeo 146022717 video my-title >}}`, 230 "(?s)\n<div class=\"video\">.*?<iframe src=\"https://player.vimeo.com/video/146022717\" title=\"my-title\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>\n", 231 }, 232 // set class (using named params) 233 { 234 `{{< vimeo id="146022717" class="video" >}}`, 235 "(?s)^<div class=\"video\">.*?<iframe src=\"https://player.vimeo.com/video/146022717\" title=\"vimeo video\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>", 236 }, 237 // set vimeo title (using named params) 238 { 239 `{{< vimeo id="146022717" class="video" title="my vimeo video" >}}`, 240 "(?s)^<div class=\"video\">.*?<iframe src=\"https://player.vimeo.com/video/146022717\" title=\"my vimeo video\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>", 241 }, 242 } { 243 var ( 244 cfg, fs = newTestCfg() 245 th = newTestHelper(cfg, fs, t) 246 ) 247 248 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 249 title: Shorty 250 --- 251 %s`, this.in)) 252 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) 253 254 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 255 256 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 257 258 } 259 } 260 261 func TestShortcodeGist(t *testing.T) { 262 t.Parallel() 263 264 for _, this := range []struct { 265 in, expected string 266 }{ 267 { 268 `{{< gist spf13 7896402 >}}`, 269 "(?s)^<script type=\"application/javascript\" src=\"https://gist.github.com/spf13/7896402.js\"></script>", 270 }, 271 { 272 `{{< gist spf13 7896402 "img.html" >}}`, 273 "(?s)^<script type=\"application/javascript\" src=\"https://gist.github.com/spf13/7896402.js\\?file=img.html\"></script>", 274 }, 275 } { 276 var ( 277 cfg, fs = newTestCfg() 278 th = newTestHelper(cfg, fs, t) 279 ) 280 281 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 282 title: Shorty 283 --- 284 %s`, this.in)) 285 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) 286 287 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 288 289 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 290 291 } 292 } 293 294 func TestShortcodeTweet(t *testing.T) { 295 t.Parallel() 296 297 for i, this := range []struct { 298 privacy map[string]any 299 in, resp, expected string 300 }{ 301 { 302 map[string]any{ 303 "twitter": map[string]any{ 304 "simple": true, 305 }, 306 }, 307 `{{< tweet 666616452582129664 >}}`, 308 `{"author_name":"Steve Francia","author_url":"https://twitter.com/spf13","cache_age":"3153600000","height":null,"html":"\u003cblockquote class=\"twitter-tweet\"\u003e\u003cp lang=\"en\" dir=\"ltr\"\u003eHugo 0.15 will have 30%+ faster render times thanks to this commit \u003ca href=\"https://t.co/FfzhM8bNhT\"\u003ehttps://t.co/FfzhM8bNhT\u003c/a\u003e \u003ca href=\"https://twitter.com/hashtag/gohugo?src=hash\u0026amp;ref_src=twsrc%5Etfw\"\u003e#gohugo\u003c/a\u003e \u003ca href=\"https://twitter.com/hashtag/golang?src=hash\u0026amp;ref_src=twsrc%5Etfw\"\u003e#golang\u003c/a\u003e \u003ca href=\"https://t.co/ITbMNU2BUf\"\u003ehttps://t.co/ITbMNU2BUf\u003c/a\u003e\u003c/p\u003e\u0026mdash; Steve Francia (@spf13) \u003ca href=\"https://twitter.com/spf13/status/666616452582129664?ref_src=twsrc%5Etfw\"\u003eNovember 17, 2015\u003c/a\u003e\u003c/blockquote\u003e\n\u003cscript async src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"\u003e\u003c/script\u003e\n","provider_name":"Twitter","provider_url":"https://twitter.com","type":"rich","url":"https://twitter.com/spf13/status/666616452582129664","version":"1.0","width":550}`, 309 `.twitter-tweet a`, 310 }, 311 { 312 map[string]any{ 313 "twitter": map[string]any{ 314 "simple": false, 315 }, 316 }, 317 `{{< tweet 666616452582129664 >}}`, 318 `{"author_name":"Steve Francia","author_url":"https://twitter.com/spf13","cache_age":"3153600000","height":null,"html":"\u003cblockquote class=\"twitter-tweet\"\u003e\u003cp lang=\"en\" dir=\"ltr\"\u003eHugo 0.15 will have 30%+ faster render times thanks to this commit \u003ca href=\"https://t.co/FfzhM8bNhT\"\u003ehttps://t.co/FfzhM8bNhT\u003c/a\u003e \u003ca href=\"https://twitter.com/hashtag/gohugo?src=hash\u0026amp;ref_src=twsrc%5Etfw\"\u003e#gohugo\u003c/a\u003e \u003ca href=\"https://twitter.com/hashtag/golang?src=hash\u0026amp;ref_src=twsrc%5Etfw\"\u003e#golang\u003c/a\u003e \u003ca href=\"https://t.co/ITbMNU2BUf\"\u003ehttps://t.co/ITbMNU2BUf\u003c/a\u003e\u003c/p\u003e\u0026mdash; Steve Francia (@spf13) \u003ca href=\"https://twitter.com/spf13/status/666616452582129664?ref_src=twsrc%5Etfw\"\u003eNovember 17, 2015\u003c/a\u003e\u003c/blockquote\u003e\n\u003cscript async src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"\u003e\u003c/script\u003e\n","provider_name":"Twitter","provider_url":"https://twitter.com","type":"rich","url":"https://twitter.com/spf13/status/666616452582129664","version":"1.0","width":550}`, 319 `(?s)<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Hugo 0.15 will have 30%\+ faster render times thanks to this commit <a href="https://t.co/FfzhM8bNhT">https://t.co/FfzhM8bNhT</a> <a href="https://twitter.com/hashtag/gohugo\?src=hash&ref_src=twsrc%5Etfw">#gohugo</a> <a href="https://twitter.com/hashtag/golang\?src=hash&ref_src=twsrc%5Etfw">#golang</a> <a href="https://t.co/ITbMNU2BUf">https://t.co/ITbMNU2BUf</a></p>— Steve Francia \(@spf13\) <a href="https://twitter.com/spf13/status/666616452582129664\?ref_src=twsrc%5Etfw">November 17, 2015</a></blockquote>\s*<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`, 320 }, 321 { 322 map[string]any{ 323 "twitter": map[string]any{ 324 "simple": false, 325 }, 326 }, 327 `{{< tweet user="SanDiegoZoo" id="1453110110599868418" >}}`, 328 `{"author_name":"San Diego Boo 👻 Wildlife Alliance","author_url":"https://twitter.com/sandiegozoo","cache_age":"3153600000","height":null,"html":"\u003cblockquote class=\"twitter-tweet\"\u003e\u003cp lang=\"en\" dir=\"ltr\"\u003eOwl bet you\u0026#39;ll lose this staring contest 🦉 \u003ca href=\"https://t.co/eJh4f2zncC\"\u003epic.twitter.com/eJh4f2zncC\u003c/a\u003e\u003c/p\u003e\u0026mdash; San Diego Boo 👻 Wildlife Alliance (@sandiegozoo) \u003ca href=\"https://twitter.com/sandiegozoo/status/1453110110599868418?ref_src=twsrc%5Etfw\"\u003eOctober 26, 2021\u003c/a\u003e\u003c/blockquote\u003e\n\u003cscript async src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"\u003e\u003c/script\u003e\n","provider_name":"Twitter","provider_url":"https://twitter.com","type":"rich","url":"https://twitter.com/sandiegozoo/status/1453110110599868418","version":"1.0","width":550}`, 329 `(?s)<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Owl bet you'll lose this staring contest 🦉 <a href="https://t.co/eJh4f2zncC">pic.twitter.com/eJh4f2zncC</a></p>— San Diego Boo 👻 Wildlife Alliance \(@sandiegozoo\) <a href="https://twitter.com/sandiegozoo/status/1453110110599868418\?ref_src=twsrc%5Etfw">October 26, 2021</a></blockquote>\s*<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`, 330 }, 331 } { 332 // overload getJSON to return mock API response from Twitter 333 tweetFuncMap := template.FuncMap{ 334 "getJSON": func(urlParts ...any) any { 335 var v any 336 err := json.Unmarshal([]byte(this.resp), &v) 337 if err != nil { 338 t.Fatalf("[%d] unexpected error in json.Unmarshal: %s", i, err) 339 return err 340 } 341 return v 342 }, 343 } 344 345 var ( 346 cfg, fs = newTestCfg() 347 th = newTestHelper(cfg, fs, t) 348 ) 349 350 cfg.Set("privacy", this.privacy) 351 352 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 353 title: Shorty 354 --- 355 %s`, this.in)) 356 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) 357 358 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, OverloadedTemplateFuncs: tweetFuncMap}, BuildCfg{}) 359 360 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 361 362 } 363 } 364 365 func TestShortcodeInstagram(t *testing.T) { 366 t.Parallel() 367 368 for i, this := range []struct { 369 in, hidecaption, resp, expected string 370 }{ 371 { 372 `{{< instagram BMokmydjG-M >}}`, 373 `0`, 374 `{"provider_url": "https://www.instagram.com", "media_id": "1380514280986406796_25025320", "author_name": "instagram", "height": null, "thumbnail_url": "https://scontent-amt2-1.cdninstagram.com/t51.2885-15/s640x640/sh0.08/e35/15048135_1880160212214218_7827880881132929024_n.jpg?ig_cache_key=MTM4MDUxNDI4MDk4NjQwNjc5Ng%3D%3D.2", "thumbnail_width": 640, "thumbnail_height": 640, "provider_name": "Instagram", "title": "Today, we\u2019re introducing a few new tools to help you make your story even more fun: Boomerang and mentions. We\u2019re also starting to test links inside some stories.\nBoomerang lets you turn everyday moments into something fun and unexpected. Now you can easily take a Boomerang right inside Instagram. Swipe right from your feed to open the stories camera. A new format picker under the record button lets you select \u201cBoomerang\u201d mode.\nYou can also now share who you\u2019re with or who you\u2019re thinking of by mentioning them in your story. When you add text to your story, type \u201c@\u201d followed by a username and select the person you\u2019d like to mention. Their username will appear underlined in your story. And when someone taps the mention, they'll see a pop-up that takes them to that profile.\nYou may begin to spot \u201cSee More\u201d links at the bottom of some stories. This is a test that lets verified accounts add links so it\u2019s easy to learn more. From your favorite chefs\u2019 recipes to articles from top journalists or concert dates from the musicians you love, tap \u201cSee More\u201d or swipe up to view the link right inside the app.\nTo learn more about today\u2019s updates, check out help.instagram.com.\nThese updates for Instagram Stories are available as part of Instagram version 9.7 available for iOS in the Apple App Store, for Android in Google Play and for Windows 10 in the Windows Store.", "html": "\u003cblockquote class=\"instagram-media\" data-instgrm-captioned data-instgrm-version=\"7\" style=\" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:658px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);\"\u003e\u003cdiv style=\"padding:8px;\"\u003e \u003cdiv style=\" background:#F8F8F8; line-height:0; margin-top:40px; padding:50.0% 0; text-align:center; width:100%;\"\u003e \u003cdiv style=\" background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAMUExURczMzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;\"\u003e\u003c/div\u003e\u003c/div\u003e \u003cp style=\" margin:8px 0 0 0; padding:0 4px;\"\u003e \u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#000; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none; word-wrap:break-word;\" target=\"_blank\"\u003eToday, we\u2019re introducing a few new tools to help you make your story even more fun: Boomerang and mentions. We\u2019re also starting to test links inside some stories. Boomerang lets you turn everyday moments into something fun and unexpected. Now you can easily take a Boomerang right inside Instagram. Swipe right from your feed to open the stories camera. A new format picker under the record button lets you select \u201cBoomerang\u201d mode. You can also now share who you\u2019re with or who you\u2019re thinking of by mentioning them in your story. When you add text to your story, type \u201c@\u201d followed by a username and select the person you\u2019d like to mention. Their username will appear underlined in your story. And when someone taps the mention, they\u0026#39;ll see a pop-up that takes them to that profile. You may begin to spot \u201cSee More\u201d links at the bottom of some stories. This is a test that lets verified accounts add links so it\u2019s easy to learn more. From your favorite chefs\u2019 recipes to articles from top journalists or concert dates from the musicians you love, tap \u201cSee More\u201d or swipe up to view the link right inside the app. To learn more about today\u2019s updates, check out help.instagram.com. These updates for Instagram Stories are available as part of Instagram version 9.7 available for iOS in the Apple App Store, for Android in Google Play and for Windows 10 in the Windows Store.\u003c/a\u003e\u003c/p\u003e \u003cp style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003eA photo posted by Instagram (@instagram) on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`, 375 `(?s)<blockquote class="instagram-media" data-instgrm-captioned data-instgrm-version="7" .*defer src="//platform.instagram.com/en_US/embeds.js"></script>`, 376 }, 377 { 378 `{{< instagram BMokmydjG-M hidecaption >}}`, 379 `1`, 380 `{"provider_url": "https://www.instagram.com", "media_id": "1380514280986406796_25025320", "author_name": "instagram", "height": null, "thumbnail_url": "https://scontent-amt2-1.cdninstagram.com/t51.2885-15/s640x640/sh0.08/e35/15048135_1880160212214218_7827880881132929024_n.jpg?ig_cache_key=MTM4MDUxNDI4MDk4NjQwNjc5Ng%3D%3D.2", "thumbnail_width": 640, "thumbnail_height": 640, "provider_name": "Instagram", "title": "Today, we\u2019re introducing a few new tools to help you make your story even more fun: Boomerang and mentions. We\u2019re also starting to test links inside some stories.\nBoomerang lets you turn everyday moments into something fun and unexpected. Now you can easily take a Boomerang right inside Instagram. Swipe right from your feed to open the stories camera. A new format picker under the record button lets you select \u201cBoomerang\u201d mode.\nYou can also now share who you\u2019re with or who you\u2019re thinking of by mentioning them in your story. When you add text to your story, type \u201c@\u201d followed by a username and select the person you\u2019d like to mention. Their username will appear underlined in your story. And when someone taps the mention, they'll see a pop-up that takes them to that profile.\nYou may begin to spot \u201cSee More\u201d links at the bottom of some stories. This is a test that lets verified accounts add links so it\u2019s easy to learn more. From your favorite chefs\u2019 recipes to articles from top journalists or concert dates from the musicians you love, tap \u201cSee More\u201d or swipe up to view the link right inside the app.\nTo learn more about today\u2019s updates, check out help.instagram.com.\nThese updates for Instagram Stories are available as part of Instagram version 9.7 available for iOS in the Apple App Store, for Android in Google Play and for Windows 10 in the Windows Store.", "html": "\u003cblockquote class=\"instagram-media\" data-instgrm-version=\"7\" style=\" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:658px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);\"\u003e\u003cdiv style=\"padding:8px;\"\u003e \u003cdiv style=\" background:#F8F8F8; line-height:0; margin-top:40px; padding:50.0% 0; text-align:center; width:100%;\"\u003e \u003cdiv style=\" background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAMUExURczMzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;\"\u003e\u003c/div\u003e\u003c/div\u003e\u003cp style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003e\u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`, 381 `(?s)<blockquote class="instagram-media" data-instgrm-version="7" style=" background:#FFF; border:0; .*<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`, 382 }, 383 } { 384 // overload getJSON to return mock API response from Instagram 385 instagramFuncMap := template.FuncMap{ 386 "getJSON": func(args ...any) any { 387 headers := args[len(args)-1].(map[string]any) 388 auth := headers["Authorization"] 389 if auth != "Bearer dummytoken" { 390 return fmt.Errorf("invalid access token: %q", auth) 391 } 392 var v any 393 err := json.Unmarshal([]byte(this.resp), &v) 394 if err != nil { 395 return fmt.Errorf("[%d] unexpected error in json.Unmarshal: %s", i, err) 396 } 397 return v 398 }, 399 } 400 401 var ( 402 cfg, fs = newTestCfg() 403 th = newTestHelper(cfg, fs, t) 404 ) 405 406 cfg.Set("services.instagram.accessToken", "dummytoken") 407 408 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 409 title: Shorty 410 --- 411 %s`, this.in)) 412 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content | safeHTML }}`) 413 414 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, OverloadedTemplateFuncs: instagramFuncMap}, BuildCfg{}) 415 416 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 417 418 } 419 }