| 🛟 Updated. 28d5985 k33g 4h ago | 1 | package editor |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | |
| 7 | "github.com/gdamore/tcell/v2" |
| 8 | |
| 9 | "codeberg.org/turbo-editors/turbo-core/theme" |
| 10 | ) |
| 11 | |
| 12 | // markAt returns the character in the gutter's separator column on a row, and |
| 13 | // the style it is drawn in. |
| 14 | func markAt(t *testing.T, view *View, screen tcell.SimulationScreen, row int) (rune, tcell.Style) { |
| 15 | t.Helper() |
| 16 | |
| 17 | cells, width, _ := screen.GetContents() |
| 18 | cell := cells[row*width+view.gutterWidth()-1] |
| 19 | if len(cell.Runes) == 0 { |
| 20 | return ' ', cell.Style |
| 21 | } |
| 22 | return cell.Runes[0], cell.Style |
| 23 | } |
| 24 | |
| 25 | func TestAMarkIsDrawnBesideItsLine(t *testing.T) { |
| 26 | view, screen, p := newTestView(t, "one\ntwo\nthree\n", 40, 8) |
| 27 | view.SetMarks(map[int]Severity{1: MarkError}) |
| 28 | |
| 29 | draw(t, view, screen, p) |
| 30 | |
| 31 | if got, _ := markAt(t, view, screen, 1); got != '\u00d7' { |
| 32 | t.Errorf("the gutter shows %q beside line 2, want the error mark", got) |
| 33 | } |
| 34 | if got, _ := markAt(t, view, screen, 0); got != ' ' { |
| 35 | t.Errorf("line 1 shows %q and has nothing wrong with it", got) |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | func TestAMarkDoesNotMoveTheText(t *testing.T) { |
| 40 | // The whole reason the separator column is used: the text's left edge, the |
| 41 | // cursor's screen column and the column a click lands on must all stay put. |
| 42 | view, screen, p := newTestView(t, "package main\n", 40, 8) |
| 43 | before := view.gutterWidth() |
| 44 | |
| 45 | view.SetMarks(map[int]Severity{0: MarkError}) |
| 46 | lines := draw(t, view, screen, p) |
| 47 | |
| 48 | if got := view.gutterWidth(); got != before { |
| 49 | t.Errorf("the gutter went from %d to %d columns", before, got) |
| 50 | } |
| 51 | if got := []rune(lines[0])[before]; got != 'p' { |
| 52 | t.Errorf("the text starts with %q at column %d, want it where it was", got, before) |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | func TestEachSeverityHasItsOwnGlyphAndColour(t *testing.T) { |
| 57 | // The three theme keys exist in every shipped theme and were drawn nowhere |
| 58 | // until now. A severity painted in the wrong one is invisible as a bug. |
| 59 | th, err := theme.Load("turbo-classic", "") |
| 60 | if err != nil { |
| 61 | t.Fatalf("Load() error = %v", err) |
| 62 | } |
| 63 | |
| 64 | seen := map[rune]Severity{} |
| 65 | for _, severity := range []Severity{MarkError, MarkWarning, MarkInformation, MarkHint} { |
| 66 | glyph, hasGlyph := markGlyphs[severity] |
| 67 | key, hasStyle := markStyles[severity] |
| 68 | if !hasGlyph || !hasStyle { |
| 69 | t.Fatalf("severity %v has glyph=%v style=%v", severity, hasGlyph, hasStyle) |
| 70 | } |
| 71 | if other, clash := seen[glyph]; clash && severity != MarkHint { |
| 72 | t.Errorf("severities %v and %v draw the same glyph %q", other, severity, glyph) |
| 73 | } |
| 74 | seen[glyph] = severity |
| 75 | |
| 76 | if fg, _, _ := th.Style(key).Decompose(); fg == 0 { |
| 77 | t.Errorf("severity %v resolves to no colour through %q", severity, key) |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | if markStyles[MarkError] == markStyles[MarkWarning] { |
| 82 | t.Error("an error and a warning are drawn in the same colour") |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | func TestAMarkIsDrawnInItsSeveritysColour(t *testing.T) { |
| 87 | view, screen, p := newTestView(t, "one\ntwo\n", 40, 8) |
| 88 | view.SetMarks(map[int]Severity{0: MarkError, 1: MarkWarning}) |
| 89 | |
| 90 | draw(t, view, screen, p) |
| 91 | |
| 92 | _, errorStyle := markAt(t, view, screen, 0) |
| 93 | _, warningStyle := markAt(t, view, screen, 1) |
| 94 | if errorStyle == warningStyle { |
| 95 | t.Error("the error and the warning were drawn in the same style") |
| 96 | } |
| 97 | |
| 98 | th := testTheme(t) |
| 99 | wantFg, _, _ := th.Style(theme.KeyDiagnosticError).Decompose() |
| 100 | if gotFg, _, _ := errorStyle.Decompose(); gotFg != wantFg { |
| 101 | t.Errorf("the error mark is %v, want the theme's diagnostic.error %v", gotFg, wantFg) |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | func TestHidingTheLineNumbersHidesTheMarks(t *testing.T) { |
| 106 | // The consequence of using the separator column, stated where somebody |
| 107 | // changing it will read it: there is no separator column without numbers. |
| 108 | view, screen, p := newTestView(t, "one\n", 40, 8) |
| 109 | view.SetMarks(map[int]Severity{0: MarkError}) |
| 110 | view.SetLineNumbers(false) |
| 111 | |
| 112 | lines := draw(t, view, screen, p) |
| 113 | |
| 114 | if strings.ContainsRune(strings.Join(lines, ""), '\u00d7') { |
| 115 | t.Errorf("a mark was drawn with the gutter hidden:\n%s", strings.Join(lines, "\n")) |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | func TestClearingTheMarksClearsTheGutter(t *testing.T) { |
| 120 | view, screen, p := newTestView(t, "one\n", 40, 8) |
| 121 | view.SetMarks(map[int]Severity{0: MarkError}) |
| 122 | draw(t, view, screen, p) |
| 123 | |
| 124 | view.SetMarks(nil) |
| 125 | draw(t, view, screen, p) |
| 126 | |
| 127 | if got, _ := markAt(t, view, screen, 0); got != ' ' { |
| 128 | t.Errorf("the gutter still shows %q after the marks were cleared", got) |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | func TestAZeroSeverityIsNotAMark(t *testing.T) { |
| 133 | // MarkNone is the zero value, so a map built by hand can hold one without |
| 134 | // meaning to say anything. |
| 135 | view, screen, p := newTestView(t, "one\n", 40, 8) |
| 136 | view.SetMarks(map[int]Severity{0: MarkNone}) |
| 137 | |
| 138 | // Asserted on markOf rather than on the screen: a severity with no glyph |
| 139 | // draws NUL, which a simulated screen shows as a blank — so the drawing |
| 140 | // looks right whether or not the guard is there. |
| 141 | if severity, marked := view.markOf(0); marked { |
| 142 | t.Errorf("markOf(0) = %v, true; a line marked with nothing has no mark", severity) |
| 143 | } |
| 144 | |
| 145 | draw(t, view, screen, p) |
| 146 | if got, _ := markAt(t, view, screen, 0); got != ' ' { |
| 147 | t.Errorf("the gutter shows %q for a line marked with nothing", got) |
| 148 | } |
| 149 | } |