1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-04-30 13:48:54 +08:00

Test coverage for string, segment and area functions.

This commit is contained in:
Jakub Sobon 2019-02-02 20:52:53 -05:00
parent 0ee98483ac
commit ed725da9a0
No known key found for this signature in database
GPG Key ID: F2451A77FB05D3B7
2 changed files with 577 additions and 45 deletions

View File

@ -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
}

View File

@ -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)
}
})
}
}