1package e2e23import (4 "fmt"5 "strings"6)78// Fixtures shared by the golden screens in screenlist_test.go: sheets9// typed or pasted the way a user would, and steps several screens take.1011// budget types a small sheet with text, numbers, formulas and an error the12// way a Sheets user would (Tab across, Enter back), then selects the total.13func budget(s *session) {14 s.keys("Household budget 2026", "<enter>", "<down>")15 s.keys("Rent", "<tab>", "1450", "<enter>")16 s.keys("Groceries", "<tab>", "612.4", "<enter>")17 s.keys("Transit", "<tab>", "96", "<enter>")18 s.keys("Savings rate", "<tab>", "=B3/0", "<enter>")19 s.keys("Total", "<tab>", "=SUM(B3:B5)", "<enter>")20 s.keys("<up>", "<right>")21 s.waitForBar("B7", "=SUM(B3:B5)")22}2324// formatted types a bill schedule with dates, currency and percentages,25// then styles it with Sheets' shortcuts: bold title and totals, headers26// aligned over their numbers, a struck-out cancelled bill and an italic27// note. It ends by bolding the totals, so line 3 shows the feedback.28func formatted(s *session) {29 s.keys("Bills for October", "<enter>")30 s.keys("Item", "<tab>", "Due", "<tab>", "Amount", "<tab>", "Share", "<enter>")31 s.keys("Rent", "<tab>", "10/1/2026", "<tab>", "$1,450.00", "<tab>", "=C3/C$7", "<enter>")32 s.keys("Food", "<tab>", "9/28/2026", "<tab>", "612.4", "<tab>", "=C4/C$7", "<enter>")33 s.keys("Transit", "<tab>", "9/30/2026", "<tab>", "96", "<tab>", "=C5/C$7", "<enter>")34 s.keys("Gym", "<tab>", "10/5/2026", "<tab>", "40", "<tab>", "=C6/C$7", "<enter>")35 s.keys("Total", "<tab>", "<tab>", "=SUM(C3:C6)", "<tab>", "=SUM(D3:D6)", "<enter>")36 s.keys("<down>", "<left>", "<left>", "Gym cancelled from November", "<enter>")37 // Currency for the amounts, percent for the shares.38 s.keys("<ctrl+home>", "<down>", "<down>", "<down>", "<right>", "<right>", "<shift+down>", "<shift+down>", "<ctrl+shift+4>")39 s.keys("<right>", "<up>", "<shift+down>", "<shift+down>", "<shift+down>", "<shift+down>", "<ctrl+shift+5>")40 // Bold title, bold headers with the number headers on the right.41 s.keys("<ctrl+home>", "<ctrl+b>", "<down>", "<shift+right>", "<shift+right>", "<shift+right>", "<ctrl+b>")42 s.keys("<right>", "<shift+right>", "<shift+right>", "<ctrl+shift+r>")43 // The cancelled bill is struck out; the note is italic.44 s.keys("<ctrl+home>", "<down>", "<down>", "<down>", "<down>", "<down>", "<shift+right>", "<shift+right>", "<shift+right>", "<alt+shift+5>")45 s.keys("<down>", "<down>", "<down>", "<ctrl+i>")46 s.keys("<up>", "<up>", "<shift+right>", "<shift+right>", "<shift+right>", "<ctrl+b>")47 s.waitFor("Bold on for A7:D7")48}4950// reviews types customer reviews with JEV columns, answered by a fake51// service so the screen is deterministic.52func reviews(s *session) {53 s.keys("Review", "<tab>", "Complaint?", "<tab>", "Likely", "<tab>", "Sentiment", "<tab>", "Urgency", "<enter>")54 for i, r := range []string{"Box arrived crushed", "Fast and friendly", "Wrong size, no reply"} {55 row := fmt.Sprint(i + 2)56 s.keys(r, "<tab>",57 `=JEV.TEST(A`+row+`, "Is this a complaint?")`, "<tab>",58 `=JEV.PROB(A`+row+`, "Is this a complaint?")`, "<tab>",59 `=JEV.CLASSIFY(A`+row+`, "Sentiment", "negative, positive")`, "<tab>",60 `=JEV.SCORE(A`+row+`, "Urgency", "low, mid, high")`, "<enter>")61 }62 s.keys("<up>", "<right>", "<right>", "<right>")63 s.waitFor("JEV: positive, 80% confident")64}6566// inventory pastes a stock list of 40 items with a header row, the way67// data usually arrives, and returns to A1.68func inventory(s *session) {69 items := []string{"Bolts", "Nuts", "Washers", "Hinges", "Brackets", "Screws", "Anchors", "Rivets"}70 cats := []string{"Fasteners", "Fasteners", "Fasteners", "Hardware", "Hardware", "Fasteners", "Hardware", "Fasteners"}71 rows := []string{"Item\tCategory\tBin\tQty\tPrice\tReorder"}72 for i := range 40 {73 k := i % len(items)74 rows = append(rows, fmt.Sprintf("%s %d\t%s\tB%02d\t%d\t%.2f\t%s",75 items[k], i/len(items)+1, cats[k], i%12+1, (i*37)%90+5, float64((i*53)%400)/20+0.5, []string{"no", "yes"}[i%3/2]))76 }77 s.paste(strings.Join(rows, "\n"))78 s.waitFor("Pasted 246 cells")79 s.keys("<ctrl+home>")80 s.waitForName("A1")81}8283// pivotEditor creates a pivot of the inventory in the editor: Category84// in Rows, Reorder in Columns, the sum of Qty and the average Price,85// leaving the average highlighted.86func pivotEditor(s *session) {87 inventory(s)88 s.keys("<ctrl+k>", "pivot table", "<enter>")89 s.waitFor("Data Sheet1!A1:F41")90 s.keys("<space>", "categ", "<enter>")91 s.waitFor(" Category")92 s.keys("<down>", "<space>", "reord", "<enter>")93 s.waitFor(" Reorder")94 s.keys("<down>", "<space>", "qty", "<enter>")95 s.waitFor("‹ SUM ›")96 s.keys("a", "price", "<enter>", "<left>", "<left>", "<left>")97 s.waitFor("‹ AVERAGE ›")98}99100// named is the budget with its expenses named and a formula using the101// name, ending on the total.102func named(s *session) {103 budget(s)104 s.keys("<up>", "<up>", "<up>", "<up>", "<shift+down>", "<shift+down>", "<alt+d>", "d", "<enter>", "Expenses", "<enter>")105 s.waitForName("Expenses")106 s.keys("<esc>", "<ctrl+home>", "<down>", "<right>", "<right>", "4", "<enter>")107 s.keys("<ctrl+home>", "<down>", "<down>", "<down>", "<down>", "<down>", "<down>", "<down>", "<right>")108 s.keys("=AVERAGE(Expenses)*C2", "<enter>")109 s.keys("<up>")110 s.waitForBar("B8", "=AVERAGE(Expenses)*C2")111}112113// chartOfType inserts a chart, picks the type n steps right of Column in114// the editor and keeps it, leaving the chart deselected.115func chartOfType(s *session, n int, title string) {116 spending(s)117 insertChart(s)118 for range n {119 s.keys("<right>")120 }121 s.keys("<enter>")122 s.waitFor(title + " of A1:C5")123 s.keys("<esc>")124 s.waitFor("READY")125}126127// chartWith inserts a chart, presses keys in the editor and keeps it,128// leaving the chart deselected.129func chartWith(s *session, title string, keys ...string) {130 spending(s)131 insertChart(s)132 s.keys(keys...)133 s.keys("<enter>")134 s.waitFor(title + " of A1:C5")135 s.keys("<esc>")136 s.waitFor("READY")137}138139func openImportPicker(s *session) {140 s.keys("<alt+f>", "<down>", "<down>", "<enter>")141 s.waitFor("6 of 6")142}