package beads import ( "context" "net/url" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sourcecraft.dev/bigbes/sr-ht-dolt/browse" ) // The two readings of the string "NULL", and the mask that tells them apart. // // browse renders a cell that holds no value as the text "NULL", which is what a // row storing those four characters renders as too. Flattening both to "" — as // this package did before the mask existed — is right for the first and wrong // for the second: an issue titled "NULL" arrived with an empty title, and no // projection could tell that it had one. // nullMaskFixture is one issues table carrying both readings in the same // columns: i-absent holds no value in title or closed_at, i-stored stores the // text "NULL" in both. func nullMaskFixture() *fakeSession { issues := &browse.RowPage{ Columns: []string{"id", "title", "status", "priority", "issue_type", "assignee", "created_at", "closed_at", "is_blocked"}, Rows: [][]string{ {"i-absent", "NULL", "open", "1", "task", "alice", "2024-01-01", "NULL", "0"}, {"i-stored", "NULL", "open", "1", "task", "alice", "2024-01-02", "NULL", "0"}, }, Nulls: [][]bool{ // title and closed_at hold no value at all. {false, true, false, false, false, false, false, true, false}, // every cell holds a value; the two "NULL"s are stored text. {false, false, false, false, false, false, false, false, false}, }, Total: 2, } deps := &browse.RowPage{ Columns: []string{"id", "issue_id", "depends_on_issue_id", "type"}, Rows: [][]string{}, Nulls: [][]bool{}, } return &fakeSession{rowsByTable: map[string]*browse.RowPage{ "issues": issues, "dependencies": deps, }} } // cell is where the distinction is actually made, so it is asked directly first: // the same string, read two ways, decided by the mask beside it. func TestCellReadsTheNullMaskAndNotTheString(t *testing.T) { cols := indexCols([]string{"id", "title"}) absent := rowCells{values: []string{"i-absent", "NULL"}, nulls: []bool{false, true}} stored := rowCells{values: []string{"i-stored", "NULL"}, nulls: []bool{false, false}} assert.Equal(t, "", cell(cols, absent, "title"), "a cell that holds no value reads as empty, which every projection depends on") assert.Equal(t, "NULL", cell(cols, stored, "title"), "a cell that stores the text \"NULL\" has a value, and it is that text") assert.Equal(t, "", cell(cols, stored, "nosuchcolumn"), "an absent column is still empty") } // A page that carries no mask at all cannot answer the question, and the reading // that predates the mask is all there is: "NULL" reads as absent. Every page // browse returns carries one, so this is about a page built by hand. func TestCellWithoutAMaskKeepsTheOlderReading(t *testing.T) { cols := indexCols([]string{"id", "title"}) unmasked := rowCells{values: []string{"i-1", "NULL"}} assert.Equal(t, "", cell(cols, unmasked, "title")) assert.Equal(t, "i-1", cell(cols, unmasked, "id")) } // rowsOf pairs each row with its own mask, and a page with none hands out rows // that answer "unknown" rather than rows that answer "not null". func TestRowsOfPairsEveryRowWithItsMask(t *testing.T) { page := &browse.RowPage{ Columns: []string{"id", "title"}, Rows: [][]string{{"a", "NULL"}, {"b", "NULL"}}, Nulls: [][]bool{{false, true}, {false, false}}, } rows := rowsOf(page) require.Len(t, rows, 2) assert.Equal(t, []bool{false, true}, rows[0].nulls) assert.Equal(t, []bool{false, false}, rows[1].nulls) unmasked := rowsOf(&browse.RowPage{Columns: []string{"id"}, Rows: [][]string{{"a"}}}) require.Len(t, unmasked, 1) assert.Nil(t, unmasked[0].nulls, "a page with no mask answers no mask, not an all-false one") assert.Empty(t, rowsOf(nil), "a table that is absent has no rows to pair") } // The projections are where it is felt: a title that is literally "NULL" must // survive to the detail pane, and a closed_at that holds no value must keep // reading as empty. func TestProjectionsKeepAStoredNullAndDropARealOne(t *testing.T) { ctx := context.Background() stored, err := Build(ctx, nullMaskFixture(), "main", url.Values{"issue": {"i-stored"}}) require.NoError(t, err) require.NotNil(t, stored.Issue) assert.Equal(t, "NULL", stored.Issue.Title, "the row stores those four characters: an empty title would lose them") assert.Equal(t, "NULL", stored.Issue.ClosedAt) absent, err := Build(ctx, nullMaskFixture(), "main", url.Values{"issue": {"i-absent"}}) require.NoError(t, err) require.NotNil(t, absent.Issue) assert.Equal(t, "", absent.Issue.Title, "no value is no text, as every projection reads it") assert.Equal(t, "", absent.Issue.ClosedAt) } // The board goes through the same rows, and the same two readings have to reach // the cards. func TestBoardCardsKeepAStoredNullTitle(t *testing.T) { d, err := Build(context.Background(), nullMaskFixture(), "main", url.Values{}) require.NoError(t, err) titles := map[string]string{} for _, lane := range d.Lanes { for _, c := range lane.Issues { titles[c.ID] = c.Title } } assert.Equal(t, "NULL", titles["i-stored"]) assert.Equal(t, "", titles["i-absent"]) }