diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cbc9aa410..63ed01f2e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -37,5 +37,5 @@ updates: labels: - "dependencies" commit-message: - prefix: "feat" + prefix: "chore" include: "scope" diff --git a/.golangci.yml b/.golangci.yml index 7c0a115e8..be61d89ba 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -30,10 +30,6 @@ linters: generated: lax presets: - common-false-positives - paths: - - third_party$ - - builtin$ - - examples$ issues: max-issues-per-linter: 0 max-same-issues: 0 @@ -43,7 +39,3 @@ formatters: - goimports exclusions: generated: lax - paths: - - third_party$ - - builtin$ - - examples$ diff --git a/README.md b/README.md index 07eb17742..1ea295c71 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,9 @@ [![Go ReportCard](https://goreportcard.com/badge/charmbracelet/bubbles)](https://goreportcard.com/report/charmbracelet/bubbles) Some components for [Bubble Tea](https://github.com/charmbracelet/bubbletea) -applications. These components are used in production in [Glow][glow], -[Charm][charm] and [many other applications][otherstuff]. +applications. These components are used in production in [Glow][glow], and [many other applications][otherstuff]. [glow]: https://github.com/charmbracelet/glow -[charm]: https://github.com/charmbracelet/charm [otherstuff]: https://github.com/charmbracelet/bubbletea/#bubble-tea-in-the-wild ## Spinner diff --git a/cursor/cursor.go b/cursor/cursor.go index 824b45031..7e168fd0a 100644 --- a/cursor/cursor.go +++ b/cursor/cursor.go @@ -184,11 +184,13 @@ func (m *Model) BlinkCmd() tea.Cmd { m.blinkTag++ + blinkMsg := BlinkMsg{id: m.id, tag: m.blinkTag} + return func() tea.Msg { defer cancel() <-ctx.Done() if ctx.Err() == context.DeadlineExceeded { - return BlinkMsg{id: m.id, tag: m.blinkTag} + return blinkMsg } return blinkCanceled{} } diff --git a/cursor/cursor_test.go b/cursor/cursor_test.go new file mode 100644 index 000000000..c526c4ae1 --- /dev/null +++ b/cursor/cursor_test.go @@ -0,0 +1,50 @@ +package cursor + +import ( + "sync" + "testing" + "time" +) + +// TestBlinkCmdDataRace tests for a race on [Cursor.blinkTag]. +// +// The original [Model.BlinkCmd] implementation returned a closure over the pointer receiver: +// +// return func() tea.Msg { +// defer cancel() +// <-ctx.Done() +// if ctx.Err() == context.DeadlineExceeded { +// return BlinkMsg{id: m.id, tag: m.blinkTag} +// } +// return blinkCanceled{} +// } +// +// A race on “m.blinkTag” will occur if: +// 1. [Model.BlinkCmd] is called e.g. by calling [Model.Focus] from +// ["github.com/charmbracelet/bubbletea".Model.Update]; +// 2. ["github.com/charmbracelet/bubbletea".handleCommands] is kept sufficiently busy that it does not recieve and +// execute the [Model.BlinkCmd] e.g. by other long running command or commands; +// 3. at least [Mode.BlinkSpeed] time elapses; +// 4. [Model.BlinkCmd] is called again; +// 5. ["github.com/charmbracelet/bubbletea".handleCommands] gets around to receiving and executing the original +// closure. +// +// Even if this did not formally race, the value of the tag fetched would be semantically incorrect (likely being the +// current value rather than the value at the time the closure was created). +func TestBlinkCmdDataRace(t *testing.T) { + m := New() + cmd := m.BlinkCmd() + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + time.Sleep(m.BlinkSpeed * 3) + cmd() + }() + go func() { + defer wg.Done() + time.Sleep(m.BlinkSpeed * 2) + m.BlinkCmd() + }() + wg.Wait() +} diff --git a/list/list.go b/list/list.go index 9ce24bc98..22c532ff0 100644 --- a/list/list.go +++ b/list/list.go @@ -691,6 +691,7 @@ func (m *Model) setSize(width, height int) { m.Help.Width = width m.FilterInput.SetWidth(width - promptWidth - lipgloss.Width(m.spinnerView())) m.updatePagination() + m.updateKeybindings() } func (m *Model) resetFiltering() { diff --git a/table/table.go b/table/table.go index 13241a128..953ec125d 100644 --- a/table/table.go +++ b/table/table.go @@ -305,6 +305,11 @@ func (m Model) Columns() []Column { // SetRows sets a new rows state. func (m *Model) SetRows(r []Row) { m.rows = r + + if m.cursor > len(m.rows)-1 { + m.cursor = len(m.rows) - 1 + } + m.UpdateViewport() } diff --git a/table/table_test.go b/table/table_test.go index cfe87807f..78db2424b 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -1,15 +1,213 @@ package table import ( + "reflect" "strings" "testing" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/viewport" "github.com/charmbracelet/lipgloss/v2" "github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/exp/golden" ) -func TestFromValues(t *testing.T) { +var testCols = []Column{ + {Title: "col1", Width: 10}, + {Title: "col2", Width: 10}, + {Title: "col3", Width: 10}, +} + +func TestNew(t *testing.T) { + tests := map[string]struct { + opts []Option + want Model + }{ + "Default": { + want: Model{ + // Default fields + cursor: 0, + viewport: viewport.New( + viewport.WithWidth(0), + viewport.WithHeight(20), + ), + KeyMap: DefaultKeyMap(), + Help: help.New(), + styles: DefaultStyles(), + }, + }, + "WithColumns": { + opts: []Option{ + WithColumns([]Column{ + {Title: "Foo", Width: 1}, + {Title: "Bar", Width: 2}, + }), + }, + want: Model{ + // Default fields + cursor: 0, + viewport: viewport.New( + viewport.WithWidth(0), + viewport.WithHeight(20), + ), + KeyMap: DefaultKeyMap(), + Help: help.New(), + styles: DefaultStyles(), + + // Modified fields + cols: []Column{ + {Title: "Foo", Width: 1}, + {Title: "Bar", Width: 2}, + }, + }, + }, + "WithColumns; WithRows": { + opts: []Option{ + WithColumns([]Column{ + {Title: "Foo", Width: 1}, + {Title: "Bar", Width: 2}, + }), + WithRows([]Row{ + {"1", "Foo"}, + {"2", "Bar"}, + }), + }, + want: Model{ + // Default fields + cursor: 0, + viewport: viewport.New( + viewport.WithWidth(0), + viewport.WithHeight(20), + ), + KeyMap: DefaultKeyMap(), + Help: help.New(), + styles: DefaultStyles(), + + // Modified fields + cols: []Column{ + {Title: "Foo", Width: 1}, + {Title: "Bar", Width: 2}, + }, + rows: []Row{ + {"1", "Foo"}, + {"2", "Bar"}, + }, + }, + }, + "WithHeight": { + opts: []Option{ + WithHeight(10), + }, + want: Model{ + // Default fields + cursor: 0, + KeyMap: DefaultKeyMap(), + Help: help.New(), + styles: DefaultStyles(), + + // Modified fields + // Viewport height is 1 less than the provided height when no header is present since lipgloss.Height adds 1 + viewport: viewport.New( + viewport.WithWidth(0), + viewport.WithHeight(9), + ), + }, + }, + "WithWidth": { + opts: []Option{ + WithWidth(10), + }, + want: Model{ + // Default fields + cursor: 0, + KeyMap: DefaultKeyMap(), + Help: help.New(), + styles: DefaultStyles(), + + // Modified fields + // Viewport height is 1 less than the provided height when no header is present since lipgloss.Height adds 1 + viewport: viewport.New( + viewport.WithWidth(10), + viewport.WithHeight(20), + ), + }, + }, + "WithFocused": { + opts: []Option{ + WithFocused(true), + }, + want: Model{ + // Default fields + cursor: 0, + viewport: viewport.New( + viewport.WithWidth(0), + viewport.WithHeight(20), + ), + KeyMap: DefaultKeyMap(), + Help: help.New(), + styles: DefaultStyles(), + + // Modified fields + focus: true, + }, + }, + "WithStyles": { + opts: []Option{ + WithStyles(Styles{}), + }, + want: Model{ + // Default fields + cursor: 0, + viewport: viewport.New( + viewport.WithWidth(0), + viewport.WithHeight(20), + ), + KeyMap: DefaultKeyMap(), + Help: help.New(), + + // Modified fields + styles: Styles{}, + }, + }, + "WithKeyMap": { + opts: []Option{ + WithKeyMap(KeyMap{}), + }, + want: Model{ + // Default fields + cursor: 0, + viewport: viewport.New( + viewport.WithWidth(0), + viewport.WithHeight(20), + ), + Help: help.New(), + styles: DefaultStyles(), + + // Modified fields + KeyMap: KeyMap{}, + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tc.want.UpdateViewport() + + got := New(tc.opts...) + + // NOTE(@andreynering): Funcs have different references, so we need + // to clear them out to compare the structs. + tc.want.viewport.LeftGutterFunc = nil + got.viewport.LeftGutterFunc = nil + + if !reflect.DeepEqual(tc.want, got) { + t.Errorf("\n\nwant %v\n\ngot %v", tc.want, got) + } + }) + } +} + +func TestModel_FromValues(t *testing.T) { input := "foo1,bar1\nfoo2,bar2\nfoo3,bar3" table := New(WithColumns([]Column{{Title: "Foo"}, {Title: "Bar"}})) table.FromValues(input, ",") @@ -23,12 +221,12 @@ func TestFromValues(t *testing.T) { {"foo2", "bar2"}, {"foo3", "bar3"}, } - if !deepEqual(table.rows, expect) { - t.Fatal("table rows is not equals to the input") + if !reflect.DeepEqual(table.rows, expect) { + t.Fatalf("\n\nwant %v\n\ngot %v", expect, table.rows) } } -func TestFromValuesWithTabSeparator(t *testing.T) { +func TestModel_FromValues_WithTabSeparator(t *testing.T) { input := "foo1.\tbar1\nfoo,bar,baz\tbar,2" table := New(WithColumns([]Column{{Title: "Foo"}, {Title: "Bar"}})) table.FromValues(input, "\t") @@ -41,32 +239,12 @@ func TestFromValuesWithTabSeparator(t *testing.T) { {"foo1.", "bar1"}, {"foo,bar,baz", "bar,2"}, } - if !deepEqual(table.rows, expect) { - t.Fatal("table rows is not equals to the input") - } -} - -func deepEqual(a, b []Row) bool { - if len(a) != len(b) { - return false - } - for i, r := range a { - for j, f := range r { - if f != b[i][j] { - return false - } - } + if !reflect.DeepEqual(table.rows, expect) { + t.Fatalf("\n\nwant %v\n\ngot %v", expect, table.rows) } - return true -} - -var cols = []Column{ - {Title: "col1", Width: 10}, - {Title: "col2", Width: 10}, - {Title: "col3", Width: 10}, } -func TestRenderRow(t *testing.T) { +func TestModel_RenderRow(t *testing.T) { tests := []struct { name string table *Model @@ -76,7 +254,7 @@ func TestRenderRow(t *testing.T) { name: "simple row", table: &Model{ rows: []Row{{"Foooooo", "Baaaaar", "Baaaaaz"}}, - cols: cols, + cols: testCols, styles: Styles{Cell: lipgloss.NewStyle()}, }, expected: "Foooooo Baaaaar Baaaaaz ", @@ -85,7 +263,7 @@ func TestRenderRow(t *testing.T) { name: "simple row with truncations", table: &Model{ rows: []Row{{"Foooooooooo", "Baaaaaaaaar", "Quuuuuuuuux"}}, - cols: cols, + cols: testCols, styles: Styles{Cell: lipgloss.NewStyle()}, }, expected: "Foooooooo…Baaaaaaaa…Quuuuuuuu…", @@ -94,7 +272,7 @@ func TestRenderRow(t *testing.T) { name: "simple row avoiding truncations", table: &Model{ rows: []Row{{"Fooooooooo", "Baaaaaaaar", "Quuuuuuuux"}}, - cols: cols, + cols: testCols, styles: Styles{Cell: lipgloss.NewStyle()}, }, expected: "FoooooooooBaaaaaaaarQuuuuuuuux", @@ -164,3 +342,432 @@ func ansiStrip(s string) string { s = strings.ReplaceAll(s, "\r\n", "\n") return ansi.Strip(s) } + +func TestCursorNavigation(t *testing.T) { + tests := map[string]struct { + rows []Row + action func(*Model) + want int + }{ + "New": { + rows: []Row{ + {"r1"}, + {"r2"}, + {"r3"}, + }, + action: func(_ *Model) {}, + want: 0, + }, + "MoveDown": { + rows: []Row{ + {"r1"}, + {"r2"}, + {"r3"}, + {"r4"}, + }, + action: func(t *Model) { + t.MoveDown(2) + }, + want: 2, + }, + "MoveUp": { + rows: []Row{ + {"r1"}, + {"r2"}, + {"r3"}, + {"r4"}, + }, + action: func(t *Model) { + t.cursor = 3 + t.MoveUp(2) + }, + want: 1, + }, + "GotoBottom": { + rows: []Row{ + {"r1"}, + {"r2"}, + {"r3"}, + {"r4"}, + }, + action: func(t *Model) { + t.GotoBottom() + }, + want: 3, + }, + "GotoTop": { + rows: []Row{ + {"r1"}, + {"r2"}, + {"r3"}, + {"r4"}, + }, + action: func(t *Model) { + t.cursor = 3 + t.GotoTop() + }, + want: 0, + }, + "SetCursor": { + rows: []Row{ + {"r1"}, + {"r2"}, + {"r3"}, + {"r4"}, + }, + action: func(t *Model) { + t.SetCursor(2) + }, + want: 2, + }, + "MoveDown with overflow": { + rows: []Row{ + {"r1"}, + {"r2"}, + {"r3"}, + {"r4"}, + }, + action: func(t *Model) { + t.MoveDown(5) + }, + want: 3, + }, + "MoveUp with overflow": { + rows: []Row{ + {"r1"}, + {"r2"}, + {"r3"}, + {"r4"}, + }, + action: func(t *Model) { + t.cursor = 3 + t.MoveUp(5) + }, + want: 0, + }, + "Blur does not stop movement": { + rows: []Row{ + {"r1"}, + {"r2"}, + {"r3"}, + {"r4"}, + }, + action: func(t *Model) { + t.Blur() + t.MoveDown(2) + }, + want: 2, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + table := New(WithColumns(testCols), WithRows(tc.rows)) + tc.action(&table) + + if table.Cursor() != tc.want { + t.Errorf("want %d, got %d", tc.want, table.Cursor()) + } + }) + } +} + +func TestModel_SetRows(t *testing.T) { + table := New(WithColumns(testCols)) + + if len(table.rows) != 0 { + t.Fatalf("want 0, got %d", len(table.rows)) + } + + table.SetRows([]Row{{"r1"}, {"r2"}}) + + if len(table.rows) != 2 { + t.Fatalf("want 2, got %d", len(table.rows)) + } + + want := []Row{{"r1"}, {"r2"}} + if !reflect.DeepEqual(table.rows, want) { + t.Fatalf("\n\nwant %v\n\ngot %v", want, table.rows) + } +} + +func TestModel_SetColumns(t *testing.T) { + table := New() + + if len(table.cols) != 0 { + t.Fatalf("want 0, got %d", len(table.cols)) + } + + table.SetColumns([]Column{{Title: "Foo"}, {Title: "Bar"}}) + + if len(table.cols) != 2 { + t.Fatalf("want 2, got %d", len(table.cols)) + } + + want := []Column{{Title: "Foo"}, {Title: "Bar"}} + if !reflect.DeepEqual(table.cols, want) { + t.Fatalf("\n\nwant %v\n\ngot %v", want, table.cols) + } +} + +func TestModel_View(t *testing.T) { + tests := map[string]struct { + modelFunc func() Model + skip bool + }{ + // TODO(?): should the view/output of empty tables use the same default height? (this has height 21) + "Empty": { + modelFunc: func() Model { + return New() + }, + }, + "Single row and column": { + modelFunc: func() Model { + return New( + WithColumns([]Column{ + {Title: "Name", Width: 25}, + }), + WithRows([]Row{ + {"Chocolate Digestives"}, + }), + ) + }, + }, + "Multiple rows and columns": { + modelFunc: func() Model { + return New( + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows([]Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + }), + ) + }, + }, + // TODO(fix): since the table height is tied to the viewport height, adding vertical padding to the headers' height directly increases the table height. + "Extra padding": { + modelFunc: func() Model { + s := DefaultStyles() + s.Header = lipgloss.NewStyle().Padding(2, 2) + s.Cell = lipgloss.NewStyle().Padding(2, 2) + + return New( + WithHeight(10), + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows([]Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + }), + WithStyles(s), + ) + }, + }, + "No padding": { + modelFunc: func() Model { + s := DefaultStyles() + s.Header = lipgloss.NewStyle() + s.Cell = lipgloss.NewStyle() + + return New( + WithHeight(10), + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows([]Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + }), + WithStyles(s), + ) + }, + }, + // TODO(?): the total height is modified with borderd headers, however not with bordered cells. Is this expected/desired? + "Bordered headers": { + modelFunc: func() Model { + return New( + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows([]Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + }), + WithStyles(Styles{ + Header: lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()), + }), + ) + }, + }, + // TODO(fix): Headers are not horizontally aligned with cells due to the border adding width to the cells. + "Bordered cells": { + modelFunc: func() Model { + return New( + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows([]Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + }), + WithStyles(Styles{ + Cell: lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()), + }), + ) + }, + }, + "Manual height greater than rows": { + modelFunc: func() Model { + return New( + WithHeight(6), + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows([]Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + }), + ) + }, + }, + "Manual height less than rows": { + modelFunc: func() Model { + return New( + WithHeight(2), + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows([]Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + }), + ) + }, + }, + // TODO(fix): spaces are added to the right of the viewport to fill the width, but the headers end as though they are not aware of the width. + "Manual width greater than columns": { + modelFunc: func() Model { + return New( + WithWidth(80), + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows([]Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + }), + ) + }, + }, + // TODO(fix): Setting the table width does not affect the total headers' width. Cells are wrapped. + // Headers are not affected. Truncation/resizing should match lipgloss.table functionality. + "Manual width less than columns": { + modelFunc: func() Model { + return New( + WithWidth(30), + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows([]Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + }), + ) + }, + skip: true, + }, + "Modified viewport height": { + modelFunc: func() Model { + m := New( + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows([]Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + }), + ) + + m.viewport.SetHeight(2) + + return m + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + if tc.skip { + t.Skip() + } + + table := tc.modelFunc() + + got := ansi.Strip(table.View()) + + golden.RequireEqual(t, []byte(got)) + }) + } +} + +// TODO: Fix table to make this test will pass. +func TestModel_View_CenteredInABox(t *testing.T) { + t.Skip() + + boxStyle := lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + Align(lipgloss.Center) + + table := New( + WithHeight(6), + WithWidth(80), + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows([]Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + }), + ) + + tableView := ansi.Strip(table.View()) + got := boxStyle.Render(tableView) + + golden.RequireEqual(t, []byte(got)) +} diff --git a/table/testdata/TestModel_View/Bordered_cells.golden b/table/testdata/TestModel_View/Bordered_cells.golden new file mode 100644 index 000000000..71e95988b --- /dev/null +++ b/table/testdata/TestModel_View/Bordered_cells.golden @@ -0,0 +1,21 @@ +Name Country of Orig…Dunk-able +┌─────────────────────────┐┌────────────────┐┌────────────┐ +│Chocolate Digestives ││UK ││Yes │ +└─────────────────────────┘└────────────────┘└────────────┘ +┌─────────────────────────┐┌────────────────┐┌────────────┐ +│Tim Tams ││Australia ││No │ +└─────────────────────────┘└────────────────┘└────────────┘ +┌─────────────────────────┐┌────────────────┐┌────────────┐ +│Hobnobs ││UK ││Yes │ +└─────────────────────────┘└────────────────┘└────────────┘ + + + + + + + + + + + \ No newline at end of file diff --git a/table/testdata/TestModel_View/Bordered_headers.golden b/table/testdata/TestModel_View/Bordered_headers.golden new file mode 100644 index 000000000..0e260bae2 --- /dev/null +++ b/table/testdata/TestModel_View/Bordered_headers.golden @@ -0,0 +1,23 @@ +┌─────────────────────────┐┌────────────────┐┌────────────┐ +│Name ││Country of Orig…││Dunk-able │ +└─────────────────────────┘└────────────────┘└────────────┘ +Chocolate Digestives UK Yes +Tim Tams Australia No +Hobnobs UK Yes + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/table/testdata/TestModel_View/Empty.golden b/table/testdata/TestModel_View/Empty.golden new file mode 100644 index 000000000..7b050800f --- /dev/null +++ b/table/testdata/TestModel_View/Empty.golden @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/table/testdata/TestModel_View/Extra_padding.golden b/table/testdata/TestModel_View/Extra_padding.golden new file mode 100644 index 000000000..d6f6b76e8 --- /dev/null +++ b/table/testdata/TestModel_View/Extra_padding.golden @@ -0,0 +1,14 @@ + + +  Name     Country of Orig…    Dunk-able    + + + + +  Chocolate Digestives     UK     Yes    + + + + +  Tim Tams     Australia     No    + \ No newline at end of file diff --git a/table/testdata/TestModel_View/Manual_height_greater_than_rows.golden b/table/testdata/TestModel_View/Manual_height_greater_than_rows.golden new file mode 100644 index 000000000..f89de1df9 --- /dev/null +++ b/table/testdata/TestModel_View/Manual_height_greater_than_rows.golden @@ -0,0 +1,6 @@ + Name   Country of Orig…  Dunk-able   + Chocolate Digestives   UK   Yes   + Tim Tams   Australia   No   + Hobnobs   UK   Yes   + + \ No newline at end of file diff --git a/table/testdata/TestModel_View/Manual_height_less_than_rows.golden b/table/testdata/TestModel_View/Manual_height_less_than_rows.golden new file mode 100644 index 000000000..83bded11d --- /dev/null +++ b/table/testdata/TestModel_View/Manual_height_less_than_rows.golden @@ -0,0 +1,2 @@ + Name   Country of Orig…  Dunk-able   + Chocolate Digestives   UK   Yes   \ No newline at end of file diff --git a/table/testdata/TestModel_View/Manual_width_greater_than_columns.golden b/table/testdata/TestModel_View/Manual_width_greater_than_columns.golden new file mode 100644 index 000000000..a1c06fee1 --- /dev/null +++ b/table/testdata/TestModel_View/Manual_width_greater_than_columns.golden @@ -0,0 +1,21 @@ + Name   Country of Orig…  Dunk-able   + Chocolate Digestives   UK   Yes   + Tim Tams   Australia   No   + Hobnobs   UK   Yes   + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/table/testdata/TestModel_View/Manual_width_less_than_columns.golden b/table/testdata/TestModel_View/Manual_width_less_than_columns.golden new file mode 100644 index 000000000..d2bc25b96 --- /dev/null +++ b/table/testdata/TestModel_View/Manual_width_less_than_columns.golden @@ -0,0 +1,21 @@ + Name Country of Origin Dunk-able + Chocolate Digestives UK Yes + Tim Tams Australia No + Hobnobs UK Yes + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/table/testdata/TestModel_View/Modified_viewport_height.golden b/table/testdata/TestModel_View/Modified_viewport_height.golden new file mode 100644 index 000000000..98a44eeb9 --- /dev/null +++ b/table/testdata/TestModel_View/Modified_viewport_height.golden @@ -0,0 +1,3 @@ + Name   Country of Orig…  Dunk-able   + Chocolate Digestives   UK   Yes   + Tim Tams   Australia   No   \ No newline at end of file diff --git a/table/testdata/TestModel_View/Multiple_rows_and_columns.golden b/table/testdata/TestModel_View/Multiple_rows_and_columns.golden new file mode 100644 index 000000000..d1dffc5ba --- /dev/null +++ b/table/testdata/TestModel_View/Multiple_rows_and_columns.golden @@ -0,0 +1,21 @@ + Name   Country of Orig…  Dunk-able   + Chocolate Digestives   UK   Yes   + Tim Tams   Australia   No   + Hobnobs   UK   Yes   + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/table/testdata/TestModel_View/No_padding.golden b/table/testdata/TestModel_View/No_padding.golden new file mode 100644 index 000000000..f74874668 --- /dev/null +++ b/table/testdata/TestModel_View/No_padding.golden @@ -0,0 +1,10 @@ +Name Country of Orig…Dunk-able +Chocolate Digestives UK Yes +Tim Tams Australia No +Hobnobs UK Yes + + + + + + \ No newline at end of file diff --git a/table/testdata/TestModel_View/Single_row_and_column.golden b/table/testdata/TestModel_View/Single_row_and_column.golden new file mode 100644 index 000000000..36d5110eb --- /dev/null +++ b/table/testdata/TestModel_View/Single_row_and_column.golden @@ -0,0 +1,21 @@ + Name   + Chocolate Digestives   + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/table/testdata/TestModel_View_CenteredInABox.golden b/table/testdata/TestModel_View_CenteredInABox.golden new file mode 100644 index 000000000..59d9c7030 --- /dev/null +++ b/table/testdata/TestModel_View_CenteredInABox.golden @@ -0,0 +1,8 @@ +┌────────────────────────────────────────────────────────────────────────────────┐ +│ Name Country of Orig… Dunk-able │ +│ Chocolate Digestives UK Yes │ +│ Tim Tams Australia No │ +│ Hobnobs UK Yes │ +│ │ +│ │ +└────────────────────────────────────────────────────────────────────────────────┘ \ No newline at end of file