Skip to content

Commit 7f0cc24

Browse files
fix(ui): encode html entities in task name (#6070)
Co-authored-by: Vladimir <sleuths.slews0s@icloud.com>
1 parent a169d25 commit 7f0cc24

File tree

7 files changed

+50
-13
lines changed

7 files changed

+50
-13
lines changed

packages/ui/client/components/Navigation.vue

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ function expandTests() {
6060
v-tooltip.bottom="'Collapse tests'"
6161
title="Collapse tests"
6262
:disabled="!initialized"
63+
data-testid="collapse-all"
6364
icon="i-carbon:collapse-all"
6465
@click="collapseTests()"
6566
/>
@@ -68,6 +69,7 @@ function expandTests() {
6869
v-tooltip.bottom="'Expand tests'"
6970
:disabled="!initialized"
7071
title="Expand tests"
72+
data-testid="expand-all"
7173
icon="i-carbon:expand-all"
7274
@click="expandTests()"
7375
/>

packages/ui/client/components/explorer/ExplorerItem.vue

+5-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { client, isReport, runFiles } from '~/composables/client'
77
import { coverageEnabled } from '~/composables/navigation'
88
import type { TaskTreeNodeType } from '~/composables/explorer/types'
99
import { explorerTree } from '~/composables/explorer'
10-
import { search } from '~/composables/explorer/state'
10+
import { escapeHtml, highlightRegex } from '~/composables/explorer/state'
1111
import { showSource } from '~/composables/codemirror'
1212
1313
// TODO: better handling of "opened" - it means to forcefully open the tree item and set in TasksList right now
@@ -107,16 +107,13 @@ const gridStyles = computed(() => {
107107
} ${gridColumns.join(' ')};`
108108
})
109109
110-
const highlightRegex = computed(() => {
111-
const searchString = search.value.toLowerCase()
112-
return searchString.length ? new RegExp(`(${searchString})`, 'gi') : null
113-
})
114-
110+
const escapedName = computed(() => escapeHtml(name))
115111
const highlighted = computed(() => {
116112
const regex = highlightRegex.value
113+
const useName = escapedName.value
117114
return regex
118-
? name.replace(regex, match => `<span class="highlight">${match}</span>`)
119-
: name
115+
? useName.replace(regex, match => `<span class="highlight">${match}</span>`)
116+
: useName
120117
})
121118
122119
const disableShowDetails = computed(() => type !== 'file' && disableTaskLocation)

packages/ui/client/composables/explorer/state.ts

+14
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ export const treeFilter = useLocalStorage<TreeFilterState>(
2222
},
2323
)
2424
export const search = ref<string>(treeFilter.value.search)
25+
const htmlEntities: Record<string, string> = {
26+
'&': '&amp;',
27+
'<': '&lt;',
28+
'>': '&gt;',
29+
'"': '&quot;',
30+
'\'': '&#39;',
31+
}
32+
export function escapeHtml(str: string) {
33+
return str.replace(/[&<>"']/g, m => htmlEntities[m])
34+
}
35+
export const highlightRegex = computed(() => {
36+
const searchString = search.value.toLowerCase()
37+
return searchString.length ? new RegExp(`(${escapeHtml(searchString)})`, 'gi') : null
38+
})
2539
export const isFiltered = computed(() => search.value.trim() !== '')
2640
export const filter = reactive<Filter>({
2741
failed: treeFilter.value.failed,

test/ui/fixtures/task-name.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { it, expect} from "vitest"
2+
3+
it('<MyComponent />', () => {
4+
expect(true).toBe(true)
5+
})
6+
7+
it('<>\'"', () => {
8+
expect(true).toBe(true)
9+
})

test/ui/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"private": true,
55
"scripts": {
66
"test-e2e": "GITHUB_ACTIONS=false playwright test",
7+
"test-e2e-ui": "GITHUB_ACTIONS=false playwright test --ui",
78
"test-fixtures": "vitest"
89
},
910
"devDependencies": {

test/ui/test/html-report.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ test.describe('html report', () => {
3131

3232
await page.goto(pageUrl)
3333

34-
// dashbaord
35-
await expect(page.locator('[aria-labelledby=tests]')).toContainText('6 Pass 1 Fail 7 Total')
34+
// dashboard
35+
await expect(page.locator('[aria-labelledby=tests]')).toContainText('8 Pass 1 Fail 9 Total')
3636

3737
// unhandled errors
3838
await expect(page.getByTestId('unhandled-errors')).toContainText(

test/ui/test/ui.spec.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ test.describe('ui', () => {
3636

3737
await page.goto(pageUrl)
3838

39-
// dashbaord
40-
await expect(page.locator('[aria-labelledby=tests]')).toContainText('6 Pass 1 Fail 7 Total')
39+
// dashboard
40+
await expect(page.locator('[aria-labelledby=tests]')).toContainText('8 Pass 1 Fail 9 Total')
4141

4242
// unhandled errors
4343
await expect(page.getByTestId('unhandled-errors')).toContainText(
@@ -96,7 +96,7 @@ test.describe('ui', () => {
9696

9797
// match all files when no filter
9898
await page.getByPlaceholder('Search...').fill('')
99-
await page.getByText('PASS (3)').click()
99+
await page.getByText('PASS (4)').click()
100100
await expect(page.getByTestId('details-panel').getByText('fixtures/sample.test.ts', { exact: true })).toBeVisible()
101101

102102
// match nothing
@@ -122,5 +122,19 @@ test.describe('ui', () => {
122122
await page.getByText('PASS (1)').click()
123123
await expect(page.getByTestId('details-panel').getByText('fixtures/console.test.ts', { exact: true })).toBeVisible()
124124
await expect(page.getByTestId('details-panel').getByText('fixtures/sample.test.ts', { exact: true })).toBeHidden()
125+
126+
// html entities in task names are escaped
127+
await page.locator('span').filter({ hasText: /^Pass$/ }).click()
128+
await page.getByPlaceholder('Search...').fill('<MyComponent />')
129+
// for some reason, the tree is collapsed by default: we need to click on the nav buttons to expand it
130+
await page.getByTestId('collapse-all').click()
131+
await page.getByTestId('expand-all').click()
132+
await expect(page.getByText('<MyComponent />')).toBeVisible()
133+
await expect(page.getByTestId('details-panel').getByText('fixtures/task-name.test.ts', { exact: true })).toBeVisible()
134+
135+
// html entities in task names are escaped
136+
await page.getByPlaceholder('Search...').fill('<>\'"')
137+
await expect(page.getByText('<>\'"')).toBeVisible()
138+
await expect(page.getByTestId('details-panel').getByText('fixtures/task-name.test.ts', { exact: true })).toBeVisible()
125139
})
126140
})

0 commit comments

Comments
 (0)