1package sheet23import (4 "reflect"5 "testing"6)78// column returns the inputs of rows from..to of column col.9func column(s *Sheet, col string, from, to int) []string {10 var out []string11 for r := from; r <= to; r++ {12 c := s.Cell(at(col + itoa(r)))13 if c == nil {14 out = append(out, "")15 continue16 }17 out = append(out, c.Input)18 }19 return out20}2122func itoa(n int) string { return Addr{Row: n - 1}.String()[1:] }2324func TestSortRange(t *testing.T) {25 data := map[string]string{26 "A1": "Name", "B1": "Qty",27 "A2": "pear", "B2": "3",28 "A3": "Apple", "B3": "10",29 "A4": "fig", "B4": "",30 "A5": "banana", "B5": "3",31 "A6": "7", "B6": "TRUE",32 }33 tests := []struct {34 name string35 keys []SortKey36 a, b []string37 }{38 {"by name A to Z: numbers, then text ignoring case", []SortKey{{Col: 0}},39 []string{"7", "Apple", "banana", "fig", "pear"}, []string{"TRUE", "10", "3", "", "3"}},40 {"by name Z to A", []SortKey{{Col: 0, Desc: true}},41 []string{"pear", "fig", "banana", "Apple", "7"}, []string{"3", "", "3", "10", "TRUE"}},42 {"by qty: stable ties, booleans after numbers, blanks last", []SortKey{{Col: 1}},43 []string{"pear", "banana", "Apple", "7", "fig"}, []string{"3", "3", "10", "TRUE", ""}},44 {"by qty Z to A keeps blanks last", []SortKey{{Col: 1, Desc: true}},45 []string{"7", "Apple", "pear", "banana", "fig"}, []string{"TRUE", "10", "3", "3", ""}},46 {"by qty then name Z to A", []SortKey{{Col: 1}, {Col: 0, Desc: true}},47 []string{"pear", "banana", "Apple", "7", "fig"}, []string{"3", "3", "10", "TRUE", ""}},48 }49 for _, tt := range tests {50 t.Run(tt.name, func(t *testing.T) {51 s := sheetOf(t, data)52 s.SortRange(Rect{From: at("A2"), To: at("B6")}, tt.keys)53 if got := column(s, "A", 2, 6); !reflect.DeepEqual(got, tt.a) {54 t.Errorf("A = %q, want %q", got, tt.a)55 }56 if got := column(s, "B", 2, 6); !reflect.DeepEqual(got, tt.b) {57 t.Errorf("B = %q, want %q", got, tt.b)58 }59 if got := s.Cell(at("A1")).Input; got != "Name" {60 t.Errorf("header moved: %q", got)61 }62 // One undo step restores everything.63 s.Undo()64 if got := column(s, "A", 2, 6); !reflect.DeepEqual(got, []string{"pear", "Apple", "fig", "banana", "7"}) {65 t.Errorf("after undo A = %q", got)66 }67 if s.CanUndo() {68 t.Error("sort took more than one undo step")69 }70 })71 }72}7374func TestSortMovesFormulasWithTheirRows(t *testing.T) {75 s := sheetOf(t, map[string]string{76 "A1": "3", "B1": "=A1*2", "C1": "=$A$1",77 "A2": "1", "B2": "=A2*2", "C2": "=$A$1",78 "A3": "2", "B3": "=A3*2", "C3": "=$A$1",79 "E1": "=A1", // outside the sort: left pointing at A180 })81 s.SortRange(Rect{From: at("A1"), To: at("C3")}, []SortKey{{Col: 0}})82 want := map[string]string{"A1": "1", "B1": "=A1*2", "B2": "=A2*2", "B3": "=A3*2", "C1": "=$A$1", "E1": "=A1"}83 for a, in := range want {84 if got := s.Cell(at(a)).Input; got != in {85 t.Errorf("%s = %q, want %q", a, got, in)86 }87 }88 if v := s.Value(at("B3")).Num; v != 6 {89 t.Errorf("B3 = %v, want 6", v)90 }91 if v := s.Value(at("E1")).Num; v != 1 {92 t.Errorf("E1 = %v, want 1", v)93 }94}9596func TestSortClampsToData(t *testing.T) {97 s := sheetOf(t, map[string]string{"A1": "b", "A2": "a"})98 // A whole-column selection sorts only as far as the data goes.99 s.SortRange(Rect{From: at("A1"), To: Addr{Col: 0, Row: MaxRows - 1}}, []SortKey{{Col: 0}})100 if got := column(s, "A", 1, 3); !reflect.DeepEqual(got, []string{"a", "b", ""}) {101 t.Errorf("A = %q", got)102 }103}104105func TestRegionForSortAndFilter(t *testing.T) {106 s := sheetOf(t, map[string]string{107 "B2": "x", "C2": "y", "B3": "1", "D4": "diagonal",108 "G7": "island",109 })110 tests := []struct{ from, want string }{111 {"B2", "B2:D4"},112 {"D4", "D4"}, // reaches nothing: only B2:C3 reaches it113 {"C3", "B2:D4"}, // blank, next to data114 {"A1", "B2:D4"}, // blank, touching B2 diagonally115 {"G7", "G7"},116 {"J10", "J10"},117 }118 for _, tt := range tests {119 if got := s.Region(at(tt.from)).String(); got != tt.want {120 t.Errorf("Region(%s) = %s, want %s", tt.from, got, tt.want)121 }122 }123}