test(11.1-07): planted fixtures and unit tests for fence, execution and name-form holes

- eighteen violation plants pin nested src=, Go aliases, unrun examples, build membership and command forms
- unit tests cover fence collection, captions, goLang, go doc -c, commandWord and Sync
This commit is contained in:
Jakub Zych
2026-10-01 08:45:34 +02:00
parent efc3161c3f
commit 9ba5530ca7
48 changed files with 890 additions and 0 deletions

View File

@@ -469,6 +469,140 @@ func TestCommandTokenForms(t *testing.T) {
})
}
func TestNestedFenceChecks(t *testing.T) {
body := strings.Join([]string{
"> [!NOTE]",
"> ```go",
"> x := 1",
"> ```",
"",
"- item",
"",
" ```go",
" x := 1",
" ```",
"",
"```GO",
"x := 1",
"```",
"",
"```main.go",
"x := 1",
"```",
"",
" ```go",
" x := 1",
" ```",
"",
"> [!NOTE]",
"> ```sh",
"> summer bogus:nested",
"> ```",
"",
"> ```yaml src=config/app.yaml",
"> app:",
"> ```",
"",
}, "\n")
readme := strings.Join([]string{
"# fixture",
"",
"Fixture does one thing.",
"",
"## Usage",
"",
"```go",
"x := 1",
"```",
"",
"```go src=modules/fixture/fixture.go",
"package fixture",
"```",
"",
}, "\n")
assertProblems(t, checkFixture(t, fixtureCommands, map[string]string{
"docs/index.md": page("Acme docs", "index", 0, "Text.\n"),
"docs/setup/start.md": page("Start", "setup", 10, body),
"modules/fixture/README.md": readme,
}), []string{
"docs/setup/start.md:10: snippet: go code block has no src= reference",
"docs/setup/start.md:16: snippet: go code block has no src= reference",
"docs/setup/start.md:20: snippet: GO code block has no src= reference",
"docs/setup/start.md:24: snippet: main.go code block has no src= reference",
`docs/setup/start.md:34: command: "bogus:nested" is not a summer or application command`,
"docs/setup/start.md:37: snippet: config/app.yaml: " + nestedSrcMessage,
"modules/fixture/README.md:11: snippet: modules/fixture/fixture.go: " + readmeSrcMessage,
})
}
func TestIdentifierGoDocCaseSensitive(t *testing.T) {
root := writeTree(t, map[string]string{
"go.mod": "module example.com/forms\n\ngo 1.27\n",
"modules/forms/forms.go": "package forms\n\n" +
"// OpenFromApp is a fixture.\nfunc OpenFromApp() {}\n\n" +
"// Outer holds a method.\ntype Outer struct{}\n\n" +
"// HelloWorld is a method.\nfunc (Outer) HelloWorld() {}\n",
})
idx, problems, err := buildIdentIndex(root)
if err != nil || len(problems) > 0 {
t.Fatalf("buildIdentIndex: %v %q", err, problemLines(problems))
}
for _, q := range []string{"OpenFromApp", "Outer.HelloWorld"} {
if !idx.goDoc("modules/forms", q) {
t.Errorf("goDoc(%q) = false, want true", q)
}
}
for _, q := range []string{"Openfromapp", "Outer.Helloworld"} {
if idx.goDoc("modules/forms", q) {
t.Errorf("goDoc(%q) = true, want false", q)
}
}
if got := idx.checkSpan("forms.Openfromapp"); got != "forms.Openfromapp does not exist in modules/forms" {
t.Fatalf("checkSpan = %q", got)
}
}
func TestCommandWord(t *testing.T) {
tool, app := true, false
cases := []struct {
in string
name string
tool, ok bool
}{
{"summer docs:build", "docs:build", tool, true},
{"$ summer docs:build --out site", "docs:build", tool, true},
{"FOO=1 summer no:such", "no:such", tool, true},
{"FOO=1 BAR=x ./bin/acme serve", "serve", app, true},
{"summer --root . no:such", "no:such", tool, true},
{"summer --root=. docs:build", "docs:build", tool, true},
{"summer -r . docs:build", "docs:build", tool, true},
{"summer --help", "", tool, false},
{"summer --help docs:build", "docs:build", tool, true},
{"summer -h docs:build", "docs:build", tool, true},
{"summer --root", "", tool, false},
{"summer --root .", "", tool, false},
{"summer -- docs:build", "", tool, false},
{"go run ./cmd/summer no:such", "no:such", tool, true},
{"go run ./cmd/summer --root . docs:build", "docs:build", tool, true},
{"bin/acme migrate", "migrate", app, true},
{"./bin/acme migrate", "migrate", app, true},
{"echo summer x", "", false, false},
{"# summer x", "", false, false},
{"go test ./modules/demo", "", false, false},
{"go install ./cmd/summer", "", false, false},
{"summer", "", tool, false},
{"FOO=1", "", false, false},
{"summer --version", "", tool, false},
}
for _, tc := range cases {
name, isTool, ok := commandWord(tc.in)
if name != tc.name || isTool != tc.tool || ok != tc.ok {
t.Errorf("commandWord(%q) = %q, %v, %v; want %q, %v, %v",
tc.in, name, isTool, ok, tc.name, tc.tool, tc.ok)
}
}
}
func TestExampleCommandNames(t *testing.T) {
dir := writeTree(t, map[string]string{
"a/main.go": "package main\n\nimport \"x/bonfire\"\n\nvar a = bonfire.Command{Name: \"a:one\", Description: \"d\"}\n" +

View File

@@ -0,0 +1,84 @@
package docsite
import (
"strings"
"testing"
)
func TestCollectFences(t *testing.T) {
body := strings.Join([]string{
"```go",
"top",
"```",
"",
"> [!TIP]",
"> ```go",
"> callout line",
"> ```",
"",
"> ```yaml",
"> quote line",
"> ```",
"",
"- item",
"",
" ```go",
" list line",
" ```",
"",
"```",
"noinfo",
"```",
"",
" ```",
" four spaces",
" ```",
"",
"```sh",
"unterminated",
}, "\n")
doc := (&site{}).parseRaw(newMarkdown(), []byte(body))
fences, err := collectFences(doc, []byte(body))
if err != nil {
t.Fatal(err)
}
if len(fences) != 6 {
t.Fatalf("fences = %d, want 6 (four-space backticks are not a fence)", len(fences))
}
want := []struct {
info, code string
line int
top bool
close int // -2 means top was not filled
}{
{"go", "top", 0, true, 2},
{"go", "callout line", 5, false, -2},
{"yaml", "quote line", 9, false, -2},
{"go", "list line", 15, false, -2},
{"", "noinfo", 19, true, -2},
{"sh", "unterminated", 27, true, -1},
}
for i, w := range want {
f := fences[i]
if f.info != w.info || f.line != w.line || f.topLevel != w.top {
t.Errorf("fence %d = info %q line %d top %v, want info %q line %d top %v",
i, f.info, f.line, f.topLevel, w.info, w.line, w.top)
}
if len(f.code) != 1 || f.code[0].text != w.code {
got := ""
if len(f.code) > 0 {
got = f.code[0].text
}
t.Errorf("fence %d code = %q, want %q", i, got, w.code)
}
if w.close != -2 && f.top.close != w.close {
t.Errorf("fence %d top.close = %d, want %d", i, f.top.close, w.close)
}
}
if _, ok := openFence(" ```go"); !ok {
t.Error("openFence rejected three leading spaces")
}
if _, ok := openFence(" ```go"); ok {
t.Error("openFence accepted four leading spaces")
}
}

View File

@@ -150,3 +150,16 @@ func TestHighlightFenceMarkup(t *testing.T) {
t.Error("a fence without src= has a caption")
}
}
func TestGoLang(t *testing.T) {
for _, lang := range []string{"go", "Go", "GO", "golang", "Golang", "main.go"} {
if !goLang(lang) {
t.Errorf("goLang(%q) = false, want true", lang)
}
}
for _, lang := range []string{"go-html-template", "go-text-template", "text", "yaml", "sh", ""} {
if goLang(lang) {
t.Errorf("goLang(%q) = true, want false", lang)
}
}
}

View File

@@ -71,6 +71,36 @@ func renderHTML(t *testing.T, s *site, p *Page) renderedPage {
return r
}
func TestFenceCaptionsOnlyVerified(t *testing.T) {
guide := page("Start", "setup", 10, strings.Join([]string{
"```go src=pkg/lib.go#Greeting",
"func Greeting() {}",
"```",
"",
"> [!TIP]",
"> ```go src=pkg/lib.go#Greeting",
"> func stale() {}",
"> ```",
"",
}, "\n"))
readme := "# alpha\n\nAlpha does one thing.\n\n```go src=modules/alpha/alpha.go\npackage alpha\n```\n"
s, pages := renderFixture(t, map[string]string{
"docs/setup/start.md": guide,
"modules/alpha/README.md": readme,
})
guideHTML := string(renderHTML(t, s, pages["setup/start"]).html)
if got := strings.Count(guideHTML, "<figcaption>"); got != 1 {
t.Fatalf("guide figcaptions = %d, want 1\n%s", got, guideHTML)
}
if !strings.Contains(guideHTML, "<figcaption>pkg/lib.go#Greeting</figcaption>") {
t.Fatalf("guide caption missing the top-level reference\n%s", guideHTML)
}
readmeHTML := string(renderHTML(t, s, pages["api/alpha"]).html)
if strings.Contains(readmeHTML, "<figcaption>") {
t.Fatalf("module README has a caption\n%s", readmeHTML)
}
}
func TestLinkRewriting(t *testing.T) {
body := strings.Join([]string{
"- [start](setup/start.md)",

View File

@@ -3,6 +3,7 @@ package docsite
import (
"os"
"path/filepath"
"runtime"
"slices"
"strings"
"testing"
@@ -320,3 +321,160 @@ func TestSnippetTestGraph(t *testing.T) {
t.Errorf("Extract with an unparsable sibling test = %v", err)
}
}
func TestSnippetRootsAndBuild(t *testing.T) {
other := "windows"
if runtime.GOOS == "windows" {
other = "linux"
}
root := writeTree(t, map[string]string{
"p/p.go": "package p\n",
"p/p_test.go": `package p
import "testing"
func Test(t *testing.T) { fromTest() }
func Test_x(t *testing.T) { fromTestX() }
func TestMain(m *testing.M) { fromMain() }
func Testable() { fromTestable() }
func fromTest() {}
func fromTestX() {}
func fromMain() {}
func fromTestable() {}
func onlyFromIgnored() {}
func ExampleOut() {
fromOut()
// Output: out
}
func ExampleEmpty() {
fromEmpty()
// Output:
}
func ExampleNone() {
fromNone()
}
func fromOut() {}
func fromEmpty() {}
func fromNone() {}
`,
"p/ignored_test.go": "//go:build ignore\n\npackage p\n\nimport \"testing\"\n\nfunc TestIgnored(t *testing.T) { onlyFromIgnored() }\n",
"p/broken.go": "//go:build ignore\n\npackage p\n\nfunc Broken() {}\n",
"p/broken_test.go": "//go:build ignore\n\npackage p\n\nfunc IgnoredTestTarget() {}\n",
"p/only_" + other + ".go": "package p\n\nfunc OnlyOS() {}\n",
"p/testdata/skip.go": "package p\n\nfunc Skip() {}\n",
"p/_old/old.go": "package p\n\nfunc Old() {}\n",
"p/testdata/app.yaml": "# docs:start db\nhost: localhost\n# docs:end db\n",
})
g, err := loadTestGraph(filepath.Join(root, "p"))
if err != nil {
t.Fatal(err)
}
for _, name := range []string{"Test", "Test_x", "TestMain", "ExampleOut", "ExampleEmpty", "fromTest", "fromTestX", "fromMain", "fromOut", "fromEmpty"} {
if !g.reachable[name] {
t.Errorf("%s is not a root or reachable from one", name)
}
}
for _, name := range []string{"Testable", "ExampleNone", "fromTestable", "fromNone", "onlyFromIgnored", "TestIgnored"} {
if g.reachable[name] {
t.Errorf("%s is reachable, want it not run", name)
}
}
mustExtract := func(ref Ref, wantErr string) {
t.Helper()
_, err := Extract(root, ref)
if wantErr == "" {
if err != nil {
t.Errorf("Extract(%s) = %v, want nil", ref, err)
}
return
}
if err == nil || !strings.Contains(err.Error(), wantErr) {
t.Errorf("Extract(%s) = %v, want %q", ref, err, wantErr)
}
}
skipped := "file is in a directory go test ./... skips"
excluded := "file is excluded from the default build"
mustExtract(Ref{"p/p_test.go", "fromOut"}, "")
mustExtract(Ref{"p/p_test.go", "fromEmpty"}, "")
mustExtract(Ref{"p/p_test.go", "fromNone"}, notRunMessage)
mustExtract(Ref{"p/p_test.go", "fromTestable"}, notRunMessage)
mustExtract(Ref{"p/p_test.go", "onlyFromIgnored"}, notRunMessage)
mustExtract(Ref{"p/broken.go", "Broken"}, excluded)
mustExtract(Ref{"p/broken_test.go", "IgnoredTestTarget"}, excluded)
mustExtract(Ref{"p/only_" + other + ".go", "OnlyOS"}, excluded)
mustExtract(Ref{"p/testdata/skip.go", "Skip"}, skipped)
mustExtract(Ref{"p/_old/old.go", "Old"}, skipped)
got, err := Extract(root, Ref{"p/testdata/app.yaml", "db"})
if err != nil || got != "host: localhost" {
t.Fatalf("Extract yaml = %q, %v", got, err)
}
}
func TestSyncParsedFences(t *testing.T) {
callout := "> [!NOTE]\n> Keep this callout text.\n"
doc := page("Start", "setup", 10, "Intro.\n\n```go src=pkg/lib.go#Greeting\nfunc stale() {}\n```\n\n"+callout+"\nOutro.\n")
root := snippetTree(t, map[string]string{"docs/setup/start.md": doc})
path := filepath.Join(root, "docs/setup/start.md")
res, problems, err := Sync(Options{Root: root})
if err != nil || len(problems) > 0 {
t.Fatalf("Sync: %v %v", err, problems)
}
if res.Files != 1 || res.Snippets != 1 {
t.Fatalf("Sync = %+v, want 1 snippet in 1 file", res)
}
got, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
wantDoc := strings.Replace(doc, "func stale() {}\n",
"// Greeting returns a greeting.\nfunc Greeting(name string) string {\n\treturn \"Hello, \" + name\n}\n", 1)
if string(got) != wantDoc {
t.Fatalf("synced file =\n%s\nwant\n%s", got, wantDoc)
}
if !strings.HasPrefix(string(got), "---\ntitle: Start\n") || !strings.Contains(string(got), callout) {
t.Fatal("frontmatter or callout text changed")
}
nested := page("Start", "setup", 10, "Intro.\n\n> [!NOTE]\n> ```go src=pkg/lib.go#Greeting\n> func stale() {}\n> ```\n\nOutro.\n")
root = snippetTree(t, map[string]string{"docs/setup/start.md": nested})
before := map[string]string{}
err = filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
}
b, err := os.ReadFile(p)
if err != nil {
return err
}
before[p] = string(b)
return nil
})
if err != nil {
t.Fatal(err)
}
res, problems, err = Sync(Options{Root: root})
if err != nil || res != (SyncResult{}) || len(problems) != 1 || !strings.Contains(problems[0].Message, nestedSrcMessage) {
t.Fatalf("nested Sync = %+v %q %v", res, problemLines(problems), err)
}
err = filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
}
b, err := os.ReadFile(p)
if err != nil {
return err
}
if string(b) != before[p] {
t.Errorf("Sync changed %s", p)
}
return nil
})
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,19 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
```sh
bin/demo no:such
```

View File

@@ -0,0 +1,3 @@
rule: command
file: docs/extras/faq.md
message: "no:such" is not a summer or application command

View File

@@ -0,0 +1,19 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
```sh
FOO=1 summer no:such
```

View File

@@ -0,0 +1,3 @@
rule: command
file: docs/extras/faq.md
message: "no:such" is not a summer or application command

View File

@@ -0,0 +1,19 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
```sh
summer --root . no:such
```

View File

@@ -0,0 +1,3 @@
rule: command
file: docs/extras/faq.md
message: "no:such" is not a summer or application command

View File

@@ -0,0 +1,19 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
```sh
go run ./cmd/summer no:such
```

View File

@@ -0,0 +1,3 @@
rule: command
file: docs/extras/faq.md
message: "no:such" is not a summer or application command

View File

@@ -0,0 +1,20 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
> [!NOTE]
> ```sh
> summer no:such
> ```

View File

@@ -0,0 +1,3 @@
rule: command
file: docs/extras/faq.md
message: "no:such" is not a summer or application command

View File

@@ -0,0 +1,20 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
> [!NOTE]
> ```go
> x := 1
> ```

View File

@@ -0,0 +1,3 @@
rule: snippet
file: docs/extras/faq.md
message: go code block has no src= reference

View File

@@ -0,0 +1,19 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
```golang
x := 1
```

View File

@@ -0,0 +1,3 @@
rule: snippet
file: docs/extras/faq.md
message: golang code block has no src= reference

View File

@@ -0,0 +1,17 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
Call `demo.Openfromapp`.

View File

@@ -0,0 +1,4 @@
package demo
// OpenFromApp is a fixture.
func OpenFromApp() {}

View File

@@ -0,0 +1,3 @@
rule: identifier
file: docs/extras/faq.md
message: demo.Openfromapp does not exist in modules/demo

View File

@@ -0,0 +1,24 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
> ```yaml src=config/app.yaml
> app:
> # docs:start db
> db:
> host: localhost
> port: 5432
> # docs:end db
> ```

View File

@@ -0,0 +1,3 @@
rule: snippet
file: docs/extras/faq.md
message: config/app.yaml: src= code block must be a top-level block

View File

@@ -0,0 +1,19 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
```go src=modules/demo/demo_test.go#ignoredOnly
func ignoredOnly() string { return "x" }
```

View File

@@ -0,0 +1,15 @@
package demo
import "testing"
func TestGreet(t *testing.T) {
// docs:start greet
g := Greeter{Name: "blog"}
got := g.Greet()
// docs:end greet
if got != "Hello, blog" {
t.Fatalf("Greet() = %q", got)
}
}
func ignoredOnly() string { return "x" }

View File

@@ -0,0 +1,7 @@
//go:build ignore
package demo
import "testing"
func TestIgnored(t *testing.T) { _ = ignoredOnly() }

View File

@@ -0,0 +1,3 @@
rule: snippet
file: docs/extras/faq.md
message: fragment is not inside a Test or Example function

View File

@@ -0,0 +1,20 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
```go src=modules/demo/broken.go#Broken
// Broken is never built.
func Broken() { undefinedCall() }
```

View File

@@ -0,0 +1,6 @@
//go:build ignore
package demo
// Broken is never built.
func Broken() { undefinedCall() }

View File

@@ -0,0 +1,3 @@
rule: snippet
file: docs/extras/faq.md
message: modules/demo/broken.go#Broken: file is excluded from the default build

View File

@@ -0,0 +1,20 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
> [!TIP]
> ```go src=modules/demo/example_test.go#ExampleHello
> BOGUS
> ```

View File

@@ -0,0 +1,3 @@
rule: snippet
file: docs/extras/faq.md
message: modules/demo/example_test.go#ExampleHello: src= code block must be a top-level block

View File

@@ -0,0 +1,20 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
> [!NOTE]
> ```go src=modules/demo/missing_test.go#ExampleNope
> fmt.Println()
> ```

View File

@@ -0,0 +1,3 @@
rule: snippet
file: docs/extras/faq.md
message: modules/demo/missing_test.go#ExampleNope: src= code block must be a top-level block

View File

@@ -0,0 +1,19 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
```go src=modules/demo/never_test.go#neverRunHelper
func neverRunHelper() {}
```

View File

@@ -0,0 +1,7 @@
package demo_test
func Example_neverRun() {
neverRunHelper()
}
func neverRunHelper() {}

View File

@@ -0,0 +1,3 @@
rule: snippet
file: docs/extras/faq.md
message: fragment is not inside a Test or Example function

View File

@@ -0,0 +1,19 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
```go src=modules/demo/never_test.go#never
_ = 1
```

View File

@@ -0,0 +1,11 @@
package demo_test
func Example_neverRun() {
neverRunHelper()
}
func neverRunHelper() {
// docs:start never
_ = 1
// docs:end never
}

View File

@@ -0,0 +1,3 @@
rule: snippet
file: docs/extras/faq.md
message: fragment is not inside a Test or Example function

View File

@@ -0,0 +1,22 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
- Greet:
```go src=modules/demo/example_test.go#ExampleHello
fmt.Println(demo.Hello("blog"))
// Output: Hello, blog
```

View File

@@ -0,0 +1,3 @@
rule: snippet
file: docs/extras/faq.md
message: modules/demo/example_test.go#ExampleHello: src= code block must be a top-level block

View File

@@ -0,0 +1,24 @@
# demo
Demo greets people by name.
```go
import "example.com/docfixture/modules/demo"
```
## Usage
Call `demo.Hello` or build a `demo.Greeter` and call `demo.Greeter.Greet`.
See the [start guide](../../docs/guide/start.md#install).
## Testing
```sh
go test ./modules/demo
```
```go src=modules/demo/example_test.go#ExampleHello
fmt.Println(demo.Hello("blog"))
// Output: Hello, blog
```

View File

@@ -0,0 +1,3 @@
rule: snippet
file: modules/demo/README.md
message: modules/demo/example_test.go#ExampleHello: module README code blocks are rendered as written

View File

@@ -0,0 +1,19 @@
---
title: FAQ
description: "Questions about the demo."
section: extras
order: 20
---
# FAQ
## Does it migrate
Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change.
```text
summer not:checked in a text fence
```
```go src=modules/demo/testdata/fixture_test.go#td
_ = 1
```

View File

@@ -0,0 +1,9 @@
package demo
import "testing"
func TestFixture(t *testing.T) {
// docs:start td
_ = 1
// docs:end td
}

View File

@@ -0,0 +1,3 @@
rule: snippet
file: docs/extras/faq.md
message: file is in a directory go test ./... skips