package codeview import ( "strings" "testing" "unicode" ) // squash is markup with every whitespace character taken out. Format // may add, move and drop whitespace and must touch nothing else, so the // two sides of a formatting agree once squashed. Whether the whitespace // it moved could show is the browser's question, not this one's, and // no drive here asks it yet (see the note above flexAttrs). func squash(s string) string { return strings.Map(func(r rune) rune { if unicode.IsSpace(r) { return -1 } return r }, s) } var formatCases = []string{ `
`, `
Release notes2 August Draft
`, `700/500`, "

After

", `
Filter
A
`, `
Back
`, `
Cancel
`, } func TestFormatChangesOnlyWhitespaceAndIsIdempotent(t *testing.T) { for _, in := range formatCases { out := Format(in) if squash(out) != squash(in) { t.Errorf("Format changed more than whitespace:\n in: %s\nout: %s", in, out) } if Format(out) != out { t.Errorf("Format is not idempotent on:\n%s\nsecond pass:\n%s", out, Format(out)) } } } // The space between two inline elements, and between text and an // inline element, is rendered wherever the container is not one the // formatter knows lays its children out as flex or grid, so Format // keeps it exactly. An unknown container stays inline. func TestFormatKeepsTheSpaceBetweenInlineElements(t *testing.T) { for in, want := range map[string]string{ `
A B
`: "
\n A B\n
", `

Hello world again

`: "

Hello world again

", `x y`: `x y`, "
  • X\nY
  • ": "
  • \n X\nY\n
  • ", `

    Total $1 due

    `: `

    Total $1 due

    `, ``: ``, `
    ab
    `: "
    \n ab\n
    ", } { if got := Format(in); got != want { t.Errorf("Format(%q)\n got %q\nwant %q", in, got, want) } } } func TestFormatBreaksAtBlockTagsAndIndents(t *testing.T) { got := Format(`
    `) want := "
    \n
    \n
    \n \n
    \n
    \n
    " if got != want { t.Errorf("got\n%s\nwant\n%s", got, want) } } // Inside a flex or grid container whitespace between children renders // as nothing, so every child gets a line: the busy button's spinner and // its label, and the two buttons beside each other. A button holding // only its label keeps it inline. func TestFormatPutsEachChildOfAFlexContainerOnALine(t *testing.T) { got := Format(formatCases[5]) want := "
    \n" + " \n" + " Back\n" + "
    " if got != want { t.Errorf("got\n%s\nwant\n%s", got, want) } got = Format(formatCases[2]) want = "\n \n 700/500\n" if got != want { t.Errorf("got\n%s\nwant\n%s", got, want) } } // A hidden input renders nothing, so the space beside it shows only if // something visible shares its line. At the head of a form, before the // first thing drawn, each one gets a line. func TestFormatPutsSilentElementsOnLinesOfTheirOwn(t *testing.T) { got := Format(formatCases[6]) want := "
    \n" + " \n" + " \n" + " Cancel\n" + " \n" + "
    " if got != want { t.Errorf("got\n%s\nwant\n%s", got, want) } // In a sentence, a hidden input between two words keeps the line it // is on: a break there would be a space between them. in := `

    onetwo

    ` if got := Format(in); got != in { t.Errorf("a hidden input inside running text was broken out:\n%s", got) } } // An element with nothing in it stays with nothing in it: :empty is a // selector tokens.css uses, and a line break would stop it matching. func TestFormatLeavesAnEmptyElementEmpty(t *testing.T) { for _, in := range []string{ `
    `, ``, `
    Order
    `, } { out := Format(in) for _, empty := range []string{``, ``, ``} { if strings.Contains(in, empty) && !strings.Contains(out, empty) { t.Errorf("an empty element got content:\n%s", out) } } } } // A
     or ",
    		``,
    	} {
    		in := "
    " + raw + "
    " out := Format(in) if !strings.Contains(out, raw) { t.Errorf("a raw zone changed:\n in: %s\nout: %s", in, out) } } in := `

    t

    ` out := Format(in) for _, v := range []string{`data-tip="a > b"`, `title='x
    y'`} { if !strings.Contains(out, v) { t.Errorf("an attribute value changed: %s", out) } } if strings.Count(out, "\n") != 2 { t.Errorf("a '>' inside a quoted value was taken for the end of a tag:\n%s", out) } } // A select sits in its line like a word: the space after it shows, so // the formatter may lay out its option list, where nothing between two // options renders, but must not break around the select itself. func TestFormatKeepsASelectInItsLine(t *testing.T) { in := `

    Pick B

    ` if got := Format(in); !strings.Contains(got, "Pick B") { t.Errorf("a select in running text was broken out:\n%s", got) } got := Format(`
    `) want := "
    \n \n
    " if got != want { t.Errorf("got\n%s\nwant\n%s", got, want) } } // Under white-space: pre and its kin a line break is a line break and // an indent is spaces, whatever the container. An element that says so // in its style attribute is left byte for byte, its children included, // as a
     is.
    func TestFormatLeavesWhitespacePreservingStylesAlone(t *testing.T) {
    	for _, style := range []string{"white-space:pre", "white-space: pre-wrap", "white-space:pre-line", "white-space: break-spaces", "white-space-collapse: preserve"} {
    		button := ``
    		got := Format(`
    ` + button + `Back
    `) if !strings.Contains(got, button) { t.Errorf("%s: the button's content was reformatted:\n%s", style, got) } } // nowrap collapses whitespace like normal, so it changes nothing. got := Format(`
    `) if !strings.Contains(got, "\n Pay\n") { t.Errorf("white-space: nowrap stopped the formatter:\n%s", got) } } // HTML allows whitespace either side of an attribute's =, and the // formatter's reading of a tag has to see the attribute the browser // sees: style = "white-space:pre" preserves whitespace exactly as // style="white-space:pre" does. func TestFormatReadsAnAttributeWithSpacesAroundItsEquals(t *testing.T) { button := "" got := Format(`
    ` + button + `Back
    `) if !strings.Contains(got, button) { t.Errorf("a style written with spaces around = was not seen:\n%s", got) } attrs := attrsOf("") for name, want := range map[string]string{"type": "hidden", "name": "n", "value": "v", "checked": ""} { if got, ok := attrs[name]; !ok || got != want { t.Errorf("attrsOf: %s = %q (present %v), want %q", name, got, ok, want) } } if len(attrs) != 4 { t.Errorf("attrsOf read %d attributes, want 4: %v", len(attrs), attrs) } }