fix(ui): fix duplicate colored error message (#10258) · vitest-dev/vitest@035e3bb (original) (raw)

Original file line number Diff line number Diff line change
@@ -2,8 +2,6 @@
2 2 import type { RunnerTask, RunnerTestFile, RunnerTestSuite } from 'vitest'
3 3 import { computed } from 'vue'
4 4 import { config } from '~/composables/client'
5 -import { isDark } from '~/composables/dark'
6 -import { mapLeveledTaskStacks } from '~/composables/error'
7 5 import FailureScreenshot from '../FailureScreenshot.vue'
8 6 import ViewReportError from './ViewReportError.vue'
9 7
@@ -53,9 +51,7 @@ const failed = computed(() => {
53 51 }
54 52 failedFlatMap.unshift(fileErrorTask)
55 53 }
56 - return failedFlatMap.length > 0
57 - ? mapLeveledTaskStacks(isDark.value, failedFlatMap)
58 - : failedFlatMap
54 + return failedFlatMap
59 55 })
60 56 </script>
61 57
@@ -70,23 +66,14 @@ const failed = computed(() => {
70 66 m-2
71 67 rounded
72 68 :style="{
73 - 'margin-left': `${
74 - task.result?.htmlError ? 0.5 : 2 * (task as LeveledTask).level + 0.5
75 - }rem`,
69 + 'margin-left': `${2 * (task as LeveledTask).level + 0.5}rem`,
76 70 }"
77 71 >
78 72 <div flex="~ gap-2 items-center">
79 73 <span>{{ task.name }}</span>
80 74 <FailureScreenshot :task="task" />
81 75 </div>
82 - <div
83 -v-if="task.result?.htmlError"
84 -class="scrolls scrolls-rounded task-error"
85 -data-testid="task-error"
86 - >
87 - <pre v-html="task.result.htmlError" />
88 - </div>
89 - <template v-else-if="task.result?.errors && config.root">
76 + <template v-if="task.result?.errors && config.root">
90 77 <ViewReportError
91 78 v-for="(error, idx) of task.result.errors"
92 79 :key="idx"
Original file line number Diff line number Diff line change
@@ -32,6 +32,10 @@ const diff = computed(() =>
32 32 : undefined,
33 33 )
34 34
35 +const message = computed(() =>
36 + filter.value.toHtml(escapeHtml(props.error.message |
37 +)
38 +
35 39 function showCode(stack: ParsedStack) {
36 40 if (isTestFile(stack.file, props.filename)) {
37 41 return showLocationSource(props.fileId, stack)
@@ -42,7 +46,7 @@ function showCode(stack: ParsedStack) {
42 46
43 47 <template>
44 48 <div class="scrolls scrolls-rounded task-error">
45 - <pre><b>{{ error.name }}</b>: {{ error.message }}</pre>
49 + <pre><b>{{ error.name }}</b>: <span v-html="message" /></pre>
46 50 <div
47 51 v-for="(stack, i) of error.stacks"
48 52 :key="i"
Original file line number Diff line number Diff line change
@@ -3,8 +3,6 @@ import type { RunnerTestCase } from 'vitest'
3 3 import { computed } from 'vue'
4 4 import { getAttachmentUrl, sanitizeFilePath } from '~/composables/attachments'
5 5 import { config } from '~/composables/client'
6 -import { isDark } from '~/composables/dark'
7 -import { mapLeveledTaskStacks } from '~/composables/error'
8 6 import { getLocationString, openLocation } from '~/composables/location'
9 7 import AnnotationAttachmentImage from '../AnnotationAttachmentImage.vue'
10 8 import Artifacts from '../artifacts/Artifacts.vue'
@@ -19,7 +17,7 @@ const failed = computed(() => {
19 17 if (!props.test.result |
20 18 return null
21 19 }
22 - return mapLeveledTaskStacks(isDark.value, [props.test])[0] as RunnerTestCase | null
20 + return props.test
23 21 })
24 22
25 23 // filter out internal TaskMeta
@@ -47,14 +45,7 @@ const meta = computed(() => {
47 45 rounded
48 46 >
49 47 <FailureScreenshot :task="test" />
50 - <div
51 -v-if="test.result?.htmlError"
52 -class="scrolls scrolls-rounded task-error"
53 -data-testid="task-error"
54 - >
55 - <pre v-html="test.result.htmlError" />
56 - </div>
57 - <template v-else-if="test.result?.errors && config.root">
48 + <template v-if="test.result?.errors && config.root">
58 49 <ViewReportError
59 50 v-for="(error, idx) of test.result.errors"
60 51 :key="idx"
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
1 -import type Convert from 'ansi-to-html'
2 -import type { RunnerTask, TestError } from 'vitest'
1 +import type { TestError } from 'vitest'
3 2 import { parseStacktrace } from '@vitest/utils/source-map'
4 3 import Filter from 'ansi-to-html'
5 -import { escapeHtml } from '~/utils/escape'
6 -
7 -declare module '@vitest/runner' {
8 -interface TaskResult {
9 -htmlError?: string
10 -}
11 -}
12 4
13 5 export function isTestFile(name: string, fileName?: string) {
14 6 return fileName && name.endsWith(fileName)
@@ -60,51 +52,3 @@ export function parseError(e: unknown) {
60 52
61 53 return error
62 54 }
63 -
64 -function createHtmlError(filter: Convert, error: TestError) {
65 -let htmlError = ''
66 -if (error.message?.includes('\x1B')) {
67 -htmlError = `${error.name}: ${filter.toHtml(
68 - escapeHtml(error.message),
69 - )}`
70 -}
71 -
72 -const startStrWithX1B = error.stack?.includes('\x1B')
73 -if (startStrWithX1B) {
74 -if (htmlError.length > 0) {
75 -htmlError += filter.toHtml(
76 -escapeHtml((error.stack) as string),
77 -)
78 -}
79 -else {
80 -htmlError = `${error.name}: ${
81 - error.message
82 - }${filter.toHtml(
83 - escapeHtml((error.stack) as string),
84 - )}`
85 -}
86 -}
87 -
88 -if (htmlError.length > 0) {
89 -return htmlError
90 -}
91 -return null
92 -}
93 -
94 -export function mapLeveledTaskStacks(dark: boolean, tasks: RunnerTask[]) {
95 -const filter = createAnsiToHtmlFilter(dark)
96 -return tasks.map((t) => {
97 -const result = t.result
98 -if (!result |
99 -return t
100 -}
101 -const errors = result.errors
102 -?.map(error => createHtmlError(filter, error))
103 -.filter(error => error != null)
104 -.join('

')
105 -if (errors?.length) {
106 -result.htmlError = errors
107 -}
108 -return t
109 -})
110 -}
Original file line number Diff line number Diff line change
@@ -5,6 +5,6 @@
5 5 pnpm test
6 6
7 7 # run fixture projects
8 -pnpm test-fixtures
8 +pnpm test-fixtures --ui
9 9 pnpm test-fixtures --root fixtures-trace
10 10 ```
Original file line number Diff line number Diff line change
@@ -4,3 +4,11 @@ import { expect, it } from "vitest"
4 4 it('escape html in error diff', () => {
5 5 expect('').toBe("")
6 6 })
7 +
8 +it('colored error message', () => {
9 +const blue = '\x1B[34m'
10 +const reset = '\x1B[0m'
11 +const message = `${blue}this-is-blue${reset}`
12 +const error = new Error(message)
13 +throw error
14 +})
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
1 +import type { Page } from '@playwright/test'
1 2 import type { PreviewServer } from 'vite'
2 3 import { readFileSync } from 'node:fs'
3 4 import { Writable } from 'node:stream'
@@ -52,9 +53,7 @@ test.describe('html report', () => {
52 53 await page.goto(pageUrl)
53 54
54 55 // dashboard
55 -await expect(page.getByTestId('pass-entry')).toContainText('17 Pass')
56 -await expect(page.getByTestId('fail-entry')).toContainText('2 Fail')
57 -await expect(page.getByTestId('total-entry')).toContainText('19 Total')
56 +await assertTestCounts(page, { pass: 17, fail: 3 })
58 57
59 58 // unhandled errors
60 59 await expect(page.getByTestId('unhandled-errors')).toContainText(
@@ -264,3 +263,8 @@ test.describe('html report', () => {
264 263 })
265 264 })
266 265 })
266 +
267 +async function assertTestCounts(page: Page, options: { pass: number; fail: number }) {
268 +await expect.soft(page.getByTestId('tests-entry'))
269 +.toContainText(`${options.pass} Pass options.failFail{options.fail} Fail options.failFail{options.pass + options.fail} Total`)
270 +}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
1 +import type { Page } from '@playwright/test'
1 2 import type { Vitest } from 'vitest/node'
2 3 import { readFileSync } from 'node:fs'
3 4 import { Writable } from 'node:stream'
@@ -71,9 +72,7 @@ test.describe('ui', () => {
71 72 await page.goto(pageUrl)
72 73
73 74 // dashboard
74 -await expect(page.getByTestId('pass-entry')).toContainText('17 Pass')
75 -await expect(page.getByTestId('fail-entry')).toContainText('2 Fail')
76 -await expect(page.getByTestId('total-entry')).toContainText('19 Total')
75 +await assertTestCounts(page, { pass: 17, fail: 3 })
77 76
78 77 // unhandled errors
79 78 await expect(page.getByTestId('unhandled-errors')).toContainText(
@@ -254,6 +253,9 @@ test.describe('ui', () => {
254 253 await item.hover()
255 254 await item.getByTestId('btn-open-details').click({ force: true })
256 255 await expect(page.getByTestId('diff')).toContainText('- Expected + Received + ')
256 +
257 +await getExplorerItem(page, 'colored error message').click()
258 +await expect(page.getByTestId('report')).toHaveText('Error: this-is-blue - /fixtures/error.test.ts:12:17')
257 259 })
258 260
259 261 test('file-filter', async ({ page }) => {
@@ -376,6 +378,16 @@ test.describe('ui', () => {
376 378 })
377 379 })
378 380
381 +// TODO: consolidate in https://github.com/vitest-dev/vitest/pull/10237
382 +function getExplorerItem(page: Page, name: string) {
383 +return page.getByTestId('explorer-item').and(page.getByLabel(name, { exact: true }))
384 +}
385 +
386 +async function assertTestCounts(page: Page, options: { pass: number; fail: number }) {
387 +await expect.soft(page.getByTestId('tests-entry'))
388 +.toContainText(`${options.pass} Pass options.failFail{options.fail} Fail options.failFail{options.pass + options.fail} Total`)
389 +}
390 +
379 391 test.describe('standalone', () => {
380 392 let vitest: Vitest | undefined
381 393