embedded_shortcodes_test.go (25911B)
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 "encoding/json" 18 "fmt" 19 "html/template" 20 "path/filepath" 21 "strings" 22 "testing" 23 24 "github.com/spf13/cast" 25 26 "github.com/gohugoio/hugo/deps" 27 28 qt "github.com/frankban/quicktest" 29 ) 30 31 const ( 32 testBaseURL = "http://foo/bar" 33 ) 34 35 func TestShortcodeCrossrefs(t *testing.T) { 36 t.Parallel() 37 38 for _, relative := range []bool{true, false} { 39 doTestShortcodeCrossrefs(t, relative) 40 } 41 } 42 43 func doTestShortcodeCrossrefs(t *testing.T, relative bool) { 44 var ( 45 cfg, fs = newTestCfg() 46 c = qt.New(t) 47 ) 48 49 cfg.Set("baseURL", testBaseURL) 50 51 var refShortcode string 52 var expectedBase string 53 54 if relative { 55 refShortcode = "relref" 56 expectedBase = "/bar" 57 } else { 58 refShortcode = "ref" 59 expectedBase = testBaseURL 60 } 61 62 path := filepath.FromSlash("blog/post.md") 63 in := fmt.Sprintf(`{{< %s "%s" >}}`, refShortcode, path) 64 65 writeSource(t, fs, "content/"+path, simplePageWithURL+": "+in) 66 67 expected := fmt.Sprintf(`%s/simple/url/`, expectedBase) 68 69 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 70 71 c.Assert(len(s.RegularPages()), qt.Equals, 1) 72 73 content, err := s.RegularPages()[0].Content() 74 c.Assert(err, qt.IsNil) 75 output := cast.ToString(content) 76 77 if !strings.Contains(output, expected) { 78 t.Errorf("Got\n%q\nExpected\n%q", output, expected) 79 } 80 } 81 82 func TestShortcodeHighlight(t *testing.T) { 83 t.Parallel() 84 85 for _, this := range []struct { 86 in, expected string 87 }{ 88 { 89 `{{< highlight java >}} 90 void do(); 91 {{< /highlight >}}`, 92 `(?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"`, 93 }, 94 { 95 `{{< highlight java "style=friendly" >}} 96 void do(); 97 {{< /highlight >}}`, 98 `(?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">`, 99 }, 100 } { 101 102 var ( 103 cfg, fs = newTestCfg() 104 th = newTestHelper(cfg, fs, t) 105 ) 106 107 cfg.Set("markup.highlight.style", "bw") 108 cfg.Set("markup.highlight.noClasses", true) 109 110 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 111 title: Shorty 112 --- 113 %s`, this.in)) 114 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) 115 116 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 117 118 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 119 120 } 121 } 122 123 func TestShortcodeFigure(t *testing.T) { 124 t.Parallel() 125 126 for _, this := range []struct { 127 in, expected string 128 }{ 129 { 130 `{{< figure src="/img/hugo-logo.png" >}}`, 131 "(?s)<figure>.*?<img src=\"/img/hugo-logo.png\"/>.*?</figure>", 132 }, 133 { 134 // set alt 135 `{{< figure src="/img/hugo-logo.png" alt="Hugo logo" >}}`, 136 "(?s)<figure>.*?<img src=\"/img/hugo-logo.png\".+?alt=\"Hugo logo\"/>.*?</figure>", 137 }, 138 // set title 139 { 140 `{{< figure src="/img/hugo-logo.png" title="Hugo logo" >}}`, 141 "(?s)<figure>.*?<img src=\"/img/hugo-logo.png\"/>.*?<figcaption>.*?<h4>Hugo logo</h4>.*?</figcaption>.*?</figure>", 142 }, 143 // set attr and attrlink 144 { 145 `{{< figure src="/img/hugo-logo.png" attr="Hugo logo" attrlink="/img/hugo-logo.png" >}}`, 146 "(?s)<figure>.*?<img src=\"/img/hugo-logo.png\"/>.*?<figcaption>.*?<p>.*?<a href=\"/img/hugo-logo.png\">.*?Hugo logo.*?</a>.*?</p>.*?</figcaption>.*?</figure>", 147 }, 148 } { 149 150 var ( 151 cfg, fs = newTestCfg() 152 th = newTestHelper(cfg, fs, t) 153 ) 154 155 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 156 title: Shorty 157 --- 158 %s`, this.in)) 159 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) 160 161 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 162 163 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 164 165 } 166 } 167 168 func TestShortcodeYoutube(t *testing.T) { 169 t.Parallel() 170 171 for _, this := range []struct { 172 in, expected string 173 }{ 174 { 175 `{{< youtube w7Ft2ymGmfc >}}`, 176 "(?s)\n<div style=\".*?\">.*?<iframe src=\"https://www.youtube.com/embed/w7Ft2ymGmfc\" style=\".*?\" allowfullscreen title=\"YouTube Video\">.*?</iframe>.*?</div>\n", 177 }, 178 // set class 179 { 180 `{{< youtube w7Ft2ymGmfc video>}}`, 181 "(?s)\n<div class=\"video\">.*?<iframe src=\"https://www.youtube.com/embed/w7Ft2ymGmfc\" allowfullscreen title=\"YouTube Video\">.*?</iframe>.*?</div>\n", 182 }, 183 // set class and autoplay (using named params) 184 { 185 `{{< youtube id="w7Ft2ymGmfc" class="video" autoplay="true" >}}`, 186 "(?s)\n<div class=\"video\">.*?<iframe src=\"https://www.youtube.com/embed/w7Ft2ymGmfc\\?autoplay=1\".*?allowfullscreen title=\"YouTube Video\">.*?</iframe>.*?</div>", 187 }, 188 // set custom title for accessibility) 189 { 190 `{{< youtube id="w7Ft2ymGmfc" title="A New Hugo Site in Under Two Minutes" >}}`, 191 "(?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>", 192 }, 193 } { 194 var ( 195 cfg, fs = newTestCfg() 196 th = newTestHelper(cfg, fs, t) 197 ) 198 199 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 200 title: Shorty 201 --- 202 %s`, this.in)) 203 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) 204 205 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 206 207 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 208 } 209 } 210 211 func TestShortcodeVimeo(t *testing.T) { 212 t.Parallel() 213 214 for _, this := range []struct { 215 in, expected string 216 }{ 217 { 218 `{{< vimeo 146022717 >}}`, 219 "(?s)\n<div style=\".*?\">.*?<iframe src=\"https://player.vimeo.com/video/146022717\" style=\".*?\" title=\"vimeo video\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>\n", 220 }, 221 // set class 222 { 223 `{{< vimeo 146022717 video >}}`, 224 "(?s)\n<div class=\"video\">.*?<iframe src=\"https://player.vimeo.com/video/146022717\" title=\"vimeo video\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>\n", 225 }, 226 // set vimeo title 227 { 228 `{{< vimeo 146022717 video my-title >}}`, 229 "(?s)\n<div class=\"video\">.*?<iframe src=\"https://player.vimeo.com/video/146022717\" title=\"my-title\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>\n", 230 }, 231 // set class (using named params) 232 { 233 `{{< vimeo id="146022717" class="video" >}}`, 234 "(?s)^<div class=\"video\">.*?<iframe src=\"https://player.vimeo.com/video/146022717\" title=\"vimeo video\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>", 235 }, 236 // set vimeo title (using named params) 237 { 238 `{{< vimeo id="146022717" class="video" title="my vimeo video" >}}`, 239 "(?s)^<div class=\"video\">.*?<iframe src=\"https://player.vimeo.com/video/146022717\" title=\"my vimeo video\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>", 240 }, 241 } { 242 var ( 243 cfg, fs = newTestCfg() 244 th = newTestHelper(cfg, fs, t) 245 ) 246 247 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 248 title: Shorty 249 --- 250 %s`, this.in)) 251 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) 252 253 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 254 255 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 256 257 } 258 } 259 260 func TestShortcodeGist(t *testing.T) { 261 t.Parallel() 262 263 for _, this := range []struct { 264 in, expected string 265 }{ 266 { 267 `{{< gist spf13 7896402 >}}`, 268 "(?s)^<script type=\"application/javascript\" src=\"https://gist.github.com/spf13/7896402.js\"></script>", 269 }, 270 { 271 `{{< gist spf13 7896402 "img.html" >}}`, 272 "(?s)^<script type=\"application/javascript\" src=\"https://gist.github.com/spf13/7896402.js\\?file=img.html\"></script>", 273 }, 274 } { 275 var ( 276 cfg, fs = newTestCfg() 277 th = newTestHelper(cfg, fs, t) 278 ) 279 280 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 281 title: Shorty 282 --- 283 %s`, this.in)) 284 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) 285 286 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 287 288 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 289 290 } 291 } 292 293 func TestShortcodeTweet(t *testing.T) { 294 t.Parallel() 295 296 for i, this := range []struct { 297 privacy map[string]any 298 in, resp, expected string 299 }{ 300 { 301 map[string]any{ 302 "twitter": map[string]any{ 303 "simple": true, 304 }, 305 }, 306 `{{< tweet 666616452582129664 >}}`, 307 `{"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}`, 308 `.twitter-tweet a`, 309 }, 310 { 311 map[string]any{ 312 "twitter": map[string]any{ 313 "simple": false, 314 }, 315 }, 316 `{{< tweet 666616452582129664 >}}`, 317 `{"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}`, 318 `(?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>`, 319 }, 320 { 321 map[string]any{ 322 "twitter": map[string]any{ 323 "simple": false, 324 }, 325 }, 326 `{{< tweet user="SanDiegoZoo" id="1453110110599868418" >}}`, 327 `{"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}`, 328 `(?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>`, 329 }, 330 } { 331 // overload getJSON to return mock API response from Twitter 332 tweetFuncMap := template.FuncMap{ 333 "getJSON": func(urlParts ...any) any { 334 var v any 335 err := json.Unmarshal([]byte(this.resp), &v) 336 if err != nil { 337 t.Fatalf("[%d] unexpected error in json.Unmarshal: %s", i, err) 338 return err 339 } 340 return v 341 }, 342 } 343 344 var ( 345 cfg, fs = newTestCfg() 346 th = newTestHelper(cfg, fs, t) 347 ) 348 349 cfg.Set("privacy", this.privacy) 350 351 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 352 title: Shorty 353 --- 354 %s`, this.in)) 355 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) 356 357 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, OverloadedTemplateFuncs: tweetFuncMap}, BuildCfg{}) 358 359 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 360 361 } 362 } 363 364 func TestShortcodeInstagram(t *testing.T) { 365 t.Parallel() 366 367 for i, this := range []struct { 368 in, hidecaption, resp, expected string 369 }{ 370 { 371 `{{< instagram BMokmydjG-M >}}`, 372 `0`, 373 `{"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"}`, 374 `(?s)<blockquote class="instagram-media" data-instgrm-captioned data-instgrm-version="7" .*defer src="//platform.instagram.com/en_US/embeds.js"></script>`, 375 }, 376 { 377 `{{< instagram BMokmydjG-M hidecaption >}}`, 378 `1`, 379 `{"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"}`, 380 `(?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>`, 381 }, 382 } { 383 // overload getJSON to return mock API response from Instagram 384 instagramFuncMap := template.FuncMap{ 385 "getJSON": func(args ...any) any { 386 headers := args[len(args)-1].(map[string]any) 387 auth := headers["Authorization"] 388 if auth != "Bearer dummytoken" { 389 return fmt.Errorf("invalid access token: %q", auth) 390 } 391 var v any 392 err := json.Unmarshal([]byte(this.resp), &v) 393 if err != nil { 394 return fmt.Errorf("[%d] unexpected error in json.Unmarshal: %s", i, err) 395 } 396 return v 397 }, 398 } 399 400 var ( 401 cfg, fs = newTestCfg() 402 th = newTestHelper(cfg, fs, t) 403 ) 404 405 cfg.Set("services.instagram.accessToken", "dummytoken") 406 407 writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- 408 title: Shorty 409 --- 410 %s`, this.in)) 411 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content | safeHTML }}`) 412 413 buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, OverloadedTemplateFuncs: instagramFuncMap}, BuildCfg{}) 414 415 th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) 416 417 } 418 }