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&amp;ref_src=twsrc%5Etfw">#gohugo</a> <a href="https://twitter.com/hashtag/golang\?src=hash&amp;ref_src=twsrc%5Etfw">#golang</a> <a href="https://t.co/ITbMNU2BUf">https://t.co/ITbMNU2BUf</a></p>&mdash; 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&#39;ll lose this staring contest 🦉 <a href="https://t.co/eJh4f2zncC">pic.twitter.com/eJh4f2zncC</a></p>&mdash; 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  }