package reporters_test

import (
	"fmt"
	"reflect"
	"runtime"
	"strings"
	"time"

	. "github.com/onsi/ginkgo/v2"
	"github.com/onsi/ginkgo/v2/internal"
	"github.com/onsi/ginkgo/v2/internal/test_helpers"
	"github.com/onsi/ginkgo/v2/reporters"
	"github.com/onsi/ginkgo/v2/types"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/format"
	"github.com/onsi/gomega/gbytes"
)

var MatchLines = test_helpers.MatchLines

// This is a mini-DSL for quickly and succinctly building Ginkgo SpecReports and all their attendant objects

const DELIMITER = `{{gray}}------------------------------{{/}}`
const INDENTED_DELIMITER = `  {{gray}}------------------------------{{/}}`

var cl0 = types.CodeLocation{FileName: "cl0.go", LineNumber: 12, FullStackTrace: "full-trace\ncl-0"}
var cl1 = types.CodeLocation{FileName: "cl1.go", LineNumber: 37, FullStackTrace: "full-trace\ncl-1"}
var cl2 = types.CodeLocation{FileName: "cl2.go", LineNumber: 80, FullStackTrace: "full-trace\ncl-2"}
var cl3 = types.CodeLocation{FileName: "cl3.go", LineNumber: 103, FullStackTrace: "full-trace\ncl-3"}
var cl4 = types.CodeLocation{FileName: "cl4.go", LineNumber: 144, FullStackTrace: "full-trace\ncl-4"}
var now = time.Now()

func CLS(cls ...types.CodeLocation) []types.CodeLocation { return cls }
func CTS(componentTexts ...string) []string              { return componentTexts }
func CLabels(labels ...Labels) []Labels                  { return labels }
func spr(format string, args ...any) string              { return fmt.Sprintf(format, args...) }

type FailureNodeLocation types.CodeLocation
type ForwardedPanic string
type StackTrace string

var PLACEHOLDER_TIME = now
var FORMATTED_TIME = PLACEHOLDER_TIME.Format(types.GINKGO_TIME_FORMAT)

var tlOrder = 1

func TL(options ...interface{}) types.TimelineLocation {
	out := types.TimelineLocation{
		Order: tlOrder,
		Time:  now,
	}
	tlOrder += 1
	for _, option := range options {
		switch x := option.(type) {
		case string:
			out.Offset = len(x)
		case int:
			out.Offset = x
		case time.Time:
			out.Time = x
		}
	}
	return out
}

// convenience helper to quickly make Failures
func F(options ...interface{}) types.Failure {
	failure := types.Failure{TimelineLocation: TL()}
	for _, option := range options {
		switch x := option.(type) {
		case string:
			failure.Message = x
		case types.CodeLocation:
			failure.Location = x
		case ForwardedPanic:
			failure.ForwardedPanic = string(x)
		case types.FailureNodeContext:
			failure.FailureNodeContext = x
		case int:
			failure.FailureNodeContainerIndex = x
		case FailureNodeLocation:
			failure.FailureNodeLocation = types.CodeLocation(x)
		case types.NodeType:
			failure.FailureNodeType = x
		case types.ProgressReport:
			failure.ProgressReport = x
		case types.AdditionalFailure:
			failure.AdditionalFailure = &x
		case types.TimelineLocation:
			failure.TimelineLocation = x
		}
	}
	return failure
}

func AF(state types.SpecState, options ...interface{}) types.AdditionalFailure {
	return types.AdditionalFailure{
		State:   state,
		Failure: F(options...),
	}
}

type STD string
type GW string

// convenience helper to quickly make SpecReports
func S(options ...interface{}) types.SpecReport {
	report := types.SpecReport{
		LeafNodeType:          types.NodeTypeIt,
		State:                 types.SpecStatePassed,
		NumAttempts:           1,
		MaxFlakeAttempts:      1,
		MaxMustPassRepeatedly: 1,
		RunTime:               time.Second,
	}
	for _, option := range options {
		switch x := option.(type) {
		case []string:
			report.ContainerHierarchyTexts = x
		case []types.CodeLocation:
			report.ContainerHierarchyLocations = x
		case []Labels:
			report.ContainerHierarchyLabels = [][]string{}
			for _, labels := range x {
				report.ContainerHierarchyLabels = append(report.ContainerHierarchyLabels, []string(labels))
			}
		case string:
			report.LeafNodeText = x
		case types.NodeType:
			report.LeafNodeType = x
		case types.CodeLocation:
			report.LeafNodeLocation = x
		case Labels:
			report.LeafNodeLabels = x
		case types.SpecState:
			report.State = x
		case time.Duration:
			report.RunTime = x
		case int:
			report.NumAttempts = x
		case FlakeAttempts:
			report.MaxFlakeAttempts = int(x)
		case MustPassRepeatedly:
			report.MaxMustPassRepeatedly = int(x)
		case STD:
			report.CapturedStdOutErr = string(x)
		case GW:
			report.CapturedGinkgoWriterOutput = string(x)
		case types.Failure:
			report.Failure = x
		case types.AdditionalFailure:
			report.AdditionalFailures = append(report.AdditionalFailures, x)
		case types.ReportEntry:
			report.ReportEntries = append(report.ReportEntries, x)
		case types.ProgressReport:
			report.ProgressReports = append(report.ProgressReports, x)
		case types.SpecEvent:
			report.SpecEvents = append(report.SpecEvents, x)
		}
	}
	if len(report.ContainerHierarchyLabels) == 0 {
		for range report.ContainerHierarchyTexts {
			report.ContainerHierarchyLabels = append(report.ContainerHierarchyLabels, []string{})
		}
	}
	return report
}

type ConfigFlag uint8

const (
	Succinct ConfigFlag = 1 << iota
	Normal
	Verbose
	VeryVerbose
	FullTrace
	ShowNodeEvents

	Parallel //used in the WillRun => DidRun specs to capture behavior when running in parallel
)

func (cf ConfigFlag) Has(flag ConfigFlag) bool { return cf&flag != 0 }
func (cf ConfigFlag) String() string {
	out := []string{}
	if cf.Has(Succinct) {
		out = append(out, "succinct")
	}
	if cf.Has(Normal) {
		out = append(out, "normal")
	}
	if cf.Has(Verbose) {
		out = append(out, "verbose")
	}
	if cf.Has(VeryVerbose) {
		out = append(out, "very-verbose")
	}
	if cf.Has(FullTrace) {
		out = append(out, "full-trace")
	}
	if cf.Has(ShowNodeEvents) {
		out = append(out, "show-node-events")
	}
	if cf.Has(Parallel) {
		out = append(out, "parallel")
	}
	return strings.Join(out, "|")
}

func C(flags ...ConfigFlag) types.ReporterConfig {
	f := ConfigFlag(0)
	if len(flags) > 0 {
		f = flags[0]
	}
	numVerbosity := 0
	for _, verbosityFlag := range []ConfigFlag{Succinct, Normal, Verbose, VeryVerbose} {
		if f.Has(verbosityFlag) {
			numVerbosity += 1
		}
	}
	Ω(numVerbosity).Should(BeNumerically("<=", 1), "Setting more than one of Succinct, Normal, Verbose, or VeryVerbose is a configuration error")
	return types.ReporterConfig{
		NoColor:        true,
		Succinct:       f.Has(Succinct),
		Verbose:        f.Has(Verbose),
		VeryVerbose:    f.Has(VeryVerbose),
		FullTrace:      f.Has(FullTrace),
		ShowNodeEvents: f.Has(ShowNodeEvents),
	}
}

type ConfigCase struct {
	ConfigFlags []ConfigFlag
	Expected    []any
}

func Case(args ...any) ConfigCase {
	out := ConfigCase{}
	for _, arg := range args {
		switch x := arg.(type) {
		case ConfigFlag:
			out.ConfigFlags = append(out.ConfigFlags, x)
		default:
			out.Expected = append(out.Expected, x)
		}
	}
	return out
}

type CurrentNodeText string
type CurrentStepText string
type LeafNodeText string
type AdditionalReports []string

func PR(options ...interface{}) types.ProgressReport {
	report := types.ProgressReport{
		ParallelProcess:   1,
		RunningInParallel: false,

		SpecStartTime:        now.Add(-5 * time.Second),
		CurrentNodeStartTime: now.Add(-3 * time.Second),
		CurrentStepStartTime: now.Add(-1 * time.Second),

		LeafNodeLocation:    cl0,
		CurrentNodeLocation: cl1,
		CurrentStepLocation: cl2,
		TimelineLocation:    TL(),
	}
	for _, option := range options {
		switch x := option.(type) {
		case []string:
			report.ContainerHierarchyTexts = x
		case GW:
			report.CapturedGinkgoWriterOutput = string(x)
		case LeafNodeText:
			report.LeafNodeText = string(x)
		case types.NodeType:
			report.CurrentNodeType = x
		case CurrentNodeText:
			report.CurrentNodeText = string(x)
		case CurrentStepText:
			report.CurrentStepText = string(x)
		case types.Goroutine:
			report.Goroutines = append(report.Goroutines, x)
		case []types.Goroutine:
			report.Goroutines = append(report.Goroutines, x...)
		case int:
			report.ParallelProcess = x
		case bool:
			report.RunningInParallel = x
		case string:
			report.Message = x
		case AdditionalReports:
			report.AdditionalReports = x
		case types.TimelineLocation:
			report.TimelineLocation = x
		}
	}
	return report
}

func Fn(f string, filename string, line int, options ...interface{}) types.FunctionCall {
	out := types.FunctionCall{
		Function: f,
		Filename: filename,
		Line:     line,
	}
	for _, option := range options {
		switch option := option.(type) {
		case bool:
			out.Highlight = option
		case string:
			out.Source = append(out.Source, option)
		case int:
			out.SourceHighlight = option
		}
	}

	return out
}

func G(options ...interface{}) types.Goroutine {
	goroutine := types.Goroutine{
		ID:              17,
		State:           "running",
		IsSpecGoroutine: false,
	}

	for _, option := range options {
		switch reflect.TypeOf(option) {
		case reflect.TypeOf(true):
			goroutine.IsSpecGoroutine = option.(bool)
		case reflect.TypeOf(""):
			goroutine.State = option.(string)
		case reflect.TypeOf(types.FunctionCall{}):
			goroutine.Stack = append(goroutine.Stack, option.(types.FunctionCall))
		case reflect.TypeOf([]types.FunctionCall{}):
			goroutine.Stack = append(goroutine.Stack, option.([]types.FunctionCall)...)
		}
	}

	return goroutine
}

func RE(name string, cl types.CodeLocation, args ...interface{}) types.ReportEntry {
	var tl = TL()
	finalArgs := []any{}
	for _, arg := range args {
		if theTl, isTl := arg.(types.TimelineLocation); isTl {
			tl = theTl
		} else {
			finalArgs = append(finalArgs, arg)
		}
	}
	entry, _ := internal.NewReportEntry(name, cl, finalArgs...)
	entry.Time = PLACEHOLDER_TIME
	entry.TimelineLocation = tl
	return entry
}

func SE(options ...interface{}) types.SpecEvent {
	se := types.SpecEvent{TimelineLocation: TL()}
	for _, option := range options {
		switch x := option.(type) {
		case types.SpecEventType:
			se.SpecEventType = x
		case string:
			se.Message = x
		case types.CodeLocation:
			se.CodeLocation = x
		case types.TimelineLocation:
			se.TimelineLocation = x
		case time.Duration:
			se.Duration = x
		case types.NodeType:
			se.NodeType = x
		case int:
			se.Attempt = x
		}
	}
	return se
}

/*
The strategy:
	use the individual emitters to test the details of each type of output
	only do didRun to test the various header modes and modalities around -v -vv etc.
	spot check a single comprehensive timeline.  no need to super over-do it!
*/

var _ = Describe("DefaultReporter", func() {
	var DENOTER = "•"
	var RETRY_DENOTER = "↺"
	if runtime.GOOS == "windows" {
		DENOTER = "+"
		RETRY_DENOTER = "R"
	}

	var buf *gbytes.Buffer

	BeforeEach(func() {
		buf = gbytes.NewBuffer()
		format.CharactersAroundMismatchToInclude = 100
	})

	DescribeTable("Rendering SuiteWillBegin",
		func(conf types.ReporterConfig, report types.Report, expected ...any) {
			reporter := reporters.NewDefaultReporterUnderTest(conf, buf)
			reporter.SuiteWillBegin(report)
			Expect(string(buf.Contents())).Should(MatchLines(expected...))
		},
		Entry("Default Behavior",
			C(),
			types.Report{
				SuiteDescription: "My Suite", SuitePath: "/path/to/suite", PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20},
				SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1},
			},
			"Running Suite: My Suite - /path/to/suite",
			"========================================",
			"Random Seed: {{bold}}17{{/}}",
			"",
			"Will run {{bold}}15{{/}} of {{bold}}20{{/}} specs",
			"",
		),
		Entry("With Labels",
			C(),
			types.Report{
				SuiteDescription: "My Suite", SuitePath: "/path/to/suite", SuiteLabels: []string{"dog", "fish"}, PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20},
				SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1},
			},
			"Running Suite: My Suite - /path/to/suite",
			"{{coral}}[dog, fish]{{/}} ",
			"========================================",
			"Random Seed: {{bold}}17{{/}}",
			"",
			"Will run {{bold}}15{{/}} of {{bold}}20{{/}} specs",
			"",
		),
		Entry("With long Labels",
			C(),
			types.Report{
				SuiteDescription: "My Suite", SuitePath: "/path/to/suite", SuiteLabels: []string{"dog", "fish", "kalamazoo", "kangaroo", "chicken", "asparagus"}, PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20},
				SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1},
			},
			"Running Suite: My Suite - /path/to/suite",
			"{{coral}}[dog, fish, kalamazoo, kangaroo, chicken, asparagus]{{/}} ",
			"====================================================",
			"Random Seed: {{bold}}17{{/}}",
			"",
			"Will run {{bold}}15{{/}} of {{bold}}20{{/}} specs",
			"",
		),
		Entry("When configured to randomize all specs",
			C(),
			types.Report{
				SuiteDescription: "My Suite", SuitePath: "/path/to/suite", PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20},
				SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1, RandomizeAllSpecs: true},
			},
			"Running Suite: My Suite - /path/to/suite",
			"========================================",
			"Random Seed: {{bold}}17{{/}} - will randomize all specs",
			"",
			"Will run {{bold}}15{{/}} of {{bold}}20{{/}} specs",
			"",
		),
		Entry("when configured to run in parallel",
			C(),
			types.Report{
				SuiteDescription: "My Suite", SuitePath: "/path/to/suite", PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20},
				SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 3},
			},
			"Running Suite: My Suite - /path/to/suite",
			"========================================",
			"Random Seed: {{bold}}17{{/}}",
			"",
			"Will run {{bold}}15{{/}} of {{bold}}20{{/}} specs",
			"Running in parallel across {{bold}}3{{/}} processes",
			"",
		),
		Entry("when succinct and in series",
			C(Succinct),
			types.Report{
				SuiteDescription: "My Suite", SuitePath: "/path/to/suite", PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20},
				SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1},
			},
			"[17] {{bold}}My Suite{{/}} - 15/20 specs ",
		),
		Entry("when succinct and in parallel",
			C(Succinct),
			types.Report{
				SuiteDescription: "My Suite", SuitePath: "/path/to/suite", PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20},
				SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 3},
			},
			"[17] {{bold}}My Suite{{/}} - 15/20 specs - 3 procs ",
		),
		Entry("when succinct and with labels",
			C(Succinct),
			types.Report{
				SuiteDescription: "My Suite", SuitePath: "/path/to/suite", SuiteLabels: Label("dog, fish"), PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20},
				SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 3},
			},
			"[17] {{bold}}My Suite{{/}} {{coral}}[dog, fish]{{/}} - 15/20 specs - 3 procs ",
		),
	)

	DescribeTable("WillRun",
		func(conf types.ReporterConfig, report types.SpecReport, expected ...any) {
			reporter := reporters.NewDefaultReporterUnderTest(conf, buf)
			reporter.WillRun(report)
			Expect(string(buf.Contents())).Should(MatchLines(expected...))
		},
		Entry("when not verbose, it emits nothing", C(), S(CTS("A"), CLS(cl0))),
		Entry("pending specs are not emitted", C(Verbose), S(types.SpecStatePending)),
		Entry("skipped specs are not emitted", C(Verbose), S(types.SpecStateSkipped)),
		Entry("setup nodes", C(Verbose),
			S(types.NodeTypeBeforeSuite, cl0),
			DELIMITER,
			"{{/}}{{bold}}[BeforeSuite] {{/}}",
			"{{gray}}"+cl0.String()+"{{/}}",
			"",
		),
		Entry("ReportAfterSuite nodes", C(Verbose),
			S("my report", cl0, types.NodeTypeReportAfterSuite),
			DELIMITER,
			"{{/}}{{bold}}[ReportAfterSuite] my report{{/}}",
			"{{gray}}"+cl0.String()+"{{/}}",
			"",
		),
		Entry("top-level it nodes", C(Verbose),
			S("My Test", cl0),
			DELIMITER,
			"{{/}}{{bold}}My Test{{/}}",
			"{{gray}}"+cl0.String()+"{{/}}",
			"",
		),
		Entry("nested it nodes", C(Verbose),
			S(CTS("Container", "Nested Container"), "My Test", CLS(cl0, cl1), cl2),
			DELIMITER,
			"{{/}}Container {{gray}}Nested Container {{/}}{{bold}}My Test{{/}}",
			"{{gray}}"+cl2.String()+"{{/}}",
			"",
		),
		Entry("specs with labels", C(Verbose),
			S(CTS("Container", "Nested Container"), "My Test", CLS(cl0, cl1), cl2, CLabels(Label("dog", "cat"), Label("cat", "fruit")), Label("giraffe", "gorilla", "cat")),
			DELIMITER,
			"{{/}}Container {{gray}}Nested Container {{/}}{{bold}}My Test{{/}} {{coral}}[dog, cat, fruit, giraffe, gorilla]{{/}}",
			"{{gray}}"+cl2.String()+"{{/}}",
			"",
		),
	)

	DescribeTable("WillRun => DidRun",
		func(report types.SpecReport, cases ...ConfigCase) {
			for _, c := range cases {
				for _, configFlag := range c.ConfigFlags {
					reporter := reporters.NewDefaultReporterUnderTest(C(configFlag), buf)
					report.RunningInParallel = configFlag.Has(Parallel)
					reporter.WillRun(report)
					reporter.DidRun(report)
					Expect(string(buf.Contents())).Should(MatchLines(c.Expected...), "for config: %s", configFlag)
					Ω(buf.Clear()).Should(Succeed())
				}
			}
		},
		// Passing tests
		Entry("a passing test",
			S(CLS(cl0, cl1), CTS("A", "B"), "C", cl2),
			Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel,
				"{{green}}"+DENOTER+"{{/}}"),
			Case(Verbose,
				DELIMITER,
				"{{/}}A {{gray}}B {{/}}{{bold}}C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				DELIMITER,
				""),
			Case(Verbose|Parallel,
				DELIMITER,
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{green}}{{bold}}C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				DELIMITER,
				""),
			Case(VeryVerbose,
				DELIMITER,
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{/}}{{bold}}C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{green}}{{bold}}C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				DELIMITER,
				""),
		),
		Entry("a passing suite-level node",
			S(types.NodeTypeReportAfterSuite, "C", cl0),
			Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel),
			Case(Verbose, VeryVerbose,
				DELIMITER,
				"{{/}}{{bold}}[ReportAfterSuite] C{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"{{green}}[ReportAfterSuite] PASSED [1.000 seconds]{{/}}",
				DELIMITER,
				""),
			Case(Verbose|Parallel, VeryVerbose|Parallel,
				DELIMITER,
				"{{green}}[ReportAfterSuite] PASSED [1.000 seconds]{{/}}",
				"{{green}}{{bold}}[ReportAfterSuite] C{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				DELIMITER,
				""),
		),
		Entry("a passing test that was flakey",
			S(types.NodeTypeIt, "A", cl0, 3, FlakeAttempts(4),
				AF(types.SpecStateFailed, "failed", types.NodeTypeIt, cl1, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0)),
				SE(types.SpecEventSpecRetry, 1),
				AF(types.SpecStateFailed, "failed again", types.NodeTypeIt, cl1, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0)),
				SE(types.SpecEventSpecRetry, 2),
			),
			Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel,
				DELIMITER,
				spr("{{green}}%s [FLAKEY TEST - TOOK 3 ATTEMPTS TO PASS] [1.000 seconds]{{/}}", RETRY_DENOTER),
				"{{green}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				DELIMITER,
				""),
			Case(Verbose, VeryVerbose,
				DELIMITER,
				"{{/}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				spr("{{green}}%s [FLAKEY TEST - TOOK 3 ATTEMPTS TO PASS] [1.000 seconds]{{/}}", RETRY_DENOTER),
				DELIMITER,
				""),
			Case(Verbose|Parallel,
				DELIMITER,
				spr("{{green}}%s [FLAKEY TEST - TOOK 3 ATTEMPTS TO PASS] [1.000 seconds]{{/}}", RETRY_DENOTER),
				"{{green}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				spr("  {{red}}[FAILED]{{/}} in [It] - cl1.go:37 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				spr("  {{bold}}Attempt #1 {{red}}Failed{{/}}{{bold}}.  Retrying %s{{/}} {{gray}}@ %s{{/}}", RETRY_DENOTER, FORMATTED_TIME),
				"",
				spr("  {{red}}[FAILED]{{/}} in [It] - cl1.go:37 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				spr("  {{bold}}Attempt #2 {{red}}Failed{{/}}{{bold}}.  Retrying %s{{/}} {{gray}}@ %s{{/}}", RETRY_DENOTER, FORMATTED_TIME),
				"",
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				spr("{{green}}%s [FLAKEY TEST - TOOK 3 ATTEMPTS TO PASS] [1.000 seconds]{{/}}", RETRY_DENOTER),
				"{{green}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"  {{red}}[FAILED] failed{{/}}",
				spr("  {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				spr("  {{bold}}Attempt #1 {{red}}Failed{{/}}{{bold}}.  Retrying %s{{/}} {{gray}}@ %s{{/}}", RETRY_DENOTER, FORMATTED_TIME),
				"",
				"  {{red}}[FAILED] failed again{{/}}",
				spr("  {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				spr("  {{bold}}Attempt #2 {{red}}Failed{{/}}{{bold}}.  Retrying %s{{/}} {{gray}}@ %s{{/}}", RETRY_DENOTER, FORMATTED_TIME),
				"",
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
		),
		Entry("a passing test that repeated a few times",
			S(types.NodeTypeIt, "A", cl0, 3, MustPassRepeatedly(3),
				SE(types.SpecEventSpecRepeat, 1),
				SE(types.SpecEventSpecRepeat, 2),
			),
			Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel,
				spr("{{green}}%s{{/}}", DENOTER)),
			Case(Verbose, VeryVerbose,
				DELIMITER,
				"{{/}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				DELIMITER,
				""),
			Case(Verbose|Parallel, VeryVerbose|Parallel,
				DELIMITER,
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				"{{green}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"",
				spr("  {{bold}}Attempt #1 {{green}}Passed{{/}}{{bold}}.  Repeating ↺{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"",
				spr("  {{bold}}Attempt #2 {{green}}Passed{{/}}{{bold}}.  Repeating ↺{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
		),
		Entry("a passing test with a spec report",
			S(types.NodeTypeIt, "A", cl0,
				RE("my entry", cl1),
			),
			Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel,
				DELIMITER,
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				"{{green}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{gray}}Report Entries >>{{/}}",
				spr("  {{bold}}my entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Report Entries{{/}}",
				DELIMITER,
				""),
			Case(Verbose, VeryVerbose,
				DELIMITER,
				"{{/}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				DELIMITER,
				""),
			Case(Verbose|Parallel, VeryVerbose|Parallel,
				DELIMITER,
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				"{{green}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				spr("  {{bold}}my entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
		),
		Entry("a passing test with a captured stdout/stderr",
			S(types.NodeTypeIt, "A", cl0, STD("hello there\nthis is my output")),
			Case(Succinct, Normal,
				spr("{{green}}%s{{/}}", DENOTER)),
			Case(Verbose, VeryVerbose,
				DELIMITER,
				"{{/}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				DELIMITER,
				""),
			Case(Succinct|Parallel, Normal|Parallel, Verbose|Parallel, VeryVerbose|Parallel,
				DELIMITER,
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				"{{green}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{gray}}Captured StdOut/StdErr Output >>{{/}}",
				"  hello there",
				"  this is my output",
				"  {{gray}}<< Captured StdOut/StdErr Output{{/}}",
				DELIMITER,
				""),
		),
		Entry("a passing test with a full timeline that is only visible in verbose/very-verbose mode",
			S(types.NodeTypeIt, "A", cl0, GW("some GinkgoWriter\noutput is interspersed\nhere and there\n"),
				SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0),
				PR("my progress report", LeafNodeText("A"), TL("some GinkgoWriter\n")),
				SE(types.SpecEventByStart, "My Step", cl1, TL("some GinkgoWriter\n")),
				RE("my entry", cl1, types.ReportEntryVisibilityFailureOrVerbose, TL("some GinkgoWriter\noutput is interspersed\n")),
				RE("my hidden entry", cl1, types.ReportEntryVisibilityNever, TL("some GinkgoWriter\noutput is interspersed\n")),
				SE(types.SpecEventByEnd, "My Step", cl1, time.Millisecond*200, TL("some GinkgoWriter\noutput is interspersed\n")),
				SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")),
			),
			Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel, Succinct|ShowNodeEvents, Normal|ShowNodeEvents,
				spr("{{green}}%s{{/}}", DENOTER)),
			Case(Verbose, VeryVerbose, //nothing to see here since things are emitted while streaming, which we don't simulate
				DELIMITER,
				"{{/}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				DELIMITER,
				""),
			Case(Verbose|Parallel|ShowNodeEvents,
				DELIMITER,
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				"{{green}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				spr("  > Enter {{bold}}[It]{{/}} A {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  some GinkgoWriter",
				"  my progress report",
				"    {{bold}}{{orange}}A{{/}} (Spec Runtime: 5s)",
				"      {{gray}}cl0.go:12{{/}}",
				spr("  {{bold}}STEP:{{/}} My Step {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  output is interspersed",
				spr("  {{bold}}my entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME),
				spr("  {{bold}}END STEP:{{/}} My Step {{gray}}@ %s (200ms){{/}}", FORMATTED_TIME),
				"  here and there",
				spr("  < Exit {{bold}}[It]{{/}} A {{gray}}@ %s (300ms){{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
			Case(Verbose|Parallel,
				DELIMITER,
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				"{{green}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"  some GinkgoWriter",
				"  my progress report",
				"    {{bold}}{{orange}}A{{/}} (Spec Runtime: 5s)",
				"      {{gray}}cl0.go:12{{/}}",
				spr("  {{bold}}STEP:{{/}} My Step {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  output is interspersed",
				spr("  {{bold}}my entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME),
				"  here and there",
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER),
				"{{green}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				spr("  > Enter {{bold}}[It]{{/}} A {{gray}}- cl0.go:12 @ %s{{/}}", FORMATTED_TIME),
				"  some GinkgoWriter",
				"  my progress report",
				"    {{bold}}{{orange}}A{{/}} (Spec Runtime: 5s)",
				"      {{gray}}cl0.go:12{{/}}",
				spr("  {{bold}}STEP:{{/}} My Step {{gray}}- cl1.go:37 @ %s{{/}}", FORMATTED_TIME),
				"  output is interspersed",
				spr("  {{bold}}my entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME),
				spr("  {{bold}}END STEP:{{/}} My Step {{gray}}- cl1.go:37 @ %s (200ms){{/}}", FORMATTED_TIME),
				"  here and there",
				spr("  < Exit {{bold}}[It]{{/}} A {{gray}}- cl0.go:12 @ %s (300ms){{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
		),
		// Skipped tests
		Entry("a skipped test",
			S(types.NodeTypeIt, "A", types.SpecStateSkipped, cl0),
			Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel, Verbose, Verbose|Parallel,
				"{{cyan}}S{{/}}"),
			Case(VeryVerbose,
				"{{cyan}}S [SKIPPED]{{/}}",
				"{{cyan}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				"{{cyan}}S [SKIPPED]{{/}}",
				"{{cyan}}{{bold}}A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				DELIMITER,
				""),
		),
		Entry("a user-skipped test",
			S(types.NodeTypeIt, "A", types.SpecStateSkipped, cl0,
				F("let's skip it", cl1, types.NodeTypeIt, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0)),
			),
			Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel,
				"{{cyan}}S{{/}}"),
			Case(Verbose,
				"{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}",
				"{{cyan}}{{bold}}[It] A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{cyan}}[SKIPPED] let's skip it{{/}}",
				spr("  {{cyan}}In {{bold}}[It]{{/}}{{cyan}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose,
				"{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}",
				"{{cyan}}{{bold}}[It] A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				DELIMITER,
				"",
			),
			Case(Verbose|Parallel,
				DELIMITER,
				"{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}",
				"{{cyan}}{{bold}}[It] A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{cyan}}[SKIPPED] let's skip it{{/}}",
				spr("  {{cyan}}In {{bold}}[It]{{/}}{{cyan}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				"{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}",
				"{{cyan}}{{bold}}[It] A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"  {{cyan}}[SKIPPED] let's skip it{{/}}",
				spr("  {{cyan}}In {{bold}}[It]{{/}}{{cyan}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
		),
		Entry("a user-skipped test with timeline content",
			S(types.NodeTypeIt, "A", types.SpecStateSkipped, cl0,
				GW("should we skip it?"),
				F("let's skip it", TL("should we skip it?"), cl1, types.NodeTypeIt, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0)),
			),
			Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel,
				"{{cyan}}S{{/}}"),
			Case(Verbose|Parallel,
				DELIMITER,
				"{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}",
				"{{cyan}}{{bold}}[It] A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"  should we skip it?",
				spr("  {{cyan}}[SKIPPED]{{/}} in [It] - cl1.go:37 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				"",
				"  {{cyan}}[SKIPPED] let's skip it{{/}}",
				spr("  {{cyan}}In {{bold}}[It]{{/}}{{cyan}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				"{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}",
				"{{cyan}}{{bold}}[It] A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"  should we skip it?",
				"  {{cyan}}[SKIPPED] let's skip it{{/}}",
				spr("  {{cyan}}In {{bold}}[It]{{/}}{{cyan}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
			Case(Verbose,
				"{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}",
				"{{cyan}}{{bold}}[It] A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{cyan}}[SKIPPED] let's skip it{{/}}",
				spr("  {{cyan}}In {{bold}}[It]{{/}}{{cyan}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose,
				"{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}",
				"{{cyan}}{{bold}}[It] A{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				DELIMITER,
				"",
			),
		),
		Entry("a pending test",
			S(types.NodeTypeIt, "C", types.SpecStatePending, cl2, CTS("A", "B"), CLS(cl0, cl1)),
			Case(Succinct, Succinct|Parallel,
				"{{yellow}}P{{/}}"),
			Case(Normal, Normal|Parallel, Verbose|Parallel,
				DELIMITER,
				"{{yellow}}P [PENDING]{{/}}",
				"{{/}}A {{gray}}B {{yellow}}{{bold}}C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				DELIMITER,
				"",
			),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				"{{yellow}}P [PENDING]{{/}}",
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{yellow}}{{bold}}C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				DELIMITER,
				""),
			Case(Verbose,
				"{{yellow}}P [PENDING]{{/}}",
				"{{/}}A {{gray}}B {{yellow}}{{bold}}C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				DELIMITER,
				"",
			),
			Case(VeryVerbose,
				"{{yellow}}P [PENDING]{{/}}",
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{yellow}}{{bold}}C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				DELIMITER,
				""),
		),
		Entry("a failed test with a failure in the It",
			S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateFailed,
				F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt),
			),
			Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel,
				DELIMITER, //note: no timeline because there is only a single failure in here
				spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{red}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{red}}[FAILED] failure",
				"  message{{/}}",
				spr("  {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER),
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{red}}{{bold}}[It] C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"  {{red}}[FAILED] failure",
				"  message{{/}}",
				spr("  {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
			Case(Verbose,
				DELIMITER,
				"{{/}}A {{gray}}B {{/}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{red}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{red}}[FAILED] failure",
				"  message{{/}}",
				spr("  {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec)
				DELIMITER,
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{/}}{{bold}}[It] C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER),
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{red}}{{bold}}[It] C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				DELIMITER,
				""),
		),
		Entry("a failed test with a failure in an intermediate BeforeEach",
			S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateFailed,
				F("failure\nmessage", cl3, types.FailureNodeInContainer, FailureNodeLocation(cl4), types.NodeTypeBeforeEach, 1),
			),
			Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel,
				DELIMITER, //note: no timeline because there is only a single failure in here
				spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{red}}{{bold}}B [BeforeEach] {{/}}C{{/}}",
				"  {{red}}[BeforeEach]{{/}} {{gray}}cl4.go:144{{/}}",
				"  {{gray}}[It] cl2.go:80{{/}}",
				"",
				"  {{red}}[FAILED] failure",
				"  message{{/}}",
				spr("  {{red}}In {{bold}}[BeforeEach]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER),
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  {{red}}{{bold}}B [BeforeEach]{{/}}",
				"  {{gray}}cl4.go:144{{/}}",
				"    C",
				"    {{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"  {{red}}[FAILED] failure",
				"  message{{/}}",
				spr("  {{red}}In {{bold}}[BeforeEach]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
			Case(Verbose,
				DELIMITER,
				"{{/}}A {{/}}{{bold}}B [BeforeEach] {{/}}C{{/}}",
				"  {{/}}[BeforeEach]{{/}} {{gray}}cl4.go:144{{/}}",
				"  {{gray}}[It] cl2.go:80{{/}}",
				spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{red}}{{bold}}B [BeforeEach] {{/}}C{{/}}",
				"  {{red}}[BeforeEach]{{/}} {{gray}}cl4.go:144{{/}}",
				"  {{gray}}[It] cl2.go:80{{/}}",
				"",
				"  {{red}}[FAILED] failure",
				"  message{{/}}",
				spr("  {{red}}In {{bold}}[BeforeEach]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec)
				DELIMITER,
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  {{/}}{{bold}}B [BeforeEach]{{/}}",
				"  {{gray}}cl4.go:144{{/}}",
				"    C",
				"    {{gray}}cl2.go:80{{/}}",
				spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER),
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  {{red}}{{bold}}B [BeforeEach]{{/}}",
				"  {{gray}}cl4.go:144{{/}}",
				"    C",
				"    {{gray}}cl2.go:80{{/}}",
				DELIMITER,
				""),
		),
		Entry("a failed test with a failure at the top-level",
			S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateFailed,
				F("failure\nmessage", cl3, types.FailureNodeAtTopLevel, FailureNodeLocation(cl4), types.NodeTypeAfterEach),
			),
			Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel,
				DELIMITER, //note: no timeline because there is only a single failure in here
				spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER),
				"{{red}}{{bold}}TOP-LEVEL [AfterEach] {{gray}}A {{/}}B {{gray}}C{{/}}",
				"  {{red}}[AfterEach]{{/}} {{gray}}cl4.go:144{{/}}",
				"  {{gray}}[It] cl2.go:80{{/}}",
				"",
				"  {{red}}[FAILED] failure",
				"  message{{/}}",
				spr("  {{red}}In {{bold}}[AfterEach]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER),
				"{{red}}{{bold}}TOP-LEVEL [AfterEach]{{/}}",
				"{{gray}}cl4.go:144{{/}}",
				"  A",
				"  {{gray}}cl0.go:12{{/}}",
				"    B",
				"    {{gray}}cl1.go:37{{/}}",
				"      C",
				"      {{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"  {{red}}[FAILED] failure",
				"  message{{/}}",
				spr("  {{red}}In {{bold}}[AfterEach]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
			Case(Verbose,
				DELIMITER,
				"{{/}}{{bold}}TOP-LEVEL [AfterEach] {{gray}}A {{/}}B {{gray}}C{{/}}",
				"  {{/}}[AfterEach]{{/}} {{gray}}cl4.go:144{{/}}",
				"  {{gray}}[It] cl2.go:80{{/}}",
				spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER),
				"{{red}}{{bold}}TOP-LEVEL [AfterEach] {{gray}}A {{/}}B {{gray}}C{{/}}",
				"  {{red}}[AfterEach]{{/}} {{gray}}cl4.go:144{{/}}",
				"  {{gray}}[It] cl2.go:80{{/}}",
				"",
				"  {{red}}[FAILED] failure",
				"  message{{/}}",
				spr("  {{red}}In {{bold}}[AfterEach]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec)
				DELIMITER,
				"{{/}}{{bold}}TOP-LEVEL [AfterEach]{{/}}",
				"{{gray}}cl4.go:144{{/}}",
				"  A",
				"  {{gray}}cl0.go:12{{/}}",
				"    B",
				"    {{gray}}cl1.go:37{{/}}",
				"      C",
				"      {{gray}}cl2.go:80{{/}}",
				spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER),
				"{{red}}{{bold}}TOP-LEVEL [AfterEach]{{/}}",
				"{{gray}}cl4.go:144{{/}}",
				"  A",
				"  {{gray}}cl0.go:12{{/}}",
				"    B",
				"    {{gray}}cl1.go:37{{/}}",
				"      C",
				"      {{gray}}cl2.go:80{{/}}",
				DELIMITER,
				""),
		),
		Entry("a failed test with a failure in a suite-node",
			S(types.NodeTypeBeforeSuite, cl0, types.SpecStateAborted,
				F("failure\nmessage", cl1, types.FailureNodeAtTopLevel, FailureNodeLocation(cl0), types.NodeTypeBeforeSuite),
			),
			Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel,
				DELIMITER, //note: no timeline because there is only a single failure in here
				"{{coral}}[BeforeSuite] [ABORTED] [1.000 seconds]{{/}}",
				"{{coral}}{{bold}}TOP-LEVEL [BeforeSuite] {{gray}}[BeforeSuite] {{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{coral}}[ABORTED] failure",
				"  message{{/}}",
				spr("  {{coral}}In {{bold}}[BeforeSuite]{{/}}{{coral}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				"{{coral}}[BeforeSuite] [ABORTED] [1.000 seconds]{{/}}",
				"{{coral}}{{bold}}TOP-LEVEL [BeforeSuite]{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"  [BeforeSuite] ",
				"  {{gray}}cl0.go:12{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"  {{coral}}[ABORTED] failure",
				"  message{{/}}",
				spr("  {{coral}}In {{bold}}[BeforeSuite]{{/}}{{coral}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
			Case(Verbose,
				DELIMITER,
				"{{/}}{{bold}}TOP-LEVEL [BeforeSuite] {{gray}}[BeforeSuite] {{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"{{coral}}[BeforeSuite] [ABORTED] [1.000 seconds]{{/}}",
				"{{coral}}{{bold}}TOP-LEVEL [BeforeSuite] {{gray}}[BeforeSuite] {{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"",
				"  {{coral}}[ABORTED] failure",
				"  message{{/}}",
				spr("  {{coral}}In {{bold}}[BeforeSuite]{{/}}{{coral}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec)
				DELIMITER,
				"{{/}}{{bold}}TOP-LEVEL [BeforeSuite]{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"  [BeforeSuite] ",
				"  {{gray}}cl0.go:12{{/}}",
				"{{coral}}[BeforeSuite] [ABORTED] [1.000 seconds]{{/}}",
				"{{coral}}{{bold}}TOP-LEVEL [BeforeSuite]{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"  [BeforeSuite] ",
				"  {{gray}}cl0.go:12{{/}}",
				DELIMITER,
				""),
		),
		Entry("a failed test that failed during a repetition",
			S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), 2, "C", cl2, types.SpecStateFailed, MustPassRepeatedly(3),
				F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt),
			),
			Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel,
				DELIMITER, //note: no timeline because there is only a single failure in here
				spr("{{red}}%s [FAILED] DURING REPETITION #2 [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{red}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{red}}[FAILED] failure",
				"  message{{/}}",
				spr("  {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				spr("{{red}}%s [FAILED] DURING REPETITION #2 [1.000 seconds]{{/}}", DENOTER),
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{red}}{{bold}}[It] C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"  {{red}}[FAILED] failure",
				"  message{{/}}",
				spr("  {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
			Case(Verbose,
				DELIMITER,
				"{{/}}A {{gray}}B {{/}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				spr("{{red}}%s [FAILED] DURING REPETITION #2 [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{red}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{red}}[FAILED] failure",
				"  message{{/}}",
				spr("  {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
		),
		Entry("a failed test with nothing but a failure + the associated additional-failure in the timeline",
			S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateTimedout,
				F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL(0), AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL(0))),
			),
			Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel,
				DELIMITER, //note: no timeline because there is only a single failure + its additional-failure in here
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{orange}}[TIMEDOUT] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{orange}}{{bold}}[It] C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"  {{orange}}[TIMEDOUT] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
			Case(Verbose,
				DELIMITER,
				"{{/}}A {{gray}}B {{/}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{orange}}[TIMEDOUT] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec)
				DELIMITER,
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{/}}{{bold}}[It] C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{orange}}{{bold}}[It] C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				DELIMITER,
				""),
		),
		Entry("a failed test with GinkgoWriter output",
			S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateTimedout, GW("some ginkgowriter\noutput\n"),
				F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("some ginkgowriter\n"), AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("some ginkgowriter\noutput\n"))),
			),
			Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel,
				DELIMITER,
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"  some ginkgowriter",
				spr("  {{orange}}[TIMEDOUT]{{/}} in [It] - cl3.go:103 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  output",
				spr("  {{magenta}}[PANICKED]{{/}} in [It] - cl4.go:144 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				"",
				"  {{orange}}[TIMEDOUT] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{orange}}{{bold}}[It] C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				"  some ginkgowriter",
				"  {{orange}}[TIMEDOUT] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  output",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
			Case(Verbose,
				DELIMITER,
				"{{/}}A {{gray}}B {{/}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{orange}}[TIMEDOUT] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec)
				DELIMITER,
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{/}}{{bold}}[It] C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{orange}}{{bold}}[It] C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				DELIMITER,
				""),
		),
		Entry("a failed test with timeline entries",
			S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateInterrupted, SE(types.SpecEventByStart, "a by step", cl0),
				F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL(0),
					PR("Interrupt Progress Report", types.NodeTypeIt, CurrentNodeText("C"), LeafNodeText("C"), []string{"A", "B"}),
					AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL(0))),
				RE("a report entry", cl1),
			),
			Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel,
				DELIMITER,
				spr("{{orange}}%s [INTERRUPTED] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				spr("  {{bold}}STEP:{{/}} a by step {{gray}}@ %s{{/}}", FORMATTED_TIME),
				spr("  {{orange}}[INTERRUPTED]{{/}} in [It] - cl3.go:103 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				spr("  {{magenta}}[PANICKED]{{/}} in [It] - cl4.go:144 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				spr("  {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				"",
				"  {{orange}}[INTERRUPTED] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  Interrupt Progress Report",
				"    {{/}}A {{gray}}B{{/}} {{bold}}{{orange}}C{{/}} (Spec Runtime: 5s)",
				"      {{gray}}cl0.go:12{{/}}",
				"      In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
				"        {{gray}}cl1.go:37{{/}}",
				"",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				spr("{{orange}}%s [INTERRUPTED] [1.000 seconds]{{/}}", DENOTER),
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{orange}}{{bold}}[It] C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				spr("  {{bold}}STEP:{{/}} a by step {{gray}}- cl0.go:12 @ %s{{/}}", FORMATTED_TIME),
				"  {{orange}}[INTERRUPTED] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  Interrupt Progress Report",
				"    {{/}}A {{gray}}B{{/}} {{bold}}{{orange}}C{{/}} (Spec Runtime: 5s)",
				"      {{gray}}cl0.go:12{{/}}",
				"      In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
				"        {{gray}}cl1.go:37{{/}}",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				spr("  {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
			Case(Verbose, // don't see the other timeline entries because they are emitted in realthime (which isn't shown here since we don't replay the timeline in the spec)
				DELIMITER,
				"{{/}}A {{gray}}B {{/}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				spr("{{orange}}%s [INTERRUPTED] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{orange}}[INTERRUPTED] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  Interrupt Progress Report",
				"    {{/}}A {{gray}}B{{/}} {{bold}}{{orange}}C{{/}} (Spec Runtime: 5s)",
				"      {{gray}}cl0.go:12{{/}}",
				"      In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
				"        {{gray}}cl1.go:37{{/}}",
				"",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				DELIMITER,
				""),
			Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec)
				DELIMITER,
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{/}}{{bold}}[It] C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				spr("{{orange}}%s [INTERRUPTED] [1.000 seconds]{{/}}", DENOTER),
				"A",
				"{{gray}}cl0.go:12{{/}}",
				"  B",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{orange}}{{bold}}[It] C{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				DELIMITER,
				""),
		),
		Entry("a failed test with both GinkgoWriter output and timeline entries (including multiple additional failures)",
			S(types.NodeTypeIt, Label("cat", "dog"), CLabels(Label("dolphin"), Label("gorilla", "cow")), CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateTimedout, STD("some captured stdout\n"), GW("ginkgowriter\noutput\ncleanup!"), SE(types.SpecEventByStart, "a by step", cl0),
				SE(types.SpecEventNodeStart, types.NodeTypeIt, "C", cl2, TL(0)),
				F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\n"), AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\noutput\n"), ForwardedPanic("the panic!"))),
				SE(types.SpecEventNodeEnd, types.NodeTypeIt, "C", cl2, TL("ginkgowriter\noutput\n"), time.Microsecond*87230),
				RE("a report entry", cl1, TL("ginkgowriter\noutput\n")),
				RE("a hidden report entry", cl1, TL("ginkgowriter\noutput\n"), types.ReportEntryVisibilityNever),
				AF(types.SpecStateFailed, "a subsequent failure", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeAfterEach, 0, TL("ginkgowriter\noutput\ncleanup!")),
			),
			Case(Succinct, Normal,
				DELIMITER,
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}} {{coral}}[dolphin, gorilla, cow, cat, dog]{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				spr("  {{bold}}STEP:{{/}} a by step {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  ginkgowriter",
				spr("  {{orange}}[TIMEDOUT]{{/}} in [It] - cl3.go:103 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  output",
				spr("  {{magenta}}[PANICKED]{{/}} in [It] - cl4.go:144 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				spr("  {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME),
				"  cleanup!",
				spr("  {{red}}[FAILED]{{/}} in [AfterEach] - :0 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				"",
				"  {{orange}}[TIMEDOUT] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}the panic!{{/}}",
				"",
				"  {{magenta}}Full Stack Trace{{/}}",
				"    full-trace",
				"    cl-4",
				"",
				"  There were {{bold}}{{red}}additional failures{{/}} detected.  To view them in detail run {{bold}}ginkgo -vv{{/}}",
				DELIMITER,
				""),

			Case(Succinct|ShowNodeEvents, Normal|ShowNodeEvents,
				DELIMITER,
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}} {{coral}}[dolphin, gorilla, cow, cat, dog]{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				spr("  {{bold}}STEP:{{/}} a by step {{gray}}@ %s{{/}}", FORMATTED_TIME),
				spr("  > Enter {{bold}}[It]{{/}} C {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  ginkgowriter",
				spr("  {{orange}}[TIMEDOUT]{{/}} in [It] - cl3.go:103 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  output",
				spr("  {{magenta}}[PANICKED]{{/}} in [It] - cl4.go:144 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				spr("  < Exit {{bold}}[It]{{/}} C {{gray}}@ %s (87ms){{/}}", FORMATTED_TIME),
				spr("  {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME),
				"  cleanup!",
				spr("  {{red}}[FAILED]{{/}} in [AfterEach] - :0 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				"",
				"  {{orange}}[TIMEDOUT] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}the panic!{{/}}",
				"",
				"  {{magenta}}Full Stack Trace{{/}}",
				"    full-trace",
				"    cl-4",
				"",
				"  There were {{bold}}{{red}}additional failures{{/}} detected.  To view them in detail run {{bold}}ginkgo -vv{{/}}",
				DELIMITER,
				""),
			Case(Succinct|Parallel, Normal|Parallel, Verbose|Parallel,
				DELIMITER,
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}} {{coral}}[dolphin, gorilla, cow, cat, dog]{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Captured StdOut/StdErr Output >>{{/}}",
				"  some captured stdout",
				"  {{gray}}<< Captured StdOut/StdErr Output{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				spr("  {{bold}}STEP:{{/}} a by step {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  ginkgowriter",
				spr("  {{orange}}[TIMEDOUT]{{/}} in [It] - cl3.go:103 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  output",
				spr("  {{magenta}}[PANICKED]{{/}} in [It] - cl4.go:144 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				spr("  {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME),
				"  cleanup!",
				spr("  {{red}}[FAILED]{{/}} in [AfterEach] - :0 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				"",
				"  {{orange}}[TIMEDOUT] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}the panic!{{/}}",
				"",
				"  {{magenta}}Full Stack Trace{{/}}",
				"    full-trace",
				"    cl-4",
				"",
				"  There were {{bold}}{{red}}additional failures{{/}} detected.  To view them in detail run {{bold}}ginkgo -vv{{/}}",
				DELIMITER,
				""),
			Case(Succinct|Parallel|FullTrace, Normal|Parallel|FullTrace, Verbose|Parallel|FullTrace,
				DELIMITER,
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}} {{coral}}[dolphin, gorilla, cow, cat, dog]{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Captured StdOut/StdErr Output >>{{/}}",
				"  some captured stdout",
				"  {{gray}}<< Captured StdOut/StdErr Output{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				spr("  {{bold}}STEP:{{/}} a by step {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  ginkgowriter",
				spr("  {{orange}}[TIMEDOUT]{{/}} in [It] - cl3.go:103 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  output",
				spr("  {{magenta}}[PANICKED]{{/}} in [It] - cl4.go:144 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				spr("  {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME),
				"  cleanup!",
				spr("  {{red}}[FAILED]{{/}} in [AfterEach] - :0 {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				"",
				"  {{orange}}[TIMEDOUT] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{orange}}Full Stack Trace{{/}}",
				"    full-trace",
				"    cl-3",
				"",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}the panic!{{/}}",
				"",
				"  {{magenta}}Full Stack Trace{{/}}",
				"    full-trace",
				"    cl-4",
				"",
				"  There were {{bold}}{{red}}additional failures{{/}} detected.  To view them in detail run {{bold}}ginkgo -vv{{/}}",
				DELIMITER,
				""),
			Case(VeryVerbose|Parallel,
				DELIMITER,
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"A {{coral}}[dolphin]{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"  B {{coral}}[gorilla, cow]{{/}}",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{orange}}{{bold}}[It] C{{/}} {{coral}}[cat, dog]{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				"",
				"  {{gray}}Captured StdOut/StdErr Output >>{{/}}",
				"  some captured stdout",
				"  {{gray}}<< Captured StdOut/StdErr Output{{/}}",
				"",
				"  {{gray}}Timeline >>{{/}}",
				spr("  {{bold}}STEP:{{/}} a by step {{gray}}- cl0.go:12 @ %s{{/}}", FORMATTED_TIME),
				spr("  > Enter {{bold}}[It]{{/}} C {{gray}}- cl2.go:80 @ %s{{/}}", FORMATTED_TIME),
				"  ginkgowriter",
				"  {{orange}}[TIMEDOUT] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  output",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}the panic!{{/}}",
				"",
				"  {{magenta}}Full Stack Trace{{/}}",
				"    full-trace",
				"    cl-4",
				spr("  < Exit {{bold}}[It]{{/}} C {{gray}}- cl2.go:80 @ %s (87ms){{/}}", FORMATTED_TIME),
				spr("  {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME),
				"  cleanup!",
				"  {{red}}[FAILED] a subsequent failure{{/}}",
				spr("  {{red}}In {{bold}}[AfterEach]{{/}}{{red}} at: {{bold}}:0{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"  {{gray}}<< Timeline{{/}}",
				DELIMITER,
				""),
			Case(Verbose, // don't see the other timeline entries because they are emitted in realthime (which isn't shown here since we don't replay the timeline in the spec)
				DELIMITER,
				"{{/}}A {{gray}}B {{/}}{{bold}}[It] C{{/}} {{coral}}[dolphin, gorilla, cow, cat, dog]{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}} {{coral}}[dolphin, gorilla, cow, cat, dog]{{/}}",
				"{{gray}}cl2.go:80{{/}}",
				"",
				"  {{orange}}[TIMEDOUT] failure",
				"  message{{/}}",
				spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}[PANICKED] {{/}}",
				spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
				"",
				"  {{magenta}}the panic!{{/}}",
				"",
				"  {{magenta}}Full Stack Trace{{/}}",
				"    full-trace",
				"    cl-4",
				"",
				"  There were {{bold}}{{red}}additional failures{{/}} detected.  To view them in detail run {{bold}}ginkgo -vv{{/}}",
				DELIMITER,
				""),
			Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec)
				DELIMITER,
				"A {{coral}}[dolphin]{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"  B {{coral}}[gorilla, cow]{{/}}",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{/}}{{bold}}[It] C{{/}} {{coral}}[cat, dog]{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER),
				"A {{coral}}[dolphin]{{/}}",
				"{{gray}}cl0.go:12{{/}}",
				"  B {{coral}}[gorilla, cow]{{/}}",
				"  {{gray}}cl1.go:37{{/}}",
				"    {{orange}}{{bold}}[It] C{{/}} {{coral}}[cat, dog]{{/}}",
				"    {{gray}}cl2.go:80{{/}}",
				DELIMITER,
				""),
		),
	)

	DescribeTable("Rendering SuiteDidEnd",
		func(conf types.ReporterConfig, report types.Report, expected ...any) {
			reporter := reporters.NewDefaultReporterUnderTest(conf, buf)
			reporter.SuiteDidEnd(report)
			Expect(string(buf.Contents())).Should(MatchLines(expected...))
		},

		Entry("when configured to be succinct",
			C(Succinct),
			types.Report{
				SuiteSucceeded: true,
				RunTime:        time.Minute,
				SpecReports:    types.SpecReports{S()},
			},
			" {{green}}SUCCESS!{{/}} 1m0s ",
		),
		Entry("the suite passes",
			C(),
			types.Report{
				SuiteSucceeded: true,
				PreRunStats:    types.PreRunStats{TotalSpecs: 8, SpecsThatWillRun: 8},
				RunTime:        time.Minute,
				SpecReports: types.SpecReports{
					S(types.NodeTypeBeforeSuite),
					S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed),
					S(types.SpecStatePending), S(types.SpecStatePending),
					S(types.SpecStateSkipped), S(types.SpecStateSkipped), S(types.SpecStateSkipped),
					S(types.NodeTypeAfterSuite),
				},
			},
			"",
			"{{green}}{{bold}}Ran 3 of 8 Specs in 60.000 seconds{{/}}",
			"{{green}}{{bold}}SUCCESS!{{/}} -- {{green}}{{bold}}3 Passed{{/}} | {{red}}{{bold}}0 Failed{{/}} | {{yellow}}{{bold}}2 Pending{{/}} | {{cyan}}{{bold}}3 Skipped{{/}}",
			"",
		),
		Entry("the suite passes and has flaky specs",
			C(),
			types.Report{
				SuiteSucceeded: true,
				PreRunStats:    types.PreRunStats{TotalSpecs: 10, SpecsThatWillRun: 8},
				RunTime:        time.Minute,
				SpecReports: types.SpecReports{
					S(types.NodeTypeBeforeSuite),
					S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed),
					S(types.SpecStatePassed, 3, FlakeAttempts(5)), S(types.SpecStatePassed, 4, FlakeAttempts(5)), //flakey
					S(types.SpecStatePassed, 3, MustPassRepeatedly(5)), S(types.SpecStatePassed, 4, MustPassRepeatedly(5)), //repeated
					S(types.SpecStatePending), S(types.SpecStatePending),
					S(types.SpecStateSkipped), S(types.SpecStateSkipped), S(types.SpecStateSkipped),
					S(types.NodeTypeAfterSuite),
				},
			},
			"",
			"{{green}}{{bold}}Ran 7 of 10 Specs in 60.000 seconds{{/}}",
			"{{green}}{{bold}}SUCCESS!{{/}} -- {{green}}{{bold}}7 Passed{{/}} | {{red}}{{bold}}0 Failed{{/}} | {{light-yellow}}{{bold}}2 Flaked{{/}} | {{yellow}}{{bold}}2 Pending{{/}} | {{cyan}}{{bold}}3 Skipped{{/}}",
			"",
		),
		Entry("the suite fails with one failed test",
			C(),
			types.Report{
				SuiteSucceeded: false,
				PreRunStats:    types.PreRunStats{TotalSpecs: 18, SpecsThatWillRun: 8},
				RunTime:        time.Minute,
				SpecReports: types.SpecReports{
					S(types.NodeTypeBeforeSuite),
					S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed),
					S(types.SpecStatePassed, 3, FlakeAttempts(5)), S(types.SpecStatePassed, 4, FlakeAttempts(5)), //flakey
					S(types.SpecStatePassed, 3, MustPassRepeatedly(5)), S(types.SpecStatePassed, 4, MustPassRepeatedly(5)), //repeated
					S(types.SpecStatePending), S(types.SpecStatePending),
					S(types.SpecStateSkipped), S(types.SpecStateSkipped), S(types.SpecStateSkipped),
					S(CTS("Describe A", "Context B"), "The Test", CLS(cl0, cl1), cl2,
						types.SpecStateFailed, 2,
						F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeJustBeforeEach, 1, cl4),
					),
					S(types.NodeTypeAfterSuite),
				},
			},
			"",
			"{{red}}{{bold}}Summarizing 1 Failure:{{/}}",
			"  {{red}}[FAIL]{{/}} {{/}}Describe A {{red}}{{bold}}Context B [JustBeforeEach] {{/}}The Test{{/}}",
			"  {{gray}}cl4.go:144{{/}}",
			"",
			"{{red}}{{bold}}Ran 8 of 18 Specs in 60.000 seconds{{/}}",
			"{{red}}{{bold}}FAIL!{{/}} -- {{green}}{{bold}}7 Passed{{/}} | {{red}}{{bold}}1 Failed{{/}} | {{light-yellow}}{{bold}}2 Flaked{{/}} | {{yellow}}{{bold}}2 Pending{{/}} | {{cyan}}{{bold}}3 Skipped{{/}}",
			"",
		),
		Entry("the suite fails with multiple failed tests",
			C(),
			types.Report{
				SuiteSucceeded: false,
				PreRunStats:    types.PreRunStats{TotalSpecs: 14, SpecsThatWillRun: 10},
				RunTime:        time.Minute,
				SpecReports: types.SpecReports{
					S(types.NodeTypeBeforeSuite),
					S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed),
					S(types.SpecStatePassed, 3, FlakeAttempts(5)), S(types.SpecStatePassed, 4, FlakeAttempts(5)), //flakey
					S(types.SpecStatePassed, 3, MustPassRepeatedly(5)), //repeated, and passed
					S(types.SpecStateFailed, 3, MustPassRepeatedly(5), "repeater", F("failure", types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, cl3)),               //repeated, but failed
					S(types.SpecStateFailed, 4, MustPassRepeatedly(5), "another-repeater", F("failure-again", types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, cl3)), //repeated, but failed
					S(types.SpecStatePending), S(types.SpecStatePending),
					S(types.SpecStateSkipped), S(types.SpecStateSkipped), S(types.SpecStateSkipped),
					S(CTS("Describe A", "Context B"), "The Test", CLS(cl0, cl1), cl2, CLabels(Label("cat", "dog"), Label("dog", "fish")), Label("fish", "giraffe"),
						types.SpecStateFailed, 2,
						F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeJustBeforeEach, 1, cl4),
					),
					S(CTS("Describe A"), "The Test", CLS(cl0), cl1,
						types.SpecStatePanicked, 2,
						F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl1), types.NodeTypeIt, cl2),
					),
					S("The Test", cl0,
						types.SpecStateInterrupted, 2,
						F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, cl1),
					),
					S("The Test", cl0,
						types.SpecStateAborted, 2,
						F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, cl1),
					),
					S("The Test", cl0,
						types.SpecStateTimedout, 2,
						F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, cl1),
					),
					S(types.NodeTypeAfterSuite),
				},
			},
			"",
			"{{red}}{{bold}}Summarizing 7 Failures:{{/}}",
			"  {{red}}[FAIL]{{/}} {{red}}{{bold}}[It] repeater{{/}}",
			"  {{gray}}cl3.go:103{{/}}",
			"  {{red}}[FAIL]{{/}} {{red}}{{bold}}[It] another-repeater{{/}}",
			"  {{gray}}cl3.go:103{{/}}",
			"  {{red}}[FAIL]{{/}} {{/}}Describe A {{red}}{{bold}}Context B [JustBeforeEach] {{/}}The Test{{/}} {{coral}}[cat, dog, fish, giraffe]{{/}}",
			"  {{gray}}cl4.go:144{{/}}",
			"  {{magenta}}[PANICKED!]{{/}} {{/}}Describe A {{magenta}}{{bold}}[It] The Test{{/}}",
			"  {{gray}}cl2.go:80{{/}}",
			"  {{orange}}[INTERRUPTED]{{/}} {{orange}}{{bold}}[It] The Test{{/}}",
			"  {{gray}}cl1.go:37{{/}}",
			"  {{coral}}[ABORTED]{{/}} {{coral}}{{bold}}[It] The Test{{/}}",
			"  {{gray}}cl1.go:37{{/}}",
			"  {{orange}}[TIMEDOUT]{{/}} {{orange}}{{bold}}[It] The Test{{/}}",
			"  {{gray}}cl1.go:37{{/}}",
			"",
			"{{red}}{{bold}}Ran 13 of 14 Specs in 60.000 seconds{{/}}",
			"{{red}}{{bold}}FAIL!{{/}} -- {{green}}{{bold}}6 Passed{{/}} | {{red}}{{bold}}7 Failed{{/}} | {{light-yellow}}{{bold}}2 Flaked{{/}} | {{light-yellow}}{{bold}}2 Repeated{{/}} | {{yellow}}{{bold}}2 Pending{{/}} | {{cyan}}{{bold}}3 Skipped{{/}}",
			"",
		),
		Entry("the suite fails with failed suite setups",
			C(),
			types.Report{
				SuiteSucceeded: false,
				PreRunStats:    types.PreRunStats{TotalSpecs: 10, SpecsThatWillRun: 5},
				RunTime:        time.Minute,
				SpecReports: types.SpecReports{
					S(types.NodeTypeBeforeSuite, cl0, types.SpecStateFailed, 2,
						F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeBeforeSuite, 1, cl1),
					),
					S(types.NodeTypeAfterSuite, cl2, types.SpecStateFailed, 2,
						F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeAfterSuite, 1, cl3),
					),
					S(types.NodeTypeReportAfterSuite, "my report", cl1, types.SpecStateFailed, 2,
						F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl3), types.NodeTypeReportAfterSuite, 1, cl4),
					),
				},
			},
			"",
			"{{red}}{{bold}}Summarizing 3 Failures:{{/}}",
			"  {{red}}[FAIL]{{/}} {{red}}{{bold}}[BeforeSuite] {{/}}",
			"  {{gray}}"+cl1.String()+"{{/}}",
			"  {{red}}[FAIL]{{/}} {{red}}{{bold}}[AfterSuite] {{/}}",
			"  {{gray}}"+cl3.String()+"{{/}}",
			"  {{red}}[FAIL]{{/}} {{red}}{{bold}}[ReportAfterSuite] my report{{/}}",
			"  {{gray}}"+cl4.String()+"{{/}}",
			"",
			"{{red}}{{bold}}Ran 0 of 10 Specs in 60.000 seconds{{/}}",
			"{{red}}{{bold}}FAIL!{{/}} -- {{cyan}}{{bold}}A BeforeSuite node failed so all tests were skipped.{{/}}",
			"",
		),
		Entry("when the suite includes a special failure reason",
			C(),
			types.Report{
				SuiteSucceeded:             false,
				SpecialSuiteFailureReasons: []string{"Detected pending specs and --fail-on-pending is set"},
				SuiteConfig:                types.SuiteConfig{FailOnPending: true},
				PreRunStats:                types.PreRunStats{TotalSpecs: 5, SpecsThatWillRun: 3},
				RunTime:                    time.Minute,
				SpecReports: types.SpecReports{
					S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed),
					S(types.SpecStatePending), S(types.SpecStatePending),
				},
			},
			"",
			"{{red}}{{bold}}Ran 3 of 5 Specs in 60.000 seconds{{/}}",
			"{{red}}{{bold}}FAIL! - Detected pending specs and --fail-on-pending is set{{/}} -- {{green}}{{bold}}3 Passed{{/}} | {{red}}{{bold}}0 Failed{{/}} | {{yellow}}{{bold}}2 Pending{{/}} | {{cyan}}{{bold}}0 Skipped{{/}}",
			"",
		),
		Entry("when the suite includes multiple special failure reasons",
			C(),
			types.Report{
				SuiteSucceeded:             false,
				SpecialSuiteFailureReasons: []string{"Detected pending specs and --fail-on-pending is set", "Interrupted by Timeout"},
				SuiteConfig:                types.SuiteConfig{FailOnPending: true},
				PreRunStats:                types.PreRunStats{TotalSpecs: 5, SpecsThatWillRun: 3},
				RunTime:                    time.Minute,
				SpecReports: types.SpecReports{
					S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed),
					S(types.SpecStatePending), S(types.SpecStatePending),
				},
			},
			"",
			"{{red}}{{bold}}Ran 3 of 5 Specs in 60.000 seconds{{/}}",
			"{{red}}{{bold}}FAIL! - Detected pending specs and --fail-on-pending is set, Interrupted by Timeout{{/}}",
			"{{green}}{{bold}}3 Passed{{/}} | {{red}}{{bold}}0 Failed{{/}} | {{yellow}}{{bold}}2 Pending{{/}} | {{cyan}}{{bold}}0 Skipped{{/}}",
			"",
		),
	)

	DescribeTable("EmitProgressReport",
		func(conf types.ReporterConfig, report types.ProgressReport, expected ...any) {
			reporter := reporters.NewDefaultReporterUnderTest(conf, buf)
			reporter.EmitProgressReport(report)
			Expect(string(buf.Contents())).Should(MatchLines(expected...))
		},
		//just headers to start
		Entry("With a suite node",
			C(),
			PR("A Message", types.NodeTypeBeforeSuite),
			INDENTED_DELIMITER,
			"  A Message",
			"    In {{bold}}{{orange}}[BeforeSuite]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("With a top-level spec",
			C(),
			PR("A Message", types.NodeTypeIt, CurrentNodeText("A Top-Level It"), LeafNodeText("A Top-Level It")),
			INDENTED_DELIMITER,
			"  A Message",
			"    {{bold}}{{orange}}A Top-Level It{{/}} (Spec Runtime: 5s)",
			"      {{gray}}"+cl0.String()+"{{/}}",
			"      In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"        {{gray}}"+cl1.String()+"{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("With a spec in containers",
			C(),
			PR(types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), []string{"Container A", "Container B", "Container C"}),
			INDENTED_DELIMITER,
			"  {{/}}Container A {{gray}}Container B {{/}}Container C{{/}} {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("With no current node",
			C(),
			PR(LeafNodeText("My Spec"), []string{"Container A", "Container B", "Container C"}),
			INDENTED_DELIMITER,
			"  {{/}}Container A {{gray}}Container B {{/}}Container C{{/}} {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("With a current node that is not an It",
			C(),
			PR(LeafNodeText("My Spec"), []string{"Container A", "Container B", "Container C"}, types.NodeTypeBeforeEach),
			INDENTED_DELIMITER,
			"  {{/}}Container A {{gray}}Container B {{/}}Container C{{/}} {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[BeforeEach]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("With a current node that is not an It, but has text",
			C(),
			PR(types.NodeTypeReportAfterSuite, CurrentNodeText("My Report")),
			INDENTED_DELIMITER,
			"  In {{bold}}{{orange}}[ReportAfterSuite]{{/}} {{bold}}{{orange}}My Report{{/}} (Node Runtime: 3s)",
			"    {{gray}}"+cl1.String()+"{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("With a current step",
			C(),
			PR(types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), []string{"Container A", "Container B", "Container C"}, CurrentStepText("Reticulating Splines")),
			INDENTED_DELIMITER,
			"  {{/}}Container A {{gray}}Container B {{/}}Container C{{/}} {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			"      At {{bold}}{{orange}}[By Step] Reticulating Splines{{/}} (Step Runtime: 1s)",
			"        {{gray}}"+cl2.String()+"{{/}}",
			INDENTED_DELIMITER,
			""),
		//including GinkgoWriter output
		Entry("when there is GinkgoWriter output and the spec is not running verbosely",
			C(),
			PR(
				types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"),
				GW("gw-1\ngw-2\ngw-3\ngw-4\ngw-5\ngw-6\ngw-7\ngw-8\ngw-9\ngw-10\ngw-11\ngw-12\n"),
			),
			INDENTED_DELIMITER,
			"  {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			"",
			"    {{gray}}Begin Captured GinkgoWriter Output >>{{/}}",
			"      {{gray}}...{{/}}",
			"      gw-3",
			"      gw-4",
			"      gw-5",
			"      gw-6",
			"      gw-7",
			"      gw-8",
			"      gw-9",
			"      gw-10",
			"      gw-11",
			"      gw-12",
			"    {{gray}}<< End Captured GinkgoWriter Output{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("when there is fewer than 10 lines of GinkgoWriter output",
			C(),
			PR(
				types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"),
				GW("gw-1\ngw-2\ngw-3\ngw-4\ngw-5\ngw-6\ngw-7\ngw-8\ngw-9\n"),
			),
			INDENTED_DELIMITER,
			"  {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			"",
			"    {{gray}}Begin Captured GinkgoWriter Output >>{{/}}",
			"      gw-1",
			"      gw-2",
			"      gw-3",
			"      gw-4",
			"      gw-5",
			"      gw-6",
			"      gw-7",
			"      gw-8",
			"      gw-9",
			"    {{gray}}<< End Captured GinkgoWriter Output{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("when running in verbose mode and not in parallel",
			C(Verbose),
			PR(
				types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"),
				GW("gw-1\n"),
			),
			INDENTED_DELIMITER,
			"  {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("when running in verbose mode and in parallel",
			C(),
			PR(
				true, types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"),
				GW("gw-1\n"),
			),
			INDENTED_DELIMITER,
			"  {{coral}}Progress Report for Ginkgo Process #{{bold}}1{{/}}",
			"  {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			"",
			"    {{gray}}Begin Captured GinkgoWriter Output >>{{/}}",
			"      gw-1",
			"    {{gray}}<< End Captured GinkgoWriter Output{{/}}",
			INDENTED_DELIMITER,
			""),
		//various goroutines
		Entry("with a spec goroutine",
			C(),
			PR(
				types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"),
				G(true, "sleeping",
					Fn("F1()", "fileA", 15),
					Fn("F2()", "fileB", 11, true),
					Fn("F3()", "fileC", 9),
				),
			),
			INDENTED_DELIMITER,
			"  {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			"",
			"    {{bold}}{{underline}}Spec Goroutine{{/}}",
			"    {{orange}}goroutine 17 [sleeping]{{/}}",
			"      {{gray}}F1(){{/}}",
			"        {{gray}}fileA:15{{/}}",
			"    {{orange}}{{bold}}> F2(){{/}}",
			"        {{orange}}{{bold}}fileB:11{{/}}",
			"      {{gray}}F3(){{/}}",
			"        {{gray}}fileC:9{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("with highlighted goroutines",
			C(),
			PR(
				types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"),
				G(false, "sleeping",
					Fn("F1()", "fileA", 15),
					Fn("F2()", "fileB", 11, true),
					Fn("F3()", "fileC", 9),
				),
				G(false, "sleeping as well",
					Fn("F4()", "fileB", 12, true),
					Fn("F5()", "fileC", 30),
					Fn("F6()", "fileD", 2),
				),
			),
			INDENTED_DELIMITER,
			"  {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}cl0.go:12{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}cl1.go:37{{/}}",
			"",
			"    {{bold}}{{underline}}Goroutines of Interest{{/}}",
			"    {{orange}}goroutine 17 [sleeping]{{/}}",
			"      {{gray}}F1(){{/}}",
			"        {{gray}}fileA:15{{/}}",
			"    {{orange}}{{bold}}> F2(){{/}}",
			"        {{orange}}{{bold}}fileB:11{{/}}",
			"      {{gray}}F3(){{/}}",
			"        {{gray}}fileC:9{{/}}",
			"",
			"    {{orange}}goroutine 17 [sleeping as well]{{/}}",
			"    {{orange}}{{bold}}> F4(){{/}}",
			"        {{orange}}{{bold}}fileB:12{{/}}",
			"      {{gray}}F5(){{/}}",
			"        {{gray}}fileC:30{{/}}",
			"      {{gray}}F6(){{/}}",
			"        {{gray}}fileD:2{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("with other goroutines",
			C(),
			PR(
				types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"),
				G(false, "sleeping",
					Fn("F1()", "fileA", 15),
					Fn("F2()", "fileB", 11),
					Fn("F3()", "fileC", 9),
				),
			),
			INDENTED_DELIMITER,
			"  {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			"",
			"    {{gray}}{{bold}}{{underline}}Other Goroutines{{/}}",
			"    {{gray}}goroutine 17 [sleeping]{{/}}",
			"      {{gray}}F1(){{/}}",
			"        {{gray}}fileA:15{{/}}",
			"      {{gray}}F2(){{/}}",
			"        {{gray}}fileB:11{{/}}",
			"      {{gray}}F3(){{/}}",
			"        {{gray}}fileC:9{{/}}",
			INDENTED_DELIMITER,
			""),
		//fetching source code
		Entry("when source code is found",
			C(),
			PR(
				types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"),
				G(true, "sleeping",
					Fn("F1()", "fileA", 15),
					Fn(
						"F2()", "fileB", 21, true, 2,
						"source line 1",
						"source line 2",
						"source line 3 (highlight!)",
						"source line 4",
						"source line 5",
					),
					Fn("F3()", "fileC", 9),
				),
			),
			INDENTED_DELIMITER,
			"  {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			"",
			"    {{bold}}{{underline}}Spec Goroutine{{/}}",
			"    {{orange}}goroutine 17 [sleeping]{{/}}",
			"      {{gray}}F1(){{/}}",
			"        {{gray}}fileA:15{{/}}",
			"    {{orange}}{{bold}}> F2(){{/}}",
			"        {{orange}}{{bold}}fileB:21{{/}}",
			"          | source line 1",
			"          | source line 2",
			"          {{bold}}{{orange}}> source line 3 (highlight!){{/}}",
			"          | source line 4",
			"          | source line 5",
			"      {{gray}}F3(){{/}}",
			"        {{gray}}fileC:9{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("correcting source code indentation",
			C(),
			PR(
				types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"),
				G(true, "sleeping",
					Fn("F1()", "fileA", 15),
					Fn(
						"F2()", "fileB", 26, true, 1,
						"\t\t\thello",
						"\t\t\t\tthere",
						"",
						"\t\t\tit",
						"\t\tworks",
					),
					Fn("F3()", "fileC", 9),
				),
			),
			INDENTED_DELIMITER,
			"  {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			"",
			"    {{bold}}{{underline}}Spec Goroutine{{/}}",
			"    {{orange}}goroutine 17 [sleeping]{{/}}",
			"      {{gray}}F1(){{/}}",
			"        {{gray}}fileA:15{{/}}",
			"    {{orange}}{{bold}}> F2(){{/}}",
			"        {{orange}}{{bold}}fileB:26{{/}}",
			"          | \thello",
			"          {{bold}}{{orange}}> \t\tthere{{/}}",
			"          | ",
			"          | \tit",
			"          | works",
			"      {{gray}}F3(){{/}}",
			"        {{gray}}fileC:9{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("random edge case where source code is empty (it doesn't explode)",
			C(),
			PR(
				types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"),
				G(true, "sleeping",
					Fn("F1()", "fileA", 15),
					Fn(
						"F2()", "fileB", 26, true, 1,
						"",
						"",
						"",
						"",
						"",
					),
					Fn("F3()", "fileC", 9),
				),
			),
			INDENTED_DELIMITER,
			"  {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			"",
			"    {{bold}}{{underline}}Spec Goroutine{{/}}",
			"    {{orange}}goroutine 17 [sleeping]{{/}}",
			"      {{gray}}F1(){{/}}",
			"        {{gray}}fileA:15{{/}}",
			"    {{orange}}{{bold}}> F2(){{/}}",
			"        {{orange}}{{bold}}fileB:26{{/}}",
			"          | ",
			"          {{bold}}{{orange}}> {{/}}",
			"          | ",
			"          | ",
			"          | ",
			"      {{gray}}F3(){{/}}",
			"        {{gray}}fileC:9{{/}}",
			INDENTED_DELIMITER,
			""),
		// including additional reports

		Entry("with one additional report",
			C(),
			PR(
				types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"),
				G(true, "sleeping",
					Fn("F1()", "fileA", 15),
					Fn("F2()", "fileB", 11, true),
					Fn("F3()", "fileC", 9),
				),
				G(false, "sleeping",
					Fn("F1()", "fileA", 15),
					Fn("F2()", "fileB", 11, true),
					Fn("F3()", "fileC", 9),
				),
				AdditionalReports{"{{blue}}Report 1{{/}}"},
			),
			INDENTED_DELIMITER,
			"  {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			"",
			"    {{bold}}{{underline}}Spec Goroutine{{/}}",
			"    {{orange}}goroutine 17 [sleeping]{{/}}",
			"      {{gray}}F1(){{/}}",
			"        {{gray}}fileA:15{{/}}",
			"    {{orange}}{{bold}}> F2(){{/}}",
			"        {{orange}}{{bold}}fileB:11{{/}}",
			"      {{gray}}F3(){{/}}",
			"        {{gray}}fileC:9{{/}}",
			"",
			"    {{gray}}Begin Additional Progress Reports >>{{/}}",
			"      {{blue}}Report 1{{/}}",
			"    {{gray}}<< End Additional Progress Reports{{/}}",
			"",
			"    {{bold}}{{underline}}Goroutines of Interest{{/}}",
			"    {{orange}}goroutine 17 [sleeping]{{/}}",
			"      {{gray}}F1(){{/}}",
			"        {{gray}}fileA:15{{/}}",
			"    {{orange}}{{bold}}> F2(){{/}}",
			"        {{orange}}{{bold}}fileB:11{{/}}",
			"      {{gray}}F3(){{/}}",
			"        {{gray}}fileC:9{{/}}",
			INDENTED_DELIMITER,
			""),
		Entry("with multiple additional reports",
			C(),
			PR(
				types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"),
				G(true, "sleeping",
					Fn("F1()", "fileA", 15),
					Fn("F2()", "fileB", 11, true),
					Fn("F3()", "fileC", 9),
				),
				G(false, "sleeping",
					Fn("F1()", "fileA", 15),
					Fn("F2()", "fileB", 11, true),
					Fn("F3()", "fileC", 9),
				),
				AdditionalReports{"{{blue}}Report 1{{/}}", "{{green}}Report 2{{/}}"},
			),
			INDENTED_DELIMITER,
			"  {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"    {{gray}}"+cl0.String()+"{{/}}",
			"    In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"      {{gray}}"+cl1.String()+"{{/}}",
			"",
			"    {{bold}}{{underline}}Spec Goroutine{{/}}",
			"    {{orange}}goroutine 17 [sleeping]{{/}}",
			"      {{gray}}F1(){{/}}",
			"        {{gray}}fileA:15{{/}}",
			"    {{orange}}{{bold}}> F2(){{/}}",
			"        {{orange}}{{bold}}fileB:11{{/}}",
			"      {{gray}}F3(){{/}}",
			"        {{gray}}fileC:9{{/}}",
			"",
			"    {{gray}}Begin Additional Progress Reports >>{{/}}",
			"      {{blue}}Report 1{{/}}",
			"      {{gray}}----------{{/}}",
			"      {{green}}Report 2{{/}}",
			"    {{gray}}<< End Additional Progress Reports{{/}}",
			"",
			"    {{bold}}{{underline}}Goroutines of Interest{{/}}",
			"    {{orange}}goroutine 17 [sleeping]{{/}}",
			"      {{gray}}F1(){{/}}",
			"        {{gray}}fileA:15{{/}}",
			"    {{orange}}{{bold}}> F2(){{/}}",
			"        {{orange}}{{bold}}fileB:11{{/}}",
			"      {{gray}}F3(){{/}}",
			"        {{gray}}fileC:9{{/}}",
			INDENTED_DELIMITER,
			""),
		// when running in parallel

		Entry("when running in parallel",
			C(),
			PR(
				"A Message",
				true, 3,
				types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"),
			),
			INDENTED_DELIMITER,
			"  {{coral}}Progress Report for Ginkgo Process #{{bold}}3{{/}}",
			"  A Message",
			"    {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)",
			"      {{gray}}cl0.go:12{{/}}",
			"      In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"        {{gray}}cl1.go:37{{/}}",
			INDENTED_DELIMITER,
			""),
	)

	DescribeTable("EmitFailure",
		func(conf types.ReporterConfig, af types.AdditionalFailure, expected ...any) {
			reporter := reporters.NewDefaultReporterUnderTest(conf, buf)
			reporter.EmitFailure(af.State, af.Failure)
			Expect(string(buf.Contents())).Should(MatchLines(expected...))
		},

		// silent when running succinctly or normal
		Entry("emits nothing when running with succinct verbosity",
			C(Succinct),
			AF(types.SpecStateFailed, "message"),
		),
		Entry("emits nothing when running in normal verbosity",
			C(),
			AF(types.SpecStateFailed, "message"),
		),
		// one-line summary when running verbosely
		Entry("emits a one line summary when running in verbose mode",
			C(Verbose),
			AF(types.SpecStateFailed, "message", types.NodeTypeIt, cl0),
			spr("  {{red}}[FAILED]{{/}} in [It] - cl0.go:12 {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
		),
		Entry("is cyan when skipped",
			C(Verbose),
			AF(types.SpecStateSkipped, "message", types.NodeTypeIt, cl0),
			spr("  {{cyan}}[SKIPPED]{{/}} in [It] - cl0.go:12 {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
		),
		Entry("is orange when timedout",
			C(Verbose),
			AF(types.SpecStateTimedout, "message", types.NodeTypeIt, cl0),
			spr("  {{orange}}[TIMEDOUT]{{/}} in [It] - cl0.go:12 {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
		),
		Entry("is orange when interrupted",
			C(Verbose),
			AF(types.SpecStateInterrupted, "message", types.NodeTypeIt, cl0),
			spr("  {{orange}}[INTERRUPTED]{{/}} in [It] - cl0.go:12 {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
		),
		Entry("is coral when aborted",
			C(Verbose),
			AF(types.SpecStateAborted, "message", types.NodeTypeIt, cl0),
			spr("  {{coral}}[ABORTED]{{/}} in [It] - cl0.go:12 {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
		),
		// now, when running in VeryVerbose mode - all the things emerge... (and this is what also appears in summaries at the end of tests)
		Entry("emits a full summary when running in -vv",
			C(VeryVerbose),
			AF(types.SpecStateFailed, "MY FAILURE:\nFailure Details", types.NodeTypeIt, cl0),
			"  {{red}}[FAILED] MY FAILURE:",
			"  Failure Details{{/}}",
			spr("  {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl0.go:12{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
		),
		Entry("includes the trace when FullTrace is enabled",
			C(VeryVerbose|FullTrace),
			AF(types.SpecStateFailed, "MY FAILURE:\nFailure Details", types.NodeTypeIt, cl0),
			"  {{red}}[FAILED] MY FAILURE:",
			"  Failure Details{{/}}",
			spr("  {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl0.go:12{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
			"  {{red}}Full Stack Trace{{/}}",
			"    full-trace",
			"    cl-0",
			"",
		),
		Entry("includes the forwarded panic and full-trace when a forwarded panic is provided",
			C(VeryVerbose),
			AF(types.SpecStatePanicked, "MY FAILURE:\nFailure Details", types.NodeTypeIt, cl0, ForwardedPanic("the panic!")),
			"  {{magenta}}[PANICKED] MY FAILURE:",
			"  Failure Details{{/}}",
			spr("  {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl0.go:12{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
			"  {{magenta}}the panic!{{/}}",
			"",
			"  {{magenta}}Full Stack Trace{{/}}",
			"    full-trace",
			"    cl-0",
			"",
		),
		Entry("includes a progress report and an additional failure if any are attached",
			C(VeryVerbose),
			AF(types.SpecStateTimedout, "A node timeout occurred:\nTimeout details", types.NodeTypeIt, cl0,
				PR("A Progress Report Message", types.NodeTypeIt, CurrentNodeText("A Top-Level It"), LeafNodeText("A Top-Level It")),
				AF(types.SpecStateFailed, "An additional failure", types.NodeTypeIt, cl1)),
			"  {{orange}}[TIMEDOUT] A node timeout occurred:",
			"  Timeout details{{/}}",
			spr("  {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl0.go:12{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
			"  A Progress Report Message",
			"    {{bold}}{{orange}}A Top-Level It{{/}} (Spec Runtime: 5s)",
			"      {{gray}}cl0.go:12{{/}}",
			"      In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)",
			"        {{gray}}cl1.go:37{{/}}",
			"",
			"  {{red}}[FAILED] An additional failure{{/}}",
			spr("  {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
		),
	)

	DescribeTable("EmitReportentry",
		func(conf types.ReporterConfig, reportEntry types.ReportEntry, expected ...any) {
			reporter := reporters.NewDefaultReporterUnderTest(conf, buf)
			reporter.EmitReportEntry(reportEntry)
			Expect(string(buf.Contents())).Should(MatchLines(expected...))
		},

		// silent when running succinctly or normal
		Entry("emits nothing when running with succinct verbosity",
			C(Succinct),
			RE("my report", cl0),
		),
		Entry("emits nothing when running with normal verbosity",
			C(),
			RE("my report", cl0),
		),
		Entry("emits nothing if hte report has VisiblityNever, regardless of verbosity level",
			C(Verbose),
			RE("my report", cl0, types.ReportEntryVisibilityNever),
		),
		//emitting reports with no StringRepresentation()
		Entry("emits the report",
			C(Verbose),
			RE("my report", cl0),
			spr("  {{bold}}my report{{gray}} - cl0.go:12 @ %s{{/}}", FORMATTED_TIME),
			"",
		),
		//emitting reports with a StringRepresentation()
		Entry("emits the report along with it's string representation",
			C(Verbose),
			RE("my report", cl0, 3),
			spr("  {{bold}}my report{{gray}} - cl0.go:12 @ %s{{/}}", FORMATTED_TIME),
			"    3",
			"",
		),
		Entry("emits the report along with it's string representation (and indents it correctly)",
			C(Verbose),
			RE("my report", cl0, "{{yellow}}My awesome report{{/}}\n{{coral}}Is beautiful{{/}}"),
			spr("  {{bold}}my report{{gray}} - cl0.go:12 @ %s{{/}}", FORMATTED_TIME),
			"    {{yellow}}My awesome report{{/}}",
			"    {{coral}}Is beautiful{{/}}",
			"",
		),
		//correctly handling reports that have format string components
		Entry("emits the report without running it through sprintf",
			C(Verbose),
			RE("my %f report", cl0, "{{green}}my report http://example.com/?q=%d%3%%{{/}}", cl0),
			spr("  {{bold}}my %%f report{{gray}} - cl0.go:12 @ %s{{/}}", FORMATTED_TIME),
			"    {{green}}my report http://example.com/?q=%d%3%%{{/}}",
			"",
		),
	)

	DescribeTable("EmitSpecEvent",
		func(conf types.ReporterConfig, specEvent types.SpecEvent, expected ...any) {
			reporter := reporters.NewDefaultReporterUnderTest(conf, buf)
			reporter.EmitSpecEvent(specEvent)
			Expect(string(buf.Contents())).Should(MatchLines(expected...))
		},

		// silent when running succinctly or normal
		Entry("emits nothing when running with succinct verbosity",
			C(Succinct),
			SE(types.SpecEventByStart),
		),
		Entry("emits nothing when running with normal verbosity",
			C(),
			SE(types.SpecEventByStart),
		),
		Entry("emits nothing when running with normal verbosity and ShowNodeEvents",
			C(ShowNodeEvents),
			SE(types.SpecEventByStart),
		),
		Entry("emits nothing when running with normal verbosity and ShowNodeEvents",
			C(ShowNodeEvents),
			SE(types.SpecEventByEnd),
		),
		Entry("emits nothing when running with -v and the event is not visible at that verbosity level",
			C(Verbose),
			SE(types.SpecEventByEnd),
		),
		Entry("emits the event when running with -v and ShowNodeEvents",
			C(Verbose|ShowNodeEvents),
			SE(types.SpecEventByEnd, "hello world", 90*time.Millisecond),
			spr("  {{bold}}END STEP:{{/}} hello world {{gray}}@ %s (90ms){{/}}", FORMATTED_TIME),
			"",
		),

		// when running in verbose mode
		Entry("emits By start events",
			C(Verbose),
			SE(types.SpecEventByStart, "hello world", cl0),
			spr("  {{bold}}STEP:{{/}} hello world {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
		),
		Entry("does not emit By end events",
			C(Verbose),
			SE(types.SpecEventByEnd, "hello world", cl0, 89734*time.Microsecond),
		),
		Entry("does not emit node start events",
			C(Verbose),
			SE(types.SpecEventNodeStart, "my node", types.NodeTypeIt, cl0),
		),
		Entry("does not emit node end events",
			C(Verbose),
			SE(types.SpecEventNodeEnd, "my node", types.NodeTypeIt, cl0, 89734*time.Microsecond),
		),
		Entry("emits spec repeats",
			C(Verbose),
			SE(types.SpecEventSpecRepeat, 3),
			"",
			spr("  {{bold}}Attempt #3 {{green}}Passed{{/}}{{bold}}.  Repeating ↺{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
			"",
		),
		Entry("emits spec retries",
			C(Verbose),
			SE(types.SpecEventSpecRetry, 7),
			"",
			spr("  {{bold}}Attempt #7 {{red}}Failed{{/}}{{bold}}.  Retrying ↺{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
			"",
		),
		// when running in very-verbose mode
		Entry("emits By start events",
			C(VeryVerbose),
			SE(types.SpecEventByStart, "hello world", cl0),
			spr("  {{bold}}STEP:{{/}} hello world {{gray}}- cl0.go:12 @ %s{{/}}", FORMATTED_TIME),
			"",
		),
		Entry("emits By end events",
			C(VeryVerbose),
			SE(types.SpecEventByEnd, "hello world", cl0, 89734*time.Microsecond),
			spr("  {{bold}}END STEP:{{/}} hello world {{gray}}- cl0.go:12 @ %s (90ms){{/}}", FORMATTED_TIME),
			"",
		),
		Entry("emits node start events",
			C(VeryVerbose),
			SE(types.SpecEventNodeStart, "my node", types.NodeTypeIt, cl0),
			spr("  > Enter {{bold}}[It]{{/}} my node {{gray}}- cl0.go:12 @ %s{{/}}", FORMATTED_TIME),
			"",
		),
		Entry("emits node end events",
			C(VeryVerbose),
			SE(types.SpecEventNodeEnd, "my node", types.NodeTypeIt, cl0, 89734*time.Microsecond),
			spr("  < Exit {{bold}}[It]{{/}} my node {{gray}}- cl0.go:12 @ %s (90ms){{/}}", FORMATTED_TIME),
			"",
		),
		Entry("emits spec repeats",
			C(VeryVerbose),
			SE(types.SpecEventSpecRepeat, 3),
			"",
			spr("  {{bold}}Attempt #3 {{green}}Passed{{/}}{{bold}}.  Repeating ↺{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
			"",
		),
		Entry("emits spec retries",
			C(VeryVerbose),
			SE(types.SpecEventSpecRetry, 7),
			"",
			spr("  {{bold}}Attempt #7 {{red}}Failed{{/}}{{bold}}.  Retrying ↺{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME),
			"",
			"",
		),
	)
})
