Skip to content

Commit d011623

Browse files
authored
Force UI into Running tests state when pressing "Run tests" button (exercism#7867)
* Force UI into Running tests state when pressing "Run tests" button * Adjust things to make tests pass, make faux-submission non-cancellable * Get rid of jest console.errors * Remove faux-submission on error
1 parent af9d6e8 commit d011623

File tree

5 files changed

+91
-23
lines changed

5 files changed

+91
-23
lines changed

app/javascript/components/Editor.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,7 @@ export default ({
282282
setSubmission(submission.uuid, { ...submission, testRun: testRun })
283283
},
284284

285-
// not stringifying this will lead to an infinite loop
286-
// see https://github.com/exercism/website/pull/3137#discussion_r1015500657
287-
// eslint-disable-next-line react-hooks/exhaustive-deps
288-
[setSubmission, JSON.stringify(submission)]
285+
[setSubmission, submission?.uuid]
289286
)
290287
const editorDidMount = useCallback(
291288
(editor) => {
@@ -335,7 +332,13 @@ export default ({
335332
}
336333
},
337334
})
338-
}, [submission, dispatch, revertToLastIteration, setFiles, defaultFiles])
335+
}, [
336+
submission?.uuid,
337+
dispatch,
338+
revertToLastIteration,
339+
setFiles,
340+
defaultFiles,
341+
])
339342

340343
const handleRevertToExerciseStart = useCallback(() => {
341344
if (!submission) {
@@ -378,7 +381,13 @@ export default ({
378381
}
379382
},
380383
})
381-
}, [submission, dispatch, revertToExerciseStart, setFiles, defaultFiles])
384+
}, [
385+
submission?.uuid,
386+
dispatch,
387+
revertToExerciseStart,
388+
setFiles,
389+
defaultFiles,
390+
])
382391

383392
const handleCancelled = useCallback(() => {
384393
if (!submission) {
@@ -387,7 +396,7 @@ export default ({
387396

388397
removeSubmission(submission.uuid)
389398
setHasCancelled(true)
390-
}, [removeSubmission, setHasCancelled, submission])
399+
}, [removeSubmission, setHasCancelled, submission?.uuid])
391400

392401
useEffect(() => {
393402
if (!submission) {

app/javascript/components/editor/TestRunSummary.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,8 @@ const TestRunSummaryContent = ({
264264
Estimated running time ~ {testRunner.averageTestDuration}s
265265
</span>
266266
</p>
267-
{onCancel !== undefined ? (
267+
{onCancel !== undefined &&
268+
testRun.submissionUuid !== 'faux-submission' ? (
268269
<button
269270
type="button"
270271
onClick={() => onCancel()}

app/javascript/components/editor/TestRunSummaryContainer.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const TestRunSummaryContainer = ({
2929
{
3030
endpoint: testRun.links.self,
3131
options: {
32+
enabled: testRun.submissionUuid !== 'faux-submission',
3233
refetchInterval:
3334
testRun.status === TestRunStatus.QUEUED ? REFETCH_INTERVAL : false,
3435
},
@@ -68,14 +69,16 @@ export const TestRunSummaryContainer = ({
6869
useEffect(() => {
6970
switch (testRun.status) {
7071
case TestRunStatus.QUEUED:
71-
handleQueued()
72+
if (testRun.submissionUuid !== 'faux-submission') {
73+
handleQueued()
74+
}
7275
break
7376
default:
7477
clearTimeout(timer.current)
7578
channel.current?.disconnect()
7679
break
7780
}
78-
}, [handleQueued, testRun.status])
81+
}, [handleQueued, testRun.status, testRun.submissionUuid])
7982

8083
useEffect(() => {
8184
channel.current = new TestRunChannel(testRun, (updatedTestRun: TestRun) => {

app/javascript/components/editor/useSubmissionsList.ts

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState, useEffect, useCallback } from 'react'
22
import { sendRequest } from '../../utils/send-request'
33
import { Submission, TestRunStatus, TestRun } from './types'
4-
import { File } from '../types'
4+
import { File, SubmissionTestsStatus } from '../types'
55
import { useMutation } from '@tanstack/react-query'
66
import { typecheck } from '../../utils/typecheck'
77

@@ -35,20 +35,20 @@ export const useSubmissionsList = (
3535
>(
3636
async ({ files, testResults }) => {
3737
const testResultsJson = testResults ? JSON.stringify(testResults) : null
38+
appendFaux()
3839
const { fetch } = sendRequest({
3940
endpoint: links.create,
4041
method: 'POST',
4142
body: JSON.stringify({ files, test_results_json: testResultsJson }),
4243
})
43-
4444
return fetch.then((response) =>
4545
typecheck<Submission>(response, 'submission')
4646
)
4747
},
4848
{
4949
onSuccess: (submission) => {
5050
setList([
51-
...list,
51+
...list.filter((s) => s.uuid !== 'faux-submission'),
5252
{
5353
...submission,
5454
testRun: {
@@ -70,21 +70,67 @@ export const useSubmissionsList = (
7070
},
7171
])
7272
},
73+
74+
onError: () => {
75+
setList((prevList) => {
76+
return prevList.filter((s) => s.uuid !== 'faux-submission')
77+
})
78+
},
7379
}
7480
)
7581

7682
const set = useCallback(
7783
(uuid: string, data: Submission) => {
78-
setList(list.map((s) => (s.uuid === uuid ? data : s)))
84+
setList((prevList) => prevList.map((s) => (s.uuid === uuid ? data : s)))
7985
},
80-
[JSON.stringify(list)]
86+
[setList]
8187
)
8288

89+
// append a faux submission at the moment someone clicks "Run Tests"
90+
// to force the UI into the "Running tests" state
91+
const appendFaux = useCallback(() => {
92+
const fauxSubmission: Submission = {
93+
testsStatus: SubmissionTestsStatus.NOT_QUEUED,
94+
uuid: 'faux-submission',
95+
links: {
96+
cancel: '',
97+
submit: '',
98+
testRun: '',
99+
aiHelp: '',
100+
initialFiles: '',
101+
lastIterationFiles: '',
102+
},
103+
}
104+
105+
setList((prev) => [
106+
...prev,
107+
{
108+
...fauxSubmission,
109+
testRun: {
110+
uuid: null,
111+
submissionUuid: fauxSubmission.uuid,
112+
version: 0,
113+
status: TestRunStatus.QUEUED,
114+
tests: [],
115+
message: '',
116+
messageHtml: '',
117+
output: '',
118+
outputHtml: '',
119+
highlightjsLanguage: '',
120+
links: {
121+
self: '',
122+
},
123+
tasks: [],
124+
},
125+
},
126+
])
127+
}, [setList])
128+
83129
const remove = useCallback(
84130
(uuid: string) => {
85-
setList(list.filter((s) => s.uuid !== uuid))
131+
setList((list) => list.filter((s) => s.uuid !== uuid))
86132
},
87-
[JSON.stringify(list)]
133+
[setList]
88134
)
89135

90136
const current = list[list.length - 1] || null
@@ -109,7 +155,7 @@ export const useSubmissionsList = (
109155

110156
set(current.uuid, { ...current, testRun: testRun })
111157
})
112-
}, [JSON.stringify(defaultList), set])
158+
}, [set])
113159

114160
return { current, create, set, remove }
115161
}

test/javascript/components/Editor/features/submissions.test.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ jest.mock(
22
'../../../../../app/javascript/components/editor/FileEditorCodeMirror'
33
)
44

5-
import React from 'react'
5+
import React, { act } from 'react'
66
import { waitFor, screen, fireEvent } from '@testing-library/react'
77
import userEvent from '@testing-library/user-event'
88
import '@testing-library/jest-dom/extend-expect'
@@ -60,7 +60,7 @@ afterAll(() => {
6060
})
6161

6262
test('shows message when test times out', async () => {
63-
const props = buildEditor({ overrides: { timeout: 1000 } })
63+
const props = buildEditor({ overrides: { timeout: 500 } })
6464
const { promise } = deferred()
6565

6666
server.use(
@@ -85,8 +85,13 @@ test('shows message when test times out', async () => {
8585

8686
render(<Editor {...props} />)
8787

88-
userEvent.click(screen.getByRole('button', { name: /Run Tests/ }))
88+
await act(async () => {
89+
userEvent.click(screen.getByRole('button', { name: /Run Tests/i }))
90+
})
8991
expect(await screen.findByText(/Running tests/i)).toBeInTheDocument()
92+
await act(async () => {
93+
await new Promise((resolve) => setTimeout(resolve, 1000))
94+
})
9095
expect(await screen.findByText('Your tests timed out')).toBeInTheDocument()
9196
})
9297

@@ -95,7 +100,9 @@ test('cancels a pending submission', async () => {
95100

96101
render(<Editor {...props} />)
97102

98-
userEvent.click(screen.getByRole('button', { name: /Run Tests/ }))
103+
await act(async () => {
104+
userEvent.click(screen.getByRole('button', { name: /Run Tests/i }))
105+
})
99106
expect(await screen.findByText('Running tests…')).toBeInTheDocument()
100107
userEvent.click(await screen.findByRole('button', { name: /cancel/i }))
101108

@@ -106,7 +113,9 @@ test('makes editor readonly while submitting tests', async () => {
106113

107114
render(<Editor {...props} />)
108115

109-
userEvent.click(screen.getByRole('button', { name: /Run Tests/ }))
116+
await act(async () => {
117+
userEvent.click(screen.getByRole('button', { name: /Run Tests/i }))
118+
})
110119
expect(await screen.findByText('Running tests…')).toBeInTheDocument()
111120
expect(screen.getByText('Readonly: true')).toBeInTheDocument()
112121
})

0 commit comments

Comments
 (0)