github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/markup/goldmark/integration_test.go (about)

     1  // Copyright 2021 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 goldmark_test
    15  
    16  import (
    17  	"fmt"
    18  	"strings"
    19  	"testing"
    20  
    21  	qt "github.com/frankban/quicktest"
    22  
    23  	"github.com/gohugoio/hugo/hugolib"
    24  )
    25  
    26  // Issue 9463
    27  func TestAttributeExclusion(t *testing.T) {
    28  	t.Parallel()
    29  
    30  	files := `
    31  -- config.toml --
    32  [markup.goldmark.renderer]
    33  	unsafe = false
    34  [markup.goldmark.parser.attribute]
    35  	block = true
    36  	title = true
    37  -- content/p1.md --
    38  ---
    39  title: "p1"
    40  ---
    41  ## Heading {class="a" onclick="alert('heading')"}
    42  
    43  > Blockquote
    44  {class="b" ondblclick="alert('blockquote')"}
    45  
    46  ~~~bash {id="c" onmouseover="alert('code fence')" LINENOS=true}
    47  foo
    48  ~~~
    49  -- layouts/_default/single.html --
    50  {{ .Content }}
    51  `
    52  
    53  	b := hugolib.NewIntegrationTestBuilder(
    54  		hugolib.IntegrationTestConfig{
    55  			T:           t,
    56  			TxtarString: files,
    57  			NeedsOsFS:   false,
    58  		},
    59  	).Build()
    60  
    61  	b.AssertFileContent("public/p1/index.html", `
    62  		<h2 class="a" id="heading">
    63  		<blockquote class="b">
    64  		<div class="highlight" id="c">
    65  	`)
    66  }
    67  
    68  // Issue 9511
    69  func TestAttributeExclusionWithRenderHook(t *testing.T) {
    70  	t.Parallel()
    71  
    72  	files := `
    73  -- content/p1.md --
    74  ---
    75  title: "p1"
    76  ---
    77  ## Heading {onclick="alert('renderhook')" data-foo="bar"}
    78  -- layouts/_default/single.html --
    79  {{ .Content }}
    80  -- layouts/_default/_markup/render-heading.html --
    81  <h{{ .Level }}
    82    {{- range $k, $v := .Attributes -}}
    83      {{- printf " %s=%q" $k $v | safeHTMLAttr -}}
    84    {{- end -}}
    85  >{{ .Text | safeHTML }}</h{{ .Level }}>
    86  `
    87  
    88  	b := hugolib.NewIntegrationTestBuilder(
    89  		hugolib.IntegrationTestConfig{
    90  			T:           t,
    91  			TxtarString: files,
    92  			NeedsOsFS:   false,
    93  		},
    94  	).Build()
    95  
    96  	b.AssertFileContent("public/p1/index.html", `
    97  		<h2 data-foo="bar" id="heading">Heading</h2>
    98  	`)
    99  }
   100  
   101  func TestAttributesDefaultRenderer(t *testing.T) {
   102  	t.Parallel()
   103  
   104  	files := `
   105  -- content/p1.md --
   106  ---
   107  title: "p1"
   108  ---
   109  ## Heading Attribute Which Needs Escaping { class="a < b" }
   110  -- layouts/_default/single.html --
   111  {{ .Content }}
   112  `
   113  
   114  	b := hugolib.NewIntegrationTestBuilder(
   115  		hugolib.IntegrationTestConfig{
   116  			T:           t,
   117  			TxtarString: files,
   118  			NeedsOsFS:   false,
   119  		},
   120  	).Build()
   121  
   122  	b.AssertFileContent("public/p1/index.html", `
   123  class="a &lt; b"
   124  	`)
   125  }
   126  
   127  // Issue 9558.
   128  func TestAttributesHookNoEscape(t *testing.T) {
   129  	t.Parallel()
   130  
   131  	files := `
   132  -- content/p1.md --
   133  ---
   134  title: "p1"
   135  ---
   136  ## Heading Attribute Which Needs Escaping { class="Smith & Wesson" }
   137  -- layouts/_default/_markup/render-heading.html --
   138  plain: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v }}|{{ end }}|
   139  safeHTML: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v | safeHTML }}|{{ end }}|
   140  -- layouts/_default/single.html --
   141  {{ .Content }}
   142  `
   143  
   144  	b := hugolib.NewIntegrationTestBuilder(
   145  		hugolib.IntegrationTestConfig{
   146  			T:           t,
   147  			TxtarString: files,
   148  			NeedsOsFS:   false,
   149  		},
   150  	).Build()
   151  
   152  	b.AssertFileContent("public/p1/index.html", `
   153  plain: |class: Smith &amp; Wesson|id: heading-attribute-which-needs-escaping|
   154  safeHTML: |class: Smith & Wesson|id: heading-attribute-which-needs-escaping|
   155  	`)
   156  }
   157  
   158  // Issue 9504
   159  func TestLinkInTitle(t *testing.T) {
   160  	t.Parallel()
   161  
   162  	files := `
   163  -- config.toml --
   164  -- content/p1.md --
   165  ---
   166  title: "p1"
   167  ---
   168  ## Hello [Test](https://example.com)
   169  -- layouts/_default/single.html --
   170  {{ .Content }}
   171  -- layouts/_default/_markup/render-heading.html --
   172  <h{{ .Level }} id="{{ .Anchor | safeURL }}">
   173    {{ .Text | safeHTML }}
   174    <a class="anchor" href="#{{ .Anchor | safeURL }}">#</a>
   175  </h{{ .Level }}>
   176  -- layouts/_default/_markup/render-link.html --
   177  <a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}>{{ .Text | safeHTML }}</a>
   178  
   179  `
   180  
   181  	b := hugolib.NewIntegrationTestBuilder(
   182  		hugolib.IntegrationTestConfig{
   183  			T:           t,
   184  			TxtarString: files,
   185  			NeedsOsFS:   false,
   186  		},
   187  	).Build()
   188  
   189  	b.AssertFileContent("public/p1/index.html",
   190  		"<h2 id=\"hello-testhttpsexamplecom\">\n  Hello <a href=\"https://example.com\">Test</a>\n\n  <a class=\"anchor\" href=\"#hello-testhttpsexamplecom\">#</a>\n</h2>",
   191  	)
   192  }
   193  
   194  func TestHighlight(t *testing.T) {
   195  	t.Parallel()
   196  
   197  	files := `
   198  -- config.toml --
   199  [markup]
   200  [markup.highlight]
   201  anchorLineNos = false
   202  codeFences = true
   203  guessSyntax = false
   204  hl_Lines = ''
   205  lineAnchors = ''
   206  lineNoStart = 1
   207  lineNos = false
   208  lineNumbersInTable = true
   209  noClasses = false
   210  style = 'monokai'
   211  tabWidth = 4
   212  -- layouts/_default/single.html --
   213  {{ .Content }}
   214  -- content/p1.md --
   215  ---
   216  title: "p1"
   217  ---
   218  
   219  ## Code Fences
   220  
   221  §§§bash
   222  LINE1
   223  §§§
   224  
   225  ## Code Fences No Lexer
   226  
   227  §§§moo
   228  LINE1
   229  §§§
   230  
   231  ## Code Fences Simple Attributes
   232  
   233  §§A§bash { .myclass id="myid" }
   234  LINE1
   235  §§A§
   236  
   237  ## Code Fences Line Numbers
   238  
   239  §§§bash {linenos=table,hl_lines=[8,"15-17"],linenostart=199}
   240  LINE1
   241  LINE2
   242  LINE3
   243  LINE4
   244  LINE5
   245  LINE6
   246  LINE7
   247  LINE8
   248  §§§
   249  
   250  
   251  
   252  
   253  `
   254  
   255  	b := hugolib.NewIntegrationTestBuilder(
   256  		hugolib.IntegrationTestConfig{
   257  			T:           t,
   258  			TxtarString: files,
   259  		},
   260  	).Build()
   261  
   262  	b.AssertFileContent("public/p1/index.html",
   263  		"<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"cl\">LINE1\n</span></span></code></pre></div>",
   264  		"Code Fences No Lexer</h2>\n<pre tabindex=\"0\"><code class=\"language-moo\" data-lang=\"moo\">LINE1\n</code></pre>",
   265  		"lnt",
   266  	)
   267  }
   268  
   269  func BenchmarkRenderHooks(b *testing.B) {
   270  	files := `
   271  -- config.toml --
   272  -- layouts/_default/_markup/render-heading.html --
   273  <h{{ .Level }} id="{{ .Anchor | safeURL }}">
   274  	{{ .Text | safeHTML }}
   275  	<a class="anchor" href="#{{ .Anchor | safeURL }}">#</a>
   276  </h{{ .Level }}>
   277  -- layouts/_default/_markup/render-link.html --
   278  <a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}>{{ .Text | safeHTML }}</a>
   279  -- layouts/_default/single.html --
   280  {{ .Content }}
   281  `
   282  
   283  	content := `
   284  
   285  ## Hello1 [Test](https://example.com)
   286  
   287  A.
   288  
   289  ## Hello2 [Test](https://example.com)
   290  
   291  B.
   292  
   293  ## Hello3 [Test](https://example.com)
   294  
   295  C.
   296  
   297  ## Hello4 [Test](https://example.com)
   298  
   299  D.
   300  
   301  [Test](https://example.com)
   302  
   303  ## Hello5
   304  
   305  
   306  `
   307  
   308  	for i := 1; i < 100; i++ {
   309  		files += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+content, i+1)
   310  	}
   311  
   312  	cfg := hugolib.IntegrationTestConfig{
   313  		T:           b,
   314  		TxtarString: files,
   315  	}
   316  	builders := make([]*hugolib.IntegrationTestBuilder, b.N)
   317  
   318  	for i := range builders {
   319  		builders[i] = hugolib.NewIntegrationTestBuilder(cfg)
   320  	}
   321  
   322  	b.ResetTimer()
   323  
   324  	for i := 0; i < b.N; i++ {
   325  		builders[i].Build()
   326  	}
   327  }
   328  
   329  func BenchmarkCodeblocks(b *testing.B) {
   330  	filesTemplate := `
   331  -- config.toml --
   332  [markup]
   333    [markup.highlight]
   334      anchorLineNos = false
   335      codeFences = true
   336      guessSyntax = false
   337      hl_Lines = ''
   338      lineAnchors = ''
   339      lineNoStart = 1
   340      lineNos = false
   341      lineNumbersInTable = true
   342      noClasses = true
   343      style = 'monokai'
   344      tabWidth = 4
   345  -- layouts/_default/single.html --
   346  {{ .Content }}
   347  `
   348  
   349  	content := `
   350  
   351  FENCEgo
   352  package main
   353  import "fmt"
   354  func main() {
   355      fmt.Println("hello world")
   356  }
   357  FENCE
   358  
   359  FENCEunknownlexer
   360  hello
   361  FENCE
   362  `
   363  
   364  	content = strings.ReplaceAll(content, "FENCE", "```")
   365  
   366  	for i := 1; i < 100; i++ {
   367  		filesTemplate += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+content, i+1)
   368  	}
   369  
   370  	runBenchmark := func(files string, b *testing.B) {
   371  		cfg := hugolib.IntegrationTestConfig{
   372  			T:           b,
   373  			TxtarString: files,
   374  		}
   375  		builders := make([]*hugolib.IntegrationTestBuilder, b.N)
   376  
   377  		for i := range builders {
   378  			builders[i] = hugolib.NewIntegrationTestBuilder(cfg)
   379  		}
   380  
   381  		b.ResetTimer()
   382  
   383  		for i := 0; i < b.N; i++ {
   384  			builders[i].Build()
   385  		}
   386  	}
   387  
   388  	b.Run("Default", func(b *testing.B) {
   389  		runBenchmark(filesTemplate, b)
   390  	})
   391  
   392  	b.Run("Hook no higlight", func(b *testing.B) {
   393  		files := filesTemplate + `
   394  -- layouts/_default/_markup/render-codeblock.html --
   395  {{ .Inner }}
   396  `
   397  
   398  		runBenchmark(files, b)
   399  	})
   400  
   401  }
   402  
   403  // Iisse #8959
   404  func TestHookInfiniteRecursion(t *testing.T) {
   405  	t.Parallel()
   406  
   407  	for _, renderFunc := range []string{"markdownify", ".Page.RenderString"} {
   408  		t.Run(renderFunc, func(t *testing.T) {
   409  
   410  			files := `
   411  -- config.toml --
   412  -- layouts/_default/_markup/render-link.html --
   413  <a href="{{ .Destination | safeURL }}">{{ .Text | RENDERFUNC }}</a>	
   414  -- layouts/_default/single.html --
   415  {{ .Content }}
   416  -- content/p1.md --
   417  ---
   418  title: "p1"
   419  ---
   420  
   421  https://example.org
   422  
   423  a@b.com
   424  		
   425  			
   426  			`
   427  
   428  			files = strings.ReplaceAll(files, "RENDERFUNC", renderFunc)
   429  
   430  			b, err := hugolib.NewIntegrationTestBuilder(
   431  				hugolib.IntegrationTestConfig{
   432  					T:           t,
   433  					TxtarString: files,
   434  				},
   435  			).BuildE()
   436  
   437  			b.Assert(err, qt.IsNotNil)
   438  			b.Assert(err.Error(), qt.Contains, "text is already rendered, repeating it may cause infinite recursion")
   439  
   440  		})
   441  
   442  	}
   443  
   444  }
   445  
   446  // Issue 9594
   447  func TestQuotesInImgAltAttr(t *testing.T) {
   448  	t.Parallel()
   449  
   450  	files := `
   451  -- config.toml --
   452  [markup.goldmark.extensions]
   453    typographer = false
   454  -- content/p1.md --
   455  ---
   456  title: "p1"
   457  ---
   458  !["a"](b.jpg)
   459  -- layouts/_default/single.html --
   460  {{ .Content }}
   461  `
   462  
   463  	b := hugolib.NewIntegrationTestBuilder(
   464  		hugolib.IntegrationTestConfig{
   465  			T:           t,
   466  			TxtarString: files,
   467  		},
   468  	).Build()
   469  
   470  	b.AssertFileContent("public/p1/index.html", `
   471  		<img src="b.jpg" alt="&quot;a&quot;">
   472  	`)
   473  }
   474  
   475  func TestLinkifyProtocol(t *testing.T) {
   476  	t.Parallel()
   477  
   478  	runTest := func(protocol string, withHook bool) *hugolib.IntegrationTestBuilder {
   479  
   480  		files := `
   481  -- config.toml --
   482  [markup.goldmark]
   483  [markup.goldmark.extensions]
   484  linkify = true
   485  linkifyProtocol = "PROTOCOL"
   486  -- content/p1.md --
   487  ---
   488  title: "p1"
   489  ---
   490  Link no procol: www.example.org
   491  Link http procol: http://www.example.org
   492  Link https procol: https://www.example.org
   493  
   494  -- layouts/_default/single.html --
   495  {{ .Content }}
   496  `
   497  		files = strings.ReplaceAll(files, "PROTOCOL", protocol)
   498  
   499  		if withHook {
   500  			files += `-- layouts/_default/_markup/render-link.html --
   501  <a href="{{ .Destination | safeURL }}">{{ .Text | safeHTML }}</a>`
   502  		}
   503  
   504  		return hugolib.NewIntegrationTestBuilder(
   505  			hugolib.IntegrationTestConfig{
   506  				T:           t,
   507  				TxtarString: files,
   508  			},
   509  		).Build()
   510  
   511  	}
   512  
   513  	for _, withHook := range []bool{false, true} {
   514  
   515  		b := runTest("https", withHook)
   516  
   517  		b.AssertFileContent("public/p1/index.html",
   518  			"Link no procol: <a href=\"https://www.example.org\">www.example.org</a>",
   519  			"Link http procol: <a href=\"http://www.example.org\">http://www.example.org</a>",
   520  			"Link https procol: <a href=\"https://www.example.org\">https://www.example.org</a></p>",
   521  		)
   522  
   523  		b = runTest("http", withHook)
   524  
   525  		b.AssertFileContent("public/p1/index.html",
   526  			"Link no procol: <a href=\"http://www.example.org\">www.example.org</a>",
   527  			"Link http procol: <a href=\"http://www.example.org\">http://www.example.org</a>",
   528  			"Link https procol: <a href=\"https://www.example.org\">https://www.example.org</a></p>",
   529  		)
   530  
   531  		b = runTest("gopher", withHook)
   532  
   533  		b.AssertFileContent("public/p1/index.html",
   534  			"Link no procol: <a href=\"gopher://www.example.org\">www.example.org</a>",
   535  			"Link http procol: <a href=\"http://www.example.org\">http://www.example.org</a>",
   536  			"Link https procol: <a href=\"https://www.example.org\">https://www.example.org</a></p>",
   537  		)
   538  
   539  	}
   540  }
   541  
   542  func TestGoldmarkBugs(t *testing.T) {
   543  	t.Parallel()
   544  
   545  	files := `
   546  -- config.toml --
   547  [markup.goldmark.renderer]
   548  unsafe = true
   549  -- content/p1.md --
   550  ---
   551  title: "p1"
   552  ---
   553  
   554  ## Issue 9650
   555  
   556  a <!-- b --> c
   557  
   558  ## Issue 9658
   559  
   560  - This is a list item <!-- Comment: an innocent-looking comment -->
   561  
   562  
   563  -- layouts/_default/single.html --
   564  {{ .Content }}
   565  `
   566  
   567  	b := hugolib.NewIntegrationTestBuilder(
   568  		hugolib.IntegrationTestConfig{
   569  			T:           t,
   570  			TxtarString: files,
   571  		},
   572  	).Build()
   573  
   574  	b.AssertFileContentExact("public/p1/index.html",
   575  		// Issue 9650
   576  		"<p>a <!-- b --> c</p>",
   577  		// Issue 9658 (crash)
   578  		"<li>This is a list item <!-- Comment: an innocent-looking comment --></li>",
   579  	)
   580  }