From ed725da9a0ca18c62346d28e209990bd465b0bfd Mon Sep 17 00:00:00 2001 From: Jakub Sobon Date: Sat, 2 Feb 2019 20:52:53 -0500 Subject: [PATCH] Test coverage for string, segment and area functions. --- draw/segdisp/sixteen/sixteen.go | 90 ++++- draw/segdisp/sixteen/sixteen_test.go | 532 +++++++++++++++++++++++++-- 2 files changed, 577 insertions(+), 45 deletions(-) diff --git a/draw/segdisp/sixteen/sixteen.go b/draw/segdisp/sixteen/sixteen.go index 0b97d8f..91db964 100644 --- a/draw/segdisp/sixteen/sixteen.go +++ b/draw/segdisp/sixteen/sixteen.go @@ -40,6 +40,7 @@ The following outlines segments in the display and their names. package sixteen import ( + "bytes" "errors" "fmt" "image" @@ -115,10 +116,37 @@ var characterSegments = map[rune][]Segment{ 'W': {F, E, N, L, C, B}, } -// Option is used to provide options. -type Option interface { - // set sets the provided option. - set(*Display) +// SupportsChars asserts whether the display supports all runes in the +// provided string. +// The display only supports a subset of ASCII runes. +// Returns any unsupported runes found in the string in an unspecified order. +func SupportsChars(s string) (bool, []rune) { + unsupp := map[rune]bool{} + for _, r := range s { + if _, ok := characterSegments[r]; !ok { + unsupp[r] = true + } + } + + var res []rune + for r := range unsupp { + res = append(res, r) + } + return len(res) == 0, res +} + +// Sanitize returns a copy of the string, replacing all unsupported characters +// with a space character. +func Sanitize(s string) string { + var b bytes.Buffer + for _, r := range s { + if _, ok := characterSegments[r]; !ok { + b.WriteRune(' ') + continue + } + b.WriteRune(r) + } + return b.String() } // AllSegments returns all 16 segments in an undefined order. @@ -130,6 +158,12 @@ func AllSegments() []Segment { return res } +// Option is used to provide options. +type Option interface { + // set sets the provided option. + set(*Display) +} + // option implements Option. type option func(*Display) @@ -180,7 +214,7 @@ func (d *Display) Clear(opts ...Option) { // This method is idempotent. func (d *Display) SetSegment(s Segment) error { if s <= segmentUnknown || s >= segmentMax { - return fmt.Errorf("unknown segment %v", s) + return fmt.Errorf("unknown segment %v(%d)", s, s) } d.segments[s] = true return nil @@ -190,7 +224,7 @@ func (d *Display) SetSegment(s Segment) error { // This method is idempotent. func (d *Display) ClearSegment(s Segment) error { if s <= segmentUnknown || s >= segmentMax { - return fmt.Errorf("unknown segment %v", s) + return fmt.Errorf("unknown segment %v(%d)", s, s) } d.segments[s] = false return nil @@ -200,7 +234,7 @@ func (d *Display) ClearSegment(s Segment) error { // or clears it depending on its current state. func (d *Display) ToggleSegment(s Segment) error { if s <= segmentUnknown || s >= segmentMax { - return fmt.Errorf("unknown segment %v", s) + return fmt.Errorf("unknown segment %v(%d)", s, s) } if d.segments[s] { d.segments[s] = false @@ -233,16 +267,23 @@ func (d *Display) SetCharacter(c rune) error { // Minimum valid size of a cell canvas in order to draw the segment display. const ( // MinCols is the smallest valid amount of columns in a cell area. - MinCols = 4 + MinCols = 6 // MinRowPixels is the smallest valid amount of rows in a cell area. - MinRows = 3 + MinRows = 5 ) +// aspectRatio is the desired aspect ratio of a single segment display. +var aspectRatio = image.Point{3, 5} + // Draw draws the current state of the segment display onto the canvas. // The canvas must be at least MinCols x MinRows cells, or an error will be // returned. // Any options provided to draw overwrite the values provided to New. func (d *Display) Draw(cvs *canvas.Canvas, opts ...Option) error { + for _, o := range opts { + o.set(d) + } + ar, err := Required(cvs.Area()) if err != nil { return err @@ -253,7 +294,7 @@ func (d *Display) Draw(cvs *canvas.Canvas, opts ...Option) error { return fmt.Errorf("braille.New => %v", err) } - bcAr := area.WithRatio(bc.Area(), image.Point{3, 5}) + bcAr := area.WithRatio(bc.Area(), aspectRatio) segW := segWidth(bcAr) if segW == 4 { segW = 5 @@ -307,6 +348,10 @@ func (d *Display) Draw(cvs *canvas.Canvas, opts ...Option) error { d1Ar := image.Rect(eg, botStart, eg+shortL, botStart+segW) d2Ar := image.Rect(d1Ar.Max.X+ptp, botStart, d1Ar.Max.X+ptp+shortL, botStart+segW) + var sOpts []segment.Option + if len(d.cellOpts) > 0 { + sOpts = append(sOpts, segment.CellOpts(d.cellOpts...)) + } for _, segArg := range []struct { s Segment st segment.Type @@ -334,7 +379,8 @@ func (d *Display) Draw(cvs *canvas.Canvas, opts ...Option) error { continue } log.Printf("segment.HV for %v, ar:%v", segArg.s, segArg.ar) - if err := segment.HV(bc, segArg.ar, segArg.st, segArg.opts...); err != nil { + sOpts := append(sOpts, segArg.opts...) + if err := segment.HV(bc, segArg.ar, segArg.st, sOpts...); err != nil { return fmt.Errorf("failed to draw segment %v, segment.HV => %v", segArg.s, err) } } @@ -363,6 +409,10 @@ func (d *Display) Draw(cvs *canvas.Canvas, opts ...Option) error { botREndY := int(numbers.Round(float64(cAr.Max.Y) - segPeakDist + diaLeg - float64(diaGap)*0.3)) lAr := image.Rect(botRStartX, botRStartY, botREndX, botREndY) + var dsOpts []segment.DiagonalOption + if len(d.cellOpts) > 0 { + dsOpts = append(dsOpts, segment.DiagonalCellOpts(d.cellOpts...)) + } for _, segArg := range []struct { s Segment dt segment.DiagonalType @@ -377,7 +427,7 @@ func (d *Display) Draw(cvs *canvas.Canvas, opts ...Option) error { continue } log.Printf("segment.Diagonal for %v, ar:%v", segArg.s, segArg.ar) - if err := segment.Diagonal(bc, segArg.ar, segW, segArg.dt); err != nil { + if err := segment.Diagonal(bc, segArg.ar, segW, segArg.dt, dsOpts...); err != nil { return fmt.Errorf("failed to draw segment %v, segment.Diagonal => %v", segArg.s, err) } } @@ -389,20 +439,20 @@ func (d *Display) Draw(cvs *canvas.Canvas, opts ...Option) error { // size or a smaller area that is required to draw one display. // Returns a smaller area when the provided area didn't have the required // aspect ratio. -// Returns an error if the area is too small to draw a segment display. +// Returns an error if the area is too small to draw a segment display, i.e. +// smaller than MinCols x MinRows. func Required(cellArea image.Rectangle) (image.Rectangle, error) { - bcAr := image.Rect(cellArea.Min.X, cellArea.Min.Y, cellArea.Max.X*braille.ColMult, cellArea.Max.Y*braille.RowMult) - bcArAdj := area.WithRatio(bcAr, image.Point{3, 5}) - if bcArAdj.Empty() { - return image.ZR, fmt.Errorf("cell area %v is to small to draw the segment display, need at least %d x %d cells", cellArea, MinCols, MinRows) + if cols, rows := cellArea.Dx(), cellArea.Dy(); cols < MinCols || rows < MinRows { + return image.ZR, fmt.Errorf("cell area %v is too small to draw the segment display, has %dx%d cells, need at least %dx%d cells", + cellArea, cols, rows, MinCols, MinRows) } + bcAr := image.Rect(cellArea.Min.X, cellArea.Min.Y, cellArea.Max.X*braille.ColMult, cellArea.Max.Y*braille.RowMult) + bcArAdj := area.WithRatio(bcAr, aspectRatio) + needCols := int(math.Ceil(float64(bcArAdj.Dx()) / braille.ColMult)) needRows := int(math.Ceil(float64(bcArAdj.Dy()) / braille.RowMult)) needAr := image.Rect(cellArea.Min.X, cellArea.Min.Y, cellArea.Min.X+needCols, cellArea.Min.Y+needRows) - if !needAr.In(cellArea) { - return image.ZR, fmt.Errorf("what just happened?") - } return needAr, nil } diff --git a/draw/segdisp/sixteen/sixteen_test.go b/draw/segdisp/sixteen/sixteen_test.go index 3782813..e8804a1 100644 --- a/draw/segdisp/sixteen/sixteen_test.go +++ b/draw/segdisp/sixteen/sixteen_test.go @@ -16,11 +16,14 @@ package sixteen import ( "image" + "sort" "testing" + "github.com/kylelemons/godebug/pretty" "github.com/mum4k/termdash/area" "github.com/mum4k/termdash/canvas" "github.com/mum4k/termdash/canvas/braille/testbraille" + "github.com/mum4k/termdash/cell" "github.com/mum4k/termdash/draw/segdisp/segment" "github.com/mum4k/termdash/draw/segdisp/segment/testsegment" "github.com/mum4k/termdash/terminal/faketerm" @@ -32,18 +35,78 @@ func TestDraw(t *testing.T) { opts []Option drawOpts []Option cellCanvas image.Rectangle - // If not nil, called before Draw is called - can set, clear or toggle segments. - update func(*Display) error - want func(size image.Point) *faketerm.Terminal - wantErr bool + // If not nil, it is called before Draw is called and can set, clear or + // toggle segments or characters. + update func(*Display) error + want func(size image.Point) *faketerm.Terminal + wantErr bool + wantUpdateErr bool }{ + { + desc: "fails for area not wide enough", + cellCanvas: image.Rect(0, 0, MinCols-1, MinRows), + wantErr: true, + }, + { + desc: "fails for area not tall enough", + cellCanvas: image.Rect(0, 0, MinCols, MinRows-1), + wantErr: true, + }, + { + desc: "fails to set invalid segment (too small)", + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + return d.SetSegment(Segment(-1)) + }, + wantUpdateErr: true, + }, + { + desc: "fails to set invalid segment (too large)", + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + return d.SetSegment(Segment(segmentMax)) + }, + wantUpdateErr: true, + }, + { + desc: "fails to clear invalid segment (too small)", + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + return d.ClearSegment(Segment(-1)) + }, + wantUpdateErr: true, + }, + { + desc: "fails to clear invalid segment (too large)", + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + return d.ClearSegment(Segment(segmentMax)) + }, + wantUpdateErr: true, + }, + { + desc: "fails to toggle invalid segment (too small)", + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + return d.ToggleSegment(Segment(-1)) + }, + wantUpdateErr: true, + }, + { + desc: "fails to toggle invalid segment (too large)", + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + return d.ToggleSegment(Segment(segmentMax)) + }, + wantUpdateErr: true, + }, { desc: "empty when no segments set", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), }, { desc: "smallest valid display 6x5, A1", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(A1) }, @@ -58,7 +121,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, A2", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(A2) }, @@ -73,7 +136,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, F", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(F) }, @@ -88,7 +151,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, J", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(J) }, @@ -103,7 +166,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, B", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(B) }, @@ -118,7 +181,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, G1", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(G1) }, @@ -133,7 +196,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, G2", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(G2) }, @@ -148,7 +211,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, E", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(E) }, @@ -163,7 +226,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, M", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(M) }, @@ -178,7 +241,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, C", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(C) }, @@ -193,7 +256,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, D1", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(D1) }, @@ -208,7 +271,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, D2", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(D2) }, @@ -223,7 +286,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, H", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(H) }, @@ -238,7 +301,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, K", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(K) }, @@ -254,7 +317,7 @@ func TestDraw(t *testing.T) { { desc: "smallest valid display 6x5, N", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(N) }, @@ -269,7 +332,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, L", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { return d.SetSegment(L) }, @@ -284,7 +347,7 @@ func TestDraw(t *testing.T) { }, { desc: "smallest valid display 6x5, all segments", - cellCanvas: image.Rect(0, 0, 6, 5), + cellCanvas: image.Rect(0, 0, MinCols, MinRows), update: func(d *Display) error { for _, s := range AllSegments() { if err := d.SetSegment(s); err != nil { @@ -322,14 +385,280 @@ func TestDraw(t *testing.T) { return ft }, }, + { + desc: "smallest valid display 6x5, all segments, sets cell options provided to new", + opts: []Option{ + CellOpts( + cell.FgColor(cell.ColorRed), + cell.BgColor(cell.ColorGreen), + ), + }, + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + for _, s := range AllSegments() { + if err := d.SetSegment(s); err != nil { + return err + } + } + return nil + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + bc := testbraille.MustNew(ft.Area()) + + cOpts := []cell.Option{ + cell.FgColor(cell.ColorRed), + cell.BgColor(cell.ColorGreen), + } + testsegment.MustHV(bc, image.Rect(1, 0, 4, 1), segment.Horizontal, segment.CellOpts(cOpts...)) // A1 + testsegment.MustHV(bc, image.Rect(5, 0, 8, 1), segment.Horizontal, segment.CellOpts(cOpts...)) // A2 + + testsegment.MustHV(bc, image.Rect(0, 1, 1, 8), segment.Vertical, segment.CellOpts(cOpts...)) // F + testsegment.MustHV(bc, image.Rect(4, 1, 5, 8), segment.Vertical, segment.CellOpts(cOpts...)) // J + testsegment.MustHV(bc, image.Rect(8, 1, 9, 8), segment.Vertical, segment.CellOpts(cOpts...)) // B + + testsegment.MustHV(bc, image.Rect(1, 8, 4, 9), segment.Horizontal, segment.CellOpts(cOpts...)) // G1 + testsegment.MustHV(bc, image.Rect(5, 8, 8, 9), segment.Horizontal, segment.CellOpts(cOpts...)) // G2 + + testsegment.MustHV(bc, image.Rect(0, 9, 1, 16), segment.Vertical, segment.CellOpts(cOpts...)) // E + testsegment.MustHV(bc, image.Rect(4, 9, 5, 16), segment.Vertical, segment.CellOpts(cOpts...)) // M + testsegment.MustHV(bc, image.Rect(8, 9, 9, 16), segment.Vertical, segment.CellOpts(cOpts...)) // C + + testsegment.MustHV(bc, image.Rect(1, 16, 4, 17), segment.Horizontal, segment.CellOpts(cOpts...)) // D1 + testsegment.MustHV(bc, image.Rect(5, 16, 8, 17), segment.Horizontal, segment.CellOpts(cOpts...)) // D2 + + testsegment.MustDiagonal(bc, image.Rect(1, 1, 4, 8), 1, segment.LeftToRight, segment.DiagonalCellOpts(cOpts...)) // H + testsegment.MustDiagonal(bc, image.Rect(5, 1, 8, 8), 1, segment.RightToLeft, segment.DiagonalCellOpts(cOpts...)) // K + testsegment.MustDiagonal(bc, image.Rect(1, 9, 4, 16), 1, segment.RightToLeft, segment.DiagonalCellOpts(cOpts...)) // N + testsegment.MustDiagonal(bc, image.Rect(5, 9, 8, 16), 1, segment.LeftToRight, segment.DiagonalCellOpts(cOpts...)) // L + testbraille.MustApply(bc, ft) + return ft + }, + }, + { + desc: "smallest valid display 6x5, all segments, sets cell options provided to draw", + drawOpts: []Option{ + CellOpts( + cell.FgColor(cell.ColorRed), + cell.BgColor(cell.ColorGreen), + ), + }, + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + for _, s := range AllSegments() { + if err := d.SetSegment(s); err != nil { + return err + } + } + return nil + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + bc := testbraille.MustNew(ft.Area()) + + cOpts := []cell.Option{ + cell.FgColor(cell.ColorRed), + cell.BgColor(cell.ColorGreen), + } + testsegment.MustHV(bc, image.Rect(1, 0, 4, 1), segment.Horizontal, segment.CellOpts(cOpts...)) // A1 + testsegment.MustHV(bc, image.Rect(5, 0, 8, 1), segment.Horizontal, segment.CellOpts(cOpts...)) // A2 + + testsegment.MustHV(bc, image.Rect(0, 1, 1, 8), segment.Vertical, segment.CellOpts(cOpts...)) // F + testsegment.MustHV(bc, image.Rect(4, 1, 5, 8), segment.Vertical, segment.CellOpts(cOpts...)) // J + testsegment.MustHV(bc, image.Rect(8, 1, 9, 8), segment.Vertical, segment.CellOpts(cOpts...)) // B + + testsegment.MustHV(bc, image.Rect(1, 8, 4, 9), segment.Horizontal, segment.CellOpts(cOpts...)) // G1 + testsegment.MustHV(bc, image.Rect(5, 8, 8, 9), segment.Horizontal, segment.CellOpts(cOpts...)) // G2 + + testsegment.MustHV(bc, image.Rect(0, 9, 1, 16), segment.Vertical, segment.CellOpts(cOpts...)) // E + testsegment.MustHV(bc, image.Rect(4, 9, 5, 16), segment.Vertical, segment.CellOpts(cOpts...)) // M + testsegment.MustHV(bc, image.Rect(8, 9, 9, 16), segment.Vertical, segment.CellOpts(cOpts...)) // C + + testsegment.MustHV(bc, image.Rect(1, 16, 4, 17), segment.Horizontal, segment.CellOpts(cOpts...)) // D1 + testsegment.MustHV(bc, image.Rect(5, 16, 8, 17), segment.Horizontal, segment.CellOpts(cOpts...)) // D2 + + testsegment.MustDiagonal(bc, image.Rect(1, 1, 4, 8), 1, segment.LeftToRight, segment.DiagonalCellOpts(cOpts...)) // H + testsegment.MustDiagonal(bc, image.Rect(5, 1, 8, 8), 1, segment.RightToLeft, segment.DiagonalCellOpts(cOpts...)) // K + testsegment.MustDiagonal(bc, image.Rect(1, 9, 4, 16), 1, segment.RightToLeft, segment.DiagonalCellOpts(cOpts...)) // N + testsegment.MustDiagonal(bc, image.Rect(5, 9, 8, 16), 1, segment.LeftToRight, segment.DiagonalCellOpts(cOpts...)) // L + testbraille.MustApply(bc, ft) + return ft + }, + }, + { + desc: "clears the display", + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + for _, s := range AllSegments() { + if err := d.SetSegment(s); err != nil { + return err + } + } + d.Clear() + return nil + }, + }, + { + desc: "clears the display and sets cell options", + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + d.Clear(CellOpts(cell.FgColor(cell.ColorBlue))) + return d.SetSegment(A1) + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + bc := testbraille.MustNew(ft.Area()) + + testsegment.MustHV(bc, image.Rect(1, 0, 4, 1), segment.Horizontal, segment.CellOpts(cell.FgColor(cell.ColorBlue))) // A1 + testbraille.MustApply(bc, ft) + return ft + }, + }, + { + desc: "clears some segments", + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + for _, s := range AllSegments() { + if err := d.SetSegment(s); err != nil { + return err + } + } + for _, s := range []Segment{A1, A2, G1, G2, D1, D2, L} { + if err := d.ClearSegment(s); err != nil { + return err + } + } + return nil + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + bc := testbraille.MustNew(ft.Area()) + + testsegment.MustHV(bc, image.Rect(0, 1, 1, 8), segment.Vertical) // F + testsegment.MustHV(bc, image.Rect(4, 1, 5, 8), segment.Vertical) // J + testsegment.MustHV(bc, image.Rect(8, 1, 9, 8), segment.Vertical) // B + + testsegment.MustHV(bc, image.Rect(0, 9, 1, 16), segment.Vertical) // E + testsegment.MustHV(bc, image.Rect(4, 9, 5, 16), segment.Vertical) // M + testsegment.MustHV(bc, image.Rect(8, 9, 9, 16), segment.Vertical) // C + + testsegment.MustDiagonal(bc, image.Rect(1, 1, 4, 8), 1, segment.LeftToRight) // H + testsegment.MustDiagonal(bc, image.Rect(5, 1, 8, 8), 1, segment.RightToLeft) // K + testsegment.MustDiagonal(bc, image.Rect(1, 9, 4, 16), 1, segment.RightToLeft) // N + + testbraille.MustApply(bc, ft) + return ft + }, + }, + { + desc: "toggles some segments off", + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + for _, s := range AllSegments() { + if err := d.SetSegment(s); err != nil { + return err + } + } + for _, s := range []Segment{A1, A2, G1, G2, D1, D2, L} { + if err := d.ToggleSegment(s); err != nil { + return err + } + } + return nil + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + bc := testbraille.MustNew(ft.Area()) + + testsegment.MustHV(bc, image.Rect(0, 1, 1, 8), segment.Vertical) // F + testsegment.MustHV(bc, image.Rect(4, 1, 5, 8), segment.Vertical) // J + testsegment.MustHV(bc, image.Rect(8, 1, 9, 8), segment.Vertical) // B + + testsegment.MustHV(bc, image.Rect(0, 9, 1, 16), segment.Vertical) // E + testsegment.MustHV(bc, image.Rect(4, 9, 5, 16), segment.Vertical) // M + testsegment.MustHV(bc, image.Rect(8, 9, 9, 16), segment.Vertical) // C + + testsegment.MustDiagonal(bc, image.Rect(1, 1, 4, 8), 1, segment.LeftToRight) // H + testsegment.MustDiagonal(bc, image.Rect(5, 1, 8, 8), 1, segment.RightToLeft) // K + testsegment.MustDiagonal(bc, image.Rect(1, 9, 4, 16), 1, segment.RightToLeft) // N + + testbraille.MustApply(bc, ft) + return ft + }, + }, + { + desc: "toggles some segments on", + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + for _, s := range []Segment{A1, A2, G1, G2, D1, D2, L} { + if err := d.ToggleSegment(s); err != nil { + return err + } + } + return nil + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + bc := testbraille.MustNew(ft.Area()) + + testsegment.MustHV(bc, image.Rect(1, 0, 4, 1), segment.Horizontal) // A1 + testsegment.MustHV(bc, image.Rect(5, 0, 8, 1), segment.Horizontal) // A2 + + testsegment.MustHV(bc, image.Rect(1, 8, 4, 9), segment.Horizontal) // G1 + testsegment.MustHV(bc, image.Rect(5, 8, 8, 9), segment.Horizontal) // G2 + + testsegment.MustHV(bc, image.Rect(1, 16, 4, 17), segment.Horizontal) // D1 + testsegment.MustHV(bc, image.Rect(5, 16, 8, 17), segment.Horizontal) // D2 + + testsegment.MustDiagonal(bc, image.Rect(5, 9, 8, 16), 1, segment.LeftToRight) // L + + testbraille.MustApply(bc, ft) + return ft + }, + }, + { + desc: "set is idempotent", + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + if err := d.SetSegment(A1); err != nil { + return err + } + return d.SetSegment(A1) + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + bc := testbraille.MustNew(ft.Area()) + + testsegment.MustHV(bc, image.Rect(1, 0, 4, 1), segment.Horizontal) // A1 + testbraille.MustApply(bc, ft) + return ft + }, + }, + { + desc: "clear is idempotent", + cellCanvas: image.Rect(0, 0, MinCols, MinRows), + update: func(d *Display) error { + if err := d.SetSegment(A1); err != nil { + return err + } + if err := d.ClearSegment(A1); err != nil { + return err + } + return d.ClearSegment(A1) + }, + }, } for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { d := New(tc.opts...) if tc.update != nil { - if err := tc.update(d); err != nil { - t.Fatalf("tc.update => unexpected error: %v", err) + err := tc.update(d) + if (err != nil) != tc.wantUpdateErr { + t.Errorf("tc.update => unexpected error: %v, wantUpdateErr: %v", err, tc.wantUpdateErr) + } + if err != nil { + return } } @@ -339,7 +668,7 @@ func TestDraw(t *testing.T) { } { - err := d.Draw(cvs) + err := d.Draw(cvs, tc.drawOpts...) if (err != nil) != tc.wantErr { t.Errorf("Draw => unexpected error: %v, wantErr: %v", err, tc.wantErr) } @@ -368,3 +697,156 @@ func TestDraw(t *testing.T) { }) } } + +func TestRequired(t *testing.T) { + tests := []struct { + desc string + cellArea image.Rectangle + want image.Rectangle + wantErr bool + }{ + { + desc: "fails when area isn't wide enough", + cellArea: image.Rect(0, 0, MinCols-1, MinRows), + wantErr: true, + }, + { + desc: "fails when area isn't tall enough", + cellArea: image.Rect(0, 0, MinCols, MinRows-1), + wantErr: true, + }, + { + desc: "returns same area when no adjustment needed", + cellArea: image.Rect(0, 0, MinCols, MinRows), + want: image.Rect(0, 0, MinCols, MinRows), + }, + { + desc: "adjusts width to aspect ratio", + cellArea: image.Rect(0, 0, MinCols+100, MinRows), + want: image.Rect(0, 0, MinCols, MinRows), + }, + { + desc: "adjusts height to aspect ratio", + cellArea: image.Rect(0, 0, MinCols, MinRows+100), + want: image.Rect(0, 0, MinCols, MinRows), + }, + { + desc: "adjusts larger area to aspect ratio", + cellArea: image.Rect(0, 0, MinCols*2, MinRows*4), + want: image.Rect(0, 0, 12, 10), + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + got, err := Required(tc.cellArea) + if (err != nil) != tc.wantErr { + t.Errorf("Required => unexpected error: %v, wantErr: %v", err, tc.wantErr) + } + if err != nil { + return + } + + if diff := pretty.Compare(tc.want, got); diff != "" { + t.Errorf("Required => unexpected diff (-want, +got):\n%s", diff) + } + }) + } +} + +func TestAllSegments(t *testing.T) { + want := []Segment{A1, A2, B, C, D1, D2, E, F, G1, G2, H, J, K, L, M, N} + got := AllSegments() + sort.Slice(got, func(i, j int) bool { + return int(got[i]) < int(got[j]) + }) + if diff := pretty.Compare(want, got); diff != "" { + t.Errorf("AllSegments => unexpected diff (-want, +got):\n%s", diff) + } +} + +func TestSupportsChars(t *testing.T) { + tests := []struct { + desc string + str string + wantRes bool + wantUnsupp []rune + }{ + { + desc: "supports all chars in an empty string", + wantRes: true, + }, + { + desc: "supports all chars in the string", + str: " wW ", + wantRes: true, + }, + { + desc: "supports some chars in the string", + str: " w:!W :", + wantRes: false, + wantUnsupp: []rune{':', '!'}, + }, + { + desc: "supports no chars in the string", + str: ":!()", + wantRes: false, + wantUnsupp: []rune{':', '!', '(', ')'}, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + gotRes, gotUnsupp := SupportsChars(tc.str) + if gotRes != tc.wantRes { + t.Errorf("SupportsChars(%q) => %v, %v, want %v, %v", tc.str, gotRes, gotUnsupp, tc.wantRes, tc.wantUnsupp) + } + + sort.Slice(gotUnsupp, func(i, j int) bool { + return gotUnsupp[i] < gotUnsupp[j] + }) + sort.Slice(tc.wantUnsupp, func(i, j int) bool { + return tc.wantUnsupp[i] < tc.wantUnsupp[j] + }) + if diff := pretty.Compare(tc.wantUnsupp, gotUnsupp); diff != "" { + t.Errorf("SupportsChars => unexpected unsupported characters returned, diff (-want, +got):\n%s", diff) + } + }) + } +} + +func TestSanitize(t *testing.T) { + tests := []struct { + desc string + str string + want string + }{ + { + desc: "no alternation to empty string", + }, + { + desc: "all characters are supported", + str: " wW", + want: " wW", + }, + { + desc: "some characters are supported", + str: " :w!W:", + want: " w W ", + }, + { + desc: "no characters are supported", + str: ":!()", + want: " ", + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + got := Sanitize(tc.str) + if got != tc.want { + t.Errorf("Sanitize => %q, want %q", got, tc.want) + } + }) + } +}