Skip to content

Commit 81782b4

Browse files
authored
Render HTTP Test Status (#28)
* render http test status * test rendering refactoring * fix http test status conditionals * rm janky indentation func
1 parent acbe312 commit 81782b4

File tree

6 files changed

+341
-96
lines changed

6 files changed

+341
-96
lines changed

client/lessons.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -111,19 +111,24 @@ type submitHTTPTestRequest struct {
111111
ActualHTTPRequests any `json:"actualHTTPRequests"`
112112
}
113113

114-
func SubmitHTTPTestLesson(uuid string, results any) error {
114+
func SubmitHTTPTestLesson(uuid string, results any) (*HTTPTestValidationError, error) {
115115
bytes, err := json.Marshal(submitHTTPTestRequest{ActualHTTPRequests: results})
116116
if err != nil {
117-
return err
117+
return nil, err
118118
}
119119
resp, code, err := fetchWithAuthAndPayload("POST", "/v1/lessons/"+uuid+"/http_tests", bytes)
120120
if err != nil {
121-
return err
121+
return nil, err
122122
}
123123
if code != 200 {
124-
return fmt.Errorf("failed to submit HTTP tests. code: %v: %s", code, string(resp))
124+
return nil, fmt.Errorf("failed to submit HTTP tests. code: %v: %s", code, string(resp))
125+
}
126+
var failure HTTPTestValidationError
127+
err = json.Unmarshal(resp, &failure)
128+
if err != nil || failure.ErrorMessage == nil {
129+
return nil, nil
125130
}
126-
return nil
131+
return &failure, nil
127132
}
128133

129134
type submitCLICommandRequest struct {

cmd/submit.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package cmd
22

33
import (
44
"errors"
5-
"fmt"
65

76
"github.com/bootdotdev/bootdev/checks"
87
api "github.com/bootdotdev/bootdev/client"
@@ -41,14 +40,16 @@ func submissionHandler(cmd *cobra.Command, args []string) error {
4140
}
4241
switch lesson.Lesson.Type {
4342
case "type_http_tests":
44-
results, finalBaseURL := checks.HttpTest(*lesson, &submitBaseURL)
45-
render.PrintHTTPResults(results, lesson, finalBaseURL)
43+
results, _ := checks.HttpTest(*lesson, &submitBaseURL)
44+
data := *lesson.Lesson.LessonDataHTTPTests
4645
if isSubmit {
47-
err := api.SubmitHTTPTestLesson(lessonUUID, results)
46+
failure, err := api.SubmitHTTPTestLesson(lessonUUID, results)
4847
if err != nil {
4948
return err
5049
}
51-
fmt.Println("\nSubmitted! Check the lesson on Boot.dev for results")
50+
render.HTTPSubmission(data, results, failure)
51+
} else {
52+
render.HTTPRun(data, results)
5253
}
5354
case "type_cli_command":
5455
results := checks.CLICommand(*lesson, optionalPositionalArgs)

render/command.go

+15-69
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,7 @@ import (
1515
"github.com/spf13/viper"
1616
)
1717

18-
var green lipgloss.Style
19-
var red lipgloss.Style
20-
var gray lipgloss.Style
21-
var cmdBox = lipgloss.NewStyle().Border(lipgloss.RoundedBorder())
22-
23-
type doneMsg struct {
18+
type doneCmdMsg struct {
2419
failure *api.StructuredErrCLICommand
2520
}
2621

@@ -34,21 +29,6 @@ type resolveCmdMsg struct {
3429
results *api.CLICommandResult
3530
}
3631

37-
type startTestMsg struct {
38-
text string
39-
}
40-
41-
type resolveTestMsg struct {
42-
index int
43-
passed *bool
44-
}
45-
46-
type testModel struct {
47-
text string
48-
passed *bool
49-
finished bool
50-
}
51-
5232
type cmdModel struct {
5333
command string
5434
passed *bool
@@ -57,7 +37,7 @@ type cmdModel struct {
5737
tests []testModel
5838
}
5939

60-
type rootModel struct {
40+
type cmdRootModel struct {
6141
cmds []cmdModel
6242
spinner spinner.Model
6343
failure *api.StructuredErrCLICommand
@@ -67,26 +47,26 @@ type rootModel struct {
6747
clear bool
6848
}
6949

70-
func initialModel(isSubmit bool) rootModel {
50+
func initialModelCmd(isSubmit bool) cmdRootModel {
7151
s := spinner.New()
7252
s.Spinner = spinner.Dot
73-
return rootModel{
53+
return cmdRootModel{
7454
spinner: s,
7555
isSubmit: isSubmit,
7656
cmds: []cmdModel{},
7757
}
7858
}
7959

80-
func (m rootModel) Init() tea.Cmd {
60+
func (m cmdRootModel) Init() tea.Cmd {
8161
green = lipgloss.NewStyle().Foreground(lipgloss.Color(viper.GetString("color.green")))
8262
red = lipgloss.NewStyle().Foreground(lipgloss.Color(viper.GetString("color.red")))
8363
gray = lipgloss.NewStyle().Foreground(lipgloss.Color(viper.GetString("color.gray")))
8464
return m.spinner.Tick
8565
}
8666

87-
func (m rootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
67+
func (m cmdRootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
8868
switch msg := msg.(type) {
89-
case doneMsg:
69+
case doneCmdMsg:
9070
m.failure = msg.failure
9171
if m.failure == nil && m.isSubmit {
9272
m.success = true
@@ -123,49 +103,15 @@ func (m rootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
123103
}
124104
}
125105

126-
func (m rootModel) View() string {
106+
func (m cmdRootModel) View() string {
127107
if m.clear {
128108
return ""
129109
}
130110
s := m.spinner.View()
131111
var str string
132112
for _, cmd := range m.cmds {
133-
var cmdStr string
134-
if !cmd.finished {
135-
cmdStr += fmt.Sprintf("%s %s", s, cmd.command)
136-
} else if !m.isSubmit {
137-
cmdStr += cmd.command
138-
} else if cmd.passed == nil {
139-
cmdStr += gray.Render(fmt.Sprintf("? %s", cmd.command))
140-
} else if *cmd.passed {
141-
cmdStr += green.Render(fmt.Sprintf("✓ %s", cmd.command))
142-
} else {
143-
cmdStr += red.Render(fmt.Sprintf("X %s", cmd.command))
144-
}
145-
box := cmdBox.Render(fmt.Sprintf(" %s ", cmdStr))
146-
// monkey patching the border on the box lol
147-
sliced := strings.Split(box, "\n")
148-
sliced[2] = strings.Replace(sliced[2], "─", "┬", 1)
149-
str += strings.Join(sliced, "\n")
150-
for _, test := range cmd.tests {
151-
var testStr string
152-
if !test.finished {
153-
testStr += fmt.Sprintf(" %s %s", s, test.text)
154-
} else if test.passed == nil {
155-
testStr += gray.Render(fmt.Sprintf(" ? %s", test.text))
156-
} else if *test.passed {
157-
testStr += green.Render(fmt.Sprintf(" ✓ %s", test.text))
158-
} else {
159-
testStr += red.Render(fmt.Sprintf(" X %s", test.text))
160-
}
161-
edges := " ├─"
162-
for i := 0; i < lipgloss.Height(testStr)-1; i++ {
163-
edges += "\n │ "
164-
}
165-
testStr = lipgloss.JoinHorizontal(lipgloss.Top, edges, testStr)
166-
str = lipgloss.JoinVertical(lipgloss.Left, str, testStr)
167-
}
168-
str += "\n"
113+
str += renderTestHeader(cmd.command, m.spinner, cmd.finished, m.isSubmit, cmd.passed)
114+
str += renderTests(cmd.tests, s)
169115
if cmd.results != nil && m.finalized {
170116
// render the results
171117
str += fmt.Sprintf("\n > Command exit code: %d\n", cmd.results.ExitCode)
@@ -185,7 +131,7 @@ func (m rootModel) View() string {
185131
return str
186132
}
187133

188-
func prettyPrint(test api.CLICommandTestCase) string {
134+
func prettyPrintCmd(test api.CLICommandTestCase) string {
189135
if test.ExitCode != nil {
190136
return fmt.Sprintf("Expect exit code %d", *test.ExitCode)
191137
}
@@ -239,13 +185,13 @@ func commandRenderer(
239185
) {
240186
var wg sync.WaitGroup
241187
ch := make(chan tea.Msg, 1)
242-
p := tea.NewProgram(initialModel(isSubmit), tea.WithoutSignalHandler())
188+
p := tea.NewProgram(initialModelCmd(isSubmit), tea.WithoutSignalHandler())
243189
wg.Add(1)
244190
go func() {
245191
defer wg.Done()
246192
if model, err := p.Run(); err != nil {
247193
fmt.Fprintln(os.Stderr, err)
248-
} else if r, ok := model.(rootModel); ok {
194+
} else if r, ok := model.(cmdRootModel); ok {
249195
r.clear = false
250196
r.finalized = true
251197
output := termenv.NewOutput(os.Stdout)
@@ -265,7 +211,7 @@ func commandRenderer(
265211
for i, cmd := range data.CLICommandData.Commands {
266212
ch <- startCmdMsg{cmd: results[i].FinalCommand}
267213
for _, test := range cmd.Tests {
268-
ch <- startTestMsg{text: prettyPrint(test)}
214+
ch <- startTestMsg{text: prettyPrintCmd(test)}
269215
}
270216
time.Sleep(500 * time.Millisecond)
271217
for j := range cmd.Tests {
@@ -294,7 +240,7 @@ func commandRenderer(
294240
}
295241
time.Sleep(500 * time.Millisecond)
296242

297-
ch <- doneMsg{failure: failure}
243+
ch <- doneCmdMsg{failure: failure}
298244
}()
299245
wg.Wait()
300246
}

render/common.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package render
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/charmbracelet/bubbles/spinner"
8+
"github.com/charmbracelet/lipgloss"
9+
)
10+
11+
var green lipgloss.Style
12+
var red lipgloss.Style
13+
var gray lipgloss.Style
14+
var borderBox = lipgloss.NewStyle().Border(lipgloss.RoundedBorder())
15+
16+
type testModel struct {
17+
text string
18+
passed *bool
19+
finished bool
20+
}
21+
22+
type startTestMsg struct {
23+
text string
24+
}
25+
26+
type resolveTestMsg struct {
27+
index int
28+
passed *bool
29+
}
30+
31+
func renderTestHeader(header string, spinner spinner.Model, isFinished bool, isSubmit bool, passed *bool) string {
32+
cmdStr := renderTest(header, spinner.View(), isFinished, &isSubmit, passed)
33+
box := borderBox.Render(fmt.Sprintf(" %s ", cmdStr))
34+
sliced := strings.Split(box, "\n")
35+
sliced[2] = strings.Replace(sliced[2], "─", "┬", 1)
36+
return strings.Join(sliced, "\n")
37+
}
38+
39+
func renderTests(tests []testModel, spinner string) string {
40+
var str string
41+
for _, test := range tests {
42+
testStr := renderTest(test.text, spinner, test.finished, nil, test.passed)
43+
testStr = fmt.Sprintf("%s%s", " ", testStr)
44+
45+
edges := " ├─"
46+
for i := 0; i < lipgloss.Height(testStr)-1; i++ {
47+
edges += "\n │ "
48+
}
49+
testStr = lipgloss.JoinHorizontal(lipgloss.Top, edges, testStr)
50+
str = lipgloss.JoinVertical(lipgloss.Left, str, testStr)
51+
}
52+
str += "\n"
53+
return str
54+
}
55+
56+
func renderTest(text string, spinner string, isFinished bool, isSubmit *bool, passed *bool) string {
57+
testStr := ""
58+
if !isFinished {
59+
testStr += fmt.Sprintf("%s %s", spinner, text)
60+
} else if isSubmit != nil && !*isSubmit {
61+
testStr += text
62+
} else if passed == nil {
63+
testStr += gray.Render(fmt.Sprintf("? %s", text))
64+
} else if *passed {
65+
testStr += green.Render(fmt.Sprintf("✓ %s", text))
66+
} else {
67+
testStr += red.Render(fmt.Sprintf("X %s", text))
68+
}
69+
return testStr
70+
}

0 commit comments

Comments
 (0)